]> Shamusworld >> Repos - virtualjaguar/blob - src/file.cpp
22f3c8fee3d709645ffe7942fb71a2a74d296e12
[virtualjaguar] / src / file.cpp
1 //
2 // FILE.CPP
3 //
4 // File support
5 // by James L. Hammons
6 // (C) 2010 Underground Software
7 //
8 // JLH = James L. 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[0], buffer[1], jaguarROMSize);
129
130         if (fileType == JST_ROM)
131         {
132                 memcpy(jagMemSpace + 0x800000, buffer, jaguarROMSize);
133                 delete[] buffer;
134                 return true;
135         }
136         else if (fileType == JST_ALPINE)
137         {
138                 // File extension ".ROM": Alpine image that loads/runs at $802000
139                 WriteLog("FILE: Setting up Alpine ROM... Run address: 00802000, length: %08X\n", jaguarROMSize);
140                 memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
141                 memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
142                 delete[] buffer;
143
144 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
145                 // Try setting the vector to say, $1000 and putting an instruction there that loops forever:
146                 // This kludge works! Yeah!
147                 SET32(jaguarMainRAM, 0x10, 0x00001000);
148                 SET16(jaguarMainRAM, 0x1000, 0x60FE);           // Here: bra Here
149                 return true;
150         }
151         else if (fileType == JST_ABS_TYPE1)
152         {
153                 // For ABS type 1, run address == load address
154                 uint32 loadAddress = GET32(buffer, 0x16),
155                         codeSize = GET32(buffer, 0x02) + GET32(buffer, 0x06);
156                 WriteLog("FILE: Setting up homebrew (ABS-1)... Run address: %08X, length: %08X\n", loadAddress, codeSize);
157                 memcpy(jagMemSpace + loadAddress, buffer + 0x24, codeSize);
158                 delete[] buffer;
159                 jaguarRunAddress = loadAddress;
160                 return true;
161         }
162         else if (fileType == JST_ABS_TYPE2)
163         {
164                 uint32 loadAddress = GET32(buffer, 0x28), runAddress = GET32(buffer, 0x24),
165                         codeSize = GET32(buffer, 0x18) + GET32(buffer, 0x1C);
166                 WriteLog("FILE: Setting up homebrew (ABS-2)... Run address: %08X, length: %08X\n", runAddress, codeSize);
167                 memcpy(jagMemSpace + loadAddress, buffer + 0xA8, codeSize);
168                 delete[] buffer;
169                 jaguarRunAddress = runAddress;
170                 return true;
171         }
172         else if (fileType == JST_JAGSERVER)
173         {
174                 uint32 loadAddress = GET32(buffer, 0x22), runAddress = GET32(buffer, 0x2A);
175                 WriteLog("FILE: Setting up homebrew (Jag Server)... Run address: %08X, length: %08X\n", runAddress, jaguarROMSize - 0x2E);
176                 memcpy(jagMemSpace + loadAddress, buffer + 0x2E, jaguarROMSize - 0x2E);
177                 delete[] buffer;
178                 jaguarRunAddress = runAddress;
179                 return true;
180         }
181
182         // We can assume we have JST_NONE at this point. :-P
183         return false;
184 }
185
186 //
187 // "Alpine" file loading
188 // Since the developers were coming after us with torches and pitchforks, we decided to
189 // allow this kind of thing. ;-) But ONLY FOR THE DEVS, DAMMIT! >:-U O_O
190 //
191 bool AlpineLoadFile(char * path)
192 {
193         uint8 * buffer = NULL;
194         jaguarROMSize = JaguarLoadROM(buffer, path);
195
196         if (jaguarROMSize == 0)
197         {
198                 // It's up to the GUI to deal with failure, not us. ;-)
199                 WriteLog("FILE: Could not load Alpine from file \"%s\"...\nAborting load!\n", path);
200                 return false;
201         }
202
203         jaguarMainROMCRC32 = crc32_calcCheckSum(buffer, jaguarROMSize);
204         WriteLog("CRC: %08X\n", (unsigned int)jaguarMainROMCRC32);
205         EepromInit();
206
207         jaguarRunAddress = 0x802000;
208
209         WriteLog("FILE: Setting up Alpine ROM with non-standard length... Run address: 00802000, length: %08X\n", jaguarROMSize);
210
211         memset(jagMemSpace + 0x800000, 0xFF, 0x2000);
212         memcpy(jagMemSpace + 0x802000, buffer, jaguarROMSize);
213         delete[] buffer;
214
215 // Maybe instead of this, we could try requiring the STUBULATOR ROM? Just a thought...
216         // Try setting the vector to say, $1000 and putting an instruction there that loops forever:
217         // This kludge works! Yeah!
218         SET32(jaguarMainRAM, 0x10, 0x00001000);
219         SET16(jaguarMainRAM, 0x1000, 0x60FE);           // Here: bra Here
220
221         return true;
222 }
223
224 //
225 // Get the length of a (possibly) gzipped file
226 //
227 static int gzfilelength(gzFile gd)
228 {
229    int size = 0, length = 0;
230    unsigned char buffer[0x10000];
231
232    gzrewind(gd);
233
234    do
235    {
236       // Read in chunks until EOF
237       size = gzread(gd, buffer, 0x10000);
238
239       if (size <= 0)
240         break;
241
242       length += size;
243    }
244    while (!gzeof(gd));
245
246    gzrewind(gd);
247    return length;
248 }
249
250 //
251 // Compare extension to passed in filename. If equal, return true; otherwise false.
252 //
253 static bool CheckExtension(const char * filename, const char * ext)
254 {
255         // Sanity checking...
256         if ((filename == NULL) || (ext == NULL))
257                 return false;
258
259         const char * filenameExt = strrchr(filename, '.');      // Get the file's extension (if any)
260
261         if (filenameExt == NULL)
262                 return false;
263
264         return (strcasecmp(filenameExt, ext) == 0 ? true : false);
265 }
266
267 //
268 // Get file from .ZIP
269 // Returns the size of the file inside the .ZIP file that we're looking at
270 // NOTE: If the thing we're looking for is found, it allocates it in the passed in buffer.
271 //       Which means we have to deallocate it later.
272 //
273 uint32 GetFileFromZIP(const char * zipFile, FileType type, uint8 * &buffer)
274 {
275 // NOTE: We could easily check for this by discarding anything that's larger than the RAM/ROM
276 //       size of the Jaguar console.
277 #warning "!!! FIX !!! Should have sanity checking for ROM size to prevent buffer overflow!"
278         const char ftStrings[5][32] = { "Software", "EEPROM", "Label", "Box Art", "Controller Overlay" };
279         ZIP * zip = openzip(0, 0, zipFile);
280
281         if (zip == NULL)
282         {
283                 WriteLog("FILE: Could not open file '%s'!\n", zipFile);
284                 return 0;
285         }
286
287         zipent * ze;
288         bool found = false;
289
290         // The order is here is important: If the file is found, we need to short-circuit the
291         // readzip() call because otherwise, 'ze' will be pointing to the wrong file!
292         while (!found && readzip(zip))
293         {
294                 ze = &zip->ent;
295
296                 // Here we simply rely on the file extension to tell the truth, but we know
297                 // that extensions lie like sons-a-bitches. So this is naive, we need to do
298                 // something a little more robust to keep bad things from happening here.
299 #warning "!!! Checking for image by extension can be fooled !!!"
300                 if ((type == FT_LABEL) && (CheckExtension(ze->name, ".png") || CheckExtension(ze->name, ".jpg") || CheckExtension(ze->name, ".gif")))
301                 {
302                         found = true;
303                         WriteLog("FILE: Found image file '%s'.\n", ze->name);
304                 }
305
306                 if ((type == FT_SOFTWARE) && (CheckExtension(ze->name, ".j64")
307                         || CheckExtension(ze->name, ".rom") || CheckExtension(ze->name, ".abs")
308                         || CheckExtension(ze->name, ".cof") || CheckExtension(ze->name, ".jag")))
309                 {
310                         found = true;
311                         WriteLog("FILE: Found software file '%s'.\n", ze->name);
312                 }
313
314                 if ((type == FT_EEPROM) && (CheckExtension(ze->name, ".eep") || CheckExtension(ze->name, ".eeprom")))
315                 {
316                         found = true;
317                         WriteLog("FILE: Found EEPROM file '%s'.\n", ze->name);
318                 }
319         }
320
321         uint32 fileSize = 0;
322
323         if (found)
324         {
325                 WriteLog("FILE: Uncompressing...");
326 // Insert file size sanity check here...
327                 buffer = new uint8[ze->uncompressed_size];
328
329                 if (readuncompresszip(zip, ze, (char *)buffer) == 0)
330                 {
331                         fileSize = ze->uncompressed_size;
332                         WriteLog("success! (%u bytes)\n", fileSize);
333                 }
334                 else
335                 {
336                         delete[] buffer;
337                         buffer = NULL;
338                         WriteLog("FAILED!\n");
339                 }
340         }
341         else
342                 // Didn't find what we're looking for...
343                 WriteLog("FILE: Failed to find file of type %s...\n", ftStrings[type]);
344
345         closezip(zip);
346         return fileSize;
347 }
348
349 //
350 // Parse the file type based upon file size and/or headers.
351 //
352 uint32 ParseFileType(uint8 header1, uint8 header2, uint32 size)
353 {
354         // Check headers first...
355
356         // ABS/COFF type 1
357         if (header1 == 0x60 && header2 == 0x1B)
358                 return JST_ABS_TYPE1;
359
360         // ABS/COFF type 2
361         if (header1 == 0x01 && header2 == 0x50)
362                 return JST_ABS_TYPE2;
363
364         // Jag Server
365         if (header1 == 0x60 && header2 == 0x1A)
366                 return JST_JAGSERVER;
367
368         // And if that fails, try file sizes...
369
370         // If the file size is divisible by 1M, we probably have an regular ROM.
371         // We can also check our CRC32 against the internal ROM database to be sure.
372         if ((size % 1048576) == 0)
373                 return JST_ROM;
374
375         // If the file size + 8192 bytes is divisible by 1M, we probably have an
376         // Alpine format ROM.
377         if (((size + 8192) % 1048576) == 0)
378                 return JST_ALPINE;
379
380         // Headerless crap
381         return JST_NONE;
382 }
383
384 //
385 // Check for universal header
386 //
387 bool HasUniversalHeader(uint8 * rom, uint32 romSize)
388 {
389         // Sanity check
390         if (romSize < 8192)
391                 return false;
392
393         for(int i=0; i<8192; i++)
394                 if (rom[i] != universalCartHeader[i])
395                         return false;
396
397         return true;
398 }
399
400 #if 0
401 // Misc. doco
402
403 /*
404 Stubulator ROM vectors...
405 handler 001 at $00E00008
406 handler 002 at $00E008DE
407 handler 003 at $00E008E2
408 handler 004 at $00E008E6
409 handler 005 at $00E008EA
410 handler 006 at $00E008EE
411 handler 007 at $00E008F2
412 handler 008 at $00E0054A
413 handler 009 at $00E008FA
414 handler 010 at $00000000
415 handler 011 at $00000000
416 handler 012 at $00E008FE
417 handler 013 at $00E00902
418 handler 014 at $00E00906
419 handler 015 at $00E0090A
420 handler 016 at $00E0090E
421 handler 017 at $00E00912
422 handler 018 at $00E00916
423 handler 019 at $00E0091A
424 handler 020 at $00E0091E
425 handler 021 at $00E00922
426 handler 022 at $00E00926
427 handler 023 at $00E0092A
428 handler 024 at $00E0092E
429 handler 025 at $00E0107A
430 handler 026 at $00E0107A
431 handler 027 at $00E0107A
432 handler 028 at $00E008DA
433 handler 029 at $00E0107A
434 handler 030 at $00E0107A
435 handler 031 at $00E0107A
436 handler 032 at $00000000
437
438 Let's try setting up the illegal instruction vector for a stubulated jaguar...
439
440                 SET32(jaguar_mainRam, 0x08, 0x00E008DE);
441                 SET32(jaguar_mainRam, 0x0C, 0x00E008E2);
442                 SET32(jaguar_mainRam, 0x10, 0x00E008E6);        // <-- Should be here (it is)...
443                 SET32(jaguar_mainRam, 0x14, 0x00E008EA);//*/
444
445 /*
446 ABS Format sleuthing (LBUGDEMO.ABS):
447
448 000000  60 1B 00 00 05 0C 00 04 62 C0 00 00 04 28 00 00
449 000010  12 A6 00 00 00 00 00 80 20 00 FF FF 00 80 25 0C
450 000020  00 00 40 00
451
452 DRI-format file detected...
453 Text segment size = 0x0000050c bytes
454 Data segment size = 0x000462c0 bytes
455 BSS Segment size = 0x00000428 bytes
456 Symbol Table size = 0x000012a6 bytes
457 Absolute Address for text segment = 0x00802000
458 Absolute Address for data segment = 0x0080250c
459 Absolute Address for BSS segment = 0x00004000
460
461 (CRZDEMO.ABS):
462 000000  01 50 00 03 00 00 00 00 00 03 83 10 00 00 05 3b
463 000010  00 1c 00 03 00 00 01 07 00 00 1d d0 00 03 64 98
464 000020  00 06 8b 80 00 80 20 00 00 80 20 00 00 80 3d d0
465
466 000030  2e 74 78 74 00 00 00 00 00 80 20 00 00 80 20 00 .txt (+36 bytes)
467 000040  00 00 1d d0 00 00 00 a8 00 00 00 00 00 00 00 00
468 000050  00 00 00 00 00 00 00 20
469 000058  2e 64 74 61 00 00 00 00 00 80 3d d0 00 80 3d d0 .dta (+36 bytes)
470 000068  00 03 64 98 00 00 1e 78 00 00 00 00 00 00 00 00
471 000078  00 00 00 00 00 00 00 40
472 000080  2e 62 73 73 00 00 00 00 00 00 50 00 00 00 50 00 .bss (+36 bytes)
473 000090  00 06 8b 80 00 03 83 10 00 00 00 00 00 00 00 00
474 0000a0  00 00 00 00 00 00 00 80
475
476 Header size is $A8 bytes...
477
478 BSD/COFF format file detected...
479 3 sections specified
480 Symbol Table offset = 230160                            ($00038310)
481 Symbol Table contains 1339 symbol entries       ($0000053B)
482 The additional header size is 28 bytes          ($001C)
483 Magic Number for RUN_HDR = 0x00000107
484 Text Segment Size = 7632                                        ($00001DD0)
485 Data Segment Size = 222360                                      ($00036498)
486 BSS Segment Size = 428928                                       ($00068B80)
487 Starting Address for executable = 0x00802000
488 Start of Text Segment = 0x00802000
489 Start of Data Segment = 0x00803dd0
490 */
491 #endif