I wrote a program to insert a checksum into a Game Boy cartridge. Here is a spec of the cartridge header, but I'll include the relevant information here, as well.
The header checksum is defined as the sum of the bitwise-NOT of the 52nd through the 76th bytes of the file, inclusive.
The global checksum is defined as the sum of all the bytes in the file except for the bytes of the global checksum itself.
This program requires placeholder zero bytes for these checksums. If the placeholder bytes are not zero, the program errors.
Here's the program:
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// Insert various checksums into a Game Boy cartridge file.
// Gives an error if the checksums are present prior to checksumming.
#define HEADER_DATA \
/* nop; jmp $150 */ \
"\x00\xc3\x50\x01" \
/* Nintendo logo */ \
"\xce\xed\x66\x66\xcc\x0d\x00\x0b\x03\x73\x00\x83\x00\x0c\x00\x0d" \
"\x00\x08\x11\x1f\x88\x89\x00\x0e\xdc\xcc\x6e\xe6\xdd\xdd\xd9\x99" \
"\xbb\xbb\x67\x63\x6e\x0e\xec\xcc\xdd\xdc\x99\x9f\xbb\xb9\x33\x3e"
enum cartridge_header_section_sizes
{
// oversimplification, but the extra data is not needed
ENTRY = 0x0,
MISC = ENTRY + 0x34,
HEADER_CHECKSUM = MISC + 0x19,
GLOBAL_CHECKSUM = HEADER_CHECKSUM + 0x01,
};
// checksum functions save and restore file position
static unsigned char header_checksum(FILE *const fp, size_t nbytes)
{
fpos_t pos;
unsigned char result;
if(fgetpos(fp, &pos) == -1)
return -1;
for( result = 0; nbytes--; )
{
int c = fgetc(fp);
if(c == -1)
return -1;
result += (unsigned char)~(unsigned char)c;
}
if(fsetpos(fp, &pos) == -1)
return -1;
return result;
}
static unsigned short global_checksum(FILE *const fp, size_t nbytes)
{
fpos_t pos;
unsigned short result;
if(fgetpos(fp, &pos) == -1)
return -1;
for( result = 0; nbytes--; )
{
int c = fgetc(fp);
if(c == -1)
return -1;
result += (unsigned char)c;
}
if(fsetpos(fp, &pos) == -1)
return -1;
return result;
}
int main(const int argc, const char *const *const argv)
{
if(argc != 2)
{
printf("Usage: %s gb-file\n", argv[0]);
goto fail;
}
FILE *const fp = fopen(argv[1], "r+b");
if(fp == NULL)
{
perror(argv[1]);
goto fail;
}
// -1 for the trailing null byte
unsigned char header[sizeof(HEADER_DATA) - 1];
if(fread(header, 1, sizeof(header), fp) != sizeof(header))
{
fputs("Short file: header read failed\n", stderr);
goto fail;
}
if(memcmp(header, HEADER_DATA, sizeof(header)) != 0)
{
fputs("Invalid header!\n"
"Make sure that your header contains nop; jp $150 "
"and the official Nintendo logo before running again.\n",
stderr);
goto fail;
}
errno = 0;
unsigned char hchk = header_checksum(fp, HEADER_CHECKSUM - MISC);
if(hchk == (unsigned char)-1 && errno)
{
perror("header checksum");
goto fail;
}
#ifndef NDEBUG
printf("%hx\n", hchk);
#endif
if(fseek(fp, HEADER_CHECKSUM, SEEK_SET) == -1)
{
perror("fseek");
goto fail;
}
// sanity checking
if(fgetc(fp))
{
fputs("Header checksum already in place!\n"
"Did you run the program twice?\n", stderr);
goto fail;
}
// rewind to previous position if successful
if(fseek(fp, -1, SEEK_CUR) == -1)
{
perror("fseek");
goto fail;
}
if(fwrite(&hchk, 1, sizeof(hchk), fp) != sizeof(hchk))
{
fputs("Insertion of header checksum failed.\n", stderr);
goto fail;
}
if(fseek(fp, 0, SEEK_END) == -1)
{
perror("fseek");
goto fail;
}
long size = ftell(fp);
if(size == -1)
{
perror("ftell");
goto fail;
}
if(fseek(fp, 0, SEEK_SET) == -1)
{
perror("fseek");
goto fail;
}
// eh, why not?
errno = 0;
unsigned short gchk = global_checksum(fp, (size_t)size);
if(gchk == (unsigned short)-1 && errno)
{
perror("global checksum");
goto fail;
}
#ifndef NDEBUG
printf("%hx\n", gchk);
#endif
unsigned char gchk_arr[2] = { gchk >> 8, gchk & 0xff };
if(fseek(fp, GLOBAL_CHECKSUM, SEEK_SET) == -1)
{
perror("fseek");
goto fail;
}
// more sanity checking
int c1 = fgetc(fp);
int c2 = fgetc(fp);
if(c1 || c2)
{
fputs("Global checksum already in place!\n"
"Did you run the program twice?\n", stderr);
goto fail;
}
if(fseek(fp, -2, SEEK_CUR) == -1)
{
perror("fseek");
goto fail;
}
if(fwrite(gchk_arr, 1, sizeof(gchk_arr), fp) != sizeof(gchk_arr))
{
perror("fwrite");
goto fail;
}
fflush(fp);
fclose(fp);
return EXIT_SUCCESS;
fail:
if(fp)
fclose(fp);
return EXIT_FAILURE;
}
What I'm looking for:
- Is there any way to simplify this? It seems that I read, seek, and re-read the file many times, and I repeat code a lot.
- Are my variable, structure, and function names self-explanatory? Do the comments help improve the readability of the program?
- Are there any edge-cases that I might've missed when testing this?
- Any other general advice.