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