6 // (C) 2010 Underground Software
8 // JLH = James Hammons <jlhamm@acm.org>
11 // --- ---------- -------------------------------------------------------------
12 // JLH 01/16/2010 Created this log ;-)
13 // JLH 02/28/2010 Added functions to look inside .ZIP files and handle contents
14 // JLH 06/01/2012 Added function to check ZIP file CRCs against file DB
27 #include "universalhdr.h"
31 // Private function prototypes
33 static int gzfilelength(gzFile gd);
34 static bool CheckExtension(const char * filename, const char * ext);
35 //static int ParseFileType(uint8_t header1, uint8_t header2, uint32_t size);
37 // Private variables/enums
41 // Generic ROM loading
43 uint32_t JaguarLoadROM(uint8_t * &rom, char * path)
45 // We really should have some kind of sanity checking for the ROM size here to prevent
46 // a buffer overflow... !!! FIX !!!
47 #warning "!!! FIX !!! Should have sanity checking for ROM size to prevent buffer overflow!"
50 WriteLog("JaguarLoadROM: Attempting to load file '%s'...", path);
51 char * ext = strrchr(path, '.');
53 // No filename extension == YUO FAIL IT (it is loading the file).
54 // This is naive, but it works. But should probably come up with something a little
55 // more robust, to prevent problems with dopes trying to exploit this.
58 WriteLog("FAILED!\n");
62 WriteLog("Succeeded in finding extension (%s)!\n", ext);
63 WriteLog("VJ: Loading \"%s\"...", path);
65 if (strcasecmp(ext, ".zip") == 0)
67 // Handle ZIP file loading here...
68 WriteLog("(ZIPped)...");
70 // uint8_t * buffer = NULL;
71 // romSize = GetFileFromZIP(path, FT_SOFTWARE, buffer);
72 romSize = GetFileFromZIP(path, FT_SOFTWARE, rom);
76 WriteLog("Failed!\n");
80 // memcpy(rom, buffer, romSize);
85 // Handle gzipped files transparently [Adam Green]...
87 gzFile fp = gzopen(path, "rb");
91 WriteLog("Failed!\n");
95 romSize = gzfilelength(fp);
96 rom = new uint8_t[romSize];
97 gzseek(fp, 0, SEEK_SET);
98 gzread(fp, rom, romSize);
102 WriteLog("OK (%i bytes)\n", romSize);
109 // Jaguar file loading
110 // We do a more intelligent file analysis here instead of relying on (possible false)
111 // file extensions which people don't seem to give two shits about anyway. :-(
113 bool JaguarLoadFile(char * path)
115 uint8_t * buffer = NULL;
116 jaguarROMSize = JaguarLoadROM(buffer, path);
118 if (jaguarROMSize == 0)
120 // It's up to the GUI to report errors, not us. :-)
121 WriteLog("FILE: Could not load ROM from file \"%s\"...\nAborting load!\n", path);
125 jaguarMainROMCRC32 = crc32_calcCheckSum(buffer, jaguarROMSize);
126 WriteLog("CRC: %08X\n", (unsigned int)jaguarMainROMCRC32);
127 // TODO: Check for EEPROM file in ZIP file. If there is no EEPROM in the user's EEPROM
128 // directory, copy the one from the ZIP file, if it exists.
130 jaguarRunAddress = 0x802000; // For non-BIOS runs, this is true
131 int fileType = ParseFileType(buffer, jaguarROMSize);
132 jaguarCartInserted = false;
134 if (fileType == JST_ROM)
136 jaguarCartInserted = true;
137 memcpy(jagMemSpace + 0x800000, buffer, jaguarROMSize);
138 // Checking something...
139 jaguarRunAddress = GET32(jagMemSpace, 0x800404);
140 WriteLog("FILE: Cartridge run address is reported as $%X...\n", jaguarRunAddress);
144 else if (fileType == JST_ALPINE)
146 // File extension ".ROM": Alpine image that loads/runs at $802000
147 WriteLog("FILE: Setting up Alpine ROM... Run address: 00802000, length: %08X\n", jaguarROMSize);
148 memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
149 memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
152 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
153 // Try setting the vector to say, $1000 and putting an instruction there that loops forever:
154 // This kludge works! Yeah!
155 SET32(jaguarMainRAM, 0x10, 0x00001000);
156 SET16(jaguarMainRAM, 0x1000, 0x60FE); // Here: bra Here
159 else if (fileType == JST_ABS_TYPE1)
161 // For ABS type 1, run address == load address
162 uint32_t loadAddress = GET32(buffer, 0x16),
163 codeSize = GET32(buffer, 0x02) + GET32(buffer, 0x06);
164 WriteLog("FILE: Setting up homebrew (ABS-1)... Run address: %08X, length: %08X\n", loadAddress, codeSize);
165 memcpy(jagMemSpace + loadAddress, buffer + 0x24, codeSize);
167 jaguarRunAddress = loadAddress;
170 else if (fileType == JST_ABS_TYPE2)
172 uint32_t loadAddress = GET32(buffer, 0x28), runAddress = GET32(buffer, 0x24),
173 codeSize = GET32(buffer, 0x18) + GET32(buffer, 0x1C);
174 WriteLog("FILE: Setting up homebrew (ABS-2)... Run address: %08X, length: %08X\n", runAddress, codeSize);
175 memcpy(jagMemSpace + loadAddress, buffer + 0xA8, codeSize);
177 jaguarRunAddress = runAddress;
180 // NB: This is *wrong*
182 Basically, if there is no "JAG" at position $1C, then the long there is the load/start
183 address in LITTLE ENDIAN.
184 If "JAG" is present, the the next character ("R" or "L") determines the size of the
185 JagServer command (2 bytes vs. 4). Following that are the commands themselves;
186 typically it will either be 2 (load) or 3 (load & run). Command headers go like so:
205 10: (Poll for commands)
207 12: (Load & run user program)
208 filname, terminated with NULL
213 else if (fileType == JST_JAGSERVER)
215 // This kind of shiaut should be in the detection code below...
216 // (and now it is! :-)
217 // if (buffer[0x1C] == 'J' && buffer[0x1D] == 'A' && buffer[0x1E] == 'G')
219 // Still need to do some checking here for type 2 vs. type 3. This assumes 3
220 // Also, JAGR vs. JAGL (word command size vs. long command size)
221 uint32_t loadAddress = GET32(buffer, 0x22), runAddress = GET32(buffer, 0x2A);
222 WriteLog("FILE: Setting up homebrew (Jag Server)... Run address: $%X, length: $%X\n", runAddress, jaguarROMSize - 0x2E);
223 memcpy(jagMemSpace + loadAddress, buffer + 0x2E, jaguarROMSize - 0x2E);
225 jaguarRunAddress = runAddress;
227 // Hmm. Is this kludge necessary?
228 SET32(jaguarMainRAM, 0x10, 0x00001000); // Set Exception #4 (Illegal Instruction)
229 SET16(jaguarMainRAM, 0x1000, 0x60FE); // Here: bra Here
233 // else // Special WTFOMGBBQ type here...
235 // uint32_t loadAddress = (buffer[0x1F] << 24) | (buffer[0x1E] << 16) | (buffer[0x1D] << 8) | buffer[0x1C];
236 // WriteLog("FILE: Setting up homebrew (GEMDOS WTFOMGBBQ type)... Run address: $%X, length: $%X\n", loadAddress, jaguarROMSize - 0x20);
237 // memcpy(jagMemSpace + loadAddress, buffer + 0x20, jaguarROMSize - 0x20);
239 // jaguarRunAddress = loadAddress;
243 else if (fileType == JST_WTFOMGBBQ)
245 uint32_t loadAddress = (buffer[0x1F] << 24) | (buffer[0x1E] << 16) | (buffer[0x1D] << 8) | buffer[0x1C];
246 WriteLog("FILE: Setting up homebrew (GEMDOS WTFOMGBBQ type)... Run address: $%X, length: $%X\n", loadAddress, jaguarROMSize - 0x20);
247 memcpy(jagMemSpace + loadAddress, buffer + 0x20, jaguarROMSize - 0x20);
249 jaguarRunAddress = loadAddress;
253 // We can assume we have JST_NONE at this point. :-P
259 // "Alpine" file loading
260 // Since the developers were coming after us with torches and pitchforks, we decided to
261 // allow this kind of thing. ;-) But ONLY FOR THE DEVS, DAMMIT! >:-U O_O
263 bool AlpineLoadFile(char * path)
265 uint8_t * buffer = NULL;
266 jaguarROMSize = JaguarLoadROM(buffer, path);
268 if (jaguarROMSize == 0)
270 // It's up to the GUI to deal with failure, not us. ;-)
271 WriteLog("FILE: Could not load Alpine from file \"%s\"...\nAborting load!\n", path);
275 jaguarMainROMCRC32 = crc32_calcCheckSum(buffer, jaguarROMSize);
276 WriteLog("CRC: %08X\n", (unsigned int)jaguarMainROMCRC32);
279 jaguarRunAddress = 0x802000;
281 WriteLog("FILE: Setting up Alpine ROM with non-standard length... Run address: 00802000, length: %08X\n", jaguarROMSize);
283 memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
284 memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
287 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
288 // Try setting the vector to say, $1000 and putting an instruction there that loops forever:
289 // This kludge works! Yeah!
290 SET32(jaguarMainRAM, 0x10, 0x00001000); // Set Exception #4 (Illegal Instruction)
291 SET16(jaguarMainRAM, 0x1000, 0x60FE); // Here: bra Here
298 // Get the length of a (possibly) gzipped file
300 static int gzfilelength(gzFile gd)
302 int size = 0, length = 0;
303 unsigned char buffer[0x10000];
309 // Read in chunks until EOF
310 size = gzread(gd, buffer, 0x10000);
325 // Compare extension to passed in filename. If equal, return true; otherwise false.
327 static bool CheckExtension(const uint8_t * filename, const char * ext)
329 // Sanity checking...
330 if ((filename == NULL) || (ext == NULL))
333 const char * filenameExt = strrchr((const char *)filename, '.'); // Get the file's extension (if any)
335 if (filenameExt == NULL)
338 return (strcasecmp(filenameExt, ext) == 0 ? true : false);
343 // Get file from .ZIP
344 // Returns the size of the file inside the .ZIP file that we're looking at
345 // NOTE: If the thing we're looking for is found, it allocates it in the passed in buffer.
346 // Which means we have to deallocate it later.
348 uint32_t GetFileFromZIP(const char * zipFile, FileType type, uint8_t * &buffer)
350 // NOTE: We could easily check for this by discarding anything that's larger than the RAM/ROM
351 // size of the Jaguar console.
352 #warning "!!! FIX !!! Should have sanity checking for ROM size to prevent buffer overflow!"
353 const char ftStrings[5][32] = { "Software", "EEPROM", "Label", "Box Art", "Controller Overlay" };
354 // ZIP * zip = openzip(0, 0, zipFile);
355 FILE * zip = fopen(zipFile, "rb");
359 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
367 // The order is here is important: If the file is found, we need to short-circuit the
368 // readzip() call because otherwise, 'ze' will be pointing to the wrong file!
369 // while (!found && readzip(zip))
370 while (!found && GetZIPHeader(zip, ze))
374 // Here we simply rely on the file extension to tell the truth, but we know
375 // that extensions lie like sons-a-bitches. So this is naive, we need to do
376 // something a little more robust to keep bad things from happening here.
377 #warning "!!! Checking for image by extension can be fooled !!!"
378 if ((type == FT_LABEL) && (CheckExtension(ze.filename, ".png") || CheckExtension(ze.filename, ".jpg") || CheckExtension(ze.filename, ".gif")))
381 WriteLog("FILE: Found image file '%s'.\n", ze.filename);
384 if ((type == FT_SOFTWARE) && (CheckExtension(ze.filename, ".j64")
385 || CheckExtension(ze.filename, ".rom") || CheckExtension(ze.filename, ".abs")
386 || CheckExtension(ze.filename, ".cof") || CheckExtension(ze.filename, ".coff")
387 || CheckExtension(ze.filename, ".jag")))
390 WriteLog("FILE: Found software file '%s'.\n", ze.filename);
393 if ((type == FT_EEPROM) && (CheckExtension(ze.filename, ".eep") || CheckExtension(ze.filename, ".eeprom")))
396 WriteLog("FILE: Found EEPROM file '%s'.\n", ze.filename);
400 fseek(zip, ze.compressedSize, SEEK_CUR);
403 uint32_t fileSize = 0;
407 WriteLog("FILE: Uncompressing...");
408 // Insert file size sanity check here...
409 buffer = new uint8_t[ze.uncompressedSize];
411 // if (readuncompresszip(zip, ze.compressedSize, buffer) == 0)
412 // if (UncompressFileFromZIP(zip, ze.compressedSize, buffer) == 0)
413 if (UncompressFileFromZIP(zip, ze, buffer) == 0)
415 fileSize = ze.uncompressedSize;
416 WriteLog("success! (%u bytes)\n", fileSize);
422 WriteLog("FAILED!\n");
426 // Didn't find what we're looking for...
427 WriteLog("FILE: Failed to find file of type %s...\n", ftStrings[type]);
435 uint32_t GetFileDBIdentityFromZIP(const char * zipFile)
437 FILE * zip = fopen(zipFile, "rb");
441 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
447 // Loop through all files in the zip file under consideration
448 while (GetZIPHeader(zip, ze))
450 // & loop through all known CRC32s in our file DB to see if it's there!
453 while (romList[index].crc32 != 0xFFFFFF)
455 if (romList[index].crc32 == ze.crc32)
464 // We didn't find it, so skip the compressed data...
465 fseek(zip, ze.compressedSize, SEEK_CUR);
473 bool FindFileInZIPWithCRC32(const char * zipFile, uint32_t crc)
475 FILE * zip = fopen(zipFile, "rb");
479 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
485 // Loop through all files in the zip file under consideration
486 while (GetZIPHeader(zip, ze))
494 fseek(zip, ze.compressedSize, SEEK_CUR);
503 // Parse the file type based upon file size and/or headers.
505 uint32_t ParseFileType(uint8_t * buffer, uint32_t size)
507 // Check headers first...
510 if (buffer[0] == 0x60 && buffer[1] == 0x1B)
511 return JST_ABS_TYPE1;
514 if (buffer[0] == 0x01 && buffer[1] == 0x50)
515 return JST_ABS_TYPE2;
517 // Jag Server & other old shite
518 if (buffer[0] == 0x60 && buffer[1] == 0x1A)
520 if (buffer[0x1C] == 'J' && buffer[0x1D] == 'A' && buffer[0x1E] == 'G')
521 return JST_JAGSERVER;
523 return JST_WTFOMGBBQ;
526 // And if that fails, try file sizes...
528 // If the file size is divisible by 1M, we probably have an regular ROM.
529 // We can also check our CRC32 against the internal ROM database to be sure.
530 // (We also check for the Memory Track cartridge size here as well...)
531 if ((size % 1048576) == 0 || size == 131072)
534 // If the file size + 8192 bytes is divisible by 1M, we probably have an
535 // Alpine format ROM.
536 if (((size + 8192) % 1048576) == 0)
544 // Check for universal header
546 bool HasUniversalHeader(uint8_t * rom, uint32_t romSize)
552 for(int i=0; i<8192; i++)
553 if (rom[i] != universalCartHeader[i])
563 Stubulator ROM vectors...
564 handler 001 at $00E00008
565 handler 002 at $00E008DE
566 handler 003 at $00E008E2
567 handler 004 at $00E008E6
568 handler 005 at $00E008EA
569 handler 006 at $00E008EE
570 handler 007 at $00E008F2
571 handler 008 at $00E0054A
572 handler 009 at $00E008FA
573 handler 010 at $00000000
574 handler 011 at $00000000
575 handler 012 at $00E008FE
576 handler 013 at $00E00902
577 handler 014 at $00E00906
578 handler 015 at $00E0090A
579 handler 016 at $00E0090E
580 handler 017 at $00E00912
581 handler 018 at $00E00916
582 handler 019 at $00E0091A
583 handler 020 at $00E0091E
584 handler 021 at $00E00922
585 handler 022 at $00E00926
586 handler 023 at $00E0092A
587 handler 024 at $00E0092E
588 handler 025 at $00E0107A
589 handler 026 at $00E0107A
590 handler 027 at $00E0107A
591 handler 028 at $00E008DA
592 handler 029 at $00E0107A
593 handler 030 at $00E0107A
594 handler 031 at $00E0107A
595 handler 032 at $00000000
597 Let's try setting up the illegal instruction vector for a stubulated jaguar...
599 SET32(jaguar_mainRam, 0x08, 0x00E008DE);
600 SET32(jaguar_mainRam, 0x0C, 0x00E008E2);
601 SET32(jaguar_mainRam, 0x10, 0x00E008E6); // <-- Should be here (it is)...
602 SET32(jaguar_mainRam, 0x14, 0x00E008EA);//*/
605 ABS Format sleuthing (LBUGDEMO.ABS):
607 000000 60 1B 00 00 05 0C 00 04 62 C0 00 00 04 28 00 00
608 000010 12 A6 00 00 00 00 00 80 20 00 FF FF 00 80 25 0C
611 DRI-format file detected...
612 Text segment size = 0x0000050c bytes
613 Data segment size = 0x000462c0 bytes
614 BSS Segment size = 0x00000428 bytes
615 Symbol Table size = 0x000012a6 bytes
616 Absolute Address for text segment = 0x00802000
617 Absolute Address for data segment = 0x0080250c
618 Absolute Address for BSS segment = 0x00004000
621 000000 01 50 00 03 00 00 00 00 00 03 83 10 00 00 05 3b
622 000010 00 1c 00 03 00 00 01 07 00 00 1d d0 00 03 64 98
623 000020 00 06 8b 80 00 80 20 00 00 80 20 00 00 80 3d d0
625 000030 2e 74 78 74 00 00 00 00 00 80 20 00 00 80 20 00 .txt (+36 bytes)
626 000040 00 00 1d d0 00 00 00 a8 00 00 00 00 00 00 00 00
627 000050 00 00 00 00 00 00 00 20
628 000058 2e 64 74 61 00 00 00 00 00 80 3d d0 00 80 3d d0 .dta (+36 bytes)
629 000068 00 03 64 98 00 00 1e 78 00 00 00 00 00 00 00 00
630 000078 00 00 00 00 00 00 00 40
631 000080 2e 62 73 73 00 00 00 00 00 00 50 00 00 00 50 00 .bss (+36 bytes)
632 000090 00 06 8b 80 00 03 83 10 00 00 00 00 00 00 00 00
633 0000a0 00 00 00 00 00 00 00 80
635 Header size is $A8 bytes...
637 BSD/COFF format file detected...
639 Symbol Table offset = 230160 ($00038310)
640 Symbol Table contains 1339 symbol entries ($0000053B)
641 The additional header size is 28 bytes ($001C)
642 Magic Number for RUN_HDR = 0x00000107
643 Text Segment Size = 7632 ($00001DD0)
644 Data Segment Size = 222360 ($00036498)
645 BSS Segment Size = 428928 ($00068B80)
646 Starting Address for executable = 0x00802000
647 Start of Text Segment = 0x00802000
648 Start of Data Segment = 0x00803dd0