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