starting SDL port, refactoring
[picodrive.git] / platform / common / emu.c
1 /*\r
2  * PicoDrive\r
3  * (C) notaz, 2007-2010\r
4  *\r
5  * This work is licensed under the terms of MAME license.\r
6  * See COPYING file in the top-level directory.\r
7  */\r
8 \r
9 #include <stdio.h>\r
10 #include <stdlib.h>\r
11 #include <stdarg.h>\r
12 #ifndef NO_SYNC\r
13 #include <unistd.h>\r
14 #endif\r
15 \r
16 #include "../libpicofe/posix.h"\r
17 #include "../libpicofe/input.h"\r
18 #include "../libpicofe/fonts.h"\r
19 #include "../libpicofe/lprintf.h"\r
20 #include "../libpicofe/plat.h"\r
21 #include "emu.h"\r
22 #include "input_pico.h"\r
23 #include "menu_pico.h"\r
24 #include "config.h"\r
25 \r
26 #include <pico/pico_int.h>\r
27 #include <pico/patch.h>\r
28 #include <pico/cd/cue.h>\r
29 \r
30 \r
31 #define STATUS_MSG_TIMEOUT 2000\r
32 \r
33 void *g_screen_ptr;\r
34 \r
35 #if !SCREEN_SIZE_FIXED\r
36 int g_screen_width  = SCREEN_WIDTH;\r
37 int g_screen_height = SCREEN_HEIGHT;\r
38 #endif\r
39 \r
40 char *PicoConfigFile = "config.cfg";\r
41 currentConfig_t currentConfig, defaultConfig;\r
42 int state_slot = 0;\r
43 int config_slot = 0, config_slot_current = 0;\r
44 int pico_pen_x = 320/2, pico_pen_y = 240/2;\r
45 int pico_inp_mode = 0;\r
46 int engineState = PGS_Menu;\r
47 \r
48 /* tmp buff to reduce stack usage for plats with small stack */\r
49 static char static_buff[512];\r
50 const char *rom_fname_reload;\r
51 char rom_fname_loaded[512];\r
52 int rom_loaded = 0;\r
53 int reset_timing = 0;\r
54 static unsigned int notice_msg_time;    /* when started showing */\r
55 static char noticeMsg[40];\r
56 \r
57 unsigned char *movie_data = NULL;\r
58 static int movie_size = 0;\r
59 \r
60 \r
61 /* don't use tolower() for easy old glibc binary compatibility */\r
62 static void strlwr_(char *string)\r
63 {\r
64         char *p;\r
65         for (p = string; *p; p++)\r
66                 if ('A' <= *p && *p <= 'Z')\r
67                         *p += 'a' - 'A';\r
68 }\r
69 \r
70 static int try_rfn_cut(char *fname)\r
71 {\r
72         FILE *tmp;\r
73         char *p;\r
74 \r
75         p = fname + strlen(fname) - 1;\r
76         for (; p > fname; p--)\r
77                 if (*p == '.') break;\r
78         *p = 0;\r
79 \r
80         if((tmp = fopen(fname, "rb"))) {\r
81                 fclose(tmp);\r
82                 return 1;\r
83         }\r
84         return 0;\r
85 }\r
86 \r
87 static void get_ext(const char *file, char *ext)\r
88 {\r
89         const char *p;\r
90 \r
91         p = file + strlen(file) - 4;\r
92         if (p < file) p = file;\r
93         strncpy(ext, p, 4);\r
94         ext[4] = 0;\r
95         strlwr_(ext);\r
96 }\r
97 \r
98 static void fname_ext(char *dst, int dstlen, const char *prefix, const char *ext, const char *fname)\r
99 {\r
100         int prefix_len = 0;\r
101         const char *p;\r
102 \r
103         *dst = 0;\r
104         if (prefix) {\r
105                 int len = plat_get_root_dir(dst, dstlen);\r
106                 strcpy(dst + len, prefix);\r
107                 prefix_len = len + strlen(prefix);\r
108         }\r
109 \r
110         p = fname + strlen(fname) - 1;\r
111         for (; p >= fname && *p != PATH_SEP_C; p--)\r
112                 ;\r
113         p++;\r
114         strncpy(dst + prefix_len, p, dstlen - prefix_len - 1);\r
115 \r
116         dst[dstlen - 8] = 0;\r
117         if (dst[strlen(dst) - 4] == '.')\r
118                 dst[strlen(dst) - 4] = 0;\r
119         if (ext)\r
120                 strcat(dst, ext);\r
121 }\r
122 \r
123 static void romfname_ext(char *dst, int dstlen, const char *prefix, const char *ext)\r
124 {\r
125         fname_ext(dst, dstlen, prefix, ext, rom_fname_loaded);\r
126 }\r
127 \r
128 void emu_status_msg(const char *format, ...)\r
129 {\r
130         va_list vl;\r
131         int ret;\r
132 \r
133         va_start(vl, format);\r
134         ret = vsnprintf(noticeMsg, sizeof(noticeMsg), format, vl);\r
135         va_end(vl);\r
136 \r
137         /* be sure old text gets overwritten */\r
138         for (; ret < 28; ret++)\r
139                 noticeMsg[ret] = ' ';\r
140         noticeMsg[ret] = 0;\r
141 \r
142         notice_msg_time = plat_get_ticks_ms();\r
143 }\r
144 \r
145 static const char * const biosfiles_us[] = { "us_scd1_9210", "us_scd2_9306", "SegaCDBIOS9303" };\r
146 static const char * const biosfiles_eu[] = { "eu_mcd1_9210", "eu_mcd2_9306", "eu_mcd2_9303"   };\r
147 static const char * const biosfiles_jp[] = { "jp_mcd1_9112", "jp_mcd1_9111" };\r
148 \r
149 static int find_bios(int region, const char **bios_file)\r
150 {\r
151         int i, count;\r
152         const char * const *files;\r
153         FILE *f = NULL;\r
154 \r
155         if (region == 4) { // US\r
156                 files = biosfiles_us;\r
157                 count = sizeof(biosfiles_us) / sizeof(char *);\r
158         } else if (region == 8) { // EU\r
159                 files = biosfiles_eu;\r
160                 count = sizeof(biosfiles_eu) / sizeof(char *);\r
161         } else if (region == 1 || region == 2) {\r
162                 files = biosfiles_jp;\r
163                 count = sizeof(biosfiles_jp) / sizeof(char *);\r
164         } else {\r
165                 return 0;\r
166         }\r
167 \r
168         for (i = 0; i < count; i++)\r
169         {\r
170                 emu_make_path(static_buff, files[i], sizeof(static_buff) - 4);\r
171                 strcat(static_buff, ".bin");\r
172                 f = fopen(static_buff, "rb");\r
173                 if (f) break;\r
174 \r
175                 static_buff[strlen(static_buff) - 4] = 0;\r
176                 strcat(static_buff, ".zip");\r
177                 f = fopen(static_buff, "rb");\r
178                 if (f) break;\r
179         }\r
180 \r
181         if (f) {\r
182                 lprintf("using bios: %s\n", static_buff);\r
183                 fclose(f);\r
184                 if (bios_file)\r
185                         *bios_file = static_buff;\r
186                 return 1;\r
187         } else {\r
188                 sprintf(static_buff, "no %s BIOS files found, read docs",\r
189                         region != 4 ? (region == 8 ? "EU" : "JAP") : "USA");\r
190                 menu_update_msg(static_buff);\r
191                 return 0;\r
192         }\r
193 }\r
194 \r
195 /* check if the name begins with BIOS name */\r
196 /*\r
197 static int emu_isBios(const char *name)\r
198 {\r
199         int i;\r
200         for (i = 0; i < sizeof(biosfiles_us)/sizeof(biosfiles_us[0]); i++)\r
201                 if (strstr(name, biosfiles_us[i]) != NULL) return 1;\r
202         for (i = 0; i < sizeof(biosfiles_eu)/sizeof(biosfiles_eu[0]); i++)\r
203                 if (strstr(name, biosfiles_eu[i]) != NULL) return 1;\r
204         for (i = 0; i < sizeof(biosfiles_jp)/sizeof(biosfiles_jp[0]); i++)\r
205                 if (strstr(name, biosfiles_jp[i]) != NULL) return 1;\r
206         return 0;\r
207 }\r
208 */\r
209 \r
210 static unsigned char id_header[0x100];\r
211 \r
212 /* checks if fname points to valid MegaCD image */\r
213 static int emu_cd_check(int *pregion, const char *fname_in)\r
214 {\r
215         const char *fname = fname_in;\r
216         unsigned char buf[32];\r
217         pm_file *cd_f;\r
218         int region = 4; // 1: Japan, 4: US, 8: Europe\r
219         char ext[5];\r
220         cue_track_type type = CT_UNKNOWN;\r
221         cue_data_t *cue_data = NULL;\r
222 \r
223         get_ext(fname_in, ext);\r
224         if (strcasecmp(ext, ".cue") == 0) {\r
225                 cue_data = cue_parse(fname_in);\r
226                 if (cue_data != NULL) {\r
227                         fname = cue_data->tracks[1].fname;\r
228                         type  = cue_data->tracks[1].type;\r
229                 }\r
230                 else\r
231                         return -1;\r
232         }\r
233 \r
234         cd_f = pm_open(fname);\r
235         if (cue_data != NULL)\r
236                 cue_destroy(cue_data);\r
237 \r
238         if (cd_f == NULL) return 0; // let the upper level handle this\r
239 \r
240         if (pm_read(buf, 32, cd_f) != 32) {\r
241                 pm_close(cd_f);\r
242                 return -1;\r
243         }\r
244 \r
245         if (!strncasecmp("SEGADISCSYSTEM", (char *)buf+0x00, 14)) {\r
246                 if (type && type != CT_ISO)\r
247                         elprintf(EL_STATUS, ".cue has wrong type: %i", type);\r
248                 type = CT_ISO;       // Sega CD (ISO)\r
249         }\r
250         if (!strncasecmp("SEGADISCSYSTEM", (char *)buf+0x10, 14)) {\r
251                 if (type && type != CT_BIN)\r
252                         elprintf(EL_STATUS, ".cue has wrong type: %i", type);\r
253                 type = CT_BIN;       // Sega CD (BIN)\r
254         }\r
255 \r
256         if (type == CT_UNKNOWN) {\r
257                 pm_close(cd_f);\r
258                 return 0;\r
259         }\r
260 \r
261         pm_seek(cd_f, (type == CT_ISO) ? 0x100 : 0x110, SEEK_SET);\r
262         pm_read(id_header, sizeof(id_header), cd_f);\r
263 \r
264         /* it seems we have a CD image here. Try to detect region now.. */\r
265         pm_seek(cd_f, (type == CT_ISO) ? 0x100+0x10B : 0x110+0x10B, SEEK_SET);\r
266         pm_read(buf, 1, cd_f);\r
267         pm_close(cd_f);\r
268 \r
269         if (buf[0] == 0x64) region = 8; // EU\r
270         if (buf[0] == 0xa1) region = 1; // JAP\r
271 \r
272         lprintf("detected %s Sega/Mega CD image with %s region\n",\r
273                 type == CT_BIN ? "BIN" : "ISO", region != 4 ? (region == 8 ? "EU" : "JAP") : "USA");\r
274 \r
275         if (pregion != NULL) *pregion = region;\r
276 \r
277         return type;\r
278 }\r
279 \r
280 static int detect_media(const char *fname)\r
281 {\r
282         static const short sms_offsets[] = { 0x7ff0, 0x3ff0, 0x1ff0 };\r
283         static const char *sms_exts[] = { "sms", "gg", "sg" };\r
284         static const char *md_exts[] = { "gen", "bin", "smd" };\r
285         char buff0[32], buff[32];\r
286         unsigned short *d16;\r
287         pm_file *pmf;\r
288         char ext[5];\r
289         int i;\r
290 \r
291         get_ext(fname, ext);\r
292 \r
293         // detect wrong extensions\r
294         if (!strcmp(ext, ".srm") || !strcmp(ext, "s.gz") || !strcmp(ext, ".mds")) // s.gz ~ .mds.gz\r
295                 return PM_BAD;\r
296 \r
297         /* don't believe in extensions, except .cue */\r
298         if (strcasecmp(ext, ".cue") == 0)\r
299                 return PM_CD;\r
300 \r
301         pmf = pm_open(fname);\r
302         if (pmf == NULL)\r
303                 return PM_BAD;\r
304 \r
305         if (pm_read(buff0, 32, pmf) != 32) {\r
306                 pm_close(pmf);\r
307                 return PM_BAD;\r
308         }\r
309 \r
310         if (strncasecmp("SEGADISCSYSTEM", buff0 + 0x00, 14) == 0 ||\r
311             strncasecmp("SEGADISCSYSTEM", buff0 + 0x10, 14) == 0) {\r
312                 pm_close(pmf);\r
313                 return PM_CD;\r
314         }\r
315 \r
316         /* check for SMD evil */\r
317         if (pmf->size >= 0x4200 && (pmf->size & 0x3fff) == 0x200) {\r
318                 if (pm_seek(pmf, sms_offsets[0] + 0x200, SEEK_SET) == sms_offsets[0] + 0x200 &&\r
319                     pm_read(buff, 16, pmf) == 16 &&\r
320                     strncmp("TMR SEGA", buff, 8) == 0)\r
321                         goto looks_like_sms;\r
322 \r
323                 /* could parse further but don't bother */\r
324                 goto extension_check;\r
325         }\r
326 \r
327         /* MD header? Act as TMSS BIOS here */\r
328         if (pm_seek(pmf, 0x100, SEEK_SET) == 0x100 && pm_read(buff, 16, pmf) == 16) {\r
329                 if (strncmp(buff, "SEGA", 4) == 0 || strncmp(buff, " SEG", 4) == 0)\r
330                         goto looks_like_md;\r
331         }\r
332 \r
333         for (i = 0; i < array_size(sms_offsets); i++) {\r
334                 if (pm_seek(pmf, sms_offsets[i], SEEK_SET) != sms_offsets[i])\r
335                         continue;\r
336 \r
337                 if (pm_read(buff, 16, pmf) != 16)\r
338                         continue;\r
339 \r
340                 if (strncmp("TMR SEGA", buff, 8) == 0)\r
341                         goto looks_like_sms;\r
342         }\r
343 \r
344 extension_check:\r
345         /* probably some headerless thing. Maybe check the extension after all. */\r
346         for (i = 0; i < array_size(md_exts); i++)\r
347                 if (strcasecmp(pmf->ext, md_exts[i]) == 0)\r
348                         goto looks_like_md;\r
349 \r
350         for (i = 0; i < array_size(sms_exts); i++)\r
351                 if (strcasecmp(pmf->ext, sms_exts[i]) == 0)\r
352                         goto looks_like_sms;\r
353 \r
354         /* If everything else fails, make a guess on the reset vector */\r
355         d16 = (unsigned short *)(buff0 + 4);\r
356         if ((((d16[0] << 16) | d16[1]) & 0xffffff) >= pmf->size) {\r
357                 lprintf("bad MD reset vector, assuming SMS\n");\r
358                 goto looks_like_sms;\r
359         }\r
360 \r
361 looks_like_md:\r
362         pm_close(pmf);\r
363         return PM_MD_CART;\r
364 \r
365 looks_like_sms:\r
366         pm_close(pmf);\r
367         return PM_MARK3;\r
368 }\r
369 \r
370 static int extract_text(char *dest, const unsigned char *src, int len, int swab)\r
371 {\r
372         char *p = dest;\r
373         int i;\r
374 \r
375         if (swab) swab = 1;\r
376 \r
377         for (i = len - 1; i >= 0; i--)\r
378         {\r
379                 if (src[i^swab] != ' ') break;\r
380         }\r
381         len = i + 1;\r
382 \r
383         for (i = 0; i < len; i++)\r
384         {\r
385                 unsigned char s = src[i^swab];\r
386                 if (s >= 0x20 && s < 0x7f && s != '#' && s != '|' &&\r
387                         s != '[' && s != ']' && s != '\\')\r
388                 {\r
389                         *p++ = s;\r
390                 }\r
391                 else\r
392                 {\r
393                         sprintf(p, "\\%02x", s);\r
394                         p += 3;\r
395                 }\r
396         }\r
397 \r
398         return p - dest;\r
399 }\r
400 \r
401 static char *emu_make_rom_id(const char *fname)\r
402 {\r
403         static char id_string[3+0xe*3+0x3*3+0x30*3+3];\r
404         int pos, swab = 1;\r
405 \r
406         if (PicoAHW & PAHW_MCD) {\r
407                 strcpy(id_string, "CD|");\r
408                 swab = 0;\r
409         }\r
410         else if (PicoAHW & PAHW_SMS)\r
411                 strcpy(id_string, "MS|");\r
412         else    strcpy(id_string, "MD|");\r
413         pos = 3;\r
414 \r
415         if (!(PicoAHW & PAHW_SMS)) {\r
416                 pos += extract_text(id_string + pos, id_header + 0x80, 0x0e, swab); // serial\r
417                 id_string[pos] = '|'; pos++;\r
418                 pos += extract_text(id_string + pos, id_header + 0xf0, 0x03, swab); // region\r
419                 id_string[pos] = '|'; pos++;\r
420                 pos += extract_text(id_string + pos, id_header + 0x50, 0x30, swab); // overseas name\r
421                 id_string[pos] = 0;\r
422                 if (pos > 5)\r
423                         return id_string;\r
424                 pos = 3;\r
425         }\r
426 \r
427         // can't find name in ROM, use filename\r
428         fname_ext(id_string + 3, sizeof(id_string) - 3, NULL, NULL, fname);\r
429 \r
430         return id_string;\r
431 }\r
432 \r
433 // buffer must be at least 150 byte long\r
434 void emu_get_game_name(char *str150)\r
435 {\r
436         int ret, swab = (PicoAHW & PAHW_MCD) ? 0 : 1;\r
437         char *s, *d;\r
438 \r
439         ret = extract_text(str150, id_header + 0x50, 0x30, swab); // overseas name\r
440 \r
441         for (s = d = str150 + 1; s < str150+ret; s++)\r
442         {\r
443                 if (*s == 0) break;\r
444                 if (*s != ' ' || d[-1] != ' ')\r
445                         *d++ = *s;\r
446         }\r
447         *d = 0;\r
448 }\r
449 \r
450 static void shutdown_MCD(void)\r
451 {\r
452         if ((PicoAHW & PAHW_MCD) && Pico_mcd != NULL)\r
453                 Stop_CD();\r
454         PicoAHW &= ~PAHW_MCD;\r
455 }\r
456 \r
457 static void system_announce(void)\r
458 {\r
459         const char *sys_name, *tv_standard, *extra = "";\r
460         int fps;\r
461 \r
462         if (PicoAHW & PAHW_SMS) {\r
463                 sys_name = "Master System";\r
464 #ifdef NO_SMS\r
465                 extra = " [no support]";\r
466 #endif\r
467         } else if (PicoAHW & PAHW_PICO) {\r
468                 sys_name = "Pico";\r
469         } else if (PicoAHW & PAHW_MCD) {\r
470                 sys_name = "Mega CD";\r
471                 if ((Pico.m.hardware & 0xc0) == 0x80)\r
472                         sys_name = "Sega CD";\r
473         } else if (PicoAHW & PAHW_32X) {\r
474                 sys_name = "32X";\r
475         } else {\r
476                 sys_name = "MegaDrive";\r
477                 if ((Pico.m.hardware & 0xc0) == 0x80)\r
478                         sys_name = "Genesis";\r
479         }\r
480         tv_standard = Pico.m.pal ? "PAL" : "NTSC";\r
481         fps = Pico.m.pal ? 50 : 60;\r
482 \r
483         emu_status_msg("%s %s / %dFPS%s", tv_standard, sys_name, fps, extra);\r
484 }\r
485 \r
486 // XXX: portions of this code should move to pico/\r
487 int emu_reload_rom(const char *rom_fname_in)\r
488 {\r
489         unsigned int rom_size = 0;\r
490         const char *used_rom_name = NULL;\r
491         char *rom_fname = NULL;\r
492         unsigned char *rom_data = NULL;\r
493         char ext[5];\r
494         pm_file *rom = NULL;\r
495         int cd_state = CIT_NOT_CD;\r
496         int ret, media_type, cd_region;\r
497         int cfg_loaded = 0, bad_rom = 0;\r
498         int menu_romload_started = 0;\r
499         int retval = 0;\r
500 \r
501         lprintf("emu_ReloadRom(%s)\n", rom_fname_in);\r
502 \r
503         rom_fname = strdup(rom_fname_in);\r
504         if (rom_fname == NULL)\r
505                 return 0;\r
506 \r
507         used_rom_name = rom_fname;\r
508         get_ext(rom_fname, ext);\r
509 \r
510         // early cleanup\r
511         PicoPatchUnload();\r
512         if (movie_data) {\r
513                 free(movie_data);\r
514                 movie_data = 0;\r
515         }\r
516 \r
517         if (!strcmp(ext, ".gmv"))\r
518         {\r
519                 // check for both gmv and rom\r
520                 int dummy;\r
521                 FILE *movie_file = fopen(rom_fname, "rb");\r
522                 if (!movie_file) {\r
523                         menu_update_msg("Failed to open movie.");\r
524                         goto out;\r
525                 }\r
526                 fseek(movie_file, 0, SEEK_END);\r
527                 movie_size = ftell(movie_file);\r
528                 fseek(movie_file, 0, SEEK_SET);\r
529                 if (movie_size < 64+3) {\r
530                         menu_update_msg("Invalid GMV file.");\r
531                         fclose(movie_file);\r
532                         goto out;\r
533                 }\r
534                 movie_data = malloc(movie_size);\r
535                 if (movie_data == NULL) {\r
536                         menu_update_msg("low memory.");\r
537                         fclose(movie_file);\r
538                         goto out;\r
539                 }\r
540                 dummy = fread(movie_data, 1, movie_size, movie_file);\r
541                 fclose(movie_file);\r
542                 if (strncmp((char *)movie_data, "Gens Movie TEST", 15) != 0) {\r
543                         menu_update_msg("Invalid GMV file.");\r
544                         goto out;\r
545                 }\r
546                 dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);\r
547                 if (!dummy) {\r
548                         menu_update_msg("Could't find a ROM for movie.");\r
549                         goto out;\r
550                 }\r
551                 get_ext(rom_fname, ext);\r
552                 lprintf("gmv loaded for %s\n", rom_fname);\r
553         }\r
554         else if (!strcmp(ext, ".pat"))\r
555         {\r
556                 int dummy;\r
557                 PicoPatchLoad(rom_fname);\r
558                 dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);\r
559                 if (!dummy) {\r
560                         menu_update_msg("Could't find a ROM to patch.");\r
561                         goto out;\r
562                 }\r
563                 get_ext(rom_fname, ext);\r
564         }\r
565 \r
566         media_type = detect_media(rom_fname);\r
567         if (media_type == PM_BAD) {\r
568                 menu_update_msg("Not a ROM/CD img selected.");\r
569                 goto out;\r
570         }\r
571 \r
572         shutdown_MCD();\r
573         PicoCartUnload();\r
574         rom_loaded = 0;\r
575 \r
576         PicoAHW = 0;\r
577 \r
578         if (media_type == PM_CD)\r
579         {\r
580                 // check for MegaCD image\r
581                 cd_state = emu_cd_check(&cd_region, rom_fname);\r
582                 if (cd_state >= 0 && cd_state != CIT_NOT_CD)\r
583                 {\r
584                         // valid CD image, check for BIOS..\r
585 \r
586                         // we need to have config loaded at this point\r
587                         ret = emu_read_config(rom_fname, 0);\r
588                         if (!ret) emu_read_config(NULL, 0);\r
589                         cfg_loaded = 1;\r
590 \r
591                         if (PicoRegionOverride) {\r
592                                 cd_region = PicoRegionOverride;\r
593                                 lprintf("override region to %s\n", cd_region != 4 ?\r
594                                         (cd_region == 8 ? "EU" : "JAP") : "USA");\r
595                         }\r
596                         if (!find_bios(cd_region, &used_rom_name))\r
597                                 goto out;\r
598 \r
599                         get_ext(used_rom_name, ext);\r
600                         PicoAHW |= PAHW_MCD;\r
601                 }\r
602                 else {\r
603                         menu_update_msg("Invalid CD image");\r
604                         goto out;\r
605                 }\r
606         }\r
607         else if (media_type == PM_MARK3) {\r
608                 lprintf("detected SMS ROM\n");\r
609                 PicoAHW = PAHW_SMS;\r
610         }\r
611 \r
612         rom = pm_open(used_rom_name);\r
613         if (rom == NULL) {\r
614                 menu_update_msg("Failed to open ROM");\r
615                 goto out;\r
616         }\r
617 \r
618         menu_romload_prepare(used_rom_name); // also CD load\r
619         menu_romload_started = 1;\r
620         used_rom_name = NULL; // uses static_buff\r
621 \r
622         ret = PicoCartLoad(rom, &rom_data, &rom_size, (PicoAHW & PAHW_SMS) ? 1 : 0);\r
623         pm_close(rom);\r
624         if (ret != 0) {\r
625                 if      (ret == 2) menu_update_msg("Out of memory");\r
626                 else if (ret == 3) menu_update_msg("Read failed");\r
627                 else               menu_update_msg("PicoCartLoad() failed.");\r
628                 goto out;\r
629         }\r
630 \r
631         // detect wrong files\r
632         if (strncmp((char *)rom_data, "Pico", 4) == 0)\r
633                 bad_rom = 1;\r
634         else if (!(PicoAHW & PAHW_SMS)) {\r
635                 unsigned short *d = (unsigned short *)(rom_data + 4);\r
636                 if ((((d[0] << 16) | d[1]) & 0xffffff) >= (int)rom_size) {\r
637                         lprintf("bad reset vector\n");\r
638                         bad_rom = 1;\r
639                 }\r
640         }\r
641 \r
642         if (bad_rom) {\r
643                 menu_update_msg("Bad ROM detected.");\r
644                 goto out;\r
645         }\r
646 \r
647         // load config for this ROM (do this before insert to get correct region)\r
648         if (!(PicoAHW & PAHW_MCD))\r
649                 memcpy(id_header, rom_data + 0x100, sizeof(id_header));\r
650         if (!cfg_loaded) {\r
651                 ret = emu_read_config(rom_fname, 0);\r
652                 if (!ret) emu_read_config(NULL, 0);\r
653         }\r
654 \r
655         emu_make_path(static_buff, "carthw.cfg", sizeof(static_buff));\r
656         if (PicoCartInsert(rom_data, rom_size, static_buff)) {\r
657                 menu_update_msg("Failed to load ROM.");\r
658                 goto out;\r
659         }\r
660 \r
661         // insert CD if it was detected\r
662         if (cd_state != CIT_NOT_CD) {\r
663                 ret = Insert_CD(rom_fname, cd_state);\r
664                 if (ret != 0) {\r
665                         PicoCartUnload();\r
666                         rom_data = NULL; // freed by unload\r
667                         menu_update_msg("Insert_CD() failed, invalid CD image?");\r
668                         goto out;\r
669                 }\r
670         }\r
671 \r
672         menu_romload_end();\r
673         menu_romload_started = 0;\r
674 \r
675         if (PicoPatches) {\r
676                 PicoPatchPrepare();\r
677                 PicoPatchApply();\r
678         }\r
679 \r
680         // additional movie stuff\r
681         if (movie_data)\r
682         {\r
683                 if (movie_data[0x14] == '6')\r
684                      PicoOpt |=  POPT_6BTN_PAD; // 6 button pad\r
685                 else PicoOpt &= ~POPT_6BTN_PAD;\r
686                 PicoOpt |= POPT_DIS_VDP_FIFO; // no VDP fifo timing\r
687                 if (movie_data[0xF] >= 'A') {\r
688                         if (movie_data[0x16] & 0x80) {\r
689                                 PicoRegionOverride = 8;\r
690                         } else {\r
691                                 PicoRegionOverride = 4;\r
692                         }\r
693                         PicoReset();\r
694                         // TODO: bits 6 & 5\r
695                 }\r
696                 movie_data[0x18+30] = 0;\r
697                 emu_status_msg("MOVIE: %s", (char *) &movie_data[0x18]);\r
698         }\r
699         else\r
700         {\r
701                 system_announce();\r
702                 PicoOpt &= ~POPT_DIS_VDP_FIFO;\r
703         }\r
704 \r
705         strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);\r
706         rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;\r
707         rom_loaded = 1;\r
708 \r
709         // load SRAM for this ROM\r
710         if (currentConfig.EmuOpt & EOPT_EN_SRAM)\r
711                 emu_save_load_game(1, 1);\r
712 \r
713         retval = 1;\r
714 out:\r
715         if (retval == 0 && rom_data)\r
716                 free(rom_data);\r
717         if (menu_romload_started)\r
718                 menu_romload_end();\r
719         free(rom_fname);\r
720         return retval;\r
721 }\r
722 \r
723 int emu_swap_cd(const char *fname)\r
724 {\r
725         cd_img_type cd_type;\r
726         int ret = -1;\r
727 \r
728         cd_type = emu_cd_check(NULL, fname);\r
729         if (cd_type != CIT_NOT_CD)\r
730                 ret = Insert_CD(fname, cd_type);\r
731         if (ret != 0) {\r
732                 menu_update_msg("Load failed, invalid CD image?");\r
733                 return 0;\r
734         }\r
735 \r
736         strncpy(rom_fname_loaded, fname, sizeof(rom_fname_loaded)-1);\r
737         rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;\r
738         return 1;\r
739 }\r
740 \r
741 // <base dir><end>\r
742 void emu_make_path(char *buff, const char *end, int size)\r
743 {\r
744         int pos, end_len;\r
745 \r
746         end_len = strlen(end);\r
747         pos = plat_get_root_dir(buff, size);\r
748         strncpy(buff + pos, end, size - pos);\r
749         buff[size - 1] = 0;\r
750         if (pos + end_len > size - 1)\r
751                 lprintf("Warning: path truncated: %s\n", buff);\r
752 }\r
753 \r
754 static void make_config_cfg(char *cfg_buff_512)\r
755 {\r
756         emu_make_path(cfg_buff_512, PicoConfigFile, 512-6);\r
757         if (config_slot != 0)\r
758         {\r
759                 char *p = strrchr(cfg_buff_512, '.');\r
760                 if (p == NULL)\r
761                         p = cfg_buff_512 + strlen(cfg_buff_512);\r
762                 sprintf(p, ".%i.cfg", config_slot);\r
763         }\r
764         cfg_buff_512[511] = 0;\r
765 }\r
766 \r
767 void emu_prep_defconfig(void)\r
768 {\r
769         memset(&defaultConfig, 0, sizeof(defaultConfig));\r
770         defaultConfig.EmuOpt    = 0x9d | EOPT_RAM_TIMINGS|EOPT_EN_CD_LEDS;\r
771         defaultConfig.s_PicoOpt = POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80 |\r
772                                   POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_SVP_DRC|POPT_ACC_SPRITES |\r
773                                   POPT_EN_32X|POPT_EN_PWM;\r
774         defaultConfig.s_PsndRate = 44100;\r
775         defaultConfig.s_PicoRegion = 0; // auto\r
776         defaultConfig.s_PicoAutoRgnOrder = 0x184; // US, EU, JP\r
777         defaultConfig.s_PicoCDBuffers = 0;\r
778         defaultConfig.confirm_save = EOPT_CONFIRM_SAVE;\r
779         defaultConfig.Frameskip = -1; // auto\r
780         defaultConfig.volume = 50;\r
781         defaultConfig.gamma = 100;\r
782         defaultConfig.scaling = 0;\r
783         defaultConfig.turbo_rate = 15;\r
784 \r
785         // platform specific overrides\r
786         pemu_prep_defconfig();\r
787 }\r
788 \r
789 void emu_set_defconfig(void)\r
790 {\r
791         memcpy(&currentConfig, &defaultConfig, sizeof(currentConfig));\r
792         PicoOpt = currentConfig.s_PicoOpt;\r
793         PsndRate = currentConfig.s_PsndRate;\r
794         PicoRegionOverride = currentConfig.s_PicoRegion;\r
795         PicoAutoRgnOrder = currentConfig.s_PicoAutoRgnOrder;\r
796         PicoCDBuffers = currentConfig.s_PicoCDBuffers;\r
797         p32x_msh2_multiplier = MSH2_MULTI_DEFAULT;\r
798         p32x_ssh2_multiplier = SSH2_MULTI_DEFAULT;\r
799 }\r
800 \r
801 int emu_read_config(const char *rom_fname, int no_defaults)\r
802 {\r
803         char cfg[512];\r
804         int ret;\r
805 \r
806         if (!no_defaults)\r
807                 emu_set_defconfig();\r
808 \r
809         if (rom_fname == NULL)\r
810         {\r
811                 // global config\r
812                 make_config_cfg(cfg);\r
813                 ret = config_readsect(cfg, NULL);\r
814         }\r
815         else\r
816         {\r
817                 char *sect = emu_make_rom_id(rom_fname);\r
818 \r
819                 if (config_slot != 0)\r
820                      sprintf(cfg, "game.%i.cfg", config_slot);\r
821                 else strcpy(cfg,  "game.cfg");\r
822 \r
823                 ret = -1;\r
824                 if (config_havesect(cfg, sect))\r
825                 {\r
826                         // read user's config\r
827                         int vol = currentConfig.volume;\r
828                         emu_set_defconfig();\r
829                         ret = config_readsect(cfg, sect);\r
830                         currentConfig.volume = vol; // make vol global (bah)\r
831                 }\r
832                 else\r
833                 {\r
834                         // read global config, and apply game_def.cfg on top\r
835                         make_config_cfg(cfg);\r
836                         config_readsect(cfg, NULL);\r
837                         emu_make_path(cfg, "game_def.cfg", sizeof(cfg));\r
838                         ret = config_readsect(cfg, sect);\r
839                 }\r
840 \r
841                 if (ret == 0)\r
842                 {\r
843                         lprintf("loaded cfg from sect \"%s\"\n", sect);\r
844                 }\r
845         }\r
846 \r
847         pemu_validate_config();\r
848 \r
849         // some sanity checks\r
850 #ifdef PSP\r
851         /* TODO: mv to plat_validate_config() */\r
852         if (currentConfig.CPUclock < 10 || currentConfig.CPUclock > 4096) currentConfig.CPUclock = 200;\r
853         if (currentConfig.gamma < -4 || currentConfig.gamma >  16) currentConfig.gamma = 0;\r
854         if (currentConfig.gamma2 < 0 || currentConfig.gamma2 > 2)  currentConfig.gamma2 = 0;\r
855 #endif\r
856         if (currentConfig.volume < 0 || currentConfig.volume > 99)\r
857                 currentConfig.volume = 50;\r
858 \r
859         if (ret == 0)\r
860                 config_slot_current = config_slot;\r
861 \r
862         return (ret == 0);\r
863 }\r
864 \r
865 \r
866 int emu_write_config(int is_game)\r
867 {\r
868         char cfg[512], *game_sect = NULL;\r
869         int ret, write_lrom = 0;\r
870 \r
871         if (!is_game)\r
872         {\r
873                 make_config_cfg(cfg);\r
874                 write_lrom = 1;\r
875         } else {\r
876                 if (config_slot != 0)\r
877                      sprintf(cfg, "game.%i.cfg", config_slot);\r
878                 else strcpy(cfg,  "game.cfg");\r
879                 game_sect = emu_make_rom_id(rom_fname_loaded);\r
880                 lprintf("emu_write_config: sect \"%s\"\n", game_sect);\r
881         }\r
882 \r
883         lprintf("emu_write_config: %s ", cfg);\r
884         ret = config_writesect(cfg, game_sect);\r
885         if (write_lrom) config_writelrom(cfg);\r
886 #ifndef NO_SYNC\r
887         sync();\r
888 #endif\r
889         lprintf((ret == 0) ? "(ok)\n" : "(failed)\n");\r
890 \r
891         if (ret == 0) config_slot_current = config_slot;\r
892         return ret == 0;\r
893 }\r
894 \r
895 \r
896 /* always using built-in font */\r
897 \r
898 #define mk_text_out(name, type, val, topleft, step_x, step_y) \\r
899 void name(int x, int y, const char *text)                               \\r
900 {                                                                       \\r
901         int i, l, len = strlen(text);                                   \\r
902         type *screen = (type *)(topleft) + x * step_x + y * step_y;     \\r
903                                                                         \\r
904         for (i = 0; i < len; i++, screen += 8 * step_x)                 \\r
905         {                                                               \\r
906                 for (l = 0; l < 8; l++)                                 \\r
907                 {                                                       \\r
908                         unsigned char fd = fontdata8x8[text[i] * 8 + l];\\r
909                         type *s = screen + l * step_y;                  \\r
910                         if (fd&0x80) s[step_x * 0] = val;               \\r
911                         if (fd&0x40) s[step_x * 1] = val;               \\r
912                         if (fd&0x20) s[step_x * 2] = val;               \\r
913                         if (fd&0x10) s[step_x * 3] = val;               \\r
914                         if (fd&0x08) s[step_x * 4] = val;               \\r
915                         if (fd&0x04) s[step_x * 5] = val;               \\r
916                         if (fd&0x02) s[step_x * 6] = val;               \\r
917                         if (fd&0x01) s[step_x * 7] = val;               \\r
918                 }                                                       \\r
919         }                                                               \\r
920 }\r
921 \r
922 mk_text_out(emu_text_out8,      unsigned char,    0xf0, g_screen_ptr, 1, g_screen_width)\r
923 mk_text_out(emu_text_out16,     unsigned short, 0xffff, g_screen_ptr, 1, g_screen_width)\r
924 mk_text_out(emu_text_out8_rot,  unsigned char,    0xf0,\r
925         (char *)g_screen_ptr  + (g_screen_width - 1) * g_screen_height, -g_screen_height, 1)\r
926 mk_text_out(emu_text_out16_rot, unsigned short, 0xffff,\r
927         (short *)g_screen_ptr + (g_screen_width - 1) * g_screen_height, -g_screen_height, 1)\r
928 \r
929 #undef mk_text_out\r
930 \r
931 \r
932 void update_movie(void)\r
933 {\r
934         int offs = Pico.m.frame_count*3 + 0x40;\r
935         if (offs+3 > movie_size) {\r
936                 free(movie_data);\r
937                 movie_data = 0;\r
938                 emu_status_msg("END OF MOVIE.");\r
939                 lprintf("END OF MOVIE.\n");\r
940         } else {\r
941                 // MXYZ SACB RLDU\r
942                 PicoPad[0] = ~movie_data[offs]   & 0x8f; // ! SCBA RLDU\r
943                 if(!(movie_data[offs]   & 0x10)) PicoPad[0] |= 0x40; // C\r
944                 if(!(movie_data[offs]   & 0x20)) PicoPad[0] |= 0x10; // A\r
945                 if(!(movie_data[offs]   & 0x40)) PicoPad[0] |= 0x20; // B\r
946                 PicoPad[1] = ~movie_data[offs+1] & 0x8f; // ! SCBA RLDU\r
947                 if(!(movie_data[offs+1] & 0x10)) PicoPad[1] |= 0x40; // C\r
948                 if(!(movie_data[offs+1] & 0x20)) PicoPad[1] |= 0x10; // A\r
949                 if(!(movie_data[offs+1] & 0x40)) PicoPad[1] |= 0x20; // B\r
950                 PicoPad[0] |= (~movie_data[offs+2] & 0x0A) << 8; // ! MZYX\r
951                 if(!(movie_data[offs+2] & 0x01)) PicoPad[0] |= 0x0400; // X\r
952                 if(!(movie_data[offs+2] & 0x04)) PicoPad[0] |= 0x0100; // Z\r
953                 PicoPad[1] |= (~movie_data[offs+2] & 0xA0) << 4; // ! MZYX\r
954                 if(!(movie_data[offs+2] & 0x10)) PicoPad[1] |= 0x0400; // X\r
955                 if(!(movie_data[offs+2] & 0x40)) PicoPad[1] |= 0x0100; // Z\r
956         }\r
957 }\r
958 \r
959 static int try_ropen_file(const char *fname)\r
960 {\r
961         FILE *f;\r
962 \r
963         f = fopen(fname, "rb");\r
964         if (f) {\r
965                 fclose(f);\r
966                 return 1;\r
967         }\r
968         return 0;\r
969 }\r
970 \r
971 char *emu_get_save_fname(int load, int is_sram, int slot)\r
972 {\r
973         char *saveFname = static_buff;\r
974         char ext[16];\r
975 \r
976         if (is_sram)\r
977         {\r
978                 strcpy(ext, (PicoAHW & PAHW_MCD) ? ".brm" : ".srm");\r
979                 romfname_ext(saveFname, sizeof(static_buff),\r
980                         (PicoAHW & PAHW_MCD) ? "brm"PATH_SEP : "srm"PATH_SEP, ext);\r
981                 if (!load)\r
982                         return saveFname;\r
983 \r
984                 if (try_ropen_file(saveFname))\r
985                         return saveFname;\r
986 \r
987                 romfname_ext(saveFname, sizeof(static_buff), NULL, ext);\r
988                 if (try_ropen_file(saveFname))\r
989                         return saveFname;\r
990         }\r
991         else\r
992         {\r
993                 const char *ext_main = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds.gz" : ".mds";\r
994                 const char *ext_othr = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds" : ".mds.gz";\r
995                 ext[0] = 0;\r
996                 if (slot > 0 && slot < 10)\r
997                         sprintf(ext, ".%i", slot);\r
998                 strcat(ext, ext_main);\r
999 \r
1000                 if (!load) {\r
1001                         romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);\r
1002                         return saveFname;\r
1003                 }\r
1004                 else {\r
1005                         romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);\r
1006                         if (try_ropen_file(saveFname))\r
1007                                 return saveFname;\r
1008 \r
1009                         romfname_ext(saveFname, sizeof(static_buff), NULL, ext);\r
1010                         if (try_ropen_file(saveFname))\r
1011                                 return saveFname;\r
1012 \r
1013                         // try the other ext\r
1014                         ext[0] = 0;\r
1015                         if (slot > 0 && slot < 10)\r
1016                                 sprintf(ext, ".%i", slot);\r
1017                         strcat(ext, ext_othr);\r
1018 \r
1019                         romfname_ext(saveFname, sizeof(static_buff), "mds"PATH_SEP, ext);\r
1020                         if (try_ropen_file(saveFname))\r
1021                                 return saveFname;\r
1022                 }\r
1023         }\r
1024 \r
1025         return NULL;\r
1026 }\r
1027 \r
1028 int emu_check_save_file(int slot, int *time)\r
1029 {\r
1030         return emu_get_save_fname(1, 0, slot) ? 1 : 0;\r
1031 }\r
1032 \r
1033 int emu_save_load_game(int load, int sram)\r
1034 {\r
1035         int ret = 0;\r
1036         char *saveFname;\r
1037 \r
1038         // make save filename\r
1039         saveFname = emu_get_save_fname(load, sram, state_slot);\r
1040         if (saveFname == NULL) {\r
1041                 if (!sram)\r
1042                         emu_status_msg(load ? "LOAD FAILED (missing file)" : "SAVE FAILED");\r
1043                 return -1;\r
1044         }\r
1045 \r
1046         lprintf("saveLoad (%i, %i): %s\n", load, sram, saveFname);\r
1047 \r
1048         if (sram)\r
1049         {\r
1050                 FILE *sramFile;\r
1051                 int sram_size;\r
1052                 unsigned char *sram_data;\r
1053                 int truncate = 1;\r
1054                 if (PicoAHW & PAHW_MCD)\r
1055                 {\r
1056                         if (PicoOpt & POPT_EN_MCD_RAMCART) {\r
1057                                 sram_size = 0x12000;\r
1058                                 sram_data = SRam.data;\r
1059                                 if (sram_data)\r
1060                                         memcpy32((int *)sram_data, (int *)Pico_mcd->bram, 0x2000/4);\r
1061                         } else {\r
1062                                 sram_size = 0x2000;\r
1063                                 sram_data = Pico_mcd->bram;\r
1064                                 truncate  = 0; // the .brm may contain RAM cart data after normal brm\r
1065                         }\r
1066                 } else {\r
1067                         sram_size = SRam.size;\r
1068                         sram_data = SRam.data;\r
1069                 }\r
1070                 if (sram_data == NULL)\r
1071                         return 0; // SRam forcefully disabled for this game\r
1072 \r
1073                 if (load)\r
1074                 {\r
1075                         sramFile = fopen(saveFname, "rb");\r
1076                         if (!sramFile)\r
1077                                 return -1;\r
1078                         ret = fread(sram_data, 1, sram_size, sramFile);\r
1079                         ret = ret > 0 ? 0 : -1;\r
1080                         fclose(sramFile);\r
1081                         if ((PicoAHW & PAHW_MCD) && (PicoOpt&POPT_EN_MCD_RAMCART))\r
1082                                 memcpy32((int *)Pico_mcd->bram, (int *)sram_data, 0x2000/4);\r
1083                 } else {\r
1084                         // sram save needs some special processing\r
1085                         // see if we have anything to save\r
1086                         for (; sram_size > 0; sram_size--)\r
1087                                 if (sram_data[sram_size-1]) break;\r
1088 \r
1089                         if (sram_size) {\r
1090                                 sramFile = fopen(saveFname, truncate ? "wb" : "r+b");\r
1091                                 if (!sramFile) sramFile = fopen(saveFname, "wb"); // retry\r
1092                                 if (!sramFile) return -1;\r
1093                                 ret = fwrite(sram_data, 1, sram_size, sramFile);\r
1094                                 ret = (ret != sram_size) ? -1 : 0;\r
1095                                 fclose(sramFile);\r
1096 #ifndef NO_SYNC\r
1097                                 sync();\r
1098 #endif\r
1099                         }\r
1100                 }\r
1101                 return ret;\r
1102         }\r
1103         else\r
1104         {\r
1105                 ret = PicoState(saveFname, !load);\r
1106                 if (!ret) {\r
1107 #ifndef NO_SYNC\r
1108                         if (!load) sync();\r
1109 #endif\r
1110                         emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");\r
1111                 } else {\r
1112                         emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");\r
1113                         ret = -1;\r
1114                 }\r
1115 \r
1116                 return ret;\r
1117         }\r
1118 }\r
1119 \r
1120 void emu_set_fastforward(int set_on)\r
1121 {\r
1122         static void *set_PsndOut = NULL;\r
1123         static int set_Frameskip, set_EmuOpt, is_on = 0;\r
1124 \r
1125         if (set_on && !is_on) {\r
1126                 set_PsndOut = PsndOut;\r
1127                 set_Frameskip = currentConfig.Frameskip;\r
1128                 set_EmuOpt = currentConfig.EmuOpt;\r
1129                 PsndOut = NULL;\r
1130                 currentConfig.Frameskip = 8;\r
1131                 currentConfig.EmuOpt &= ~4;\r
1132                 currentConfig.EmuOpt |= 0x40000;\r
1133                 is_on = 1;\r
1134                 emu_status_msg("FAST FORWARD");\r
1135         }\r
1136         else if (!set_on && is_on) {\r
1137                 PsndOut = set_PsndOut;\r
1138                 currentConfig.Frameskip = set_Frameskip;\r
1139                 currentConfig.EmuOpt = set_EmuOpt;\r
1140                 PsndRerate(1);\r
1141                 is_on = 0;\r
1142         }\r
1143 }\r
1144 \r
1145 static void emu_tray_open(void)\r
1146 {\r
1147         engineState = PGS_TrayMenu;\r
1148 }\r
1149 \r
1150 static void emu_tray_close(void)\r
1151 {\r
1152         emu_status_msg("CD tray closed.");\r
1153 }\r
1154 \r
1155 void emu_32x_startup(void)\r
1156 {\r
1157         plat_video_toggle_renderer(0, 0); // HACK\r
1158         system_announce();\r
1159 \r
1160         // force mode change event\r
1161         rendstatus_old = -1;\r
1162 }\r
1163 \r
1164 void emu_reset_game(void)\r
1165 {\r
1166         PicoReset();\r
1167         reset_timing = 1;\r
1168 }\r
1169 \r
1170 void run_events_pico(unsigned int events)\r
1171 {\r
1172         int lim_x;\r
1173 \r
1174         if (events & PEV_PICO_SWINP) {\r
1175                 pico_inp_mode++;\r
1176                 if (pico_inp_mode > 2)\r
1177                         pico_inp_mode = 0;\r
1178                 switch (pico_inp_mode) {\r
1179                         case 2: emu_status_msg("Input: Pen on Pad"); break;\r
1180                         case 1: emu_status_msg("Input: Pen on Storyware"); break;\r
1181                         case 0: emu_status_msg("Input: Joystick");\r
1182                                 PicoPicohw.pen_pos[0] = PicoPicohw.pen_pos[1] = 0x8000;\r
1183                                 break;\r
1184                 }\r
1185         }\r
1186         if (events & PEV_PICO_PPREV) {\r
1187                 PicoPicohw.page--;\r
1188                 if (PicoPicohw.page < 0)\r
1189                         PicoPicohw.page = 0;\r
1190                 emu_status_msg("Page %i", PicoPicohw.page);\r
1191         }\r
1192         if (events & PEV_PICO_PNEXT) {\r
1193                 PicoPicohw.page++;\r
1194                 if (PicoPicohw.page > 6)\r
1195                         PicoPicohw.page = 6;\r
1196                 emu_status_msg("Page %i", PicoPicohw.page);\r
1197         }\r
1198 \r
1199         if (pico_inp_mode == 0)\r
1200                 return;\r
1201 \r
1202         /* handle other input modes */\r
1203         if (PicoPad[0] & 1) pico_pen_y--;\r
1204         if (PicoPad[0] & 2) pico_pen_y++;\r
1205         if (PicoPad[0] & 4) pico_pen_x--;\r
1206         if (PicoPad[0] & 8) pico_pen_x++;\r
1207         PicoPad[0] &= ~0x0f; // release UDLR\r
1208 \r
1209         lim_x = (Pico.video.reg[12]&1) ? 319 : 255;\r
1210         if (pico_pen_y < 8)\r
1211                 pico_pen_y = 8;\r
1212         if (pico_pen_y > 224 - PICO_PEN_ADJUST_Y)\r
1213                 pico_pen_y = 224 - PICO_PEN_ADJUST_Y;\r
1214         if (pico_pen_x < 0)\r
1215                 pico_pen_x = 0;\r
1216         if (pico_pen_x > lim_x - PICO_PEN_ADJUST_X)\r
1217                 pico_pen_x = lim_x - PICO_PEN_ADJUST_X;\r
1218 \r
1219         PicoPicohw.pen_pos[0] = pico_pen_x;\r
1220         if (!(Pico.video.reg[12] & 1))\r
1221                 PicoPicohw.pen_pos[0] += pico_pen_x / 4;\r
1222         PicoPicohw.pen_pos[0] += 0x3c;\r
1223         PicoPicohw.pen_pos[1] = pico_inp_mode == 1 ? (0x2f8 + pico_pen_y) : (0x1fc + pico_pen_y);\r
1224 }\r
1225 \r
1226 static void do_turbo(int *pad, int acts)\r
1227 {\r
1228         static int turbo_pad = 0;\r
1229         static unsigned char turbo_cnt[3] = { 0, 0, 0 };\r
1230         int inc = currentConfig.turbo_rate * 2;\r
1231 \r
1232         if (acts & 0x1000) {\r
1233                 turbo_cnt[0] += inc;\r
1234                 if (turbo_cnt[0] >= 60)\r
1235                         turbo_pad ^= 0x10, turbo_cnt[0] = 0;\r
1236         }\r
1237         if (acts & 0x2000) {\r
1238                 turbo_cnt[1] += inc;\r
1239                 if (turbo_cnt[1] >= 60)\r
1240                         turbo_pad ^= 0x20, turbo_cnt[1] = 0;\r
1241         }\r
1242         if (acts & 0x4000) {\r
1243                 turbo_cnt[2] += inc;\r
1244                 if (turbo_cnt[2] >= 60)\r
1245                         turbo_pad ^= 0x40, turbo_cnt[2] = 0;\r
1246         }\r
1247         *pad |= turbo_pad & (acts >> 8);\r
1248 }\r
1249 \r
1250 static void run_events_ui(unsigned int which)\r
1251 {\r
1252         if (which & (PEV_STATE_LOAD|PEV_STATE_SAVE))\r
1253         {\r
1254                 int do_it = 1;\r
1255                 if ( emu_check_save_file(state_slot, NULL) &&\r
1256                         (((which & PEV_STATE_LOAD) && (currentConfig.confirm_save & EOPT_CONFIRM_LOAD)) ||\r
1257                          ((which & PEV_STATE_SAVE) && (currentConfig.confirm_save & EOPT_CONFIRM_SAVE))) )\r
1258                 {\r
1259                         const char *nm;\r
1260                         char tmp[64];\r
1261                         int keys, len;\r
1262 \r
1263                         strcpy(tmp, (which & PEV_STATE_LOAD) ? "LOAD STATE?" : "OVERWRITE SAVE?");\r
1264                         len = strlen(tmp);\r
1265                         nm = in_get_key_name(-1, -PBTN_MA3);\r
1266                         snprintf(tmp + len, sizeof(tmp) - len, "(%s=yes, ", nm);\r
1267                         len = strlen(tmp);\r
1268                         nm = in_get_key_name(-1, -PBTN_MBACK);\r
1269                         snprintf(tmp + len, sizeof(tmp) - len, "%s=no)", nm);\r
1270 \r
1271                         plat_status_msg_busy_first(tmp);\r
1272 \r
1273                         in_set_config_int(0, IN_CFG_BLOCKING, 1);\r
1274                         while (in_menu_wait_any(NULL, 50) & (PBTN_MA3|PBTN_MBACK))\r
1275                                 ;\r
1276                         while ( !((keys = in_menu_wait_any(NULL, 50)) & (PBTN_MA3|PBTN_MBACK)) )\r
1277                                 ;\r
1278                         if (keys & PBTN_MBACK)\r
1279                                 do_it = 0;\r
1280                         while (in_menu_wait_any(NULL, 50) & (PBTN_MA3|PBTN_MBACK))\r
1281                                 ;\r
1282                         in_set_config_int(0, IN_CFG_BLOCKING, 0);\r
1283                 }\r
1284                 if (do_it) {\r
1285                         plat_status_msg_busy_first((which & PEV_STATE_LOAD) ? "LOADING STATE" : "SAVING STATE");\r
1286                         PicoStateProgressCB = plat_status_msg_busy_next;\r
1287                         emu_save_load_game((which & PEV_STATE_LOAD) ? 1 : 0, 0);\r
1288                         PicoStateProgressCB = NULL;\r
1289                 }\r
1290         }\r
1291         if (which & PEV_SWITCH_RND)\r
1292         {\r
1293                 plat_video_toggle_renderer(1, 0);\r
1294         }\r
1295         if (which & (PEV_SSLOT_PREV|PEV_SSLOT_NEXT))\r
1296         {\r
1297                 if (which & PEV_SSLOT_PREV) {\r
1298                         state_slot -= 1;\r
1299                         if (state_slot < 0)\r
1300                                 state_slot = 9;\r
1301                 } else {\r
1302                         state_slot += 1;\r
1303                         if (state_slot > 9)\r
1304                                 state_slot = 0;\r
1305                 }\r
1306 \r
1307                 emu_status_msg("SAVE SLOT %i [%s]", state_slot,\r
1308                         emu_check_save_file(state_slot, NULL) ? "USED" : "FREE");\r
1309         }\r
1310         if (which & PEV_MENU)\r
1311                 engineState = PGS_Menu;\r
1312 }\r
1313 \r
1314 void emu_update_input(void)\r
1315 {\r
1316         static int prev_events = 0;\r
1317         int actions[IN_BINDTYPE_COUNT] = { 0, };\r
1318         int pl_actions[2];\r
1319         int events;\r
1320 \r
1321         in_update(actions);\r
1322 \r
1323         pl_actions[0] = actions[IN_BINDTYPE_PLAYER12];\r
1324         pl_actions[1] = actions[IN_BINDTYPE_PLAYER12] >> 16;\r
1325 \r
1326         PicoPad[0] = pl_actions[0] & 0xfff;\r
1327         PicoPad[1] = pl_actions[1] & 0xfff;\r
1328 \r
1329         if (pl_actions[0] & 0x7000)\r
1330                 do_turbo(&PicoPad[0], pl_actions[0]);\r
1331         if (pl_actions[1] & 0x7000)\r
1332                 do_turbo(&PicoPad[1], pl_actions[1]);\r
1333 \r
1334         events = actions[IN_BINDTYPE_EMU] & PEV_MASK;\r
1335 \r
1336         // volume is treated in special way and triggered every frame\r
1337         if (events & (PEV_VOL_DOWN|PEV_VOL_UP))\r
1338                 plat_update_volume(1, events & PEV_VOL_UP);\r
1339 \r
1340         if ((events ^ prev_events) & PEV_FF) {\r
1341                 emu_set_fastforward(events & PEV_FF);\r
1342                 plat_update_volume(0, 0);\r
1343                 reset_timing = 1;\r
1344         }\r
1345 \r
1346         events &= ~prev_events;\r
1347 \r
1348         if (PicoAHW == PAHW_PICO)\r
1349                 run_events_pico(events);\r
1350         if (events)\r
1351                 run_events_ui(events);\r
1352         if (movie_data)\r
1353                 update_movie();\r
1354 \r
1355         prev_events = actions[IN_BINDTYPE_EMU] & PEV_MASK;\r
1356 }\r
1357 \r
1358 static void mkdir_path(char *path_with_reserve, int pos, const char *name)\r
1359 {\r
1360         strcpy(path_with_reserve + pos, name);\r
1361         if (plat_is_dir(path_with_reserve))\r
1362                 return;\r
1363         if (mkdir(path_with_reserve, 0777) < 0)\r
1364                 lprintf("failed to create: %s\n", path_with_reserve);\r
1365 }\r
1366 \r
1367 void emu_cmn_forced_frame(int no_scale, int do_emu)\r
1368 {\r
1369         int po_old = PicoOpt;\r
1370 \r
1371         memset32(g_screen_ptr, 0, g_screen_width * g_screen_height * 2 / 4);\r
1372 \r
1373         PicoOpt &= ~POPT_ALT_RENDERER;\r
1374         PicoOpt |= POPT_ACC_SPRITES;\r
1375         if (!no_scale)\r
1376                 PicoOpt |= POPT_EN_SOFTSCALE;\r
1377 \r
1378         PicoDrawSetOutFormat(PDF_RGB555, 1);\r
1379         Pico.m.dirtyPal = 1;\r
1380         if (do_emu)\r
1381                 PicoFrame();\r
1382         else\r
1383                 PicoFrameDrawOnly();\r
1384 \r
1385         PicoOpt = po_old;\r
1386 }\r
1387 \r
1388 void emu_init(void)\r
1389 {\r
1390         char path[512];\r
1391         int pos;\r
1392 \r
1393 #if 0\r
1394         // FIXME: handle through menu, etc\r
1395         FILE *f;\r
1396         f = fopen("32X_M_BIOS.BIN", "rb");\r
1397         p32x_bios_m = malloc(2048);\r
1398         fread(p32x_bios_m, 1, 2048, f);\r
1399         fclose(f);\r
1400         f = fopen("32X_S_BIOS.BIN", "rb");\r
1401         p32x_bios_s = malloc(1024);\r
1402         fread(p32x_bios_s, 1, 1024, f);\r
1403         fclose(f);\r
1404 #endif\r
1405 \r
1406         /* make dirs for saves */\r
1407         pos = plat_get_root_dir(path, sizeof(path) - 4);\r
1408         mkdir_path(path, pos, "mds");\r
1409         mkdir_path(path, pos, "srm");\r
1410         mkdir_path(path, pos, "brm");\r
1411 \r
1412         pprof_init();\r
1413 \r
1414         make_config_cfg(path);\r
1415         config_readlrom(path);\r
1416 \r
1417         PicoInit();\r
1418         PicoMessage = plat_status_msg_busy_next;\r
1419         PicoMCDopenTray = emu_tray_open;\r
1420         PicoMCDcloseTray = emu_tray_close;\r
1421 }\r
1422 \r
1423 void emu_finish(void)\r
1424 {\r
1425         // save SRAM\r
1426         if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && SRam.changed) {\r
1427                 emu_save_load_game(0, 1);\r
1428                 SRam.changed = 0;\r
1429         }\r
1430 \r
1431         if (!(currentConfig.EmuOpt & EOPT_NO_AUTOSVCFG)) {\r
1432                 char cfg[512];\r
1433                 make_config_cfg(cfg);\r
1434                 config_writelrom(cfg);\r
1435 #ifndef NO_SYNC\r
1436                 sync();\r
1437 #endif\r
1438         }\r
1439 \r
1440         pprof_finish();\r
1441 \r
1442         PicoExit();\r
1443 }\r
1444 \r
1445 static void skip_frame(int do_audio)\r
1446 {\r
1447         PicoSkipFrame = do_audio ? 1 : 2;\r
1448         PicoFrame();\r
1449         PicoSkipFrame = 0;\r
1450 }\r
1451 \r
1452 /* our tick here is 1 us right now */\r
1453 #define ms_to_ticks(x) (unsigned int)(x * 1000)\r
1454 #define get_ticks() plat_get_ticks_us()\r
1455 \r
1456 void emu_loop(void)\r
1457 {\r
1458         int pframes_done;               /* "period" frames, used for sync */\r
1459         int frames_done, frames_shown;  /* actual frames for fps counter */\r
1460         int target_fps, target_frametime;\r
1461         unsigned int timestamp_base = 0, timestamp_fps;\r
1462         char *notice_msg = NULL;\r
1463         char fpsbuff[24];\r
1464         int i;\r
1465 \r
1466         fpsbuff[0] = 0;\r
1467 \r
1468         /* make sure we are in correct mode */\r
1469         Pico.m.dirtyPal = 1;\r
1470         rendstatus_old = -1;\r
1471 \r
1472         PicoLoopPrepare();\r
1473 \r
1474         // prepare CD buffer\r
1475         if (PicoAHW & PAHW_MCD)\r
1476                 PicoCDBufferInit();\r
1477 \r
1478         plat_video_loop_prepare();\r
1479         pemu_loop_prep();\r
1480 \r
1481         /* number of ticks per frame */\r
1482         if (Pico.m.pal) {\r
1483                 target_fps = 50;\r
1484                 target_frametime = ms_to_ticks(1000) / 50;\r
1485         } else {\r
1486                 target_fps = 60;\r
1487                 target_frametime = ms_to_ticks(1000) / 60 + 1;\r
1488         }\r
1489 \r
1490         timestamp_fps = get_ticks();\r
1491         reset_timing = 1;\r
1492 \r
1493         frames_done = frames_shown = pframes_done = 0;\r
1494 \r
1495         plat_video_wait_vsync();\r
1496 \r
1497         /* loop with resync every 1 sec. */\r
1498         while (engineState == PGS_Running)\r
1499         {\r
1500                 unsigned int timestamp;\r
1501                 int diff, diff_lim;\r
1502 \r
1503                 pprof_start(main);\r
1504 \r
1505                 timestamp = get_ticks();\r
1506                 if (reset_timing) {\r
1507                         reset_timing = 0;\r
1508                         timestamp_base = timestamp;\r
1509                         pframes_done = 0;\r
1510                 }\r
1511 \r
1512                 // show notice_msg message?\r
1513                 if (notice_msg_time != 0)\r
1514                 {\r
1515                         static int noticeMsgSum;\r
1516                         if (timestamp - ms_to_ticks(notice_msg_time) > ms_to_ticks(STATUS_MSG_TIMEOUT)) {\r
1517                                 notice_msg_time = 0;\r
1518                                 plat_status_msg_clear();\r
1519                                 notice_msg = NULL;\r
1520                         } else {\r
1521                                 int sum = noticeMsg[0] + noticeMsg[1] + noticeMsg[2];\r
1522                                 if (sum != noticeMsgSum) {\r
1523                                         plat_status_msg_clear();\r
1524                                         noticeMsgSum = sum;\r
1525                                 }\r
1526                                 notice_msg = noticeMsg;\r
1527                         }\r
1528                 }\r
1529 \r
1530                 // second changed?\r
1531                 if (timestamp - timestamp_fps >= ms_to_ticks(1000))\r
1532                 {\r
1533 #ifdef BENCHMARK\r
1534                         static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];\r
1535                         if (++bench == 10) {\r
1536                                 bench = 0;\r
1537                                 bench_fps_s = bench_fps;\r
1538                                 bf[bfp++ & 3] = bench_fps;\r
1539                                 bench_fps = 0;\r
1540                         }\r
1541                         bench_fps += frames_shown;\r
1542                         sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);\r
1543                         printf("%s\n", fpsbuff);\r
1544 #else\r
1545                         if (currentConfig.EmuOpt & EOPT_SHOW_FPS) {\r
1546                                 sprintf(fpsbuff, "%02i/%02i", frames_shown, frames_done);\r
1547                                 if (fpsbuff[5] == 0) { fpsbuff[5] = fpsbuff[6] = ' '; fpsbuff[7] = 0; }\r
1548                         }\r
1549 #endif\r
1550                         frames_shown = frames_done = 0;\r
1551                         timestamp_fps += ms_to_ticks(1000);\r
1552                 }\r
1553 #ifdef PFRAMES\r
1554                 sprintf(fpsbuff, "%i", Pico.m.frame_count);\r
1555 #endif\r
1556 \r
1557                 if (timestamp - timestamp_base >= ms_to_ticks(1000))\r
1558                 {\r
1559                         if ((currentConfig.EmuOpt & EOPT_NO_FRMLIMIT) && currentConfig.Frameskip >= 0)\r
1560                                 pframes_done = 0;\r
1561                         else\r
1562                                 pframes_done -= target_fps;\r
1563                         if (pframes_done < -2) {\r
1564                                 /* don't drag more than 2 frames behind */\r
1565                                 pframes_done = -2;\r
1566                                 timestamp_base = timestamp - 2 * target_frametime;\r
1567                         }\r
1568                         else\r
1569                                 timestamp_base += ms_to_ticks(1000);\r
1570                 }\r
1571 \r
1572                 diff = timestamp - timestamp_base;\r
1573                 diff_lim = (pframes_done + 1) * target_frametime;\r
1574 \r
1575                 if (currentConfig.Frameskip >= 0) // frameskip enabled\r
1576                 {\r
1577                         for (i = 0; i < currentConfig.Frameskip; i++) {\r
1578                                 emu_update_input();\r
1579                                 skip_frame(1);\r
1580                                 pframes_done++; frames_done++;\r
1581                                 diff_lim += target_frametime;\r
1582 \r
1583                                 if (!(currentConfig.EmuOpt & (EOPT_NO_FRMLIMIT|EOPT_EXT_FRMLIMIT))) {\r
1584                                         timestamp = get_ticks();\r
1585                                         diff = timestamp - timestamp_base;\r
1586                                         if (!reset_timing && diff < diff_lim) // we are too fast\r
1587                                                 plat_wait_till_us(timestamp_base + diff_lim);\r
1588                                 }\r
1589                         }\r
1590                 }\r
1591                 else if (diff > diff_lim)\r
1592                 {\r
1593                         /* no time left for this frame - skip */\r
1594                         /* limit auto frameskip to 8 */\r
1595                         if (frames_done / 8 <= frames_shown) {\r
1596                                 emu_update_input();\r
1597                                 skip_frame(diff < diff_lim + target_frametime * 16);\r
1598                                 pframes_done++; frames_done++;\r
1599                                 continue;\r
1600                         }\r
1601                 }\r
1602 \r
1603                 emu_update_input();\r
1604                 PicoFrame();\r
1605                 pemu_finalize_frame(fpsbuff, notice_msg);\r
1606 \r
1607                 // plat_video_flip();\r
1608 \r
1609                 /* frame limiter */\r
1610                 if (!reset_timing && !(currentConfig.EmuOpt & (EOPT_NO_FRMLIMIT|EOPT_EXT_FRMLIMIT)))\r
1611                 {\r
1612                         timestamp = get_ticks();\r
1613                         diff = timestamp - timestamp_base;\r
1614 \r
1615                         // sleep or vsync if we are still too fast\r
1616                         if (diff < diff_lim)\r
1617                         {\r
1618                                 // we are too fast\r
1619                                 plat_wait_till_us(timestamp_base + diff_lim - target_frametime / 4);\r
1620                                 if (currentConfig.EmuOpt & EOPT_VSYNC)\r
1621                                         plat_video_wait_vsync();\r
1622                         }\r
1623                 }\r
1624 \r
1625                 // XXX: for some plats it might be better to flip before vsync\r
1626                 // (due to shadow registers in display hw)\r
1627                 plat_video_flip();\r
1628 \r
1629                 pframes_done++; frames_done++; frames_shown++;\r
1630 \r
1631                 pprof_end(main);\r
1632         }\r
1633 \r
1634         emu_set_fastforward(0);\r
1635 \r
1636         // save SRAM\r
1637         if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && SRam.changed) {\r
1638                 plat_status_msg_busy_first("Writing SRAM/BRAM...");\r
1639                 emu_save_load_game(0, 1);\r
1640                 SRam.changed = 0;\r
1641         }\r
1642 \r
1643         pemu_loop_end();\r
1644 \r
1645         // pemu_loop_end() might want to do 1 frame for bg image,\r
1646         // so free CD buffer here\r
1647         if (PicoAHW & PAHW_MCD)\r
1648                 PicoCDBufferFree();\r
1649 }\r
1650 \r