Merge pull request #4 from vobe/master
[picodrive.git] / pico / cart.c
1 /*\r
2  * PicoDrive\r
3  * (c) Copyright Dave, 2004\r
4  * (C) notaz, 2006-2010\r
5  *\r
6  * This work is licensed under the terms of MAME license.\r
7  * See COPYING file in the top-level directory.\r
8  */\r
9 \r
10 #include "pico_int.h"\r
11 #include "../zlib/zlib.h"\r
12 #include "../cpu/debug.h"\r
13 #include "../unzip/unzip.h"\r
14 #include "../unzip/unzip_stream.h"\r
15 \r
16 \r
17 static int rom_alloc_size;\r
18 static const char *rom_exts[] = { "bin", "gen", "smd", "iso", "sms", "gg", "sg" };\r
19 \r
20 void (*PicoCartUnloadHook)(void);\r
21 void (*PicoCartMemSetup)(void);\r
22 \r
23 void (*PicoCartLoadProgressCB)(int percent) = NULL;\r
24 void (*PicoCDLoadProgressCB)(const char *fname, int percent) = NULL; // handled in Pico/cd/cd_file.c\r
25 \r
26 int PicoGameLoaded;\r
27 \r
28 static void PicoCartDetect(const char *carthw_cfg);\r
29 \r
30 /* cso struct */\r
31 typedef struct _cso_struct\r
32 {\r
33   unsigned char in_buff[2*2048];\r
34   unsigned char out_buff[2048];\r
35   struct {\r
36     char          magic[4];\r
37     unsigned int  unused;\r
38     unsigned int  total_bytes;\r
39     unsigned int  total_bytes_high; // ignored here\r
40     unsigned int  block_size;  // 10h\r
41     unsigned char ver;\r
42     unsigned char align;\r
43     unsigned char reserved[2];\r
44   } header;\r
45   unsigned int  fpos_in;  // input file read pointer\r
46   unsigned int  fpos_out; // pos in virtual decompressed file\r
47   int block_in_buff;      // block which we have read in in_buff\r
48   int pad;\r
49   int index[0];\r
50 }\r
51 cso_struct;\r
52 \r
53 static int uncompress2(void *dest, int destLen, void *source, int sourceLen)\r
54 {\r
55     z_stream stream;\r
56     int err;\r
57 \r
58     stream.next_in = (Bytef*)source;\r
59     stream.avail_in = (uInt)sourceLen;\r
60     stream.next_out = dest;\r
61     stream.avail_out = (uInt)destLen;\r
62 \r
63     stream.zalloc = NULL;\r
64     stream.zfree = NULL;\r
65 \r
66     err = inflateInit2(&stream, -15);\r
67     if (err != Z_OK) return err;\r
68 \r
69     err = inflate(&stream, Z_FINISH);\r
70     if (err != Z_STREAM_END) {\r
71         inflateEnd(&stream);\r
72         return err;\r
73     }\r
74     //*destLen = stream.total_out;\r
75 \r
76     return inflateEnd(&stream);\r
77 }\r
78 \r
79 static const char *get_ext(const char *path)\r
80 {\r
81   const char *ext;\r
82   if (strlen(path) < 4)\r
83     return ""; // no ext\r
84 \r
85   // allow 2 or 3 char extensions for now\r
86   ext = path + strlen(path) - 2;\r
87   if (ext[-1] != '.') ext--;\r
88   if (ext[-1] != '.')\r
89     return "";\r
90   return ext;\r
91 }\r
92 \r
93 pm_file *pm_open(const char *path)\r
94 {\r
95   pm_file *file = NULL;\r
96   const char *ext;\r
97   FILE *f;\r
98 \r
99   if (path == NULL)\r
100     return NULL;\r
101 \r
102   ext = get_ext(path);\r
103 #ifndef NO_ZLIB\r
104   if (strcasecmp(ext, "zip") == 0)\r
105   {\r
106     struct zipent *zipentry;\r
107     gzFile gzf = NULL;\r
108     ZIP *zipfile;\r
109     int i;\r
110 \r
111     zipfile = openzip(path);\r
112     if (zipfile != NULL)\r
113     {\r
114       /* search for suitable file (right extension or large enough file) */\r
115       while ((zipentry = readzip(zipfile)) != NULL)\r
116       {\r
117         ext = get_ext(zipentry->name);\r
118 \r
119         if (zipentry->uncompressed_size >= 32*1024)\r
120           goto found_rom_zip;\r
121 \r
122         for (i = 0; i < sizeof(rom_exts)/sizeof(rom_exts[0]); i++)\r
123           if (strcasecmp(ext, rom_exts[i]) == 0)\r
124             goto found_rom_zip;\r
125       }\r
126 \r
127       /* zipfile given, but nothing found suitable for us inside */\r
128       goto zip_failed;\r
129 \r
130 found_rom_zip:\r
131       /* try to convert to gzip stream, so we could use standard gzio functions from zlib */\r
132       gzf = zip2gz(zipfile, zipentry);\r
133       if (gzf == NULL)  goto zip_failed;\r
134 \r
135       file = calloc(1, sizeof(*file));\r
136       if (file == NULL) goto zip_failed;\r
137       file->file  = zipfile;\r
138       file->param = gzf;\r
139       file->size  = zipentry->uncompressed_size;\r
140       file->type  = PMT_ZIP;\r
141       strncpy(file->ext, ext, sizeof(file->ext) - 1);\r
142       return file;\r
143 \r
144 zip_failed:\r
145       if (gzf) {\r
146         gzclose(gzf);\r
147         zipfile->fp = NULL; // gzclose() closed it\r
148       }\r
149       closezip(zipfile);\r
150       return NULL;\r
151     }\r
152   }\r
153   else\r
154 #endif\r
155   if (strcasecmp(ext, "cso") == 0)\r
156   {\r
157     cso_struct *cso = NULL, *tmp = NULL;\r
158     int size;\r
159     f = fopen(path, "rb");\r
160     if (f == NULL)\r
161       goto cso_failed;\r
162 \r
163 #ifdef __GP2X__\r
164     /* we use our own buffering */\r
165     setvbuf(f, NULL, _IONBF, 0);\r
166 #endif\r
167 \r
168     cso = malloc(sizeof(*cso));\r
169     if (cso == NULL)\r
170       goto cso_failed;\r
171 \r
172     if (fread(&cso->header, 1, sizeof(cso->header), f) != sizeof(cso->header))\r
173       goto cso_failed;\r
174 \r
175     if (strncmp(cso->header.magic, "CISO", 4) != 0) {\r
176       elprintf(EL_STATUS, "cso: bad header");\r
177       goto cso_failed;\r
178     }\r
179 \r
180     if (cso->header.block_size != 2048) {\r
181       elprintf(EL_STATUS, "cso: bad block size (%u)", cso->header.block_size);\r
182       goto cso_failed;\r
183     }\r
184 \r
185     size = ((cso->header.total_bytes >> 11) + 1)*4 + sizeof(*cso);\r
186     tmp = realloc(cso, size);\r
187     if (tmp == NULL)\r
188       goto cso_failed;\r
189     cso = tmp;\r
190     elprintf(EL_STATUS, "allocated %i bytes for CSO struct", size);\r
191 \r
192     size -= sizeof(*cso); // index size\r
193     if (fread(cso->index, 1, size, f) != size) {\r
194       elprintf(EL_STATUS, "cso: premature EOF");\r
195       goto cso_failed;\r
196     }\r
197 \r
198     // all ok\r
199     cso->fpos_in = ftell(f);\r
200     cso->fpos_out = 0;\r
201     cso->block_in_buff = -1;\r
202     file = calloc(1, sizeof(*file));\r
203     if (file == NULL) goto cso_failed;\r
204     file->file  = f;\r
205     file->param = cso;\r
206     file->size  = cso->header.total_bytes;\r
207     file->type  = PMT_CSO;\r
208     return file;\r
209 \r
210 cso_failed:\r
211     if (cso != NULL) free(cso);\r
212     if (f != NULL) fclose(f);\r
213     return NULL;\r
214   }\r
215 \r
216   /* not a zip, treat as uncompressed file */\r
217   f = fopen(path, "rb");\r
218   if (f == NULL) return NULL;\r
219 \r
220   file = calloc(1, sizeof(*file));\r
221   if (file == NULL) {\r
222     fclose(f);\r
223     return NULL;\r
224   }\r
225   fseek(f, 0, SEEK_END);\r
226   file->file  = f;\r
227   file->param = NULL;\r
228   file->size  = ftell(f);\r
229   file->type  = PMT_UNCOMPRESSED;\r
230   strncpy(file->ext, ext, sizeof(file->ext) - 1);\r
231   fseek(f, 0, SEEK_SET);\r
232 \r
233 #ifdef __GP2X__\r
234   if (file->size > 0x400000)\r
235     /* we use our own buffering */\r
236     setvbuf(f, NULL, _IONBF, 0);\r
237 #endif\r
238 \r
239   return file;\r
240 }\r
241 \r
242 size_t pm_read(void *ptr, size_t bytes, pm_file *stream)\r
243 {\r
244   int ret;\r
245 \r
246   if (stream->type == PMT_UNCOMPRESSED)\r
247   {\r
248     ret = fread(ptr, 1, bytes, stream->file);\r
249   }\r
250 #ifndef NO_ZLIB\r
251   else if (stream->type == PMT_ZIP)\r
252   {\r
253     gzFile gf = stream->param;\r
254     int err;\r
255     ret = gzread(gf, ptr, bytes);\r
256     err = gzerror2(gf);\r
257     if (ret > 0 && (err == Z_DATA_ERROR || err == Z_STREAM_END))\r
258       /* we must reset stream pointer or else next seek/read fails */\r
259       gzrewind(gf);\r
260   }\r
261 #endif\r
262   else if (stream->type == PMT_CSO)\r
263   {\r
264     cso_struct *cso = stream->param;\r
265     int read_pos, read_len, out_offs, rret;\r
266     int block = cso->fpos_out >> 11;\r
267     int index = cso->index[block];\r
268     int index_end = cso->index[block+1];\r
269     unsigned char *out = ptr, *tmp_dst;\r
270 \r
271     ret = 0;\r
272     while (bytes != 0)\r
273     {\r
274       out_offs = cso->fpos_out&0x7ff;\r
275       if (out_offs == 0 && bytes >= 2048)\r
276            tmp_dst = out;\r
277       else tmp_dst = cso->out_buff;\r
278 \r
279       read_pos = (index&0x7fffffff) << cso->header.align;\r
280 \r
281       if (index < 0) {\r
282         if (read_pos != cso->fpos_in)\r
283           fseek(stream->file, read_pos, SEEK_SET);\r
284         rret = fread(tmp_dst, 1, 2048, stream->file);\r
285         cso->fpos_in = read_pos + rret;\r
286         if (rret != 2048) break;\r
287       } else {\r
288         read_len = (((index_end&0x7fffffff) << cso->header.align) - read_pos) & 0xfff;\r
289         if (block != cso->block_in_buff)\r
290         {\r
291           if (read_pos != cso->fpos_in)\r
292             fseek(stream->file, read_pos, SEEK_SET);\r
293           rret = fread(cso->in_buff, 1, read_len, stream->file);\r
294           cso->fpos_in = read_pos + rret;\r
295           if (rret != read_len) {\r
296             elprintf(EL_STATUS, "cso: read failed @ %08x", read_pos);\r
297             break;\r
298           }\r
299           cso->block_in_buff = block;\r
300         }\r
301         rret = uncompress2(tmp_dst, 2048, cso->in_buff, read_len);\r
302         if (rret != 0) {\r
303           elprintf(EL_STATUS, "cso: uncompress failed @ %08x with %i", read_pos, rret);\r
304           break;\r
305         }\r
306       }\r
307 \r
308       rret = 2048;\r
309       if (out_offs != 0 || bytes < 2048) {\r
310         //elprintf(EL_STATUS, "cso: unaligned/nonfull @ %08x, offs=%i, len=%u", cso->fpos_out, out_offs, bytes);\r
311         if (bytes < rret) rret = bytes;\r
312         if (2048 - out_offs < rret) rret = 2048 - out_offs;\r
313         memcpy(out, tmp_dst + out_offs, rret);\r
314       }\r
315       ret += rret;\r
316       out += rret;\r
317       cso->fpos_out += rret;\r
318       bytes -= rret;\r
319       block++;\r
320       index = index_end;\r
321       index_end = cso->index[block+1];\r
322     }\r
323   }\r
324   else\r
325     ret = 0;\r
326 \r
327   return ret;\r
328 }\r
329 \r
330 int pm_seek(pm_file *stream, long offset, int whence)\r
331 {\r
332   if (stream->type == PMT_UNCOMPRESSED)\r
333   {\r
334     fseek(stream->file, offset, whence);\r
335     return ftell(stream->file);\r
336   }\r
337 #ifndef NO_ZLIB\r
338   else if (stream->type == PMT_ZIP)\r
339   {\r
340     if (PicoMessage != NULL && offset > 6*1024*1024) {\r
341       long pos = gztell((gzFile) stream->param);\r
342       if (offset < pos || offset - pos > 6*1024*1024)\r
343         PicoMessage("Decompressing data...");\r
344     }\r
345     return gzseek((gzFile) stream->param, offset, whence);\r
346   }\r
347 #endif\r
348   else if (stream->type == PMT_CSO)\r
349   {\r
350     cso_struct *cso = stream->param;\r
351     switch (whence)\r
352     {\r
353       case SEEK_CUR: cso->fpos_out += offset; break;\r
354       case SEEK_SET: cso->fpos_out  = offset; break;\r
355       case SEEK_END: cso->fpos_out  = cso->header.total_bytes - offset; break;\r
356     }\r
357     return cso->fpos_out;\r
358   }\r
359   else\r
360     return -1;\r
361 }\r
362 \r
363 int pm_close(pm_file *fp)\r
364 {\r
365   int ret = 0;\r
366 \r
367   if (fp == NULL) return EOF;\r
368 \r
369   if (fp->type == PMT_UNCOMPRESSED)\r
370   {\r
371     fclose(fp->file);\r
372   }\r
373 #ifndef NO_ZLIB\r
374   else if (fp->type == PMT_ZIP)\r
375   {\r
376     ZIP *zipfile = fp->file;\r
377     gzclose((gzFile) fp->param);\r
378     zipfile->fp = NULL; // gzclose() closed it\r
379     closezip(zipfile);\r
380   }\r
381 #endif\r
382   else if (fp->type == PMT_CSO)\r
383   {\r
384     free(fp->param);\r
385     fclose(fp->file);\r
386   }\r
387   else\r
388     ret = EOF;\r
389 \r
390   free(fp);\r
391   return ret;\r
392 }\r
393 \r
394 // byteswap, data needs to be int aligned, src can match dst\r
395 void Byteswap(void *dst, const void *src, int len)\r
396 {\r
397   const unsigned int *ps = src;\r
398   unsigned int *pd = dst;\r
399   int i, m;\r
400 \r
401   if (len < 2)\r
402     return;\r
403 \r
404   m = 0x00ff00ff;\r
405   for (i = 0; i < len / 4; i++) {\r
406     unsigned int t = ps[i];\r
407     pd[i] = ((t & m) << 8) | ((t & ~m) >> 8);\r
408   }\r
409 }\r
410 \r
411 // Interleve a 16k block and byteswap\r
412 static int InterleveBlock(unsigned char *dest,unsigned char *src)\r
413 {\r
414   int i=0;\r
415   for (i=0;i<0x2000;i++) dest[(i<<1)  ]=src[       i]; // Odd\r
416   for (i=0;i<0x2000;i++) dest[(i<<1)+1]=src[0x2000+i]; // Even\r
417   return 0;\r
418 }\r
419 \r
420 // Decode a SMD file\r
421 static int DecodeSmd(unsigned char *data,int len)\r
422 {\r
423   unsigned char *temp=NULL;\r
424   int i=0;\r
425 \r
426   temp=(unsigned char *)malloc(0x4000);\r
427   if (temp==NULL) return 1;\r
428   memset(temp,0,0x4000);\r
429 \r
430   // Interleve each 16k block and shift down by 0x200:\r
431   for (i=0; i+0x4200<=len; i+=0x4000)\r
432   {\r
433     InterleveBlock(temp,data+0x200+i); // Interleve 16k to temporary buffer\r
434     memcpy(data+i,temp,0x4000); // Copy back in\r
435   }\r
436 \r
437   free(temp);\r
438   return 0;\r
439 }\r
440 \r
441 static unsigned char *PicoCartAlloc(int filesize, int is_sms)\r
442 {\r
443   unsigned char *rom;\r
444 \r
445   if (is_sms) {\r
446     // make size power of 2 for easier banking handling\r
447     int s = 0, tmp = filesize;\r
448     while ((tmp >>= 1) != 0)\r
449       s++;\r
450     if (filesize > (1 << s))\r
451       s++;\r
452     rom_alloc_size = 1 << s;\r
453     // be sure we can cover all address space\r
454     if (rom_alloc_size < 0x10000)\r
455       rom_alloc_size = 0x10000;\r
456   }\r
457   else {\r
458     // make alloc size at least sizeof(mcd_state),\r
459     // in case we want to switch to CD mode\r
460     if (filesize < sizeof(mcd_state))\r
461       filesize = sizeof(mcd_state);\r
462 \r
463     // align to 512K for memhandlers\r
464     rom_alloc_size = (filesize + 0x7ffff) & ~0x7ffff;\r
465   }\r
466 \r
467   if (rom_alloc_size - filesize < 4)\r
468     rom_alloc_size += 4; // padding for out-of-bound exec protection\r
469 \r
470   // Allocate space for the rom plus padding\r
471   // use special address for 32x dynarec\r
472   rom = plat_mmap(0x02000000, rom_alloc_size, 0, 0);\r
473   return rom;\r
474 }\r
475 \r
476 int PicoCartLoad(pm_file *f,unsigned char **prom,unsigned int *psize,int is_sms)\r
477 {\r
478   unsigned char *rom;\r
479   int size, bytes_read;\r
480 \r
481   if (f == NULL)\r
482     return 1;\r
483 \r
484   size = f->size;\r
485   if (size <= 0) return 1;\r
486   size = (size+3)&~3; // Round up to a multiple of 4\r
487 \r
488   // Allocate space for the rom plus padding\r
489   rom = PicoCartAlloc(size, is_sms);\r
490   if (rom == NULL) {\r
491     elprintf(EL_STATUS, "out of memory (wanted %i)", size);\r
492     return 2;\r
493   }\r
494 \r
495   if (PicoCartLoadProgressCB != NULL)\r
496   {\r
497     // read ROM in blocks, just for fun\r
498     int ret;\r
499     unsigned char *p = rom;\r
500     bytes_read=0;\r
501     do\r
502     {\r
503       int todo = size - bytes_read;\r
504       if (todo > 256*1024) todo = 256*1024;\r
505       ret = pm_read(p,todo,f);\r
506       bytes_read += ret;\r
507       p += ret;\r
508       PicoCartLoadProgressCB(bytes_read * 100 / size);\r
509     }\r
510     while (ret > 0);\r
511   }\r
512   else\r
513     bytes_read = pm_read(rom,size,f); // Load up the rom\r
514   if (bytes_read <= 0) {\r
515     elprintf(EL_STATUS, "read failed");\r
516     free(rom);\r
517     return 3;\r
518   }\r
519 \r
520   if (!is_sms)\r
521   {\r
522     // maybe we are loading MegaCD BIOS?\r
523     if (!(PicoAHW & PAHW_MCD) && size == 0x20000 && (!strncmp((char *)rom+0x124, "BOOT", 4) ||\r
524          !strncmp((char *)rom+0x128, "BOOT", 4))) {\r
525       PicoAHW |= PAHW_MCD;\r
526     }\r
527 \r
528     // Check for SMD:\r
529     if (size >= 0x4200 && (size&0x3fff) == 0x200 &&\r
530         ((rom[0x2280] == 'S' && rom[0x280] == 'E') || (rom[0x280] == 'S' && rom[0x2281] == 'E'))) {\r
531       elprintf(EL_STATUS, "SMD format detected.");\r
532       DecodeSmd(rom,size); size-=0x200; // Decode and byteswap SMD\r
533     }\r
534     else Byteswap(rom, rom, size); // Just byteswap\r
535   }\r
536   else\r
537   {\r
538     if (size >= 0x4200 && (size&0x3fff) == 0x200) {\r
539       elprintf(EL_STATUS, "SMD format detected.");\r
540       // at least here it's not interleaved\r
541       size -= 0x200;\r
542       memmove(rom, rom + 0x200, size);\r
543     }\r
544   }\r
545 \r
546   if (prom)  *prom = rom;\r
547   if (psize) *psize = size;\r
548 \r
549   return 0;\r
550 }\r
551 \r
552 // Insert a cartridge:\r
553 int PicoCartInsert(unsigned char *rom, unsigned int romsize, const char *carthw_cfg)\r
554 {\r
555   // notaz: add a 68k "jump one op back" opcode to the end of ROM.\r
556   // This will hang the emu, but will prevent nasty crashes.\r
557   // note: 4 bytes are padded to every ROM\r
558   if (rom != NULL)\r
559     *(unsigned long *)(rom+romsize) = 0xFFFE4EFA; // 4EFA FFFE byteswapped\r
560 \r
561   Pico.rom=rom;\r
562   Pico.romsize=romsize;\r
563 \r
564   if (SRam.data) {\r
565     free(SRam.data);\r
566     SRam.data = NULL;\r
567   }\r
568 \r
569   if (PicoCartUnloadHook != NULL) {\r
570     PicoCartUnloadHook();\r
571     PicoCartUnloadHook = NULL;\r
572   }\r
573   pdb_cleanup();\r
574 \r
575   PicoAHW &= PAHW_MCD|PAHW_SMS;\r
576 \r
577   PicoCartMemSetup = NULL;\r
578   PicoDmaHook = NULL;\r
579   PicoResetHook = NULL;\r
580   PicoLineHook = NULL;\r
581   PicoLoadStateHook = NULL;\r
582   carthw_chunks = NULL;\r
583 \r
584   if (!(PicoAHW & (PAHW_MCD|PAHW_SMS)))\r
585     PicoCartDetect(carthw_cfg);\r
586 \r
587   // setup correct memory map for loaded ROM\r
588   switch (PicoAHW) {\r
589     default:\r
590       elprintf(EL_STATUS|EL_ANOMALY, "starting in unknown hw configuration: %x", PicoAHW);\r
591     case 0:\r
592     case PAHW_SVP:  PicoMemSetup(); break;\r
593     case PAHW_MCD:  PicoMemSetupCD(); break;\r
594     case PAHW_PICO: PicoMemSetupPico(); break;\r
595     case PAHW_SMS:  PicoMemSetupMS(); break;\r
596   }\r
597 \r
598   if (PicoCartMemSetup != NULL)\r
599     PicoCartMemSetup();\r
600 \r
601   if (PicoAHW & PAHW_SMS)\r
602     PicoPowerMS();\r
603   else\r
604     PicoPower();\r
605 \r
606   PicoGameLoaded = 1;\r
607   return 0;\r
608 }\r
609 \r
610 int PicoCartResize(int newsize)\r
611 {\r
612   void *tmp = plat_mremap(Pico.rom, rom_alloc_size, newsize);\r
613   if (tmp == NULL)\r
614     return -1;\r
615 \r
616   Pico.rom = tmp;\r
617   rom_alloc_size = newsize;\r
618   return 0;\r
619 }\r
620 \r
621 void PicoCartUnload(void)\r
622 {\r
623   if (PicoCartUnloadHook != NULL) {\r
624     PicoCartUnloadHook();\r
625     PicoCartUnloadHook = NULL;\r
626   }\r
627 \r
628   if (PicoAHW & PAHW_32X)\r
629     PicoUnload32x();\r
630 \r
631   if (Pico.rom != NULL) {\r
632     SekFinishIdleDet();\r
633     plat_munmap(Pico.rom, rom_alloc_size);\r
634     Pico.rom = NULL;\r
635   }\r
636   PicoGameLoaded = 0;\r
637 }\r
638 \r
639 static unsigned int rom_crc32(void)\r
640 {\r
641   unsigned int crc;\r
642   elprintf(EL_STATUS, "caclulating CRC32..");\r
643 \r
644   // have to unbyteswap for calculation..\r
645   Byteswap(Pico.rom, Pico.rom, Pico.romsize);\r
646   crc = crc32(0, Pico.rom, Pico.romsize);\r
647   Byteswap(Pico.rom, Pico.rom, Pico.romsize);\r
648   return crc;\r
649 }\r
650 \r
651 static int rom_strcmp(int rom_offset, const char *s1)\r
652 {\r
653   int i, len = strlen(s1);\r
654   const char *s_rom = (const char *)Pico.rom;\r
655   if (rom_offset + len > Pico.romsize)\r
656     return 0;\r
657   for (i = 0; i < len; i++)\r
658     if (s1[i] != s_rom[(i + rom_offset) ^ 1])\r
659       return 1;\r
660   return 0;\r
661 }\r
662 \r
663 static unsigned int rom_read32(int addr)\r
664 {\r
665   unsigned short *m = (unsigned short *)(Pico.rom + addr);\r
666   return (m[0] << 16) | m[1];\r
667 }\r
668 \r
669 static char *sskip(char *s)\r
670 {\r
671   while (*s && isspace_(*s))\r
672     s++;\r
673   return s;\r
674 }\r
675 \r
676 static void rstrip(char *s)\r
677 {\r
678   char *p;\r
679   for (p = s + strlen(s) - 1; p >= s; p--)\r
680     if (isspace_(*p))\r
681       *p = 0;\r
682 }\r
683 \r
684 static int parse_3_vals(char *p, int *val0, int *val1, int *val2)\r
685 {\r
686   char *r;\r
687   *val0 = strtoul(p, &r, 0);\r
688   if (r == p)\r
689     goto bad;\r
690   p = sskip(r);\r
691   if (*p++ != ',')\r
692     goto bad;\r
693   *val1 = strtoul(p, &r, 0);\r
694   if (r == p)\r
695     goto bad;\r
696   p = sskip(r);\r
697   if (*p++ != ',')\r
698     goto bad;\r
699   *val2 = strtoul(p, &r, 0);\r
700   if (r == p)\r
701     goto bad;\r
702 \r
703   return 1;\r
704 bad:\r
705   return 0;\r
706 }\r
707 \r
708 static int is_expr(const char *expr, char **pr)\r
709 {\r
710   int len = strlen(expr);\r
711   char *p = *pr;\r
712 \r
713   if (strncmp(expr, p, len) != 0)\r
714     return 0;\r
715   p = sskip(p + len);\r
716   if (*p != '=')\r
717     return 0; // wrong or malformed\r
718 \r
719   *pr = sskip(p + 1);\r
720   return 1;\r
721 }\r
722 \r
723 #include "carthw_cfg.c"\r
724 \r
725 static void parse_carthw(const char *carthw_cfg, int *fill_sram)\r
726 {\r
727   int line = 0, any_checks_passed = 0, skip_sect = 0;\r
728   const char *s, *builtin = builtin_carthw_cfg;\r
729   int tmp, rom_crc = 0;\r
730   char buff[256], *p, *r;\r
731   FILE *f;\r
732 \r
733   f = fopen(carthw_cfg, "r");\r
734   if (f == NULL)\r
735     f = fopen("pico/carthw.cfg", "r");\r
736   if (f == NULL)\r
737     elprintf(EL_STATUS, "couldn't open carthw.cfg!");\r
738 \r
739   for (;;)\r
740   {\r
741     if (f != NULL) {\r
742       p = fgets(buff, sizeof(buff), f);\r
743       if (p == NULL)\r
744         break;\r
745     }\r
746     else {\r
747       if (*builtin == 0)\r
748         break;\r
749       for (s = builtin; *s != 0 && *s != '\n'; s++)\r
750         ;\r
751       while (*s == '\n')\r
752         s++;\r
753       tmp = s - builtin;\r
754       if (tmp > sizeof(buff) - 1)\r
755         tmp = sizeof(buff) - 1;\r
756       memcpy(buff, builtin, tmp);\r
757       buff[tmp] = 0;\r
758       p = buff;\r
759       builtin = s;\r
760     }\r
761 \r
762     line++;\r
763     p = sskip(p);\r
764     if (*p == 0 || *p == '#')\r
765       continue;\r
766 \r
767     if (*p == '[') {\r
768       any_checks_passed = 0;\r
769       skip_sect = 0;\r
770       continue;\r
771     }\r
772     \r
773     if (skip_sect)\r
774       continue;\r
775 \r
776     /* look for checks */\r
777     if (is_expr("check_str", &p))\r
778     {\r
779       int offs;\r
780       offs = strtoul(p, &r, 0);\r
781       if (offs < 0 || offs > Pico.romsize) {\r
782         elprintf(EL_STATUS, "carthw:%d: check_str offs out of range: %d\n", line, offs);\r
783         goto bad;\r
784       }\r
785       p = sskip(r);\r
786       if (*p != ',')\r
787         goto bad;\r
788       p = sskip(p + 1);\r
789       if (*p != '"')\r
790         goto bad;\r
791       p++;\r
792       r = strchr(p, '"');\r
793       if (r == NULL)\r
794         goto bad;\r
795       *r = 0;\r
796 \r
797       if (rom_strcmp(offs, p) == 0)\r
798         any_checks_passed = 1;\r
799       else\r
800         skip_sect = 1;\r
801       continue;\r
802     }\r
803     else if (is_expr("check_size_gt", &p))\r
804     {\r
805       int size;\r
806       size = strtoul(p, &r, 0);\r
807       if (r == p || size < 0)\r
808         goto bad;\r
809 \r
810       if (Pico.romsize > size)\r
811         any_checks_passed = 1;\r
812       else\r
813         skip_sect = 1;\r
814       continue;\r
815     }\r
816     else if (is_expr("check_csum", &p))\r
817     {\r
818       int csum;\r
819       csum = strtoul(p, &r, 0);\r
820       if (r == p || (csum & 0xffff0000))\r
821         goto bad;\r
822 \r
823       if (csum == (rom_read32(0x18c) & 0xffff))\r
824         any_checks_passed = 1;\r
825       else\r
826         skip_sect = 1;\r
827       continue;\r
828     }\r
829     else if (is_expr("check_crc32", &p))\r
830     {\r
831       unsigned int crc;\r
832       crc = strtoul(p, &r, 0);\r
833       if (r == p)\r
834         goto bad;\r
835 \r
836       if (rom_crc == 0)\r
837         rom_crc = rom_crc32();\r
838       if (crc == rom_crc)\r
839         any_checks_passed = 1;\r
840       else\r
841         skip_sect = 1;\r
842       continue;\r
843     }\r
844 \r
845     /* now time for actions */\r
846     if (is_expr("hw", &p)) {\r
847       if (!any_checks_passed)\r
848         goto no_checks;\r
849       rstrip(p);\r
850 \r
851       if      (strcmp(p, "svp") == 0)\r
852         PicoSVPStartup();\r
853       else if (strcmp(p, "pico") == 0)\r
854         PicoInitPico();\r
855       else if (strcmp(p, "prot") == 0)\r
856         carthw_sprot_startup();\r
857       else if (strcmp(p, "ssf2_mapper") == 0)\r
858         carthw_ssf2_startup();\r
859       else if (strcmp(p, "x_in_1_mapper") == 0)\r
860         carthw_Xin1_startup();\r
861       else if (strcmp(p, "realtec_mapper") == 0)\r
862         carthw_realtec_startup();\r
863       else if (strcmp(p, "radica_mapper") == 0)\r
864         carthw_radica_startup();\r
865       else if (strcmp(p, "piersolar_mapper") == 0)\r
866         carthw_pier_startup();\r
867       else if (strcmp(p, "prot_lk3") == 0)\r
868         carthw_prot_lk3_startup();\r
869       else {\r
870         elprintf(EL_STATUS, "carthw:%d: unsupported mapper: %s", line, p);\r
871         skip_sect = 1;\r
872       }\r
873       continue;\r
874     }\r
875     if (is_expr("sram_range", &p)) {\r
876       int start, end;\r
877 \r
878       if (!any_checks_passed)\r
879         goto no_checks;\r
880       rstrip(p);\r
881 \r
882       start = strtoul(p, &r, 0);\r
883       if (r == p)\r
884         goto bad;\r
885       p = sskip(r);\r
886       if (*p != ',')\r
887         goto bad;\r
888       p = sskip(p + 1);\r
889       end = strtoul(p, &r, 0);\r
890       if (r == p)\r
891         goto bad;\r
892       if (((start | end) & 0xff000000) || start > end) {\r
893         elprintf(EL_STATUS, "carthw:%d: bad sram_range: %08x - %08x", line, start, end);\r
894         goto bad_nomsg;\r
895       }\r
896       SRam.start = start;\r
897       SRam.end = end;\r
898       continue;\r
899     }\r
900     else if (is_expr("prop", &p)) {\r
901       if (!any_checks_passed)\r
902         goto no_checks;\r
903       rstrip(p);\r
904 \r
905       if      (strcmp(p, "no_sram") == 0)\r
906         SRam.flags &= ~SRF_ENABLED;\r
907       else if (strcmp(p, "no_eeprom") == 0)\r
908         SRam.flags &= ~SRF_EEPROM;\r
909       else if (strcmp(p, "filled_sram") == 0)\r
910         *fill_sram = 1;\r
911       else if (strcmp(p, "force_6btn") == 0)\r
912         PicoQuirks |= PQUIRK_FORCE_6BTN;\r
913       else {\r
914         elprintf(EL_STATUS, "carthw:%d: unsupported prop: %s", line, p);\r
915         goto bad_nomsg;\r
916       }\r
917       elprintf(EL_STATUS, "game prop: %s", p);\r
918       continue;\r
919     }\r
920     else if (is_expr("eeprom_type", &p)) {\r
921       int type;\r
922       if (!any_checks_passed)\r
923         goto no_checks;\r
924       rstrip(p);\r
925 \r
926       type = strtoul(p, &r, 0);\r
927       if (r == p || type < 0)\r
928         goto bad;\r
929       SRam.eeprom_type = type;\r
930       SRam.flags |= SRF_EEPROM;\r
931       continue;\r
932     }\r
933     else if (is_expr("eeprom_lines", &p)) {\r
934       int scl, sda_in, sda_out;\r
935       if (!any_checks_passed)\r
936         goto no_checks;\r
937       rstrip(p);\r
938 \r
939       if (!parse_3_vals(p, &scl, &sda_in, &sda_out))\r
940         goto bad;\r
941       if (scl < 0 || scl > 15 || sda_in < 0 || sda_in > 15 ||\r
942           sda_out < 0 || sda_out > 15)\r
943         goto bad;\r
944 \r
945       SRam.eeprom_bit_cl = scl;\r
946       SRam.eeprom_bit_in = sda_in;\r
947       SRam.eeprom_bit_out= sda_out;\r
948       continue;\r
949     }\r
950     else if ((tmp = is_expr("prot_ro_value16", &p)) || is_expr("prot_rw_value16", &p)) {\r
951       int addr, mask, val;\r
952       if (!any_checks_passed)\r
953         goto no_checks;\r
954       rstrip(p);\r
955 \r
956       if (!parse_3_vals(p, &addr, &mask, &val))\r
957         goto bad;\r
958 \r
959       carthw_sprot_new_location(addr, mask, val, tmp ? 1 : 0);\r
960       continue;\r
961     }\r
962 \r
963 \r
964 bad:\r
965     elprintf(EL_STATUS, "carthw:%d: unrecognized expression: %s", line, buff);\r
966 bad_nomsg:\r
967     skip_sect = 1;\r
968     continue;\r
969 \r
970 no_checks:\r
971     elprintf(EL_STATUS, "carthw:%d: command without any checks before it: %s", line, buff);\r
972     skip_sect = 1;\r
973     continue;\r
974   }\r
975 \r
976   if (f != NULL)\r
977     fclose(f);\r
978 }\r
979 \r
980 /*\r
981  * various cart-specific things, which can't be handled by generic code\r
982  */\r
983 static void PicoCartDetect(const char *carthw_cfg)\r
984 {\r
985   int fill_sram = 0;\r
986 \r
987   memset(&SRam, 0, sizeof(SRam));\r
988   if (Pico.rom[0x1B1] == 'R' && Pico.rom[0x1B0] == 'A')\r
989   {\r
990     SRam.start =  rom_read32(0x1B4) & ~0xff000001; // align\r
991     SRam.end   = (rom_read32(0x1B8) & ~0xff000000) | 1;\r
992     if (Pico.rom[0x1B2] & 0x40)\r
993       // EEPROM\r
994       SRam.flags |= SRF_EEPROM;\r
995     SRam.flags |= SRF_ENABLED;\r
996   }\r
997   if (SRam.end == 0 || SRam.start > SRam.end)\r
998   {\r
999     // some games may have bad headers, like S&K and Sonic3\r
1000     // note: majority games use 0x200000 as starting address, but there are some which\r
1001     // use something else (0x300000 by HardBall '95). Luckily they have good headers.\r
1002     SRam.start = 0x200000;\r
1003     SRam.end   = 0x203FFF;\r
1004     SRam.flags |= SRF_ENABLED;\r
1005   }\r
1006 \r
1007   // set EEPROM defaults, in case it gets detected\r
1008   SRam.eeprom_type   = 0; // 7bit (24C01)\r
1009   SRam.eeprom_bit_cl = 1;\r
1010   SRam.eeprom_bit_in = 0;\r
1011   SRam.eeprom_bit_out= 0;\r
1012 \r
1013   if (carthw_cfg != NULL)\r
1014     parse_carthw(carthw_cfg, &fill_sram);\r
1015 \r
1016   if (SRam.flags & SRF_ENABLED)\r
1017   {\r
1018     if (SRam.flags & SRF_EEPROM)\r
1019       SRam.size = 0x2000;\r
1020     else\r
1021       SRam.size = SRam.end - SRam.start + 1;\r
1022 \r
1023     SRam.data = calloc(SRam.size, 1);\r
1024     if (SRam.data == NULL)\r
1025       SRam.flags &= ~SRF_ENABLED;\r
1026 \r
1027     if (SRam.eeprom_type == 1)  // 1 == 0 in PD EEPROM code\r
1028       SRam.eeprom_type = 0;\r
1029   }\r
1030 \r
1031   if ((SRam.flags & SRF_ENABLED) && fill_sram)\r
1032   {\r
1033     elprintf(EL_STATUS, "SRAM fill");\r
1034     memset(SRam.data, 0xff, SRam.size);\r
1035   }\r
1036 \r
1037   // Unusual region 'code'\r
1038   if (rom_strcmp(0x1f0, "EUROPE") == 0 || rom_strcmp(0x1f0, "Europe") == 0)\r
1039     *(int *) (Pico.rom + 0x1f0) = 0x20204520;\r
1040 }\r
1041 \r
1042 // vim:shiftwidth=2:expandtab\r