SVP stubs
[picodrive.git] / Pico / Cart.c
1 // This is part of Pico Library\r
2 \r
3 // (c) Copyright 2004 Dave, All rights reserved.\r
4 // (c) Copyright 2006-2007, Grazvydas "notaz" Ignotas\r
5 // Free for non-commercial use.\r
6 \r
7 // For commercial use, separate licencing terms must be obtained.\r
8 \r
9 \r
10 #include "PicoInt.h"\r
11 #include "../zlib/zlib.h"\r
12 #include "../unzip/unzip.h"\r
13 #include "../unzip/unzip_stream.h"\r
14 \r
15 \r
16 static char *rom_exts[] = { "bin", "gen", "smd", "iso" };\r
17 \r
18 void (*PicoCartLoadProgressCB)(int percent) = NULL;\r
19 void (*PicoCDLoadProgressCB)(int percent) = NULL; // handled in Pico/cd/cd_file.c\r
20 \r
21 /* cso struct */\r
22 typedef struct _cso_struct\r
23 {\r
24   unsigned char in_buff[2*2048];\r
25   unsigned char out_buff[2048];\r
26   struct {\r
27     char          magic[4];\r
28     unsigned int  unused;\r
29     unsigned int  total_bytes;\r
30     unsigned int  total_bytes_high; // ignored here\r
31     unsigned int  block_size;  // 10h\r
32     unsigned char ver;\r
33     unsigned char align;\r
34     unsigned char reserved[2];\r
35   } header;\r
36   unsigned int  fpos_in;  // input file read pointer\r
37   unsigned int  fpos_out; // pos in virtual decompressed file\r
38   int block_in_buff;      // block which we have read in in_buff\r
39   int pad;\r
40   int index[0];\r
41 }\r
42 cso_struct;\r
43 \r
44 static int uncompress2(void *dest, int destLen, void *source, int sourceLen)\r
45 {\r
46     z_stream stream;\r
47     int err;\r
48 \r
49     stream.next_in = (Bytef*)source;\r
50     stream.avail_in = (uInt)sourceLen;\r
51     stream.next_out = dest;\r
52     stream.avail_out = (uInt)destLen;\r
53 \r
54     stream.zalloc = NULL;\r
55     stream.zfree = NULL;\r
56 \r
57     err = inflateInit2(&stream, -15);\r
58     if (err != Z_OK) return err;\r
59 \r
60     err = inflate(&stream, Z_FINISH);\r
61     if (err != Z_STREAM_END) {\r
62         inflateEnd(&stream);\r
63         return err;\r
64     }\r
65     //*destLen = stream.total_out;\r
66 \r
67     return inflateEnd(&stream);\r
68 }\r
69 \r
70 pm_file *pm_open(const char *path)\r
71 {\r
72   pm_file *file = NULL;\r
73   const char *ext;\r
74   FILE *f;\r
75 \r
76   if (path == NULL) return NULL;\r
77 \r
78   if (strlen(path) < 5) ext = NULL; // no ext\r
79   else ext = path + strlen(path) - 3;\r
80 \r
81   if (ext && strcasecmp(ext, "zip") == 0)\r
82   {\r
83     struct zipent *zipentry;\r
84     gzFile gzf = NULL;\r
85     ZIP *zipfile;\r
86     int i;\r
87 \r
88     zipfile = openzip(path);\r
89 \r
90     if (zipfile != NULL)\r
91     {\r
92       /* search for suitable file (right extension or large enough file) */\r
93       while ((zipentry = readzip(zipfile)) != NULL)\r
94       {\r
95         if (zipentry->uncompressed_size >= 128*1024) goto found_rom_zip;\r
96         if (strlen(zipentry->name) < 5) continue;\r
97 \r
98         ext = zipentry->name+strlen(zipentry->name)-3;\r
99         for (i = 0; i < sizeof(rom_exts)/sizeof(rom_exts[0]); i++)\r
100           if (strcasecmp(ext, rom_exts[i]) == 0) goto found_rom_zip;\r
101       }\r
102 \r
103       /* zipfile given, but nothing found suitable for us inside */\r
104       goto zip_failed;\r
105 \r
106 found_rom_zip:\r
107       /* try to convert to gzip stream, so we could use standard gzio functions from zlib */\r
108       gzf = zip2gz(zipfile, zipentry);\r
109       if (gzf == NULL)  goto zip_failed;\r
110 \r
111       file = malloc(sizeof(*file));\r
112       if (file == NULL) goto zip_failed;\r
113       file->file  = zipfile;\r
114       file->param = gzf;\r
115       file->size  = zipentry->uncompressed_size;\r
116       file->type  = PMT_ZIP;\r
117       return file;\r
118 \r
119 zip_failed:\r
120       if (gzf) {\r
121         gzclose(gzf);\r
122         zipfile->fp = NULL; // gzclose() closed it\r
123       }\r
124       closezip(zipfile);\r
125       return NULL;\r
126     }\r
127   }\r
128   else if (ext && strcasecmp(ext, "cso") == 0)\r
129   {\r
130     cso_struct *cso = NULL, *tmp = NULL;\r
131     int size;\r
132     f = fopen(path, "rb");\r
133     if (f == NULL)\r
134       goto cso_failed;\r
135 \r
136     cso = malloc(sizeof(*cso));\r
137     if (cso == NULL)\r
138       goto cso_failed;\r
139 \r
140     if (fread(&cso->header, 1, sizeof(cso->header), f) != sizeof(cso->header))\r
141       goto cso_failed;\r
142 \r
143     if (strncmp(cso->header.magic, "CISO", 4) != 0) {\r
144       elprintf(EL_STATUS, "cso: bad header");\r
145       goto cso_failed;\r
146     }\r
147 \r
148     if (cso->header.block_size != 2048) {\r
149       elprintf(EL_STATUS, "cso: bad block size (%u)", cso->header.block_size);\r
150       goto cso_failed;\r
151     }\r
152 \r
153     size = ((cso->header.total_bytes >> 11) + 1)*4 + sizeof(*cso);\r
154     tmp = realloc(cso, size);\r
155     if (tmp == NULL)\r
156       goto cso_failed;\r
157     cso = tmp;\r
158     elprintf(EL_STATUS, "allocated %i bytes for CSO struct", size);\r
159 \r
160     size -= sizeof(*cso); // index size\r
161     if (fread(cso->index, 1, size, f) != size) {\r
162       elprintf(EL_STATUS, "cso: premature EOF");\r
163       goto cso_failed;\r
164     }\r
165 \r
166     // all ok\r
167     cso->fpos_in = ftell(f);\r
168     cso->fpos_out = 0;\r
169     cso->block_in_buff = -1;\r
170     file = malloc(sizeof(*file));\r
171     if (file == NULL) goto cso_failed;\r
172     file->file  = f;\r
173     file->param = cso;\r
174     file->size  = cso->header.total_bytes;\r
175     file->type  = PMT_CSO;\r
176     return file;\r
177 \r
178 cso_failed:\r
179     if (cso != NULL) free(cso);\r
180     if (f != NULL) fclose(f);\r
181     return NULL;\r
182   }\r
183 \r
184   /* not a zip, treat as uncompressed file */\r
185   f = fopen(path, "rb");\r
186   if (f == NULL) return NULL;\r
187 \r
188   /* we use our own buffering */\r
189   setvbuf(f, NULL, _IONBF, 0);\r
190 \r
191   file = malloc(sizeof(*file));\r
192   if (file == NULL) {\r
193     fclose(f);\r
194     return NULL;\r
195   }\r
196   fseek(f, 0, SEEK_END);\r
197   file->file  = f;\r
198   file->param = NULL;\r
199   file->size  = ftell(f);\r
200   file->type  = PMT_UNCOMPRESSED;\r
201   fseek(f, 0, SEEK_SET);\r
202   return file;\r
203 }\r
204 \r
205 size_t pm_read(void *ptr, size_t bytes, pm_file *stream)\r
206 {\r
207   int ret;\r
208 \r
209   if (stream->type == PMT_UNCOMPRESSED)\r
210   {\r
211     ret = fread(ptr, 1, bytes, stream->file);\r
212   }\r
213   else if (stream->type == PMT_ZIP)\r
214   {\r
215     gzFile gf = stream->param;\r
216     int err;\r
217     ret = gzread(gf, ptr, bytes);\r
218     err = gzerror2(gf);\r
219     if (ret > 0 && (err == Z_DATA_ERROR || err == Z_STREAM_END))\r
220       /* we must reset stream pointer or else next seek/read fails */\r
221       gzrewind(gf);\r
222   }\r
223   else if (stream->type == PMT_CSO)\r
224   {\r
225     cso_struct *cso = stream->param;\r
226     int read_pos, read_len, out_offs, rret;\r
227     int block = cso->fpos_out >> 11;\r
228     int index = cso->index[block];\r
229     int index_end = cso->index[block+1];\r
230     unsigned char *out = ptr, *tmp_dst;\r
231 \r
232     ret = 0;\r
233     while (bytes != 0)\r
234     {\r
235       out_offs = cso->fpos_out&0x7ff;\r
236       if (out_offs == 0 && bytes >= 2048)\r
237            tmp_dst = out;\r
238       else tmp_dst = cso->out_buff;\r
239 \r
240       read_pos = (index&0x7fffffff) << cso->header.align;\r
241 \r
242       if (index < 0) {\r
243         if (read_pos != cso->fpos_in)\r
244           fseek(stream->file, read_pos, SEEK_SET);\r
245         rret = fread(tmp_dst, 1, 2048, stream->file);\r
246         cso->fpos_in = read_pos + rret;\r
247         if (rret != 2048) break;\r
248       } else {\r
249         read_len = (((index_end&0x7fffffff) << cso->header.align) - read_pos) & 0xfff;\r
250         if (block != cso->block_in_buff)\r
251         {\r
252           if (read_pos != cso->fpos_in)\r
253             fseek(stream->file, read_pos, SEEK_SET);\r
254           rret = fread(cso->in_buff, 1, read_len, stream->file);\r
255           cso->fpos_in = read_pos + rret;\r
256           if (rret != read_len) {\r
257             elprintf(EL_STATUS, "cso: read failed @ %08x", read_pos);\r
258             break;\r
259           }\r
260           cso->block_in_buff = block;\r
261         }\r
262         rret = uncompress2(tmp_dst, 2048, cso->in_buff, read_len);\r
263         if (rret != 0) {\r
264           elprintf(EL_STATUS, "cso: uncompress failed @ %08x with %i", read_pos, rret);\r
265           break;\r
266         }\r
267       }\r
268 \r
269       rret = 2048;\r
270       if (out_offs != 0 || bytes < 2048) {\r
271         //elprintf(EL_STATUS, "cso: unaligned/nonfull @ %08x, offs=%i, len=%u", cso->fpos_out, out_offs, bytes);\r
272         if (bytes < rret) rret = bytes;\r
273         if (2048 - out_offs < rret) rret = 2048 - out_offs;\r
274         memcpy(out, tmp_dst + out_offs, rret);\r
275       }\r
276       ret += rret;\r
277       out += rret;\r
278       cso->fpos_out += rret;\r
279       bytes -= rret;\r
280       block++;\r
281       index = index_end;\r
282       index_end = cso->index[block+1];\r
283     }\r
284   }\r
285   else\r
286     ret = 0;\r
287 \r
288   return ret;\r
289 }\r
290 \r
291 int pm_seek(pm_file *stream, long offset, int whence)\r
292 {\r
293   if (stream->type == PMT_UNCOMPRESSED)\r
294   {\r
295     fseek(stream->file, offset, whence);\r
296     return ftell(stream->file);\r
297   }\r
298   else if (stream->type == PMT_ZIP)\r
299   {\r
300     if (PicoMessage != NULL && offset > 6*1024*1024) {\r
301       long pos = gztell((gzFile) stream->param);\r
302       if (offset < pos || offset - pos > 6*1024*1024)\r
303         PicoMessage("Decompressing data...");\r
304     }\r
305     return gzseek((gzFile) stream->param, offset, whence);\r
306   }\r
307   else if (stream->type == PMT_CSO)\r
308   {\r
309     cso_struct *cso = stream->param;\r
310     switch (whence)\r
311     {\r
312       case SEEK_CUR: cso->fpos_out += offset; break;\r
313       case SEEK_SET: cso->fpos_out  = offset; break;\r
314       case SEEK_END: cso->fpos_out  = cso->header.total_bytes - offset; break;\r
315     }\r
316     return cso->fpos_out;\r
317   }\r
318   else\r
319     return -1;\r
320 }\r
321 \r
322 int pm_close(pm_file *fp)\r
323 {\r
324   int ret = 0;\r
325 \r
326   if (fp == NULL) return EOF;\r
327 \r
328   if (fp->type == PMT_UNCOMPRESSED)\r
329   {\r
330     fclose(fp->file);\r
331   }\r
332   else if (fp->type == PMT_ZIP)\r
333   {\r
334     ZIP *zipfile = fp->file;\r
335     gzclose((gzFile) fp->param);\r
336     zipfile->fp = NULL; // gzclose() closed it\r
337     closezip(zipfile);\r
338   }\r
339   else if (fp->type == PMT_CSO)\r
340   {\r
341     free(fp->param);\r
342     fclose(fp->file);\r
343   }\r
344   else\r
345     ret = EOF;\r
346 \r
347   free(fp);\r
348   return ret;\r
349 }\r
350 \r
351 \r
352 void Byteswap(unsigned char *data,int len)\r
353 {\r
354   int i=0;\r
355 \r
356   if (len<2) return; // Too short\r
357 \r
358   do\r
359   {\r
360     unsigned short *pd=(unsigned short *)(data+i);\r
361     int value=*pd; // Get 2 bytes\r
362 \r
363     value=(value<<8)|(value>>8); // Byteswap it\r
364     *pd=(unsigned short)value; // Put 2b ytes\r
365     i+=2;\r
366   }\r
367   while (i+2<=len);\r
368 }\r
369 \r
370 // Interleve a 16k block and byteswap\r
371 static int InterleveBlock(unsigned char *dest,unsigned char *src)\r
372 {\r
373   int i=0;\r
374   for (i=0;i<0x2000;i++) dest[(i<<1)  ]=src[       i]; // Odd\r
375   for (i=0;i<0x2000;i++) dest[(i<<1)+1]=src[0x2000+i]; // Even\r
376   return 0;\r
377 }\r
378 \r
379 // Decode a SMD file\r
380 static int DecodeSmd(unsigned char *data,int len)\r
381 {\r
382   unsigned char *temp=NULL;\r
383   int i=0;\r
384 \r
385   temp=(unsigned char *)malloc(0x4000);\r
386   if (temp==NULL) return 1;\r
387   memset(temp,0,0x4000);\r
388 \r
389   // Interleve each 16k block and shift down by 0x200:\r
390   for (i=0; i+0x4200<=len; i+=0x4000)\r
391   {\r
392     InterleveBlock(temp,data+0x200+i); // Interleve 16k to temporary buffer\r
393     memcpy(data+i,temp,0x4000); // Copy back in\r
394   }\r
395 \r
396   free(temp);\r
397   return 0;\r
398 }\r
399 \r
400 static unsigned char *cd_realloc(void *old, int filesize)\r
401 {\r
402   unsigned char *rom;\r
403   rom=realloc(old, sizeof(mcd_state));\r
404   if (rom) memset(rom+0x20000, 0, sizeof(mcd_state)-0x20000);\r
405   return rom;\r
406 }\r
407 \r
408 static unsigned char *PicoCartAlloc(int filesize)\r
409 {\r
410   int alloc_size;\r
411   unsigned char *rom;\r
412 \r
413   if (PicoMCD & 1) return cd_realloc(NULL, filesize);\r
414 \r
415   alloc_size=filesize+0x7ffff;\r
416   if((filesize&0x3fff)==0x200) alloc_size-=0x200;\r
417   alloc_size&=~0x7ffff; // use alloc size of multiples of 512K, so that memhandlers could be set up more efficiently\r
418   if((filesize&0x3fff)==0x200) alloc_size+=0x200;\r
419   else if(alloc_size-filesize < 4) alloc_size+=4; // padding for out-of-bound exec protection\r
420 \r
421   // Allocate space for the rom plus padding\r
422   rom=(unsigned char *)malloc(alloc_size);\r
423   if(rom) memset(rom+alloc_size-0x80000,0,0x80000);\r
424   return rom;\r
425 }\r
426 \r
427 int PicoCartLoad(pm_file *f,unsigned char **prom,unsigned int *psize)\r
428 {\r
429   unsigned char *rom=NULL; int size, bytes_read;\r
430   if (f==NULL) return 1;\r
431 \r
432   size=f->size;\r
433   if (size <= 0) return 1;\r
434   size=(size+3)&~3; // Round up to a multiple of 4\r
435 \r
436   // Allocate space for the rom plus padding\r
437   rom=PicoCartAlloc(size);\r
438   if (rom==NULL) {\r
439     elprintf(EL_STATUS, "out of memory (wanted %i)", size);\r
440     return 1;\r
441   }\r
442 \r
443   if (PicoCartLoadProgressCB != NULL)\r
444   {\r
445     // read ROM in blocks, just for fun\r
446     int ret;\r
447     unsigned char *p = rom;\r
448     bytes_read=0;\r
449     do\r
450     {\r
451       int todo = size - bytes_read;\r
452       if (todo > 256*1024) todo = 256*1024;\r
453       ret = pm_read(p,todo,f);\r
454       bytes_read += ret;\r
455       p += ret;\r
456       PicoCartLoadProgressCB(bytes_read * 100 / size);\r
457     }\r
458     while (ret > 0);\r
459   }\r
460   else\r
461     bytes_read = pm_read(rom,size,f); // Load up the rom\r
462   if (bytes_read <= 0) {\r
463     elprintf(EL_STATUS, "read failed");\r
464     free(rom);\r
465     return 1;\r
466   }\r
467 \r
468   // maybe we are loading MegaCD BIOS?\r
469   if (!(PicoMCD&1) && size == 0x20000 && (!strncmp((char *)rom+0x124, "BOOT", 4) || !strncmp((char *)rom+0x128, "BOOT", 4))) {\r
470     PicoMCD |= 1;\r
471     rom = cd_realloc(rom, size);\r
472   }\r
473 \r
474   // Check for SMD:\r
475   if ((size&0x3fff)==0x200) { DecodeSmd(rom,size); size-=0x200; } // Decode and byteswap SMD\r
476   else Byteswap(rom,size); // Just byteswap\r
477 \r
478   if (prom)  *prom=rom;\r
479   if (psize) *psize=size;\r
480 \r
481   return 0;\r
482 }\r
483 \r
484 // Insert/remove a cartridge:\r
485 int PicoCartInsert(unsigned char *rom,unsigned int romsize)\r
486 {\r
487   // notaz: add a 68k "jump one op back" opcode to the end of ROM.\r
488   // This will hang the emu, but will prevent nasty crashes.\r
489   // note: 4 bytes are padded to every ROM\r
490   if(rom != NULL)\r
491     *(unsigned long *)(rom+romsize) = 0xFFFE4EFA; // 4EFA FFFE byteswapped\r
492 \r
493   Pico.rom=rom;\r
494   Pico.romsize=romsize;\r
495 \r
496   // setup correct memory map for loaded ROM\r
497   if (PicoMCD & 1)\r
498        PicoMemSetupCD();\r
499   else PicoMemSetup();\r
500   PicoMemReset();\r
501 \r
502   if (!(PicoMCD & 1))\r
503     PicoCartDetect();\r
504 \r
505   return PicoReset(1);\r
506 }\r
507 \r
508 int PicoUnloadCart(unsigned char* romdata)\r
509 {\r
510   free(romdata);\r
511   return 0;\r
512 }\r
513 \r
514 static int rom_strcmp(int rom_offset, const char *s1)\r
515 {\r
516   int i, len = strlen(s1);\r
517   const char *s_rom = (const char *)Pico.rom + rom_offset;\r
518   for (i = 0; i < len; i++)\r
519     if (s1[i] != s_rom[i^1])\r
520       return 1;\r
521   return 0;\r
522 }\r
523 \r
524 static int name_cmp(const char *name)\r
525 {\r
526   return rom_strcmp(0x150, name);\r
527 }\r
528 \r
529 /* various cart-specific things, which can't be handled by generic code */\r
530 void PicoCartDetect(void)\r
531 {\r
532   int sram_size = 0, csum;\r
533   if(SRam.data) free(SRam.data); SRam.data=0;\r
534   Pico.m.sram_reg = 0;\r
535 \r
536   csum = PicoRead32(0x18c) & 0xffff;\r
537 \r
538   if (Pico.rom[0x1B1] == 'R' && Pico.rom[0x1B0] == 'A')\r
539   {\r
540     if (Pico.rom[0x1B2] & 0x40)\r
541     {\r
542       // EEPROM\r
543       SRam.start = PicoRead32(0x1B4) & ~1; // zero address is used for clock by some games\r
544       SRam.end   = PicoRead32(0x1B8);\r
545       sram_size  = 0x2000;\r
546       Pico.m.sram_reg |= 4;\r
547     } else {\r
548       // normal SRAM\r
549       SRam.start = PicoRead32(0x1B4) & ~0xff;\r
550       SRam.end   = PicoRead32(0x1B8) | 1;\r
551       sram_size  = SRam.end - SRam.start + 1;\r
552     }\r
553     SRam.start &= ~0xff000000;\r
554     SRam.end   &= ~0xff000000;\r
555     Pico.m.sram_reg |= 0x10; // SRAM was detected\r
556   }\r
557   if (sram_size <= 0)\r
558   {\r
559     // some games may have bad headers, like S&K and Sonic3\r
560     // note: majority games use 0x200000 as starting address, but there are some which\r
561     // use something else (0x300000 by HardBall '95). Luckily they have good headers.\r
562     SRam.start = 0x200000;\r
563     SRam.end   = 0x203FFF;\r
564     sram_size  = 0x004000;\r
565   }\r
566 \r
567   if (sram_size)\r
568   {\r
569     SRam.data = (unsigned char *) calloc(sram_size, 1);\r
570     if(!SRam.data) return;\r
571   }\r
572   SRam.changed = 0;\r
573 \r
574   // set EEPROM defaults, in case it gets detected\r
575   SRam.eeprom_type   = 0; // 7bit (24C01)\r
576   SRam.eeprom_abits  = 3; // eeprom access must be odd addr for: bit0 ~ cl, bit1 ~ in\r
577   SRam.eeprom_bit_cl = 1;\r
578   SRam.eeprom_bit_in = 0;\r
579   SRam.eeprom_bit_out= 0;\r
580 \r
581   // some known EEPROM data (thanks to EkeEke)\r
582   if (name_cmp("COLLEGE SLAM") == 0 ||\r
583       name_cmp("FRANK THOMAS BIGHURT BASEBAL") == 0)\r
584   {\r
585     SRam.eeprom_type = 3;\r
586     SRam.eeprom_abits = 2;\r
587     SRam.eeprom_bit_cl = 0;\r
588   }\r
589   else if (name_cmp("NBA JAM TOURNAMENT EDITION") == 0 ||\r
590            name_cmp("NFL QUARTERBACK CLUB") == 0)\r
591   {\r
592     SRam.eeprom_type = 2;\r
593     SRam.eeprom_abits = 2;\r
594     SRam.eeprom_bit_cl = 0;\r
595   }\r
596   else if (name_cmp("NBA JAM") == 0)\r
597   {\r
598     SRam.eeprom_type = 2;\r
599     SRam.eeprom_bit_out = 1;\r
600     SRam.eeprom_abits = 0;\r
601   }\r
602   else if (name_cmp("NHLPA HOCKEY '93") == 0 ||\r
603            name_cmp("NHLPA Hockey '93") == 0 ||\r
604            name_cmp("RINGS OF POWER") == 0)\r
605   {\r
606     SRam.start = SRam.end = 0x200000;\r
607     Pico.m.sram_reg = 0x14;\r
608     SRam.eeprom_abits = 0;\r
609     SRam.eeprom_bit_cl = 6;\r
610     SRam.eeprom_bit_in = 7;\r
611     SRam.eeprom_bit_out= 7;\r
612   }\r
613   else if ( name_cmp("MICRO MACHINES II") == 0 ||\r
614            (name_cmp("        ") == 0 && // Micro Machines {Turbo Tournament '96, Military - It's a Blast!}\r
615            (csum == 0x165e || csum == 0x168b || csum == 0xCEE0 || csum == 0x2C41)))\r
616   {\r
617     SRam.start = 0x300000;\r
618     SRam.end   = 0x380001;\r
619     Pico.m.sram_reg = 0x14;\r
620     SRam.eeprom_type = 2;\r
621     SRam.eeprom_abits = 0;\r
622     SRam.eeprom_bit_cl = 1;\r
623     SRam.eeprom_bit_in = 0;\r
624     SRam.eeprom_bit_out= 7;\r
625   }\r
626 \r
627   // Some games malfunction if SRAM is not filled with 0xff\r
628   if (name_cmp("DINO DINI'S SOCCER") == 0 ||\r
629       name_cmp("MICRO MACHINES II") == 0)\r
630     memset(SRam.data, 0xff, sram_size);\r
631 \r
632   // Unusual region 'code'\r
633   if (rom_strcmp(0x1f0, "EUROPE") == 0)\r
634     *(int *) (Pico.rom+0x1f0) = 0x20204520;\r
635 \r
636   // SVP detection\r
637   if (name_cmp("Virtua Racing") == 0)\r
638   {\r
639     PicoSVPInit();\r
640     PicoRead16Hook = PicoSVPRead16;\r
641     PicoWrite8Hook = PicoSVPWrite8;\r
642   }\r
643 }\r
644 \r