/*
 * Descramble Apple II Elite.  Tested with the 4am crack.
 *
 * ELA/ELB1 may be extracted with CiderPress in "preserve Apple II formats"
 * mode.  Provide the names of the extracted files as arguments to
 * this program.
 *
 * The generated program should be started by jumping to $4592 instead of $4000
 * (want to skip the SCRN move and the descrambling).
 *
 * Copyright 2020 faddenSoft.  Licensed under the Apache License, Version 2.0.
 */
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/*
Memory map:

  $0A00-3FFF: ELA, offset +0000 to +35FF
  $4000-8FFF: ELB1, offset +0000 to +4FFF
  $9000-BFFF: ELA, offset +3600 to +65FF


Descramble function (in ELB1 at $459F):

         lda     #$e9       ;check for end when Y=E9
         sta     DESCRAM_END ; so last byte we tweak is $xxEA
         lda     #$45       ;stop when we hit $45E9
         sta     DESCRAM_END+1
         lda     #$bf       ;first byte is $BFFE
         ldy     #$fe
         ldx     #$15       ;initial sub value
         jsr     :DoDescram
         lda     #$5f
         sta     DESCRAM_END
         lda     #$0b       ;stop when we hit $B5F
         sta     DESCRAM_END+1
         lda     #$1f       ;start at $1FFF
         ldy     #$ff
         ldx     #$69       ;initial value
:DoDescram stx   ]des_sub
         sta     ]src_ptr+1
         lda     #$00
         sta     ]src_ptr
:DesLoop lda     (]src_ptr),y ;get old value
         sec
         sbc     ]des_sub   ;subtract rolling sub
         sta     (]src_ptr),y ;store it
         sta     ]des_sub   ;update rolling sub
         tya                ;Y == 0?
         bne     :NoDec     ;not yet
         dec     ]src_ptr+1 ;yes, dec high byte
:NoDec   dey                ;advance
         cpy     DESCRAM_END ;do we need to check for done?
         bne     :DesLoop   ;not here
         lda     ]src_ptr+1 ;check the ptr high byte
         cmp     DESCRAM_END+1 ;reached the end?
         bne     :DesLoop   ;not yet
         rts
*/

// Works from high memory to low memory, so startOff > endOff.
void descramble(uint8_t* mem, int startOff, int endOff, uint8_t sub) {
    while (startOff > endOff) {
        sub = mem[startOff] = (uint8_t) (mem[startOff] - sub);
        startOff--;
    }
}

void xorRegion(uint8_t* mem, int offset, int length, uint8_t xorVal, int doZeroFlag) {
    while (length--) {
        if (doZeroFlag || mem[offset] != 0) {
            mem[offset] ^= xorVal;
        }
        offset++;
    }
}

int replace(uint8_t* mem, int offset, uint8_t oldVal, uint8_t newVal) {
    if (mem[offset] != oldVal) {
        return -1;
    }
    mem[offset] = newVal;
    return 0;
}

const int START = 0x0a00;

int main(int argc, char** argv) {
    if (argc != 4) {
        fprintf(stderr, "Usage: descram_elite <ELA> <ELB1> <output>\n");
        return 2;
    }

    size_t memSize = 0xc000 - START;
    uint8_t* mem = new uint8_t[memSize];

    FILE* fp = fopen(argv[1], "rb");
    if (fp == NULL) {
        fprintf(stderr, "Unable to open '%s': %s\n", argv[1], strerror(errno));
        fclose(fp);
        return 1;
    }

    size_t count = 0x4000 - START;
    if (fread(mem + 0x0000, 1, count, fp) != count) {
        fprintf(stderr, "Unable to read '%s' part 1\n", argv[1]);
        fclose(fp);
        return 1;
    }

    count = 0xc000 - 0x9000;
    if (fread(mem + (0x9000 - START), 1, count, fp) != count) {
        fprintf(stderr, "Unable to read '%s' part 2\n", argv[1]);
        fclose(fp);
        return 1;
    }
    fclose(fp);

    fp = fopen(argv[2], "rb");
    if (fp == NULL) {
        fprintf(stderr, "Unable to open '%s': %s\n", argv[2], strerror(errno));
        fclose(fp);
        return 1;
    }

    count = 0x9000 - 0x4000;
    if (fread(mem + (0x4000 - START), 1, count, fp) != count) {
        fprintf(stderr, "Unable to read '%s'\n", argv[2]);
        fclose(fp);
        return 1;
    }
    fclose(fp);

    // descramble code
    descramble(mem, 0xbffe - START, 0x45e9 - START, 0x15);
    descramble(mem, 0x1fff - START, 0x0b5f - START, 0x69);

    // "decrypt" text regions
    // (this is not very useful -- digrams and tokenization dominate)
    if (0) {
        // This breaks things because $00 is a valid token (prints player's
        // cash balance).  Since we're not XORing the zeroes, we end up with
        // $00==string-end and $00==show-balance, and no way to tell them
        // apart.  We could EOR everything and change the string-end code to
        // look for #$23, but the code checks with BEQ/BNE so we'd need to
        // insert a multi-byte patch.  Not worth it.
        xorRegion(mem, 0xb60 - START, 0xf1b - 0xb60, 0x23, 0);
        if (replace(mem, 0x7201 - START, 0x23, 0x00) != 0) {
            fprintf(stderr, "Unable to negate flight string xor\n");
            return 1;
        }

        // This seems to work fine.
        xorRegion(mem, 0xf40 - START, 0x1a5e - 0xf40, 0x57, 1);
        if (replace(mem, 0x4b85 - START, 0x57, 0x00) != 0 ||
            replace(mem, 0x4b9a - START, 0x57, 0x00) != 0)
        {
            fprintf(stderr, "Unable to negate docked string xor\n");
            return 1;
        }
    }

    // save the result
    fp = fopen(argv[3], "wb");
    if (fp == NULL) {
        fprintf(stderr, "Unable to open '%s': %s\n", argv[3], strerror(errno));
        fclose(fp);
        return 1;
    }

    if (fwrite(mem, 1, memSize, fp) != memSize) {
        fprintf(stderr, "Unable to write '%s'\n", argv[3]);
        fclose(fp);
        return 1;
    }
    fclose(fp);
    delete[] mem;

    printf("Success\n");
    return 0;
}
