]> Shamusworld >> Repos - virtualjaguar/blob - src/file.cpp
Minor tweaks to Makefiles, added command line switches for most options.
[virtualjaguar] / src / file.cpp
1 //
2 // FILE.CPP
3 //
4 // File support
5 // by James Hammons
6 // (C) 2010 Underground Software
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // Who  When        What
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 //
15
16 #include "file.h"
17
18 #include <stdarg.h>
19 #include <string.h>
20 #include "crc32.h"
21 #include "eeprom.h"
22 #include "jaguar.h"
23 #include "log.h"
24 #include "memory.h"
25 #include "universalhdr.h"
26 #include "unzip.h"
27 #include "zlib.h"
28
29 // Private function prototypes
30
31 static int gzfilelength(gzFile gd);
32 static bool CheckExtension(const char * filename, const char * ext);
33 //static int ParseFileType(uint8 header1, uint8 header2, uint32 size);
34
35 // Private variables/enums
36
37
38 //
39 // Generic ROM loading
40 //
41 uint32 JaguarLoadROM(uint8 * &rom, char * path)
42 {
43 // We really should have some kind of sanity checking for the ROM size here to prevent
44 // a buffer overflow... !!! FIX !!!
45 #warning "!!! FIX !!! Should have sanity checking for ROM size to prevent buffer overflow!"
46         uint32 romSize = 0;
47
48         WriteLog("JaguarLoadROM: Attempting to load file '%s'...", path);
49         char * ext = strrchr(path, '.');
50
51         // No filename extension == YUO FAIL IT (it is loading the file).
52         // This is naive, but it works. But should probably come up with something a little
53         // more robust, to prevent problems with dopes trying to exploit this.
54         if (ext == NULL)
55         {
56                 WriteLog("FAILED!\n");
57                 return 0;
58         }
59
60         WriteLog("Succeeded in finding extension (%s)!\n", ext);
61         WriteLog("VJ: Loading \"%s\"...", path);
62
63         if (strcasecmp(ext, ".zip") == 0)
64         {
65                 // Handle ZIP file loading here...
66                 WriteLog("(ZIPped)...");
67
68 //              uint8_t * buffer = NULL;
69 //              romSize = GetFileFromZIP(path, FT_SOFTWARE, buffer);
70                 romSize = GetFileFromZIP(path, FT_SOFTWARE, rom);
71
72                 if (romSize == 0)
73                 {
74                         WriteLog("Failed!\n");
75                         return 0;
76                 }
77
78 //              memcpy(rom, buffer, romSize);
79 //              delete[] buffer;
80         }
81         else
82         {
83                 // Handle gzipped files transparently [Adam Green]...
84
85                 gzFile fp = gzopen(path, "rb");
86
87                 if (fp == NULL)
88                 {
89                         WriteLog("Failed!\n");
90                         return 0;
91                 }
92
93                 romSize = gzfilelength(fp);
94                 rom = new uint8[romSize];
95                 gzseek(fp, 0, SEEK_SET);
96                 gzread(fp, rom, romSize);
97                 gzclose(fp);
98         }
99
100         WriteLog("OK (%i bytes)\n", romSize);
101
102         return romSize;
103 }
104
105 //
106 // Jaguar file loading
107 // We do a more intelligent file analysis here instead of relying on (possible false)
108 // file extensions which people don't seem to give two shits about anyway. :-(
109 //
110 bool JaguarLoadFile(char * path)
111 {
112         uint8 * buffer = NULL;
113         jaguarROMSize = JaguarLoadROM(buffer, path);
114
115         if (jaguarROMSize == 0)
116         {
117                 // It's up to the GUI to report errors, not us. :-)
118                 WriteLog("FILE: Could not load ROM from file \"%s\"...\nAborting load!\n", path);
119                 return false;
120         }
121
122         jaguarMainROMCRC32 = crc32_calcCheckSum(buffer, jaguarROMSize);
123         WriteLog("CRC: %08X\n", (unsigned int)jaguarMainROMCRC32);
124 // TODO: Check for EEPROM file in ZIP file. If there is no EEPROM in the user's EEPROM
125 //       directory, copy the one from the ZIP file, if it exists.
126         EepromInit();
127         jaguarRunAddress = 0x802000;                                    // For non-BIOS runs, this is true
128         int fileType = ParseFileType(buffer, jaguarROMSize);
129         jaguarCartInserted = false;
130
131         if (fileType == JST_ROM)
132         {
133                 jaguarCartInserted = true;
134                 memcpy(jagMemSpace + 0x800000, buffer, jaguarROMSize);
135                 delete[] buffer;
136                 return true;
137         }
138         else if (fileType == JST_ALPINE)
139         {
140                 // File extension ".ROM": Alpine image that loads/runs at $802000
141                 WriteLog("FILE: Setting up Alpine ROM... Run address: 00802000, length: %08X\n", jaguarROMSize);
142                 memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
143                 memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
144                 delete[] buffer;
145
146 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
147                 // Try setting the vector to say, $1000 and putting an instruction there that loops forever:
148                 // This kludge works! Yeah!
149                 SET32(jaguarMainRAM, 0x10, 0x00001000);
150                 SET16(jaguarMainRAM, 0x1000, 0x60FE);           // Here: bra Here
151                 return true;
152         }
153         else if (fileType == JST_ABS_TYPE1)
154         {
155                 // For ABS type 1, run address == load address
156                 uint32 loadAddress = GET32(buffer, 0x16),
157                         codeSize = GET32(buffer, 0x02) + GET32(buffer, 0x06);
158                 WriteLog("FILE: Setting up homebrew (ABS-1)... Run address: %08X, length: %08X\n", loadAddress, codeSize);
159                 memcpy(jagMemSpace + loadAddress, buffer + 0x24, codeSize);
160                 delete[] buffer;
161                 jaguarRunAddress = loadAddress;
162                 return true;
163         }
164         else if (fileType == JST_ABS_TYPE2)
165         {
166                 uint32 loadAddress = GET32(buffer, 0x28), runAddress = GET32(buffer, 0x24),
167                         codeSize = GET32(buffer, 0x18) + GET32(buffer, 0x1C);
168                 WriteLog("FILE: Setting up homebrew (ABS-2)... Run address: %08X, length: %08X\n", runAddress, codeSize);
169                 memcpy(jagMemSpace + loadAddress, buffer + 0xA8, codeSize);
170                 delete[] buffer;
171                 jaguarRunAddress = runAddress;
172                 return true;
173         }
174         // NB: This is *wrong*
175         /*
176         Basically, if there is no "JAG" at position $1C, then the long there is the load/start
177         address in LITTLE ENDIAN.
178         If "JAG" is present, the the next character ("R" or "L") determines the size of the
179         JagServer command (2 bytes vs. 4). Following that are the commands themselves;
180         typically it will either be 2 (load) or 3 (load & run). Command headers go like so:
181         2:
182         Load address (long)
183         Length (long)
184         payload
185         3:
186         Load address (long)
187         Length (long)
188         Run address (long)
189         payload
190         5: (Reset)
191         [command only]
192         7: (Run at address)
193         Run address (long)
194         [no payload]
195         9: (Clear memory)
196         Start address (long)
197         End address (long)
198         [no payload]
199         10: (Poll for commands)
200         [command only]
201         12: (Load & run user program)
202         filname, terminated with NULL
203         [no payload]
204         $FFFF: (Halt)
205         [no payload]
206         */
207         else if (fileType == JST_JAGSERVER)
208         {
209                 // This kind of shiaut should be in the detection code below...
210                 // (and now it is! :-)
211 //              if (buffer[0x1C] == 'J' && buffer[0x1D] == 'A' && buffer[0x1E] == 'G')
212 //              {
213                         // Still need to do some checking here for type 2 vs. type 3. This assumes 3
214                         // Also, JAGR vs. JAGL (word command size vs. long command size)
215                         uint32 loadAddress = GET32(buffer, 0x22), runAddress = GET32(buffer, 0x2A);
216                         WriteLog("FILE: Setting up homebrew (Jag Server)... Run address: $%X, length: $%X\n", runAddress, jaguarROMSize - 0x2E);
217                         memcpy(jagMemSpace + loadAddress, buffer + 0x2E, jaguarROMSize - 0x2E);
218                         delete[] buffer;
219                         jaguarRunAddress = runAddress;
220                         return true;
221 //              }
222 //              else // Special WTFOMGBBQ type here...
223 //              {
224 //                      uint32_t loadAddress = (buffer[0x1F] << 24) | (buffer[0x1E] << 16) | (buffer[0x1D] << 8) | buffer[0x1C];
225 //                      WriteLog("FILE: Setting up homebrew (GEMDOS WTFOMGBBQ type)... Run address: $%X, length: $%X\n", loadAddress, jaguarROMSize - 0x20);
226 //                      memcpy(jagMemSpace + loadAddress, buffer + 0x20, jaguarROMSize - 0x20);
227 //                      delete[] buffer;
228 //                      jaguarRunAddress = loadAddress;
229 //                      return true;
230 //              }
231         }
232         else if (fileType == JST_WTFOMGBBQ)
233         {
234                 uint32_t loadAddress = (buffer[0x1F] << 24) | (buffer[0x1E] << 16) | (buffer[0x1D] << 8) | buffer[0x1C];
235                 WriteLog("FILE: Setting up homebrew (GEMDOS WTFOMGBBQ type)... Run address: $%X, length: $%X\n", loadAddress, jaguarROMSize - 0x20);
236                 memcpy(jagMemSpace + loadAddress, buffer + 0x20, jaguarROMSize - 0x20);
237                 delete[] buffer;
238                 jaguarRunAddress = loadAddress;
239                 return true;
240         }
241
242         // We can assume we have JST_NONE at this point. :-P
243         return false;
244 }
245
246 //
247 // "Alpine" file loading
248 // Since the developers were coming after us with torches and pitchforks, we decided to
249 // allow this kind of thing. ;-) But ONLY FOR THE DEVS, DAMMIT! >:-U O_O
250 //
251 bool AlpineLoadFile(char * path)
252 {
253         uint8 * buffer = NULL;
254         jaguarROMSize = JaguarLoadROM(buffer, path);
255
256         if (jaguarROMSize == 0)
257         {
258                 // It's up to the GUI to deal with failure, not us. ;-)
259                 WriteLog("FILE: Could not load Alpine from file \"%s\"...\nAborting load!\n", path);
260                 return false;
261         }
262
263         jaguarMainROMCRC32 = crc32_calcCheckSum(buffer, jaguarROMSize);
264         WriteLog("CRC: %08X\n", (unsigned int)jaguarMainROMCRC32);
265         EepromInit();
266
267         jaguarRunAddress = 0x802000;
268
269         WriteLog("FILE: Setting up Alpine ROM with non-standard length... Run address: 00802000, length: %08X\n", jaguarROMSize);
270
271         memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
272         memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
273         delete[] buffer;
274
275 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
276         // Try setting the vector to say, $1000 and putting an instruction there that loops forever:
277         // This kludge works! Yeah!
278         SET32(jaguarMainRAM, 0x10, 0x00001000);         // Set Exception #4 (Illegal Instruction)
279         SET16(jaguarMainRAM, 0x1000, 0x60FE);           // Here: bra Here
280
281         return true;
282 }
283
284 //
285 // Get the length of a (possibly) gzipped file
286 //
287 static int gzfilelength(gzFile gd)
288 {
289    int size = 0, length = 0;
290    unsigned char buffer[0x10000];
291
292    gzrewind(gd);
293
294    do
295    {
296       // Read in chunks until EOF
297       size = gzread(gd, buffer, 0x10000);
298
299       if (size <= 0)
300         break;
301
302       length += size;
303    }
304    while (!gzeof(gd));
305
306    gzrewind(gd);
307    return length;
308 }
309
310 //
311 // Compare extension to passed in filename. If equal, return true; otherwise false.
312 //
313 static bool CheckExtension(const char * filename, const char * ext)
314 {
315         // Sanity checking...
316         if ((filename == NULL) || (ext == NULL))
317                 return false;
318
319         const char * filenameExt = strrchr(filename, '.');      // Get the file's extension (if any)
320
321         if (filenameExt == NULL)
322                 return false;
323
324         return (strcasecmp(filenameExt, ext) == 0 ? true : false);
325 }
326
327 //
328 // Get file from .ZIP
329 // Returns the size of the file inside the .ZIP file that we're looking at
330 // NOTE: If the thing we're looking for is found, it allocates it in the passed in buffer.
331 //       Which means we have to deallocate it later.
332 //
333 uint32 GetFileFromZIP(const char * zipFile, FileType type, uint8 * &buffer)
334 {
335 // NOTE: We could easily check for this by discarding anything that's larger than the RAM/ROM
336 //       size of the Jaguar console.
337 #warning "!!! FIX !!! Should have sanity checking for ROM size to prevent buffer overflow!"
338         const char ftStrings[5][32] = { "Software", "EEPROM", "Label", "Box Art", "Controller Overlay" };
339         ZIP * zip = openzip(0, 0, zipFile);
340
341         if (zip == NULL)
342         {
343                 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
344                 return 0;
345         }
346
347         zipent * ze;
348         bool found = false;
349
350         // The order is here is important: If the file is found, we need to short-circuit the
351         // readzip() call because otherwise, 'ze' will be pointing to the wrong file!
352         while (!found && readzip(zip))
353         {
354                 ze = &zip->ent;
355
356                 // Here we simply rely on the file extension to tell the truth, but we know
357                 // that extensions lie like sons-a-bitches. So this is naive, we need to do
358                 // something a little more robust to keep bad things from happening here.
359 #warning "!!! Checking for image by extension can be fooled !!!"
360                 if ((type == FT_LABEL) && (CheckExtension(ze->name, ".png") || CheckExtension(ze->name, ".jpg") || CheckExtension(ze->name, ".gif")))
361                 {
362                         found = true;
363                         WriteLog("FILE: Found image file '%s'.\n", ze->name);
364                 }
365
366                 if ((type == FT_SOFTWARE) && (CheckExtension(ze->name, ".j64")
367                         || CheckExtension(ze->name, ".rom") || CheckExtension(ze->name, ".abs")
368                         || CheckExtension(ze->name, ".cof") || CheckExtension(ze->name, ".coff")
369                         || CheckExtension(ze->name, ".jag")))
370                 {
371                         found = true;
372                         WriteLog("FILE: Found software file '%s'.\n", ze->name);
373                 }
374
375                 if ((type == FT_EEPROM) && (CheckExtension(ze->name, ".eep") || CheckExtension(ze->name, ".eeprom")))
376                 {
377                         found = true;
378                         WriteLog("FILE: Found EEPROM file '%s'.\n", ze->name);
379                 }
380         }
381
382         uint32 fileSize = 0;
383
384         if (found)
385         {
386                 WriteLog("FILE: Uncompressing...");
387 // Insert file size sanity check here...
388                 buffer = new uint8[ze->uncompressed_size];
389
390                 if (readuncompresszip(zip, ze, (char *)buffer) == 0)
391                 {
392                         fileSize = ze->uncompressed_size;
393                         WriteLog("success! (%u bytes)\n", fileSize);
394                 }
395                 else
396                 {
397                         delete[] buffer;
398                         buffer = NULL;
399                         WriteLog("FAILED!\n");
400                 }
401         }
402         else
403                 // Didn't find what we're looking for...
404                 WriteLog("FILE: Failed to find file of type %s...\n", ftStrings[type]);
405
406         closezip(zip);
407         return fileSize;
408 }
409
410 //
411 // Parse the file type based upon file size and/or headers.
412 //
413 uint32 ParseFileType(uint8_t * buffer, uint32 size)
414 {
415         // Check headers first...
416
417         // ABS/COFF type 1
418         if (buffer[0] == 0x60 && buffer[1] == 0x1B)
419                 return JST_ABS_TYPE1;
420
421         // ABS/COFF type 2
422         if (buffer[0] == 0x01 && buffer[1] == 0x50)
423                 return JST_ABS_TYPE2;
424
425         // Jag Server & other old shite
426         if (buffer[0] == 0x60 && buffer[1] == 0x1A)
427         {
428                 if (buffer[0x1C] == 'J' && buffer[0x1D] == 'A' && buffer[0x1E] == 'G')
429                         return JST_JAGSERVER;
430                 else
431                         return JST_WTFOMGBBQ;
432         }
433
434         // And if that fails, try file sizes...
435
436         // If the file size is divisible by 1M, we probably have an regular ROM.
437         // We can also check our CRC32 against the internal ROM database to be sure.
438         // (We also check for the Memory Track cartridge size here as well...)
439         if ((size % 1048576) == 0 || size == 131072)
440                 return JST_ROM;
441
442         // If the file size + 8192 bytes is divisible by 1M, we probably have an
443         // Alpine format ROM.
444         if (((size + 8192) % 1048576) == 0)
445                 return JST_ALPINE;
446
447         // Headerless crap
448         return JST_NONE;
449 }
450
451 //
452 // Check for universal header
453 //
454 bool HasUniversalHeader(uint8 * rom, uint32 romSize)
455 {
456         // Sanity check
457         if (romSize < 8192)
458                 return false;
459
460         for(int i=0; i<8192; i++)
461                 if (rom[i] != universalCartHeader[i])
462                         return false;
463
464         return true;
465 }
466
467 #if 0
468 // Misc. doco
469
470 /*
471 Stubulator ROM vectors...
472 handler 001 at $00E00008
473 handler 002 at $00E008DE
474 handler 003 at $00E008E2
475 handler 004 at $00E008E6
476 handler 005 at $00E008EA
477 handler 006 at $00E008EE
478 handler 007 at $00E008F2
479 handler 008 at $00E0054A
480 handler 009 at $00E008FA
481 handler 010 at $00000000
482 handler 011 at $00000000
483 handler 012 at $00E008FE
484 handler 013 at $00E00902
485 handler 014 at $00E00906
486 handler 015 at $00E0090A
487 handler 016 at $00E0090E
488 handler 017 at $00E00912
489 handler 018 at $00E00916
490 handler 019 at $00E0091A
491 handler 020 at $00E0091E
492 handler 021 at $00E00922
493 handler 022 at $00E00926
494 handler 023 at $00E0092A
495 handler 024 at $00E0092E
496 handler 025 at $00E0107A
497 handler 026 at $00E0107A
498 handler 027 at $00E0107A
499 handler 028 at $00E008DA
500 handler 029 at $00E0107A
501 handler 030 at $00E0107A
502 handler 031 at $00E0107A
503 handler 032 at $00000000
504
505 Let's try setting up the illegal instruction vector for a stubulated jaguar...
506
507                 SET32(jaguar_mainRam, 0x08, 0x00E008DE);
508                 SET32(jaguar_mainRam, 0x0C, 0x00E008E2);
509                 SET32(jaguar_mainRam, 0x10, 0x00E008E6);        // <-- Should be here (it is)...
510                 SET32(jaguar_mainRam, 0x14, 0x00E008EA);//*/
511
512 /*
513 ABS Format sleuthing (LBUGDEMO.ABS):
514
515 000000  60 1B 00 00 05 0C 00 04 62 C0 00 00 04 28 00 00
516 000010  12 A6 00 00 00 00 00 80 20 00 FF FF 00 80 25 0C
517 000020  00 00 40 00
518
519 DRI-format file detected...
520 Text segment size = 0x0000050c bytes
521 Data segment size = 0x000462c0 bytes
522 BSS Segment size = 0x00000428 bytes
523 Symbol Table size = 0x000012a6 bytes
524 Absolute Address for text segment = 0x00802000
525 Absolute Address for data segment = 0x0080250c
526 Absolute Address for BSS segment = 0x00004000
527
528 (CRZDEMO.ABS):
529 000000  01 50 00 03 00 00 00 00 00 03 83 10 00 00 05 3b
530 000010  00 1c 00 03 00 00 01 07 00 00 1d d0 00 03 64 98
531 000020  00 06 8b 80 00 80 20 00 00 80 20 00 00 80 3d d0
532
533 000030  2e 74 78 74 00 00 00 00 00 80 20 00 00 80 20 00 .txt (+36 bytes)
534 000040  00 00 1d d0 00 00 00 a8 00 00 00 00 00 00 00 00
535 000050  00 00 00 00 00 00 00 20
536 000058  2e 64 74 61 00 00 00 00 00 80 3d d0 00 80 3d d0 .dta (+36 bytes)
537 000068  00 03 64 98 00 00 1e 78 00 00 00 00 00 00 00 00
538 000078  00 00 00 00 00 00 00 40
539 000080  2e 62 73 73 00 00 00 00 00 00 50 00 00 00 50 00 .bss (+36 bytes)
540 000090  00 06 8b 80 00 03 83 10 00 00 00 00 00 00 00 00
541 0000a0  00 00 00 00 00 00 00 80
542
543 Header size is $A8 bytes...
544
545 BSD/COFF format file detected...
546 3 sections specified
547 Symbol Table offset = 230160                            ($00038310)
548 Symbol Table contains 1339 symbol entries       ($0000053B)
549 The additional header size is 28 bytes          ($001C)
550 Magic Number for RUN_HDR = 0x00000107
551 Text Segment Size = 7632                                        ($00001DD0)
552 Data Segment Size = 222360                                      ($00036498)
553 BSS Segment Size = 428928                                       ($00068B80)
554 Starting Address for executable = 0x00802000
555 Start of Text Segment = 0x00802000
556 Start of Data Segment = 0x00803dd0
557 */
558 #endif