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