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