/*
  (c) 2008 Ender
  Will recalcuate checksum in Phoenix BIOS images
  Note: Works only on little endian systems.
*/

#include <stdio.h>

int main(int argc, char **argv)
{
    printf("PhnxCksm 0.2 2008-04-26\n(c) Ender\n\n");
    if(argc != 2) {
        printf("This utility will recalculate checksum in Phoenix BIOS image.\nUsage: \n  phnxcksm bios.rom\n  phnxcksm otherone.wph\n\nAnd that's it.\n");
        return 0;
    }

    FILE *f = fopen(argv[1], "rb+");
    int filesize, romsize, start, end, numdwords, checksum_pos = 0, i;
    unsigned long sum, old_checksum, new_checksum, tmp, extd = ('E')|('X'<<8)|('T'<<16)|('D'<<24);
    
    if(!f) {
        printf("Failed to open file %s.\n", argv[1]);
        return 5;
    }
    fseek(f, 0, SEEK_END);
    filesize = ftell(f);

    romsize = -1;
    while(filesize != 0) {
        romsize++;
        filesize >>= 1;
    }
    
    // beginning of checksum block: if rom>1MB then starts at end-1MB
    start = (romsize > 20 ? (1 << romsize) - (1 << 20) : 0);
    // bootblock (last 64KB) is not checksummed
    end = (1 << romsize) - (1 << 16);
    numdwords = (end - start) >> 2;
    
    
    printf("Okay, file open.\nROM size %Xh, extended checksum block start %Xh, end %Xh.\n", 1 << romsize, start, end);

    if(start == 0) {
        printf("Need to skip ESCD, enter size (or 0 for default of 8192 bytes): ");
        scanf("%d", &start);
        if(start == 0) start = 8192;
        numdwords = (end - start) / 4;
    }
    
    printf("Reading...\n");
    
    fseek(f, start, SEEK_SET);
    for(i = 0, sum = 0; i < numdwords; i++) {
        if(tmp == extd) {
            checksum_pos = ftell(f);
            printf("Checksum found at %Xh.\n", checksum_pos);
            fread(&old_checksum, 4, 1, f);
            tmp = 0;
        }
        else {
            fread(&tmp, 4, 1, f);
        }
        sum += tmp;
    }
    
    if(checksum_pos == 0) {
        printf("Extended checksum not found in the scanned location. Quitting.\n");
        return 5;
    }
    
    printf("Seems good. Block sum is %Xh, old checksum %Xh.\n", sum, old_checksum);
    
    new_checksum = (~sum) + 1;
    if(old_checksum == new_checksum) {
        printf("New checksum %Xh is the same. Nothing needs to be done. Quitting.\n", new_checksum);
        return 0;
    }
    
    printf("New checksum %Xh, writing to location %Xh... ", new_checksum, checksum_pos);
    fseek(f, checksum_pos, SEEK_SET);
    fwrite(&new_checksum, 4, 1, f);
    printf("Done.\n");
    
    printf("\n---------------- WARNING! ----------------\n");
    printf("If you don't know what you're doing and just want the flashing utility to stop complaining about your modified BIOS, there's a very high risk of rendering your computer unusable. Don't use this just to fool flashing utility.\n");
    printf("---------------- WARNING! ----------------\n");
    
    fclose(f);

    return 0;
}
