A very simplistic dumper for file entries for Clockwork Knight 1. I'm hoping to expand this so that I can reverse engineer the archive format used.
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
- static char buffer[8192];
- /* The first file in the archive has this name.
- * Because I don't know what header before the file entry table looks like
- * yet, I'm just going to skip past this first readable entry.
- */
- static const char key[] = "A.BIN;1";
- /* Tries to sychronize the end of a file entry. We know the following about
- * file entries:
- * File entries are 34 bytes, plus the name string, which may have nul-
- * padding.
- * File entries seem to start with 0x0030, 0x0028, or 0x002E.
- * 2E: All levels and menus, though "SNDDRV.BIN" also?
- * 30: Cinepak (CPK) movies and "SNDTBL" (soundfonts?), but also:
- * GAMEOVER.BIN (game over screen?)
- * PLAYERCG.BIN (continue screen?)
- * PLAYERTB.BIN (???)
- * 28: CDDA, maybe CD audio descriptors for the other 2 CD tracks?
- * Movies in the CPK format (Cinepak, documented elsewhere) start with 30.
- * Names are zero-padded, but this seems to pad them to an odd byte offset.
- * This might indicate the leading byte is actually a trailing byte?
- * Names have a leading size byte, which includes the length of a trailing
- * ";1". This is probably not actually part of the name, since there is
- * another file name table in the ROM that doesn't include this suffix.
- * Names consist of uppercase ASCII, decimal digits, and full-stops. I think
- * they are just DOS or UDF names.
- *
- * This function tries to find the end of file entry by finding the length
- * byte for the name, which is then validated by seeing if it points just past
- * a ";1", and all the characters in between are uppercase, punctuation, or
- * digits.
- *
- * This could probably be foiled, but it seems to work for the Clockwork Knight
- * ROM at least
- */
- static int find_file_entry_end(FILE *file, unsigned *out_name_length){
- fpos_t pos;
- unsigned i, at, e;
- fgetpos(file, &pos);
- memset(buffer, 0, 512);
- fread(buffer, 1, 512, file);
- fsetpos(file, &pos);
- for(i = 0; i < 256; i++){
- if(((signed char*)buffer)[i] > 2){
- at = ((unsigned char*)buffer)[i];
- #if 0
- printf("Testing %02x: %c, %c\n",
- at,
- isprint(buffer[at + i - 1]) ? buffer[at + i - 1] : '.',
- isprint(buffer[at + i - 0]) ? buffer[at + i - 0] : '.');
- #endif
- if(buffer[at + i - 1] == ';' && buffer[at + i - 0] == '1'){
- for(e = 0; e < at; e++){
- if(!isalnum(buffer[i + e + 1]) && !ispunct(buffer[i + e + 1])){
- #if 0
- printf("Can't be name because %02x is %02x\n",
- e,
- ((unsigned char*)buffer)[i + e + 1]);
- #endif
- goto not_upper_ascii;
- }
- }
- *out_name_length = at;
- return i + 1;
- }
- not_upper_ascii:
- (void)0;
- }
- }
- return EOF;
- }
- int main(int argc, char *argv[]){
- FILE *file;
- unsigned search_offset, i;
- int e;
- char *found, c;
- if(argc != 2){
- printf("usage: %s <track1.bin>\n", argv[0]);
- return -1;
- }
- if((file = fopen(argv[1], "rb")) == NULL){
- puts("Couldn't open file:");
- puts(argv[1]);
- return -2;
- }
- /* search for "A.BIN;1" */
- found = NULL;
- for(search_offset = 0; search_offset < 100 && found == NULL; search_offset++){
- fseek(file, 0xB7C0 + (search_offset * 4096), SEEK_SET);
- fread(buffer, 1, 4096 + sizeof(key), file);
- for(i = 0; i < 4096 + sizeof(key); i++){
- #if 0
- #ifndef NDEBUG
- unsigned e;
- printf("Comparing: %08X:\n", 0xB7C0 + (search_offset * 4096) + i);
- for(e = 0; e < sizeof(key) - 1; e++)
- printf("%02x", ((unsigned char*)buffer)[i + e]);
- putchar('\n');
- for(e = 0; e < sizeof(key) - 1; e++)
- printf("%02x", ((unsigned char*)key)[e]);
- putchar('\n');
- #endif
- #endif
- if(memcmp(buffer + i, key, sizeof(key) - 1) == 0){
- /* TODO: Why is this an extra -1? */
- i += 0xB7C0 + (search_offset * 4096) + sizeof(key) - 1 - 1;
- #ifndef NDEBUG
- printf("Found level archive header at %08x\n", i);
- #endif
- fseek(file, i, SEEK_SET);
- found = buffer;
- break;
- }
- }
- }
- if(found == NULL){
- puts("This doesn't look like a Clockwork Knight disc");
- return -3;
- }
- /* This is kinda wacky, but just dump out each byte until the name. */
- for(;;){
- e = find_file_entry_end(file, &i);
- if(e == EOF)
- break;
- while(e--){
- printf("%02X,", (unsigned char)fgetc(file));
- }
- /* putchar('\n'); */
- fread(buffer, 1, i, file);
- if(i < 3){
- printf("Illegal name length of %i\n", i);
- }
- else{
- if(buffer[i - 2] != ';' || buffer[i - 1] != '1'){
- puts("WARNING: Invalid string terminator");
- }
- else{
- buffer[i - 2] = '\0';
- printf("Name length: %i: %s\n", i - 2, buffer);
- }
- }
- }
- return 0;
- }