8e6ad540c05e50cdadc57b1801dcff275f2b3792
[picodrive.git] / platform / common / emu.c
1 /*\r
2  * PicoDrive\r
3  * (C) notaz, 2007-2010\r
4  * (C) irixxxx, 2019-2024\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 <stdio.h>\r
11 #include <stdlib.h>\r
12 #include <string.h>\r
13 #include <stdarg.h>\r
14 #ifdef __GP2X__\r
15 #include <unistd.h>\r
16 #endif\r
17 \r
18 #include "../libpicofe/posix.h"\r
19 #include "../libpicofe/input.h"\r
20 #include "../libpicofe/fonts.h"\r
21 #include "../libpicofe/sndout.h"\r
22 #include "../libpicofe/lprintf.h"\r
23 #include "../libpicofe/readpng.h"\r
24 #include "../libpicofe/plat.h"\r
25 #include "emu.h"\r
26 #include "keyboard.h"\r
27 #include "input_pico.h"\r
28 #include "menu_pico.h"\r
29 #include "config_file.h"\r
30 \r
31 #include <pico/pico_int.h>\r
32 #include <pico/patch.h>\r
33 \r
34 #if defined(__GNUC__) && __GNUC__ >= 7\r
35 #pragma GCC diagnostic ignored "-Wformat-truncation"\r
36 #endif\r
37 \r
38 #ifndef _WIN32\r
39 #define PATH_SEP      "/"\r
40 #define PATH_SEP_C    '/'\r
41 #else\r
42 #define PATH_SEP      "\\"\r
43 #define PATH_SEP_C    '\\'\r
44 #endif\r
45 \r
46 #define STATUS_MSG_TIMEOUT 2000\r
47 \r
48 void *g_screen_ptr;\r
49 \r
50 int g_screen_width  = 320;\r
51 int g_screen_height = 240;\r
52 int g_screen_ppitch = 320; // pitch in pixels\r
53 \r
54 const char *PicoConfigFile = "config2.cfg";\r
55 currentConfig_t currentConfig, defaultConfig;\r
56 int state_slot = 0;\r
57 int config_slot = 0, config_slot_current = 0;\r
58 int pico_pen_x = 320/2, pico_pen_y = 240/2;\r
59 int pico_inp_mode;\r
60 int flip_after_sync;\r
61 int engineState = PGS_Menu;\r
62 \r
63 int grab_mode;\r
64 int kbd_mode;\r
65 struct vkbd *vkbd;\r
66 int mouse_x, mouse_y;\r
67 \r
68 static int pico_page;\r
69 static int pico_w, pico_h;\r
70 static u16 *pico_overlay;\r
71 static int pico_pad;\r
72 \r
73 static short __attribute__((aligned(4))) sndBuffer[2*54000/50];\r
74 \r
75 /* tmp buff to reduce stack usage for plats with small stack */\r
76 static char static_buff[512];\r
77 const char *rom_fname_reload;\r
78 char rom_fname_loaded[512];\r
79 int reset_timing = 0;\r
80 static unsigned int notice_msg_time;    /* when started showing */\r
81 static char noticeMsg[40];\r
82 \r
83 unsigned char *movie_data = NULL;\r
84 static int movie_size = 0;\r
85 \r
86 \r
87 /* don't use tolower() for easy old glibc binary compatibility */\r
88 static void strlwr_(char *string)\r
89 {\r
90         char *p;\r
91         for (p = string; *p; p++)\r
92                 if ('A' <= *p && *p <= 'Z')\r
93                         *p += 'a' - 'A';\r
94 }\r
95 \r
96 static int try_rfn_cut(char *fname)\r
97 {\r
98         FILE *tmp;\r
99         char *p;\r
100 \r
101         p = fname + strlen(fname) - 1;\r
102         for (; p > fname; p--)\r
103                 if (*p == '.') break;\r
104         *p = 0;\r
105 \r
106         if((tmp = fopen(fname, "rb"))) {\r
107                 fclose(tmp);\r
108                 return 1;\r
109         }\r
110         return 0;\r
111 }\r
112 \r
113 static void get_ext(const char *file, char *ext)\r
114 {\r
115         const char *p;\r
116 \r
117         p = file + strlen(file) - 4;\r
118         if (p < file) p = file;\r
119         strncpy(ext, p, 4);\r
120         ext[4] = 0;\r
121         strlwr_(ext);\r
122 }\r
123 \r
124 static void fname_ext(char *dst, int dstlen, const char *prefix, const char *ext, const char *fname)\r
125 {\r
126         int prefix_len = 0;\r
127         const char *p;\r
128 \r
129         *dst = 0;\r
130         if (prefix) {\r
131                 int len = plat_get_root_dir(dst, dstlen);\r
132                 strcpy(dst + len, prefix);\r
133                 prefix_len = len + strlen(prefix);\r
134         }\r
135 \r
136         p = fname + strlen(fname) - 1;\r
137         for (; p >= fname && *p != PATH_SEP_C; p--)\r
138                 ;\r
139         p++;\r
140         strncpy(dst + prefix_len, p, dstlen - prefix_len - 1);\r
141 \r
142         dst[dstlen - 8] = 0;\r
143         if ((p = strrchr(dst, '.')) != NULL)\r
144                 dst[p-dst] = 0;\r
145         if (ext)\r
146                 strcat(dst, ext);\r
147 }\r
148 \r
149 static void romfname_ext(char *dst, int dstlen, const char *prefix, const char *ext)\r
150 {\r
151         fname_ext(dst, dstlen, prefix, ext, rom_fname_loaded);\r
152 }\r
153 \r
154 void emu_status_msg(const char *format, ...)\r
155 {\r
156         va_list vl;\r
157         int ret;\r
158 \r
159         va_start(vl, format);\r
160         ret = vsnprintf(noticeMsg, sizeof(noticeMsg), format, vl);\r
161         va_end(vl);\r
162 \r
163         /* be sure old text gets overwritten */\r
164         for (; ret < 28; ret++)\r
165                 noticeMsg[ret] = ' ';\r
166         noticeMsg[ret] = 0;\r
167 \r
168         notice_msg_time = plat_get_ticks_ms();\r
169 }\r
170 \r
171 static const char * const biosfiles_us[] = {\r
172         "us_scd2_9306", "SegaCDBIOS9303", "us_scd1_9210", "bios_CD_U"\r
173 };\r
174 static const char * const biosfiles_eu[] = {\r
175         "eu_mcd2_9306", "eu_mcd2_9303", "eu_mcd1_9210", "bios_CD_E"\r
176 };\r
177 static const char * const biosfiles_jp[] = {\r
178         "jp_mcd2_921222", "jp_mcd1_9112", "jp_mcd1_9111", "bios_CD_J"\r
179 };\r
180 \r
181 static const char *find_bios(int *region, const char *cd_fname)\r
182 {\r
183         int i, count;\r
184         const char * const *files;\r
185         FILE *f = NULL;\r
186         int ret;\r
187 \r
188         // we need to have config loaded at this point\r
189         ret = emu_read_config(cd_fname, 0);\r
190         if (!ret) emu_read_config(NULL, 0);\r
191 \r
192         if (PicoIn.regionOverride) {\r
193                 *region = PicoIn.regionOverride;\r
194                 lprintf("override region to %s\n", *region != 4 ?\r
195                         (*region == 8 ? "EU" : "JAP") : "USA");\r
196         }\r
197 \r
198         // locate BIOS file\r
199         if (*region == 4) { // US\r
200                 files = biosfiles_us;\r
201                 count = sizeof(biosfiles_us) / sizeof(char *);\r
202         } else if (*region == 8) { // EU\r
203                 files = biosfiles_eu;\r
204                 count = sizeof(biosfiles_eu) / sizeof(char *);\r
205         } else if (*region == 1 || *region == 2) {\r
206                 files = biosfiles_jp;\r
207                 count = sizeof(biosfiles_jp) / sizeof(char *);\r
208         } else {\r
209                 return 0;\r
210         }\r
211 \r
212         for (i = 0; i < count; i++)\r
213         {\r
214                 emu_make_path(static_buff, files[i], sizeof(static_buff) - 4);\r
215                 strcat(static_buff, ".bin");\r
216                 f = fopen(static_buff, "rb");\r
217                 if (f) break;\r
218 \r
219                 static_buff[strlen(static_buff) - 4] = 0;\r
220                 strcat(static_buff, ".zip");\r
221                 f = fopen(static_buff, "rb");\r
222                 if (f) break;\r
223 \r
224                 strcpy(static_buff, files[i]);\r
225                 strcat(static_buff, ".bin");\r
226                 f = fopen(static_buff, "rb");\r
227                 if (f) break;\r
228 \r
229                 static_buff[strlen(static_buff) - 4] = 0;\r
230                 strcat(static_buff, ".zip");\r
231                 f = fopen(static_buff, "rb");\r
232                 if (f) break;\r
233         }\r
234 \r
235         if (f) {\r
236                 lprintf("using bios: %s\n", static_buff);\r
237                 fclose(f);\r
238                 return static_buff;\r
239         } else {\r
240                 sprintf(static_buff, "no %s BIOS files found, read docs",\r
241                         *region != 4 ? (*region == 8 ? "EU" : "JAP") : "USA");\r
242                 menu_update_msg(static_buff);\r
243                 return NULL;\r
244         }\r
245 }\r
246 \r
247 static const char *find_msu(const char *cd_fname)\r
248 {\r
249         int i;\r
250 \r
251         // look for MSU.MD or MD+ rom file. XXX another extension list? ugh...\r
252         static const char *md_exts[] = { "gen", "smd", "md", "32x" };\r
253         char *ext = strrchr(cd_fname, '.');\r
254         int extpos = ext ? ext-cd_fname : strlen(cd_fname);\r
255         strcpy(static_buff, cd_fname);\r
256         static_buff[extpos++] = '.';\r
257         for (i = 0; i < ARRAY_SIZE(md_exts); i++) {\r
258                 strcpy(static_buff+extpos, md_exts[i]);\r
259                 if (access(static_buff, R_OK) == 0) {\r
260                         printf("found MSU rom: %s\n",static_buff);\r
261                         return static_buff;\r
262                 }\r
263         }\r
264         return NULL;\r
265 }\r
266 \r
267 /* check if the name begins with BIOS name */\r
268 /*\r
269 static int emu_isBios(const char *name)\r
270 {\r
271         int i;\r
272         for (i = 0; i < sizeof(biosfiles_us)/sizeof(biosfiles_us[0]); i++)\r
273                 if (strstr(name, biosfiles_us[i]) != NULL) return 1;\r
274         for (i = 0; i < sizeof(biosfiles_eu)/sizeof(biosfiles_eu[0]); i++)\r
275                 if (strstr(name, biosfiles_eu[i]) != NULL) return 1;\r
276         for (i = 0; i < sizeof(biosfiles_jp)/sizeof(biosfiles_jp[0]); i++)\r
277                 if (strstr(name, biosfiles_jp[i]) != NULL) return 1;\r
278         return 0;\r
279 }\r
280 */\r
281 \r
282 static int extract_text(char *dest, const unsigned char *src, int len, int swab)\r
283 {\r
284         char *p = dest;\r
285         int i;\r
286 \r
287         if (swab) swab = 1;\r
288 \r
289         for (i = len - 1; i >= 0; i--)\r
290         {\r
291                 if (src[i^swab] != ' ') break;\r
292         }\r
293         len = i + 1;\r
294 \r
295         for (i = 0; i < len; i++)\r
296         {\r
297                 unsigned char s = src[i^swab];\r
298                 if (s >= 0x20 && s < 0x7f && s != '#' && s != '|' &&\r
299                         s != '[' && s != ']' && s != '\\')\r
300                 {\r
301                         *p++ = s;\r
302                 }\r
303                 else\r
304                 {\r
305                         sprintf(p, "\\%02x", s);\r
306                         p += 3;\r
307                 }\r
308         }\r
309 \r
310         return p - dest;\r
311 }\r
312 \r
313 static char *emu_make_rom_id(const char *fname)\r
314 {\r
315         static char id_string[3+0xe*3+0x3*3+0x30*3+3];\r
316         int pos, swab = 1;\r
317 \r
318         if (PicoIn.AHW & PAHW_MCD) {\r
319                 strcpy(id_string, "CD|");\r
320                 swab = 0;\r
321         }\r
322         else if (PicoIn.AHW & PAHW_SMS)\r
323                 strcpy(id_string, "MS|");\r
324         else    strcpy(id_string, "MD|");\r
325         pos = 3;\r
326 \r
327         if (!(PicoIn.AHW & PAHW_SMS)) {\r
328                 pos += extract_text(id_string + pos, media_id_header + 0x80, 0x0e, swab); // serial\r
329                 id_string[pos] = '|'; pos++;\r
330                 pos += extract_text(id_string + pos, media_id_header + 0xf0, 0x03, swab); // region\r
331                 id_string[pos] = '|'; pos++;\r
332                 pos += extract_text(id_string + pos, media_id_header + 0x50, 0x30, swab); // overseas name\r
333                 id_string[pos] = 0;\r
334                 if (pos > 5)\r
335                         return id_string;\r
336                 pos = 3;\r
337         }\r
338 \r
339         // can't find name in ROM, use filename\r
340         fname_ext(id_string + 3, sizeof(id_string) - 3, NULL, NULL, fname);\r
341 \r
342         return id_string;\r
343 }\r
344 \r
345 // buffer must be at least 150 byte long\r
346 void emu_get_game_name(char *str150)\r
347 {\r
348         int ret, swab = (PicoIn.AHW & PAHW_MCD) ? 0 : 1;\r
349         char *s, *d;\r
350 \r
351         ret = extract_text(str150, media_id_header + 0x50, 0x30, swab); // overseas name\r
352 \r
353         for (s = d = str150 + 1; s < str150+ret; s++)\r
354         {\r
355                 if (*s == 0) break;\r
356                 if (*s != ' ' || d[-1] != ' ')\r
357                         *d++ = *s;\r
358         }\r
359         *d = 0;\r
360 }\r
361 \r
362 static void system_announce(void)\r
363 {\r
364         const char *sys_name, *tv_standard, *extra = "";\r
365         int fps;\r
366 \r
367         if (PicoIn.AHW & PAHW_SMS) {\r
368                 sys_name = "Master System";\r
369                 if (PicoIn.AHW & PAHW_GG)\r
370                         sys_name = "Game Gear";\r
371                 else if (PicoIn.AHW & PAHW_SG)\r
372                         sys_name = "SG-1000";\r
373                 else if (PicoIn.AHW & PAHW_SC)\r
374                         sys_name = "SC-3000";\r
375                 else if (Pico.m.hardware & PMS_HW_JAP)\r
376                         sys_name = "Mark III";\r
377 #ifdef NO_SMS\r
378                 extra = " [no support]";\r
379 #endif\r
380         } else if (PicoIn.AHW & PAHW_PICO) {\r
381                 sys_name = "Pico";\r
382         } else if ((PicoIn.AHW & (PAHW_32X|PAHW_MCD)) == (PAHW_32X|PAHW_MCD)) {\r
383                 sys_name = "32X + Mega CD";\r
384                 if ((Pico.m.hardware & 0xc0) == 0x80)\r
385                         sys_name = "32X + Sega CD";\r
386         } else if (PicoIn.AHW & PAHW_MCD) {\r
387                 sys_name = "Mega CD";\r
388                 if ((Pico.m.hardware & 0xc0) == 0x80)\r
389                         sys_name = "Sega CD";\r
390         } else if (PicoIn.AHW & PAHW_32X) {\r
391                 sys_name = "32X";\r
392         } else {\r
393                 sys_name = "Mega Drive";\r
394                 if ((Pico.m.hardware & 0xc0) == 0x80)\r
395                         sys_name = "Genesis";\r
396         }\r
397         tv_standard = Pico.m.pal ? "PAL" : "NTSC";\r
398         fps = Pico.m.pal ? 50 : 60;\r
399 \r
400         emu_status_msg("%s %s / %dFPS%s", tv_standard, sys_name, fps, extra);\r
401 }\r
402 \r
403 static void do_region_override(const char *media_fname)\r
404 {\r
405         // we only need to override region if config tells us so\r
406         int ret = emu_read_config(media_fname, 0);\r
407         if (!ret) emu_read_config(NULL, 0);\r
408 }\r
409 \r
410 int emu_reload_rom(const char *rom_fname_in)\r
411 {\r
412         // use setting before rom config is loaded\r
413         int autoload = g_autostateld_opt;\r
414         char *rom_fname = NULL;\r
415         char ext[5];\r
416         enum media_type_e media_type;\r
417         int menu_romload_started = 0;\r
418         char carthw_path[512];\r
419         int retval = 0;\r
420 \r
421         lprintf("emu_ReloadRom(%s)\n", rom_fname_in);\r
422 \r
423         rom_fname = strdup(rom_fname_in);\r
424         if (rom_fname == NULL)\r
425                 return 0;\r
426 \r
427         get_ext(rom_fname, ext);\r
428 \r
429         // early cleanup\r
430         PicoPatchUnload();\r
431         if (movie_data) {\r
432                 free(movie_data);\r
433                 movie_data = 0;\r
434         }\r
435 \r
436         if (!strcasecmp(ext, ".gmv"))\r
437         {\r
438                 // check for both gmv and rom\r
439                 int dummy;\r
440                 FILE *movie_file = fopen(rom_fname, "rb");\r
441                 if (!movie_file) {\r
442                         menu_update_msg("Failed to open movie.");\r
443                         goto out;\r
444                 }\r
445                 fseek(movie_file, 0, SEEK_END);\r
446                 movie_size = ftell(movie_file);\r
447                 fseek(movie_file, 0, SEEK_SET);\r
448                 if (movie_size < 64+3) {\r
449                         menu_update_msg("Invalid GMV file.");\r
450                         fclose(movie_file);\r
451                         goto out;\r
452                 }\r
453                 movie_data = malloc(movie_size);\r
454                 if (movie_data == NULL) {\r
455                         menu_update_msg("low memory.");\r
456                         fclose(movie_file);\r
457                         goto out;\r
458                 }\r
459                 dummy = fread(movie_data, 1, movie_size, movie_file);\r
460                 fclose(movie_file);\r
461                 if (strncmp((char *)movie_data, "Gens Movie TEST", 15) != 0) {\r
462                         menu_update_msg("Invalid GMV file.");\r
463                         goto out;\r
464                 }\r
465                 dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);\r
466                 if (!dummy) {\r
467                         menu_update_msg("Could't find a ROM for movie.");\r
468                         goto out;\r
469                 }\r
470                 get_ext(rom_fname, ext);\r
471                 lprintf("gmv loaded for %s\n", rom_fname);\r
472         }\r
473         else if (!strcasecmp(ext, ".pat"))\r
474         {\r
475                 int dummy;\r
476                 PicoPatchLoad(rom_fname);\r
477                 dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);\r
478                 if (!dummy) {\r
479                         menu_update_msg("Could't find a ROM to patch.");\r
480                         goto out;\r
481                 }\r
482                 get_ext(rom_fname, ext);\r
483         }\r
484 \r
485         menu_romload_prepare(rom_fname); // also CD load\r
486         menu_romload_started = 1;\r
487 \r
488         emu_make_path(carthw_path, "carthw.cfg", sizeof(carthw_path));\r
489 \r
490         media_type = PicoLoadMedia(rom_fname, NULL, 0, carthw_path,\r
491                         find_bios, find_msu, do_region_override);\r
492 \r
493         switch (media_type) {\r
494         case PM_BAD_DETECT:\r
495                 menu_update_msg("Not a ROM/CD img selected.");\r
496                 goto out;\r
497         case PM_BAD_CD:\r
498                 menu_update_msg("Invalid CD image");\r
499                 goto out;\r
500         case PM_BAD_CD_NO_BIOS:\r
501                 // find_bios() prints a message\r
502                 goto out;\r
503         case PM_ERROR:\r
504                 menu_update_msg("Load error");\r
505                 goto out;\r
506         default:\r
507                 break;\r
508         }\r
509 \r
510         // make quirks visible in UI\r
511         if (PicoIn.quirks & PQUIRK_FORCE_6BTN)\r
512                 currentConfig.input_dev0 = PICO_INPUT_PAD_6BTN;\r
513 \r
514         menu_romload_end();\r
515         menu_romload_started = 0;\r
516 \r
517         if (PicoPatches) {\r
518                 PicoPatchPrepare();\r
519                 PicoPatchApply();\r
520         }\r
521 \r
522         // additional movie stuff\r
523         if (movie_data)\r
524         {\r
525                 enum input_device indev = (movie_data[0x14] == '6') ?\r
526                         PICO_INPUT_PAD_6BTN : PICO_INPUT_PAD_3BTN;\r
527                 PicoSetInputDevice(0, indev);\r
528                 PicoSetInputDevice(1, indev);\r
529 \r
530                 PicoIn.opt |= POPT_DIS_VDP_FIFO; // no VDP fifo timing\r
531                 if (movie_data[0xF] >= 'A') {\r
532                         if (movie_data[0x16] & 0x80) {\r
533                                 PicoIn.regionOverride = 8;\r
534                         } else {\r
535                                 PicoIn.regionOverride = 4;\r
536                         }\r
537                         PicoReset();\r
538                         // TODO: bits 6 & 5\r
539                 }\r
540                 movie_data[0x18+30] = 0;\r
541                 emu_status_msg("MOVIE: %s", (char *) &movie_data[0x18]);\r
542         }\r
543         else\r
544         {\r
545                 PicoSetInputDevice(0, currentConfig.input_dev0);\r
546                 PicoSetInputDevice(1, currentConfig.input_dev1);\r
547 \r
548                 system_announce();\r
549                 PicoIn.opt &= ~POPT_DIS_VDP_FIFO;\r
550         }\r
551 \r
552         strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);\r
553         rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;\r
554 \r
555         // load SRAM for this ROM\r
556         if (currentConfig.EmuOpt & EOPT_EN_SRAM)\r
557                 emu_save_load_game(1, 1);\r
558 \r
559         // state autoload?\r
560         if (autoload) {\r
561                 int time, newest = 0, newest_slot = -1;\r
562                 int slot;\r
563 \r
564                 for (slot = 0; slot < 10; slot++) {\r
565                         if (emu_check_save_file(slot, &time)) {\r
566                                 if (time > newest) {\r
567                                         newest = time;\r
568                                         newest_slot = slot;\r
569                                 }\r
570                         }\r
571                 }\r
572 \r
573                 if (newest_slot >= 0) {\r
574                         lprintf("autoload slot %d\n", newest_slot);\r
575                         state_slot = newest_slot;\r
576                         emu_save_load_game(1, 0);\r
577                 }\r
578                 else {\r
579                         lprintf("no save to autoload.\n");\r
580                 }\r
581         }\r
582 \r
583         retval = 1;\r
584 out:\r
585         if (menu_romload_started)\r
586                 menu_romload_end();\r
587         free(rom_fname);\r
588         return retval;\r
589 }\r
590 \r
591 int emu_swap_cd(const char *fname)\r
592 {\r
593         enum cd_track_type cd_type;\r
594         int ret = -1;\r
595 \r
596         cd_type = PicoCdCheck(fname, NULL);\r
597         if (cd_type != CT_UNKNOWN)\r
598                 ret = cdd_load(fname, cd_type);\r
599         if (ret != 0) {\r
600                 menu_update_msg("Load failed, invalid CD image?");\r
601                 return 0;\r
602         }\r
603 \r
604         strncpy(rom_fname_loaded, fname, sizeof(rom_fname_loaded)-1);\r
605         rom_fname_loaded[sizeof(rom_fname_loaded) - 1] = 0;\r
606 \r
607         return 1;\r
608 }\r
609 \r
610 int emu_play_tape(const char *fname)\r
611 {\r
612         int ret;\r
613 \r
614         ret = PicoPlayTape(fname);\r
615         if (ret != 0) {\r
616                 menu_update_msg("loading tape failed");\r
617                 return 0;\r
618         }\r
619         return 1;\r
620 }\r
621 \r
622 int emu_record_tape(const char *ext)\r
623 {\r
624         int ret;\r
625 \r
626         fname_ext(static_buff, sizeof(static_buff), "tape"PATH_SEP, ext, rom_fname_loaded);\r
627         ret = PicoRecordTape(static_buff);\r
628         if (ret != 0) {\r
629                 menu_update_msg("recording tape failed");\r
630                 return 0;\r
631         }\r
632         return 1;\r
633 }\r
634 \r
635 // <base dir><end>\r
636 void emu_make_path(char *buff, const char *end, int size)\r
637 {\r
638         int pos, end_len;\r
639 \r
640         end_len = strlen(end);\r
641         pos = plat_get_root_dir(buff, size);\r
642         strncpy(buff + pos, end, size - pos);\r
643         buff[size - 1] = 0;\r
644         if (pos + end_len > size - 1)\r
645                 lprintf("Warning: path truncated: %s\n", buff);\r
646 }\r
647 \r
648 static void make_config_cfg(char *cfg_buff_512)\r
649 {\r
650         emu_make_path(cfg_buff_512, PicoConfigFile, 512-6);\r
651         if (config_slot != 0)\r
652         {\r
653                 char *p = strrchr(cfg_buff_512, '.');\r
654                 if (p == NULL)\r
655                         p = cfg_buff_512 + strlen(cfg_buff_512);\r
656                 sprintf(p, ".%i.cfg", config_slot);\r
657         }\r
658         cfg_buff_512[511] = 0;\r
659 }\r
660 \r
661 void emu_prep_defconfig(void)\r
662 {\r
663         memset(&defaultConfig, 0, sizeof(defaultConfig));\r
664         defaultConfig.EmuOpt    = EOPT_EN_SRAM | EOPT_EN_SOUND | EOPT_16BPP |\r
665                                   EOPT_EN_CD_LEDS | EOPT_GZIP_SAVES | EOPT_PICO_PEN;\r
666         defaultConfig.s_PicoOpt = POPT_EN_SNDFILTER|POPT_EN_GG_LCD|POPT_EN_YM2413 |\r
667                                   POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80 |\r
668                                   POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_MCD_GFX |\r
669                                   POPT_EN_DRC|POPT_ACC_SPRITES |\r
670                                   POPT_EN_32X|POPT_EN_PWM;\r
671         defaultConfig.s_PsndRate = 44100;\r
672         defaultConfig.s_PicoRegion = 0; // auto\r
673         defaultConfig.s_PicoAutoRgnOrder = 0x184; // US, EU, JP\r
674         defaultConfig.s_hwSelect = PHWS_AUTO;\r
675         defaultConfig.s_PicoCDBuffers = 0;\r
676         defaultConfig.s_PicoSndFilterAlpha = 0x10000 * 60 / 100;\r
677         defaultConfig.confirm_save = EOPT_CONFIRM_SAVE;\r
678         defaultConfig.Frameskip = -1; // auto\r
679         defaultConfig.input_dev0 = PICO_INPUT_PAD_3BTN;\r
680         defaultConfig.input_dev1 = PICO_INPUT_PAD_3BTN;\r
681         defaultConfig.volume = 50;\r
682         defaultConfig.gamma = 100;\r
683         defaultConfig.scaling = 0;\r
684         defaultConfig.turbo_rate = 15;\r
685         defaultConfig.msh2_khz = PICO_MSH2_HZ / 1000;\r
686         defaultConfig.ssh2_khz = PICO_SSH2_HZ / 1000;\r
687         defaultConfig.max_skip = 4;\r
688 \r
689         // platform specific overrides\r
690         pemu_prep_defconfig();\r
691 }\r
692 \r
693 void emu_set_defconfig(void)\r
694 {\r
695         memcpy(&currentConfig, &defaultConfig, sizeof(currentConfig));\r
696         PicoIn.opt = currentConfig.s_PicoOpt;\r
697         PicoIn.sndRate = currentConfig.s_PsndRate;\r
698         PicoIn.regionOverride = currentConfig.s_PicoRegion;\r
699         PicoIn.autoRgnOrder = currentConfig.s_PicoAutoRgnOrder;\r
700         PicoIn.hwSelect = currentConfig.s_hwSelect;\r
701         PicoIn.sndFilterAlpha = currentConfig.s_PicoSndFilterAlpha;\r
702 }\r
703 \r
704 int emu_read_config(const char *rom_fname, int no_defaults)\r
705 {\r
706         char cfg[512];\r
707         int ret;\r
708 \r
709         if (!no_defaults)\r
710                 emu_set_defconfig();\r
711 \r
712         if (rom_fname == NULL)\r
713         {\r
714                 // global config\r
715                 make_config_cfg(cfg);\r
716                 ret = config_readsect(cfg, NULL);\r
717         }\r
718         else\r
719         {\r
720                 char ext[16];\r
721                 int vol;\r
722 \r
723                 if (config_slot != 0)\r
724                         snprintf(ext, sizeof(ext), ".%i.cfg", config_slot);\r
725                 else\r
726                         strcpy(ext, ".cfg");\r
727 \r
728                 fname_ext(cfg, sizeof(cfg), "cfg"PATH_SEP, ext, rom_fname);\r
729 \r
730                 // read user's config\r
731                 vol = currentConfig.volume;\r
732                 ret = config_readsect(cfg, NULL);\r
733                 currentConfig.volume = vol; // make vol global (bah)\r
734 \r
735                 if (ret != 0)\r
736                 {\r
737                         // read global config, and apply game_def.cfg on top\r
738                         make_config_cfg(cfg);\r
739                         config_readsect(cfg, NULL);\r
740 \r
741                         emu_make_path(cfg, "game_def.cfg", sizeof(cfg));\r
742                         ret = config_readsect(cfg, emu_make_rom_id(rom_fname));\r
743                 }\r
744         }\r
745 \r
746         pemu_validate_config();\r
747         PicoIn.overclockM68k = currentConfig.overclock_68k;\r
748 \r
749         // some sanity checks\r
750         if (currentConfig.volume < 0 || currentConfig.volume > 99)\r
751                 currentConfig.volume = 50;\r
752 \r
753         if (ret == 0)\r
754                 config_slot_current = config_slot;\r
755 \r
756         return (ret == 0);\r
757 }\r
758 \r
759 \r
760 int emu_write_config(int is_game)\r
761 {\r
762         char cfg[512];\r
763         int ret, write_lrom = 0;\r
764 \r
765         if (!is_game)\r
766         {\r
767                 make_config_cfg(cfg);\r
768                 write_lrom = 1;\r
769         } else {\r
770                 char ext[16];\r
771 \r
772                 if (config_slot != 0)\r
773                         snprintf(ext, sizeof(ext), ".%i.cfg", config_slot);\r
774                 else\r
775                         strcpy(ext, ".cfg");\r
776 \r
777                 romfname_ext(cfg, sizeof(cfg), "cfg"PATH_SEP, ext);\r
778         }\r
779 \r
780         lprintf("emu_write_config: %s ", cfg);\r
781         ret = config_write(cfg);\r
782         if (write_lrom) config_writelrom(cfg);\r
783 #ifdef __GP2X__\r
784         sync();\r
785 #endif\r
786         lprintf((ret == 0) ? "(ok)\n" : "(failed)\n");\r
787 \r
788         if (ret == 0) config_slot_current = config_slot;\r
789         return ret == 0;\r
790 }\r
791 \r
792 \r
793 /* always using built-in font */\r
794 \r
795 #define mk_text_out(name, type, val, topleft, step_x, step_y) \\r
796 void name(int x, int y, const char *text)                               \\r
797 {                                                                       \\r
798         int i, l, len = strlen(text);                                   \\r
799         type *screen = (type *)(topleft) + x * step_x + y * step_y;     \\r
800                                                                         \\r
801         for (i = 0; i < len; i++, screen += 8 * step_x)                 \\r
802         {                                                               \\r
803                 for (l = 0; l < 8; l++)                                 \\r
804                 {                                                       \\r
805                         unsigned char fd = fontdata8x8[text[i] * 8 + l];\\r
806                         type *s = screen + l * step_y;                  \\r
807                         if (fd&0x80) s[step_x * 0] = val;               \\r
808                         if (fd&0x40) s[step_x * 1] = val;               \\r
809                         if (fd&0x20) s[step_x * 2] = val;               \\r
810                         if (fd&0x10) s[step_x * 3] = val;               \\r
811                         if (fd&0x08) s[step_x * 4] = val;               \\r
812                         if (fd&0x04) s[step_x * 5] = val;               \\r
813                         if (fd&0x02) s[step_x * 6] = val;               \\r
814                         if (fd&0x01) s[step_x * 7] = val;               \\r
815                 }                                                       \\r
816         }                                                               \\r
817 }\r
818 \r
819 mk_text_out(emu_text_out8,      unsigned char,    0xf0, g_screen_ptr, 1, g_screen_ppitch)\r
820 mk_text_out(emu_text_out16,     unsigned short, 0xffff, g_screen_ptr, 1, g_screen_ppitch)\r
821 mk_text_out(emu_text_out8_rot,  unsigned char,    0xf0,\r
822         (char *)g_screen_ptr  + (g_screen_ppitch - 1) * g_screen_height, -g_screen_height, 1)\r
823 mk_text_out(emu_text_out16_rot, unsigned short, 0xffff,\r
824         (short *)g_screen_ptr + (g_screen_ppitch - 1) * g_screen_height, -g_screen_height, 1)\r
825 \r
826 #undef mk_text_out\r
827 \r
828 void emu_osd_text16(int x, int y, const char *text)\r
829 {\r
830         int len = strlen(text) * 8;\r
831         int i, h;\r
832 \r
833         len++;\r
834         if (x + len > g_screen_width)\r
835                 len = g_screen_width - x;\r
836 \r
837         for (h = 0; h < 8; h++) {\r
838                 unsigned short *p;\r
839                 p = (unsigned short *)g_screen_ptr\r
840                         + x + g_screen_ppitch * (y + h);\r
841                 for (i = len; i > 0; i--, p++)\r
842                         *p = (*p >> 2) & 0x39e7;\r
843         }\r
844         emu_text_out16(x, y, text);\r
845 }\r
846 \r
847 static void update_movie(void)\r
848 {\r
849         int offs = Pico.m.frame_count*3 + 0x40;\r
850         if (offs+3 > movie_size) {\r
851                 free(movie_data);\r
852                 movie_data = 0;\r
853                 emu_status_msg("END OF MOVIE.");\r
854                 lprintf("END OF MOVIE.\n");\r
855         } else {\r
856                 // MXYZ SACB RLDU\r
857                 PicoIn.pad[0] = ~movie_data[offs]   & 0x8f; // ! SCBA RLDU\r
858                 if(!(movie_data[offs]   & 0x10)) PicoIn.pad[0] |= 0x40; // C\r
859                 if(!(movie_data[offs]   & 0x20)) PicoIn.pad[0] |= 0x10; // A\r
860                 if(!(movie_data[offs]   & 0x40)) PicoIn.pad[0] |= 0x20; // B\r
861                 PicoIn.pad[1] = ~movie_data[offs+1] & 0x8f; // ! SCBA RLDU\r
862                 if(!(movie_data[offs+1] & 0x10)) PicoIn.pad[1] |= 0x40; // C\r
863                 if(!(movie_data[offs+1] & 0x20)) PicoIn.pad[1] |= 0x10; // A\r
864                 if(!(movie_data[offs+1] & 0x40)) PicoIn.pad[1] |= 0x20; // B\r
865                 PicoIn.pad[0] |= (~movie_data[offs+2] & 0x0A) << 8; // ! MZYX\r
866                 if(!(movie_data[offs+2] & 0x01)) PicoIn.pad[0] |= 0x0400; // X\r
867                 if(!(movie_data[offs+2] & 0x04)) PicoIn.pad[0] |= 0x0100; // Z\r
868                 PicoIn.pad[1] |= (~movie_data[offs+2] & 0xA0) << 4; // ! MZYX\r
869                 if(!(movie_data[offs+2] & 0x10)) PicoIn.pad[1] |= 0x0400; // X\r
870                 if(!(movie_data[offs+2] & 0x40)) PicoIn.pad[1] |= 0x0100; // Z\r
871         }\r
872 }\r
873 \r
874 static int try_ropen_file(const char *fname, int *time)\r
875 {\r
876         struct stat st;\r
877         FILE *f;\r
878 \r
879         f = fopen(fname, "rb");\r
880         if (f) {\r
881                 if (time != NULL) {\r
882                         *time = 0;\r
883                         if (fstat(fileno(f), &st) == 0)\r
884                                 *time = (int)st.st_mtime;\r
885                 }\r
886                 fclose(f);\r
887                 return 1;\r
888         }\r
889         return 0;\r
890 }\r
891 \r
892 char *emu_get_save_fname(int load, int is_sram, int slot, int *time)\r
893 {\r
894         char *saveFname = static_buff;\r
895         char ext[16];\r
896 \r
897         if (is_sram)\r
898         {\r
899                 strcpy(ext, (PicoIn.AHW & PAHW_MCD) && Pico.romsize == 0 ? ".brm" : ".srm");\r
900                 romfname_ext(saveFname, sizeof(static_buff),\r
901                         (PicoIn.AHW & PAHW_MCD) && Pico.romsize == 0 ? "brm"PATH_SEP : "srm"PATH_SEP, ext);\r
902                 if (!load)\r
903                         return saveFname;\r
904 \r
905                 if (try_ropen_file(saveFname, time))\r
906                         return saveFname;\r
907 \r
908                 romfname_ext(saveFname, sizeof(static_buff), NULL, ext);\r
909                 if (try_ropen_file(saveFname, time))\r
910                         return saveFname;\r
911         }\r
912         else\r
913         {\r
914                 const char *ext_main = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds.gz" : ".mds";\r
915                 const char *ext_othr = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds" : ".mds.gz";\r
916                 ext[0] = 0;\r
917                 if (slot > 0 && slot < 10)\r
918                         sprintf(ext, ".%i", slot);\r
919                 strcat(ext, ext_main);\r
920 \r
921                 if (!load) {\r
922                         romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);\r
923                         return saveFname;\r
924                 }\r
925                 else {\r
926                         romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);\r
927                         if (try_ropen_file(saveFname, time))\r
928                                 return saveFname;\r
929 \r
930                         romfname_ext(saveFname, sizeof(static_buff), NULL, ext);\r
931                         if (try_ropen_file(saveFname, time))\r
932                                 return saveFname;\r
933 \r
934                         // try the other ext\r
935                         ext[0] = 0;\r
936                         if (slot > 0 && slot < 10)\r
937                                 sprintf(ext, ".%i", slot);\r
938                         strcat(ext, ext_othr);\r
939 \r
940                         romfname_ext(saveFname, sizeof(static_buff), "mds"PATH_SEP, ext);\r
941                         if (try_ropen_file(saveFname, time))\r
942                                 return saveFname;\r
943                 }\r
944         }\r
945 \r
946         return NULL;\r
947 }\r
948 \r
949 int emu_check_save_file(int slot, int *time)\r
950 {\r
951         return emu_get_save_fname(1, 0, slot, time) ? 1 : 0;\r
952 }\r
953 \r
954 int emu_save_load_game(int load, int sram)\r
955 {\r
956         int ret = 0;\r
957         char *saveFname;\r
958 \r
959         // make save filename\r
960         saveFname = emu_get_save_fname(load, sram, state_slot, NULL);\r
961         if (saveFname == NULL) {\r
962                 if (!sram)\r
963                         emu_status_msg(load ? "LOAD FAILED (missing file)" : "SAVE FAILED");\r
964                 return -1;\r
965         }\r
966 \r
967         lprintf("saveLoad (%i, %i): %s\n", load, sram, saveFname);\r
968 \r
969         if (sram)\r
970         {\r
971                 FILE *sramFile;\r
972                 int sram_size;\r
973                 unsigned char *sram_data;\r
974                 int truncate = 1;\r
975                 if ((PicoIn.AHW & PAHW_MCD) && Pico.romsize == 0)\r
976                 {\r
977                         if (PicoIn.opt & POPT_EN_MCD_RAMCART) {\r
978                                 sram_size = 0x12000;\r
979                                 sram_data = Pico.sv.data;\r
980                                 if (sram_data)\r
981                                         memcpy(sram_data, Pico_mcd->bram, 0x2000);\r
982                         } else {\r
983                                 sram_size = 0x2000;\r
984                                 sram_data = Pico_mcd->bram;\r
985                                 truncate  = 0; // the .brm may contain RAM cart data after normal brm\r
986                         }\r
987                 } else {\r
988                         sram_size = Pico.sv.size;\r
989                         sram_data = Pico.sv.data;\r
990                 }\r
991                 if (sram_data == NULL)\r
992                         return 0; // cart saves forcefully disabled for this game\r
993 \r
994                 if (load)\r
995                 {\r
996                         sramFile = fopen(saveFname, "rb");\r
997                         if (!sramFile)\r
998                                 return -1;\r
999                         ret = fread(sram_data, 1, sram_size, sramFile);\r
1000                         ret = ret > 0 ? 0 : -1;\r
1001                         fclose(sramFile);\r
1002                         if ((PicoIn.AHW & PAHW_MCD) && Pico.romsize == 0 && (PicoIn.opt&POPT_EN_MCD_RAMCART))\r
1003                                 memcpy(Pico_mcd->bram, sram_data, 0x2000);\r
1004                 } else {\r
1005                         // sram save needs some special processing\r
1006                         // see if we have anything to save\r
1007                         for (; sram_size > 0; sram_size--)\r
1008                                 if (sram_data[sram_size-1]) break;\r
1009 \r
1010                         if (sram_size) {\r
1011                                 sramFile = fopen(saveFname, truncate ? "wb" : "r+b");\r
1012                                 if (!sramFile) sramFile = fopen(saveFname, "wb"); // retry\r
1013                                 if (!sramFile) return -1;\r
1014                                 ret = fwrite(sram_data, 1, sram_size, sramFile);\r
1015                                 ret = (ret != sram_size) ? -1 : 0;\r
1016                                 fclose(sramFile);\r
1017 #ifdef __GP2X__\r
1018                                 sync();\r
1019 #endif\r
1020                         }\r
1021                 }\r
1022                 return ret;\r
1023         }\r
1024         else\r
1025         {\r
1026                 ret = PicoState(saveFname, !load);\r
1027                 if (!ret) {\r
1028 #ifdef __GP2X__\r
1029                         if (!load) sync();\r
1030 #endif\r
1031                         emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");\r
1032                 } else {\r
1033                         emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");\r
1034                         ret = -1;\r
1035                 }\r
1036 \r
1037                 return ret;\r
1038         }\r
1039 }\r
1040 \r
1041 void emu_set_fastforward(int set_on)\r
1042 {\r
1043         static void *set_PsndOut = NULL;\r
1044         static int set_Frameskip, set_EmuOpt, is_on = 0;\r
1045 \r
1046         if (set_on && !is_on) {\r
1047                 set_PsndOut = PicoIn.sndOut;\r
1048                 set_Frameskip = currentConfig.Frameskip;\r
1049                 set_EmuOpt = currentConfig.EmuOpt;\r
1050                 PicoIn.sndOut = NULL;\r
1051                 currentConfig.Frameskip = 8;\r
1052                 currentConfig.EmuOpt &= ~EOPT_EN_SOUND;\r
1053                 currentConfig.EmuOpt |= EOPT_NO_FRMLIMIT;\r
1054                 is_on = 1;\r
1055                 emu_status_msg("FAST FORWARD");\r
1056         }\r
1057         else if (!set_on && is_on) {\r
1058                 PicoIn.sndOut = set_PsndOut;\r
1059                 currentConfig.Frameskip = set_Frameskip;\r
1060                 currentConfig.EmuOpt = set_EmuOpt;\r
1061                 PsndRerate(1);\r
1062                 is_on = 0;\r
1063         }\r
1064 }\r
1065 \r
1066 static void emu_tray_open(void)\r
1067 {\r
1068         engineState = PGS_TrayMenu;\r
1069 }\r
1070 \r
1071 static void emu_tray_close(void)\r
1072 {\r
1073         emu_status_msg("CD tray closed.");\r
1074 }\r
1075 \r
1076 void emu_32x_startup(void)\r
1077 {\r
1078         plat_video_toggle_renderer(0, 0); // HACK\r
1079         system_announce();\r
1080 }\r
1081 \r
1082 void emu_reset_game(void)\r
1083 {\r
1084         PicoReset();\r
1085         reset_timing = 1;\r
1086 }\r
1087 \r
1088 static u16 *load_pico_overlay(int page, int w, int h)\r
1089 {\r
1090         static const char *pic_exts[] = { "png", "PNG" };\r
1091         char *ext, *fname = NULL;\r
1092         int extpos, i;\r
1093 \r
1094         if (pico_page == page && pico_w == w && pico_h == h)\r
1095                 return pico_overlay;\r
1096         pico_page = page;\r
1097         pico_w = w, pico_h = h;\r
1098 \r
1099         ext = strrchr(rom_fname_loaded, '.');\r
1100         extpos = ext ? ext-rom_fname_loaded : strlen(rom_fname_loaded);\r
1101         strcpy(static_buff, rom_fname_loaded);\r
1102         static_buff[extpos++] = '_';\r
1103         if (page < 0) {\r
1104                 static_buff[extpos++] = 'p';\r
1105                 static_buff[extpos++] = 'a';\r
1106                 static_buff[extpos++] = 'd';\r
1107         } else\r
1108                 static_buff[extpos++] = '0'+PicoPicohw.page;\r
1109         static_buff[extpos++] = '.';\r
1110 \r
1111         for (i = 0; i < ARRAY_SIZE(pic_exts); i++) {\r
1112                 strcpy(static_buff+extpos, pic_exts[i]);\r
1113                 if (access(static_buff, R_OK) == 0) {\r
1114                         printf("found Pico file: %s\n", static_buff);\r
1115                         fname = static_buff;\r
1116                         break;\r
1117                 }\r
1118         }\r
1119 \r
1120         pico_overlay = realloc(pico_overlay, w*h*2);\r
1121         memset(pico_overlay, 0, w*h*2);\r
1122         if (!fname || !pico_overlay || readpng(pico_overlay, fname, READPNG_SCALE, w, h)) {\r
1123                 if (pico_overlay)\r
1124                         free(pico_overlay);\r
1125                 pico_overlay = NULL;\r
1126         }\r
1127 \r
1128         return pico_overlay;\r
1129 }\r
1130 \r
1131 void emu_pico_overlay(u16 *pd, int w, int h, int pitch)\r
1132 {\r
1133         u16 *overlay = NULL;\r
1134         int y, oh = h;\r
1135 \r
1136         // get overlay\r
1137         if (pico_inp_mode == 1) {\r
1138                 oh = (w/2 < h ? w/2 : h); // storyware has squished h\r
1139                 overlay = load_pico_overlay(PicoPicohw.page, w, oh);\r
1140         } else if (pico_inp_mode == 2)\r
1141                 overlay = load_pico_overlay(-1, w, oh);\r
1142 \r
1143         // copy overlay onto buffer\r
1144         if (overlay) {\r
1145                 for (y = 0; y < oh; y++)\r
1146                         memcpy(pd + y*pitch, overlay + y*w, w*2);\r
1147                 if (y < h)\r
1148                         memset(pd + y*pitch, 0, w*2);\r
1149         }\r
1150 }\r
1151 \r
1152 void run_events_pico(unsigned int events)\r
1153 {\r
1154         // treat pad ports equal to support pad in one and mouse in the other\r
1155         PicoIn.pad[0] |= PicoIn.pad[1];\r
1156 \r
1157         if (events & PEV_PICO_PPREV) {\r
1158                 PicoPicohw.page--;\r
1159                 if (PicoPicohw.page < 0)\r
1160                         PicoPicohw.page = 0;\r
1161                 emu_status_msg("Page %i", PicoPicohw.page);\r
1162         }\r
1163         if (events & PEV_PICO_PNEXT) {\r
1164                 PicoPicohw.page++;\r
1165                 if (PicoPicohw.page > 7)\r
1166                         PicoPicohw.page = 7;\r
1167                 if (PicoPicohw.page == 7) {\r
1168                         // Used in games that require the Keyboard Pico peripheral\r
1169                         emu_status_msg("Test Page");\r
1170                 } else {\r
1171                         emu_status_msg("Page %i", PicoPicohw.page);\r
1172                 }\r
1173         }\r
1174         if (events & PEV_PICO_STORY) {\r
1175                 if (pico_inp_mode == 1) {\r
1176                         pico_inp_mode = 0;\r
1177                         emu_status_msg("Input: D-Pad");\r
1178                 } else {\r
1179                         pico_inp_mode = 1;\r
1180                         emu_status_msg("Input: Pen on Storyware");\r
1181                 }\r
1182         }\r
1183         if (events & PEV_PICO_PAD) {\r
1184                 if (pico_inp_mode == 2) {\r
1185                         pico_inp_mode = 0;\r
1186                         emu_status_msg("Input: D-Pad");\r
1187                 } else {\r
1188                         pico_inp_mode = 2;\r
1189                         emu_status_msg("Input: Pen on Pad");\r
1190                 }\r
1191         }\r
1192 \r
1193         if ((currentConfig.EmuOpt & EOPT_PICO_PEN) &&\r
1194                         (PicoIn.pad[0]&0x20) && pico_inp_mode && pico_overlay) {\r
1195                 pico_inp_mode = 0;\r
1196                 emu_status_msg("Input: D-Pad");\r
1197         }\r
1198 \r
1199         PicoPicohw.kb.active = (PicoIn.opt & POPT_EN_KBD ? kbd_mode : 0);\r
1200 \r
1201         if (pico_inp_mode == 0)\r
1202                 return;\r
1203 \r
1204         /* handle other input modes using the pen */\r
1205         if (PicoIn.opt & POPT_EN_MOUSE) {\r
1206                 pico_pen_x = PicoIn.mouse[0];\r
1207                 pico_pen_y = PicoIn.mouse[1];\r
1208         } else {\r
1209                 if (PicoIn.pad[0] & 1) pico_pen_y--;\r
1210                 if (PicoIn.pad[0] & 2) pico_pen_y++;\r
1211                 if (PicoIn.pad[0] & 4) pico_pen_x--;\r
1212                 if (PicoIn.pad[0] & 8) pico_pen_x++;\r
1213                 PicoIn.pad[0] &= ~0x0f; // release UDLR\r
1214         }\r
1215 \r
1216         if ((pico_pad ^ PicoIn.pad[0]) & PicoIn.pad[0] & (1<<GBTN_START)) {\r
1217                 PicoPicohw.pen_pos[0] ^= 0x8000;\r
1218                 PicoPicohw.pen_pos[1] ^= 0x8000;\r
1219                 emu_status_msg("Pen %s", PicoPicohw.pen_pos[0] & 0x8000 ? "Up" : "Down");\r
1220         }\r
1221         pico_pad = PicoIn.pad[0];\r
1222 \r
1223         /* cursor position, cursor drawing must not cross screen borders */\r
1224         if (pico_pen_y < PICO_PEN_ADJUST_Y)\r
1225                 pico_pen_y = PICO_PEN_ADJUST_Y;\r
1226         if (pico_pen_y > 223-1 - PICO_PEN_ADJUST_Y)\r
1227                 pico_pen_y = 223-1 - PICO_PEN_ADJUST_Y;\r
1228         if (pico_pen_x < PICO_PEN_ADJUST_X)\r
1229                 pico_pen_x = PICO_PEN_ADJUST_X;\r
1230         if (pico_pen_x > 319-1 - PICO_PEN_ADJUST_X)\r
1231                 pico_pen_x = 319-1 - PICO_PEN_ADJUST_X;\r
1232 \r
1233         PicoPicohw.pen_pos[0] &= 0x8000;\r
1234         PicoPicohw.pen_pos[1] &= 0x8000;\r
1235         PicoPicohw.pen_pos[0] |= 0x03c + pico_pen_x;\r
1236         PicoPicohw.pen_pos[1] |= (pico_inp_mode == 1 ? 0x2f8 : 0x1fc) + pico_pen_y;\r
1237 }\r
1238 \r
1239 static void do_turbo(unsigned short *pad, int acts)\r
1240 {\r
1241         static int turbo_pad = 0;\r
1242         static unsigned char turbo_cnt[3] = { 0, 0, 0 };\r
1243         int inc = currentConfig.turbo_rate * 2;\r
1244 \r
1245         if (acts & 0x1000) {\r
1246                 turbo_cnt[0] += inc;\r
1247                 if (turbo_cnt[0] >= 60)\r
1248                         turbo_pad ^= 0x10, turbo_cnt[0] = 0;\r
1249         }\r
1250         if (acts & 0x2000) {\r
1251                 turbo_cnt[1] += inc;\r
1252                 if (turbo_cnt[1] >= 60)\r
1253                         turbo_pad ^= 0x20, turbo_cnt[1] = 0;\r
1254         }\r
1255         if (acts & 0x4000) {\r
1256                 turbo_cnt[2] += inc;\r
1257                 if (turbo_cnt[2] >= 60)\r
1258                         turbo_pad ^= 0x40, turbo_cnt[2] = 0;\r
1259         }\r
1260         *pad |= turbo_pad & (acts >> 8);\r
1261 }\r
1262 \r
1263 static void run_events_ui(unsigned int which)\r
1264 {\r
1265         if (which & (PEV_STATE_LOAD|PEV_STATE_SAVE))\r
1266         {\r
1267                 int do_it = 1;\r
1268                 if ( emu_check_save_file(state_slot, NULL) &&\r
1269                         (((which & PEV_STATE_LOAD) && (currentConfig.confirm_save & EOPT_CONFIRM_LOAD)) ||\r
1270                          ((which & PEV_STATE_SAVE) && (currentConfig.confirm_save & EOPT_CONFIRM_SAVE))) )\r
1271                 {\r
1272                         const char *nm;\r
1273                         char tmp[64];\r
1274                         int keys, len;\r
1275 \r
1276                         strcpy(tmp, (which & PEV_STATE_LOAD) ? "LOAD STATE? " : "OVERWRITE SAVE? ");\r
1277                         len = strlen(tmp);\r
1278                         nm = in_get_key_name(-1, -PBTN_MOK);\r
1279                         snprintf(tmp + len, sizeof(tmp) - len, "(%s=yes, ", nm);\r
1280                         len = strlen(tmp);\r
1281                         nm = in_get_key_name(-1, -PBTN_MBACK);\r
1282                         snprintf(tmp + len, sizeof(tmp) - len, "%s=no)", nm);\r
1283 \r
1284                         plat_status_msg_busy_first(tmp);\r
1285 \r
1286                         in_set_config_int(0, IN_CFG_BLOCKING, 1);\r
1287                         while (in_menu_wait_any(NULL, 50) & (PBTN_MOK | PBTN_MBACK))\r
1288                                 ;\r
1289                         while ( !((keys = in_menu_wait_any(NULL, 50)) & (PBTN_MOK | PBTN_MBACK)))\r
1290                                 ;\r
1291                         if (keys & PBTN_MBACK)\r
1292                                 do_it = 0;\r
1293                         while (in_menu_wait_any(NULL, 50) & (PBTN_MOK | PBTN_MBACK))\r
1294                                 ;\r
1295                         in_set_config_int(0, IN_CFG_BLOCKING, 0);\r
1296                         plat_status_msg_clear();\r
1297                 }\r
1298                 if (do_it) {\r
1299                         plat_status_msg_busy_first((which & PEV_STATE_LOAD) ? "LOADING STATE" : "SAVING STATE");\r
1300                         PicoStateProgressCB = plat_status_msg_busy_next;\r
1301                         emu_save_load_game((which & PEV_STATE_LOAD) ? 1 : 0, 0);\r
1302                         PicoStateProgressCB = NULL;\r
1303                 }\r
1304                 plat_status_msg_busy_done();\r
1305         }\r
1306         if (which & (PEV_SSLOT_PREV|PEV_SSLOT_NEXT))\r
1307         {\r
1308                 if (which & PEV_SSLOT_PREV) {\r
1309                         state_slot -= 1;\r
1310                         if (state_slot < 0)\r
1311                                 state_slot = 9;\r
1312                 } else {\r
1313                         state_slot += 1;\r
1314                         if (state_slot > 9)\r
1315                                 state_slot = 0;\r
1316                 }\r
1317 \r
1318                 emu_status_msg("SAVE SLOT %i [%s]", state_slot,\r
1319                         emu_check_save_file(state_slot, NULL) ? "USED" : "FREE");\r
1320         }\r
1321         if (which & PEV_SWITCH_RND)\r
1322         {\r
1323                 plat_video_toggle_renderer(1, 0);\r
1324         }\r
1325         if (which & PEV_GRAB_INPUT)\r
1326         {\r
1327                 if (PicoIn.opt & POPT_EN_MOUSE) {\r
1328                         grab_mode = !grab_mode;\r
1329                         in_update_analog(0, 2, &mouse_x);\r
1330                         in_update_analog(0, 3, &mouse_y);\r
1331                         in_update_analog(0, 0, &mouse_x);\r
1332                         in_update_analog(0, 1, &mouse_y);\r
1333                         emu_status_msg("Mouse capture %s", grab_mode ? "on" : "off");\r
1334                 } else {\r
1335                         grab_mode = 0;\r
1336                         emu_status_msg("No mouse configured");\r
1337                 }\r
1338 \r
1339                 plat_grab_cursor(grab_mode);\r
1340         }\r
1341         if (which & PEV_SWITCH_KBD)\r
1342         {\r
1343                 if (! (PicoIn.opt & POPT_EN_KBD)) {\r
1344                         kbd_mode = 0;\r
1345                         emu_status_msg("No keyboard configured");\r
1346                 } else {\r
1347                         kbd_mode = !kbd_mode;\r
1348                         emu_status_msg("Keyboard %s", kbd_mode ? "on" : "off");\r
1349                 }\r
1350                 if (! kbd_mode)\r
1351                         plat_video_clear_buffers();\r
1352         }\r
1353         if (which & PEV_RESET)\r
1354                 emu_reset_game();\r
1355         if (which & PEV_MENU)\r
1356                 engineState = PGS_Menu;\r
1357 }\r
1358 \r
1359 void emu_update_input(void)\r
1360 {\r
1361         static int prev_events = 0;\r
1362         int actions[IN_BINDTYPE_COUNT] = { 0, };\r
1363         int actions_kbd[IN_BIND_LAST] = { 0, };\r
1364         int pl_actions[4];\r
1365         int count_kbd = 0, buttons = 0;\r
1366         int events, i = 0;\r
1367 \r
1368         in_update(actions);\r
1369 \r
1370         pl_actions[0] = actions[IN_BINDTYPE_PLAYER12];\r
1371         pl_actions[1] = actions[IN_BINDTYPE_PLAYER12] >> 16;\r
1372         pl_actions[2] = actions[IN_BINDTYPE_PLAYER34];\r
1373         pl_actions[3] = actions[IN_BINDTYPE_PLAYER34] >> 16;\r
1374 \r
1375         events = actions[IN_BINDTYPE_EMU] & PEV_MASK;\r
1376 \r
1377         // update mouse coordinates if there is an emulated mouse\r
1378         if (PicoIn.opt & POPT_EN_MOUSE) {\r
1379                 if (!grab_mode) {\r
1380                         in_update_analog(0, 0, &PicoIn.mouse[0]);\r
1381                         in_update_analog(0, 1, &PicoIn.mouse[1]);\r
1382                         // scale mouse coordinates from -1024..1024 to 0..screen_w/h\r
1383                         PicoIn.mouse[0] = (PicoIn.mouse[0]+1024) * g_screen_width /2048;\r
1384                         PicoIn.mouse[1] = (PicoIn.mouse[1]+1024) * g_screen_height/2048;\r
1385                 } else {\r
1386                         int xrel, yrel;\r
1387                         in_update_analog(0, 2, &xrel);\r
1388                         in_update_analog(0, 3, &yrel);\r
1389                         mouse_x += xrel, mouse_y += yrel;\r
1390                         // scale mouse coordinates from -1024..1024 to 0..screen_w/h\r
1391                         PicoIn.mouse[0] = (mouse_x+1024) * g_screen_width /2048;\r
1392                         PicoIn.mouse[1] = (mouse_y+1024) * g_screen_height/2048;\r
1393                 }\r
1394 \r
1395                 in_update_analog(0, -1, &i); // get mouse buttons, bit 2-0 = RML\r
1396                 if (PicoIn.AHW & PAHW_PICO) {\r
1397                         // TODO is maintaining 2 different mappings necessary?\r
1398                         if (i & 1) buttons |= 1<<GBTN_C;        // pen button\r
1399                         if (i & 2) buttons |= 1<<GBTN_B;        // red button\r
1400                         if (i & 4) buttons |= 1<<GBTN_START;    // pen up/down\r
1401                 } else {\r
1402                         if (i & 1) buttons |= 1<<GBTN_B;        // as Sega Mouse\r
1403                         if (i & 2) buttons |= 1<<GBTN_START;\r
1404                         if (i & 4) buttons |= 1<<GBTN_C;\r
1405                 }\r
1406 \r
1407                 if (currentConfig.input_dev0 == PICO_INPUT_MOUSE)\r
1408                         pl_actions[0] |= buttons;\r
1409                 if (currentConfig.input_dev1 == PICO_INPUT_MOUSE)\r
1410                         pl_actions[1] |= buttons;\r
1411         }\r
1412 \r
1413         if (kbd_mode) {\r
1414                 int mask = (PicoIn.AHW & PAHW_PICO ? 0xf : 0x0);\r
1415                 if (currentConfig.keyboard == 2)\r
1416                         count_kbd = in_update_kbd(actions_kbd);\r
1417                 else if (currentConfig.keyboard == 1)\r
1418                         count_kbd = vkbd_update(vkbd, pl_actions[0], actions_kbd);\r
1419 \r
1420                 // FIXME: Only passthrough joystick input to avoid collisions\r
1421                 // with PS/2 bindings. Ideally we should check if the device this\r
1422                 // input originated from is the same as the device used for\r
1423                 // PS/2 input, and passthrough if they are different devices.\r
1424                 PicoIn.pad[0] = pl_actions[0] & mask;\r
1425                 PicoIn.pad[1] = pl_actions[1] & mask;\r
1426                 PicoIn.pad[2] = pl_actions[2] & mask;\r
1427                 PicoIn.pad[3] = pl_actions[3] & mask;\r
1428 \r
1429                 // Ignore events mapped to bindings that collide with PS/2 peripherals.\r
1430                 // Note that calls to emu_set_fastforward() should be avoided as well,\r
1431                 // since fast-forward activates even with parameter set_on = 0.\r
1432                 events &= PEV_SWITCH_KBD;\r
1433         } else {\r
1434                 PicoIn.pad[0] = pl_actions[0] & 0xfff;\r
1435                 PicoIn.pad[1] = pl_actions[1] & 0xfff;\r
1436                 PicoIn.pad[2] = pl_actions[2] & 0xfff;\r
1437                 PicoIn.pad[3] = pl_actions[3] & 0xfff;\r
1438 \r
1439                 if (pl_actions[0] & 0x7000)\r
1440                         do_turbo(&PicoIn.pad[0], pl_actions[0]);\r
1441                 if (pl_actions[1] & 0x7000)\r
1442                         do_turbo(&PicoIn.pad[1], pl_actions[1]);\r
1443                 if (pl_actions[2] & 0x7000)\r
1444                         do_turbo(&PicoIn.pad[2], pl_actions[2]);\r
1445                 if (pl_actions[3] & 0x7000)\r
1446                         do_turbo(&PicoIn.pad[3], pl_actions[3]);\r
1447 \r
1448                 if ((events ^ prev_events) & PEV_FF) {\r
1449                         emu_set_fastforward(events & PEV_FF);\r
1450                         plat_update_volume(0, 0);\r
1451                         reset_timing = 1;\r
1452                 }\r
1453         }\r
1454 \r
1455         // volume is treated in special way and triggered every frame\r
1456         if (events & (PEV_VOL_DOWN|PEV_VOL_UP))\r
1457                 plat_update_volume(1, events & PEV_VOL_UP);\r
1458 \r
1459         events &= ~prev_events;\r
1460 \r
1461         // update keyboard input, actions only updated if keyboard mode active\r
1462         PicoIn.kbd = 0;\r
1463         for (i = 0; i < count_kbd; i++) {\r
1464                 if (actions_kbd[i]) {\r
1465                         unsigned int key = (actions_kbd[i] & 0xff);\r
1466                         if (key == PEVB_KBD_LSHIFT || key == PEVB_KBD_RSHIFT ||\r
1467                             key == PEVB_KBD_CTRL || key == PEVB_KBD_FUNC) {\r
1468                                 PicoIn.kbd = (PicoIn.kbd & 0x00ff) | (key << 8);\r
1469                         } else {\r
1470                                 PicoIn.kbd = (PicoIn.kbd & 0xff00) | key;\r
1471                         }\r
1472                 }\r
1473         }\r
1474 \r
1475         if (PicoIn.AHW & PAHW_PICO)\r
1476                 run_events_pico(events);\r
1477         if (events)\r
1478                 run_events_ui(events);\r
1479         if (movie_data)\r
1480                 update_movie();\r
1481 \r
1482         prev_events = actions[IN_BINDTYPE_EMU] & PEV_MASK;\r
1483 }\r
1484 \r
1485 static void mkdir_path(char *path_with_reserve, int pos, const char *name)\r
1486 {\r
1487         strcpy(path_with_reserve + pos, name);\r
1488         if (plat_is_dir(path_with_reserve))\r
1489                 return;\r
1490         if (mkdir(path_with_reserve, 0755) < 0)\r
1491                 lprintf("failed to create: %s\n", path_with_reserve);\r
1492 }\r
1493 \r
1494 void emu_cmn_forced_frame(int no_scale, int do_emu, void *buf)\r
1495 {\r
1496         int po_old = PicoIn.opt;\r
1497         int y;\r
1498 \r
1499         for (y = 0; y < g_screen_height; y++)\r
1500                 memset32((short *)g_screen_ptr + g_screen_ppitch * y, 0,\r
1501                          g_screen_width * 2 / 4);\r
1502 \r
1503         PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);\r
1504         PicoIn.opt |= POPT_ACC_SPRITES;\r
1505         if (!no_scale && currentConfig.scaling)\r
1506                 PicoIn.opt |= POPT_EN_SOFTSCALE;\r
1507 \r
1508         PicoDrawSetOutFormat(PDF_RGB555, 1);\r
1509         PicoDrawSetOutBuf(buf, g_screen_ppitch * 2);\r
1510         Pico.m.dirtyPal = 1;\r
1511         if (do_emu)\r
1512                 PicoFrame();\r
1513         else\r
1514                 PicoFrameDrawOnly();\r
1515 \r
1516         PicoIn.opt = po_old;\r
1517 }\r
1518 \r
1519 void emu_init(void)\r
1520 {\r
1521         char path[512];\r
1522         int pos;\r
1523 \r
1524 #if 0\r
1525         // FIXME: handle through menu, etc\r
1526         FILE *f;\r
1527         f = fopen("32X_M_BIOS.BIN", "rb");\r
1528         p32x_bios_m = malloc(2048);\r
1529         fread(p32x_bios_m, 1, 2048, f);\r
1530         fclose(f);\r
1531         f = fopen("32X_S_BIOS.BIN", "rb");\r
1532         p32x_bios_s = malloc(1024);\r
1533         fread(p32x_bios_s, 1, 1024, f);\r
1534         fclose(f);\r
1535 #endif\r
1536 \r
1537         /* make dirs for saves */\r
1538         pos = plat_get_root_dir(path, sizeof(path) - 4);\r
1539         mkdir_path(path, pos, "mds");\r
1540         mkdir_path(path, pos, "srm");\r
1541         mkdir_path(path, pos, "brm");\r
1542         mkdir_path(path, pos, "tape");\r
1543         mkdir_path(path, pos, "cfg");\r
1544 \r
1545         pprof_init();\r
1546 \r
1547         make_config_cfg(path);\r
1548         config_readlrom(path);\r
1549 \r
1550         PicoInit();\r
1551         PicoIn.osdMessage = plat_status_msg_busy_next;\r
1552         PicoIn.mcdTrayOpen = emu_tray_open;\r
1553         PicoIn.mcdTrayClose = emu_tray_close;\r
1554 \r
1555         sndout_init();\r
1556 }\r
1557 \r
1558 void emu_finish(void)\r
1559 {\r
1560         // save SRAM\r
1561         if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && Pico.sv.changed) {\r
1562                 emu_save_load_game(0, 1);\r
1563                 Pico.sv.changed = 0;\r
1564         }\r
1565 \r
1566         if (!(currentConfig.EmuOpt & EOPT_NO_AUTOSVCFG)) {\r
1567                 char cfg[512];\r
1568                 make_config_cfg(cfg);\r
1569                 config_writelrom(cfg);\r
1570 #ifdef __GP2X__\r
1571                 sync();\r
1572 #endif\r
1573         }\r
1574 \r
1575         pprof_finish();\r
1576 \r
1577         PicoExit();\r
1578         sndout_exit();\r
1579 }\r
1580 \r
1581 static void snd_write_nonblocking(int len)\r
1582 {\r
1583         sndout_write_nb(PicoIn.sndOut, len);\r
1584 }\r
1585 \r
1586 void emu_sound_start(void)\r
1587 {\r
1588         PicoIn.sndOut = NULL;\r
1589 \r
1590         // auto-select rate?\r
1591         if (PicoIn.sndRate > 52000 && PicoIn.sndRate < 54000)\r
1592                 PicoIn.sndRate = YM2612_NATIVE_RATE();\r
1593         if (currentConfig.EmuOpt & EOPT_EN_SOUND)\r
1594         {\r
1595                 int is_stereo = (PicoIn.opt & POPT_EN_STEREO) ? 1 : 0;\r
1596 \r
1597                 memset(sndBuffer, 0, sizeof(sndBuffer));\r
1598                 PicoIn.sndOut = sndBuffer;\r
1599                 PsndRerate(Pico.m.frame_count ? 1 : 0);\r
1600 \r
1601                 printf("starting audio: %i len: %i stereo: %i, pal: %i\n",\r
1602                         PicoIn.sndRate, Pico.snd.len, is_stereo, Pico.m.pal);\r
1603 \r
1604                 sndout_start(PicoIn.sndRate, is_stereo);\r
1605                 PicoIn.writeSound = snd_write_nonblocking;\r
1606                 plat_update_volume(0, 0);\r
1607         }\r
1608 }\r
1609 \r
1610 void emu_sound_stop(void)\r
1611 {\r
1612         sndout_stop();\r
1613 }\r
1614 \r
1615 void emu_sound_wait(void)\r
1616 {\r
1617         sndout_wait();\r
1618 }\r
1619 \r
1620 static void emu_loop_prep(void)\r
1621 {\r
1622         static int pal_old = -1;\r
1623         static int filter_old = -1;\r
1624 \r
1625         if (currentConfig.CPUclock != plat_target_cpu_clock_get())\r
1626                 plat_target_cpu_clock_set(currentConfig.CPUclock);\r
1627 \r
1628         if (Pico.m.pal != pal_old) {\r
1629                 plat_target_lcdrate_set(Pico.m.pal);\r
1630                 pal_old = Pico.m.pal;\r
1631         }\r
1632 \r
1633         if (currentConfig.filter != filter_old) {\r
1634                 plat_target_hwfilter_set(currentConfig.filter);\r
1635                 filter_old = currentConfig.filter;\r
1636         }\r
1637 \r
1638         plat_target_gamma_set(currentConfig.gamma, 0);\r
1639 \r
1640         vkbd = NULL;\r
1641         if (currentConfig.keyboard == 1) {\r
1642                 if (PicoIn.AHW & PAHW_SMS) vkbd = vkbd_init(0);\r
1643                 else if (PicoIn.AHW & PAHW_PICO) vkbd = vkbd_init(1);\r
1644         }\r
1645         PicoIn.opt &= ~POPT_EN_KBD;\r
1646         if (((PicoIn.AHW & PAHW_PICO) || (PicoIn.AHW & PAHW_SC)) && currentConfig.keyboard)\r
1647                 PicoIn.opt |= POPT_EN_KBD;\r
1648 \r
1649         PicoIn.opt &= ~POPT_EN_MOUSE;\r
1650         if (!(PicoIn.AHW & PAHW_8BIT) && (currentConfig.input_dev0 == PICO_INPUT_MOUSE ||\r
1651                                         currentConfig.input_dev1 == PICO_INPUT_MOUSE)) {\r
1652                 PicoIn.opt |= POPT_EN_MOUSE;\r
1653                 plat_grab_cursor(grab_mode);\r
1654         }\r
1655 \r
1656         pemu_loop_prep();\r
1657 }\r
1658 \r
1659 /* our tick here is 1 us right now */\r
1660 #define ms_to_ticks(x)  (int)(x * 1000)\r
1661 #define get_ticks()     plat_get_ticks_us()\r
1662 #define vsync_delay     ms_to_ticks(1)\r
1663 \r
1664 void emu_loop(void)\r
1665 {\r
1666         int frames_done, frames_shown;  /* actual frames for fps counter */\r
1667         int target_frametime;\r
1668         unsigned int timestamp = 0;\r
1669         unsigned int timestamp_aim = 0;\r
1670         unsigned int timestamp_fps = 0;\r
1671         char *notice_msg = NULL;\r
1672         char fpsbuff[24];\r
1673         int fskip_cnt = 0;\r
1674 \r
1675         fpsbuff[0] = 0;\r
1676 \r
1677         PicoLoopPrepare();\r
1678 \r
1679         plat_video_loop_prepare();\r
1680         emu_loop_prep();\r
1681         pemu_sound_start();\r
1682 \r
1683         /* number of ticks per frame */\r
1684         if (Pico.m.pal)\r
1685                 target_frametime = ms_to_ticks(1000) / 50;\r
1686         else\r
1687                 target_frametime = ms_to_ticks(1000) / 60;\r
1688 \r
1689         reset_timing = 1;\r
1690         frames_done = frames_shown = 0;\r
1691 \r
1692         /* loop with resync every 1 sec. */\r
1693         while (engineState == PGS_Running)\r
1694         {\r
1695                 int skip = 0;\r
1696                 int diff;\r
1697 \r
1698                 pprof_start(main);\r
1699 \r
1700                 if (reset_timing) {\r
1701                         reset_timing = 0;\r
1702                         plat_video_wait_vsync();\r
1703                         timestamp_aim = get_ticks();\r
1704                         timestamp_fps = timestamp_aim;\r
1705                         fskip_cnt = 0;\r
1706                 }\r
1707                 else if (currentConfig.EmuOpt & EOPT_NO_FRMLIMIT) {\r
1708                         timestamp_aim = get_ticks();\r
1709                 }\r
1710 \r
1711                 timestamp = get_ticks();\r
1712 \r
1713                 // show notice_msg message?\r
1714                 if (notice_msg_time != 0)\r
1715                 {\r
1716                         static int noticeMsgSum;\r
1717                         if (timestamp - ms_to_ticks(notice_msg_time)\r
1718                              > ms_to_ticks(STATUS_MSG_TIMEOUT))\r
1719                         {\r
1720                                 notice_msg_time = 0;\r
1721                                 notice_msg = NULL;\r
1722                                 plat_status_msg_clear();\r
1723                         }\r
1724                         else {\r
1725                                 int sum = noticeMsg[0] + noticeMsg[1] + noticeMsg[2];\r
1726                                 if (sum != noticeMsgSum) {\r
1727                                         plat_status_msg_clear();\r
1728                                         noticeMsgSum = sum;\r
1729                                 }\r
1730                                 notice_msg = noticeMsg;\r
1731                         }\r
1732                 }\r
1733 \r
1734                 // second changed?\r
1735                 if (timestamp - timestamp_fps >= ms_to_ticks(1000))\r
1736                 {\r
1737 #ifdef BENCHMARK\r
1738                         static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];\r
1739                         if (++bench == 10) {\r
1740                                 bench = 0;\r
1741                                 bench_fps_s = bench_fps;\r
1742                                 bf[bfp++ & 3] = bench_fps;\r
1743                                 bench_fps = 0;\r
1744                         }\r
1745                         bench_fps += frames_shown;\r
1746                         sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);\r
1747                         printf("%s\n", fpsbuff);\r
1748 #else\r
1749                         if (currentConfig.EmuOpt & EOPT_SHOW_FPS)\r
1750                                 snprintf(fpsbuff, 8, "%02i/%02i  ", frames_shown, frames_done);\r
1751 #endif\r
1752                         frames_shown = frames_done = 0;\r
1753                         timestamp_fps += ms_to_ticks(1000);\r
1754                 }\r
1755 #ifdef PFRAMES\r
1756                 sprintf(fpsbuff, "%i", Pico.m.frame_count);\r
1757 #endif\r
1758 \r
1759                 diff = timestamp_aim - timestamp;\r
1760 \r
1761                 if (currentConfig.Frameskip >= 0) // frameskip enabled (or 0)\r
1762                 {\r
1763                         if (fskip_cnt < currentConfig.Frameskip) {\r
1764                                 fskip_cnt++;\r
1765                                 skip = 1;\r
1766                         }\r
1767                         else {\r
1768                                 fskip_cnt = 0;\r
1769                         }\r
1770                 }\r
1771                 else if (diff < -target_frametime)\r
1772                 {\r
1773                         /* no time left for this frame - skip */\r
1774                         /* limit auto frameskip to max_skip */\r
1775                         if (fskip_cnt < currentConfig.max_skip) {\r
1776                                 fskip_cnt++;\r
1777                                 skip = 1;\r
1778                         }\r
1779                         else {\r
1780                                 fskip_cnt = 0;\r
1781                         }\r
1782                 } else\r
1783                         fskip_cnt = 0;\r
1784 \r
1785                 // don't go in debt too much\r
1786                 while (diff < -target_frametime * 3) {\r
1787                         timestamp_aim += target_frametime;\r
1788                         diff = timestamp_aim - timestamp;\r
1789                 }\r
1790 \r
1791                 emu_update_input();\r
1792 \r
1793                 // 3D glasses\r
1794                 skip |= (PicoIn.AHW & PAHW_SMS) &&\r
1795                         (Pico.m.hardware & PMS_HW_3D) &&\r
1796                         (PicoMem.zram[0x1ffb] & 1);\r
1797 \r
1798                 if (skip) {\r
1799                         int do_audio = diff > -target_frametime * 2;\r
1800                         PicoIn.skipFrame = do_audio ? 1 : 2;\r
1801                         PicoFrame();\r
1802                         PicoIn.skipFrame = 0;\r
1803                 }\r
1804                 else {\r
1805                         PicoFrame();\r
1806                         pemu_finalize_frame(fpsbuff, notice_msg);\r
1807                         frames_shown++;\r
1808                 }\r
1809                 frames_done++;\r
1810                 timestamp_aim += target_frametime;\r
1811 \r
1812                 if (!skip && !flip_after_sync)\r
1813                         plat_video_flip();\r
1814 \r
1815                 /* frame limiter */\r
1816                 if (!skip && !reset_timing\r
1817                     && !(currentConfig.EmuOpt & (EOPT_NO_FRMLIMIT|EOPT_EXT_FRMLIMIT)))\r
1818                 {\r
1819                         unsigned int timestamp = get_ticks();\r
1820                         diff = timestamp_aim - timestamp;\r
1821 \r
1822                         // sleep or vsync if we are still too fast\r
1823                         if (diff > target_frametime + vsync_delay && (currentConfig.EmuOpt & EOPT_VSYNC)) {\r
1824                                 // we are too fast\r
1825                                 plat_video_wait_vsync();\r
1826                                 timestamp = get_ticks();\r
1827                                 diff = timestamp_aim - timestamp;\r
1828                         }\r
1829                         if (diff > target_frametime + vsync_delay) {\r
1830                                 // still too fast\r
1831                                 plat_wait_till_us(timestamp + (diff - target_frametime));\r
1832                         }\r
1833                 }\r
1834 \r
1835                 if (!skip && flip_after_sync)\r
1836                         plat_video_flip();\r
1837 \r
1838                 pprof_end(main);\r
1839         }\r
1840 \r
1841         emu_set_fastforward(0);\r
1842 \r
1843         // save SRAM\r
1844         if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && Pico.sv.changed) {\r
1845                 plat_status_msg_busy_first("Writing SRAM/BRAM...");\r
1846                 emu_save_load_game(0, 1);\r
1847                 Pico.sv.changed = 0;\r
1848         }\r
1849 \r
1850         pemu_loop_end();\r
1851         emu_sound_stop();\r
1852         plat_grab_cursor(0);\r
1853 }\r