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
15 // JLH 06/01/2012 Added function to check ZIP file CRCs against file DB
28 #include "universalhdr.h"
32 // Private function prototypes
34 static int gzfilelength(gzFile gd);
35 static bool CheckExtension(const char * filename, const char * ext);
36 //static int ParseFileType(uint8_t header1, uint8_t header2, uint32_t size);
38 // Private variables/enums
42 // Generic ROM loading
44 uint32_t JaguarLoadROM(uint8_t * &rom, char * path)
46 // We really should have some kind of sanity checking for the ROM size here to prevent
47 // a buffer overflow... !!! FIX !!!
48 #warning "!!! FIX !!! Should have sanity checking for ROM size to prevent buffer overflow!"
51 WriteLog("JaguarLoadROM: Attempting to load file '%s'...", path);
52 char * ext = strrchr(path, '.');
54 // No filename extension == YUO FAIL IT (it is loading the file).
55 // This is naive, but it works. But should probably come up with something a little
56 // more robust, to prevent problems with dopes trying to exploit this.
59 WriteLog("FAILED!\n");
63 WriteLog("Succeeded in finding extension (%s)!\n", ext);
64 WriteLog("VJ: Loading \"%s\"...", path);
66 if (strcasecmp(ext, ".zip") == 0)
68 // Handle ZIP file loading here...
69 WriteLog("(ZIPped)...");
71 // uint8_t * buffer = NULL;
72 // romSize = GetFileFromZIP(path, FT_SOFTWARE, buffer);
73 romSize = GetFileFromZIP(path, FT_SOFTWARE, rom);
77 WriteLog("Failed!\n");
81 // memcpy(rom, buffer, romSize);
86 // Handle gzipped files transparently [Adam Green]...
88 gzFile fp = gzopen(path, "rb");
92 WriteLog("Failed!\n");
96 romSize = gzfilelength(fp);
97 rom = new uint8_t[romSize];
98 gzseek(fp, 0, SEEK_SET);
99 gzread(fp, rom, romSize);
103 WriteLog("OK (%i bytes)\n", romSize);
110 // Jaguar file loading
111 // We do a more intelligent file analysis here instead of relying on (possible
112 // false) file extensions which people don't seem to give two shits about
115 bool JaguarLoadFile(char * path)
117 uint8_t * buffer = NULL;
118 jaguarROMSize = JaguarLoadROM(buffer, path);
120 if (jaguarROMSize == 0)
122 // It's up to the GUI to report errors, not us. :-)
123 WriteLog("FILE: Could not load ROM from file \"%s\"...\nAborting load!\n", path);
127 jaguarMainROMCRC32 = crc32_calcCheckSum(buffer, jaguarROMSize);
128 WriteLog("CRC: %08X\n", (unsigned int)jaguarMainROMCRC32);
129 // TODO: Check for EEPROM file in ZIP file. If there is no EEPROM in the user's EEPROM
130 // directory, copy the one from the ZIP file, if it exists.
132 jaguarRunAddress = 0x802000; // For non-BIOS runs, this is true
133 int fileType = ParseFileType(buffer, jaguarROMSize);
134 jaguarCartInserted = false;
136 if (fileType == JST_ROM)
138 jaguarCartInserted = true;
139 memcpy(jagMemSpace + 0x800000, buffer, jaguarROMSize);
140 // Checking something...
141 jaguarRunAddress = GET32(jagMemSpace, 0x800404);
142 WriteLog("FILE: Cartridge run address is reported as $%X...\n", jaguarRunAddress);
146 else if (fileType == JST_ALPINE)
148 // File extension ".ROM": Alpine image that loads/runs at $802000
149 WriteLog("FILE: Setting up Alpine ROM... Run address: 00802000, length: %08X\n", jaguarROMSize);
150 memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
151 memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
154 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
155 // Try setting the vector to say, $1000 and putting an instruction there that loops forever:
156 // This kludge works! Yeah!
157 SET32(jaguarMainRAM, 0x10, 0x00001000);
158 SET16(jaguarMainRAM, 0x1000, 0x60FE); // Here: bra Here
161 else if (fileType == JST_ABS_TYPE1)
163 // For ABS type 1, run address == load address
164 uint32_t loadAddress = GET32(buffer, 0x16),
165 codeSize = GET32(buffer, 0x02) + GET32(buffer, 0x06);
166 WriteLog("FILE: Setting up homebrew (ABS-1)... Run address: %08X, length: %08X\n", loadAddress, codeSize);
167 memcpy(jagMemSpace + loadAddress, buffer + 0x24, codeSize);
169 jaguarRunAddress = loadAddress;
172 else if (fileType == JST_ABS_TYPE2)
174 uint32_t loadAddress = GET32(buffer, 0x28), runAddress = GET32(buffer, 0x24),
175 codeSize = GET32(buffer, 0x18) + GET32(buffer, 0x1C);
176 WriteLog("FILE: Setting up homebrew (ABS-2)... Run address: %08X, length: %08X\n", runAddress, codeSize);
177 memcpy(jagMemSpace + loadAddress, buffer + 0xA8, codeSize);
179 jaguarRunAddress = runAddress;
182 // NB: This is *wrong*
184 Basically, if there is no "JAG" at position $1C, then the long there is the load/start
185 address in LITTLE ENDIAN.
186 If "JAG" is present, the the next character ("R" or "L") determines the size of the
187 JagServer command (2 bytes vs. 4). Following that are the commands themselves;
188 typically it will either be 2 (load) or 3 (load & run). Command headers go like so:
207 10: (Poll for commands)
209 12: (Load & run user program)
210 filname, terminated with NULL
215 else if (fileType == JST_JAGSERVER)
217 // This kind of shiaut should be in the detection code below...
218 // (and now it is! :-)
219 // if (buffer[0x1C] == 'J' && buffer[0x1D] == 'A' && buffer[0x1E] == 'G')
221 // Still need to do some checking here for type 2 vs. type 3. This assumes 3
222 // Also, JAGR vs. JAGL (word command size vs. long command size)
223 uint32_t loadAddress = GET32(buffer, 0x22), runAddress = GET32(buffer, 0x2A);
224 WriteLog("FILE: Setting up homebrew (Jag Server)... Run address: $%X, length: $%X\n", runAddress, jaguarROMSize - 0x2E);
225 memcpy(jagMemSpace + loadAddress, buffer + 0x2E, jaguarROMSize - 0x2E);
227 jaguarRunAddress = runAddress;
229 // Hmm. Is this kludge necessary?
230 SET32(jaguarMainRAM, 0x10, 0x00001000); // Set Exception #4 (Illegal Instruction)
231 SET16(jaguarMainRAM, 0x1000, 0x60FE); // Here: bra Here
235 // else // Special WTFOMGBBQ type here...
237 // uint32_t loadAddress = (buffer[0x1F] << 24) | (buffer[0x1E] << 16) | (buffer[0x1D] << 8) | buffer[0x1C];
238 // WriteLog("FILE: Setting up homebrew (GEMDOS WTFOMGBBQ type)... Run address: $%X, length: $%X\n", loadAddress, jaguarROMSize - 0x20);
239 // memcpy(jagMemSpace + loadAddress, buffer + 0x20, jaguarROMSize - 0x20);
241 // jaguarRunAddress = loadAddress;
245 else if (fileType == JST_WTFOMGBBQ)
247 uint32_t loadAddress = (buffer[0x1F] << 24) | (buffer[0x1E] << 16) | (buffer[0x1D] << 8) | buffer[0x1C];
248 WriteLog("FILE: Setting up homebrew (GEMDOS WTFOMGBBQ type)... Run address: $%X, length: $%X\n", loadAddress, jaguarROMSize - 0x20);
249 memcpy(jagMemSpace + loadAddress, buffer + 0x20, jaguarROMSize - 0x20);
251 jaguarRunAddress = loadAddress;
255 // We can assume we have JST_NONE at this point. :-P
256 WriteLog("FILE: Failed to load headerless file.\n");
262 // "Alpine" file loading
263 // Since the developers were coming after us with torches and pitchforks, we
264 // decided to allow this kind of thing. ;-) But ONLY FOR THE DEVS, DAMMIT! >:-U
267 bool AlpineLoadFile(char * path)
269 uint8_t * buffer = NULL;
270 jaguarROMSize = JaguarLoadROM(buffer, path);
272 if (jaguarROMSize == 0)
274 // It's up to the GUI to deal with failure, not us. ;-)
275 WriteLog("FILE: Could not load Alpine from file \"%s\"...\nAborting load!\n", path);
279 jaguarMainROMCRC32 = crc32_calcCheckSum(buffer, jaguarROMSize);
280 WriteLog("CRC: %08X\n", (unsigned int)jaguarMainROMCRC32);
283 jaguarRunAddress = 0x802000;
285 WriteLog("FILE: Setting up Alpine ROM with non-standard length... Run address: 00802000, length: %08X\n", jaguarROMSize);
287 memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
288 memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
291 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
292 // Try setting the vector to say, $1000 and putting an instruction there
293 // that loops forever:
294 // This kludge works! Yeah!
295 SET32(jaguarMainRAM, 0x10, 0x00001000); // Set Exception #4 (Illegal Instruction)
296 SET16(jaguarMainRAM, 0x1000, 0x60FE); // Here: bra Here
303 // Get the length of a (possibly) gzipped file
305 static int gzfilelength(gzFile gd)
307 int size = 0, length = 0;
308 unsigned char buffer[0x10000];
314 // Read in chunks until EOF
315 size = gzread(gd, buffer, 0x10000);
330 // Compare extension to passed in filename. If equal, return true; otherwise false.
332 static bool CheckExtension(const uint8_t * filename, const char * ext)
334 // Sanity checking...
335 if ((filename == NULL) || (ext == NULL))
338 const char * filenameExt = strrchr((const char *)filename, '.'); // Get the file's extension (if any)
340 if (filenameExt == NULL)
343 return (strcasecmp(filenameExt, ext) == 0 ? true : false);
348 // Get file from .ZIP
349 // Returns the size of the file inside the .ZIP file that we're looking at
350 // NOTE: If the thing we're looking for is found, it allocates it in the passed in buffer.
351 // Which means we have to deallocate it later.
353 uint32_t GetFileFromZIP(const char * zipFile, FileType type, uint8_t * &buffer)
355 // NOTE: We could easily check for this by discarding anything that's larger than the RAM/ROM
356 // size of the Jaguar console.
357 #warning "!!! FIX !!! Should have sanity checking for ROM size to prevent buffer overflow!"
358 const char ftStrings[5][32] = { "Software", "EEPROM", "Label", "Box Art", "Controller Overlay" };
359 // ZIP * zip = openzip(0, 0, zipFile);
360 FILE * zip = fopen(zipFile, "rb");
364 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
372 // The order is here is important: If the file is found, we need to short-circuit the
373 // readzip() call because otherwise, 'ze' will be pointing to the wrong file!
374 // while (!found && readzip(zip))
375 while (!found && GetZIPHeader(zip, ze))
379 // Here we simply rely on the file extension to tell the truth, but we know
380 // that extensions lie like sons-a-bitches. So this is naive, we need to do
381 // something a little more robust to keep bad things from happening here.
382 #warning "!!! Checking for image by extension can be fooled !!!"
383 if ((type == FT_LABEL) && (CheckExtension(ze.filename, ".png") || CheckExtension(ze.filename, ".jpg") || CheckExtension(ze.filename, ".gif")))
386 WriteLog("FILE: Found image file '%s'.\n", ze.filename);
389 if ((type == FT_SOFTWARE) && (CheckExtension(ze.filename, ".j64")
390 || CheckExtension(ze.filename, ".rom") || CheckExtension(ze.filename, ".abs")
391 || CheckExtension(ze.filename, ".cof") || CheckExtension(ze.filename, ".coff")
392 || CheckExtension(ze.filename, ".jag")))
395 WriteLog("FILE: Found software file '%s'.\n", ze.filename);
398 if ((type == FT_EEPROM) && (CheckExtension(ze.filename, ".eep") || CheckExtension(ze.filename, ".eeprom")))
401 WriteLog("FILE: Found EEPROM file '%s'.\n", ze.filename);
405 fseek(zip, ze.compressedSize, SEEK_CUR);
408 uint32_t fileSize = 0;
412 WriteLog("FILE: Uncompressing...");
413 // Insert file size sanity check here...
414 buffer = new uint8_t[ze.uncompressedSize];
416 // if (readuncompresszip(zip, ze.compressedSize, buffer) == 0)
417 // if (UncompressFileFromZIP(zip, ze.compressedSize, buffer) == 0)
418 if (UncompressFileFromZIP(zip, ze, buffer) == 0)
420 fileSize = ze.uncompressedSize;
421 WriteLog("success! (%u bytes)\n", fileSize);
427 WriteLog("FAILED!\n");
431 // Didn't find what we're looking for...
432 WriteLog("FILE: Failed to find file of type %s...\n", ftStrings[type]);
440 uint32_t GetFileDBIdentityFromZIP(const char * zipFile)
442 FILE * zip = fopen(zipFile, "rb");
446 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
452 // Loop through all files in the zip file under consideration
453 while (GetZIPHeader(zip, ze))
455 // & loop through all known CRC32s in our file DB to see if it's there!
458 while (romList[index].crc32 != 0xFFFFFF)
460 if (romList[index].crc32 == ze.crc32)
469 // We didn't find it, so skip the compressed data...
470 fseek(zip, ze.compressedSize, SEEK_CUR);
478 bool FindFileInZIPWithCRC32(const char * zipFile, uint32_t crc)
480 FILE * zip = fopen(zipFile, "rb");
484 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
490 // Loop through all files in the zip file under consideration
491 while (GetZIPHeader(zip, ze))
499 fseek(zip, ze.compressedSize, SEEK_CUR);
508 // Parse the file type based upon file size and/or headers.
510 uint32_t ParseFileType(uint8_t * buffer, uint32_t size)
512 // Check headers first...
515 if (buffer[0] == 0x60 && buffer[1] == 0x1B)
516 return JST_ABS_TYPE1;
519 if (buffer[0] == 0x01 && buffer[1] == 0x50)
520 return JST_ABS_TYPE2;
522 // Jag Server & other old shite
523 if (buffer[0] == 0x60 && buffer[1] == 0x1A)
525 if (buffer[0x1C] == 'J' && buffer[0x1D] == 'A' && buffer[0x1E] == 'G')
526 return JST_JAGSERVER;
528 return JST_WTFOMGBBQ;
531 // And if that fails, try file sizes...
533 // If the file size is divisible by 1M, we probably have an regular ROM.
534 // We can also check our CRC32 against the internal ROM database to be sure.
535 // (We also check for the Memory Track cartridge size here as well...)
536 if ((size % 1048576) == 0 || size == 131072)
539 // If the file size + 8192 bytes is divisible by 1M, we probably have an
540 // Alpine format ROM.
541 if (((size + 8192) % 1048576) == 0)
549 // Check for universal header
551 bool HasUniversalHeader(uint8_t * rom, uint32_t romSize)
557 for(int i=0; i<8192; i++)
558 if (rom[i] != universalCartHeader[i])
568 Stubulator ROM vectors...
569 handler 001 at $00E00008
570 handler 002 at $00E008DE
571 handler 003 at $00E008E2
572 handler 004 at $00E008E6
573 handler 005 at $00E008EA
574 handler 006 at $00E008EE
575 handler 007 at $00E008F2
576 handler 008 at $00E0054A
577 handler 009 at $00E008FA
578 handler 010 at $00000000
579 handler 011 at $00000000
580 handler 012 at $00E008FE
581 handler 013 at $00E00902
582 handler 014 at $00E00906
583 handler 015 at $00E0090A
584 handler 016 at $00E0090E
585 handler 017 at $00E00912
586 handler 018 at $00E00916
587 handler 019 at $00E0091A
588 handler 020 at $00E0091E
589 handler 021 at $00E00922
590 handler 022 at $00E00926
591 handler 023 at $00E0092A
592 handler 024 at $00E0092E
593 handler 025 at $00E0107A
594 handler 026 at $00E0107A
595 handler 027 at $00E0107A
596 handler 028 at $00E008DA
597 handler 029 at $00E0107A
598 handler 030 at $00E0107A
599 handler 031 at $00E0107A
600 handler 032 at $00000000
602 Let's try setting up the illegal instruction vector for a stubulated jaguar...
604 SET32(jaguar_mainRam, 0x08, 0x00E008DE);
605 SET32(jaguar_mainRam, 0x0C, 0x00E008E2);
606 SET32(jaguar_mainRam, 0x10, 0x00E008E6); // <-- Should be here (it is)...
607 SET32(jaguar_mainRam, 0x14, 0x00E008EA);//*/
610 ABS Format sleuthing (LBUGDEMO.ABS):
612 000000 60 1B 00 00 05 0C 00 04 62 C0 00 00 04 28 00 00
613 000010 12 A6 00 00 00 00 00 80 20 00 FF FF 00 80 25 0C
616 DRI-format file detected...
617 Text segment size = 0x0000050c bytes
618 Data segment size = 0x000462c0 bytes
619 BSS Segment size = 0x00000428 bytes
620 Symbol Table size = 0x000012a6 bytes
621 Absolute Address for text segment = 0x00802000
622 Absolute Address for data segment = 0x0080250c
623 Absolute Address for BSS segment = 0x00004000
626 000000 01 50 00 03 00 00 00 00 00 03 83 10 00 00 05 3b
627 000010 00 1c 00 03 00 00 01 07 00 00 1d d0 00 03 64 98
628 000020 00 06 8b 80 00 80 20 00 00 80 20 00 00 80 3d d0
630 000030 2e 74 78 74 00 00 00 00 00 80 20 00 00 80 20 00 .txt (+36 bytes)
631 000040 00 00 1d d0 00 00 00 a8 00 00 00 00 00 00 00 00
632 000050 00 00 00 00 00 00 00 20
633 000058 2e 64 74 61 00 00 00 00 00 80 3d d0 00 80 3d d0 .dta (+36 bytes)
634 000068 00 03 64 98 00 00 1e 78 00 00 00 00 00 00 00 00
635 000078 00 00 00 00 00 00 00 40
636 000080 2e 62 73 73 00 00 00 00 00 00 50 00 00 00 50 00 .bss (+36 bytes)
637 000090 00 06 8b 80 00 03 83 10 00 00 00 00 00 00 00 00
638 0000a0 00 00 00 00 00 00 00 80
640 Header size is $A8 bytes...
642 BSD/COFF format file detected...
644 Symbol Table offset = 230160 ($00038310)
645 Symbol Table contains 1339 symbol entries ($0000053B)
646 The additional header size is 28 bytes ($001C)
647 Magic Number for RUN_HDR = 0x00000107
648 Text Segment Size = 7632 ($00001DD0)
649 Data Segment Size = 222360 ($00036498)
650 BSS Segment Size = 428928 ($00068B80)
651 Starting Address for executable = 0x00802000
652 Start of Text Segment = 0x00802000
653 Start of Data Segment = 0x00803dd0