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