sram bugfix + savestate refactoring
[picodrive.git] / platform / common / emu.c
index 08efdb2..08dbe0b 100644 (file)
@@ -1,10 +1,11 @@
-// (c) Copyright 2006-2007 notaz, All rights reserved.\r
+// (c) Copyright 2006-2009 notaz, All rights reserved.\r
 // Free for non-commercial use.\r
 \r
 // For commercial use, separate licencing terms must be obtained.\r
 \r
 #include <stdio.h>\r
 #include <stdlib.h>\r
+#include <stdarg.h>\r
 #ifndef NO_SYNC\r
 #include <unistd.h>\r
 #endif\r
 #include <pico/pico_int.h>\r
 #include <pico/patch.h>\r
 #include <pico/cd/cue.h>\r
-#include <zlib/zlib.h>\r
 \r
 \r
+#define STATUS_MSG_TIMEOUT 2000\r
+\r
 void *g_screen_ptr;\r
 \r
 #if !SCREEN_SIZE_FIXED\r
@@ -44,6 +46,8 @@ char rom_fname_reload[512] = { 0, };
 char rom_fname_loaded[512] = { 0, };\r
 int rom_loaded = 0;\r
 int reset_timing = 0;\r
+static unsigned int notice_msg_time;   /* when started showing */\r
+static char noticeMsg[40];\r
 \r
 unsigned char *movie_data = NULL;\r
 static int movie_size = 0;\r
@@ -75,9 +79,9 @@ static int try_rfn_cut(char *fname)
        return 0;\r
 }\r
 \r
-static void get_ext(char *file, char *ext)\r
+static void get_ext(const char *file, char *ext)\r
 {\r
-       char *p;\r
+       const char *p;\r
 \r
        p = file + strlen(file) - 4;\r
        if (p < file) p = file;\r
@@ -86,15 +90,32 @@ static void get_ext(char *file, char *ext)
        strlwr_(ext);\r
 }\r
 \r
-static const char *biosfiles_us[] = { "us_scd1_9210", "us_scd2_9306", "SegaCDBIOS9303" };\r
-static const char *biosfiles_eu[] = { "eu_mcd1_9210", "eu_mcd2_9306", "eu_mcd2_9303"   };\r
-static const char *biosfiles_jp[] = { "jp_mcd1_9112", "jp_mcd1_9111" };\r
+void emu_status_msg(const char *format, ...)\r
+{\r
+       va_list vl;\r
+       int ret;\r
+\r
+       va_start(vl, format);\r
+       ret = vsnprintf(noticeMsg, sizeof(noticeMsg), format, vl);\r
+       va_end(vl);\r
+\r
+       /* be sure old text gets overwritten */\r
+       for (; ret < 28; ret++)\r
+               noticeMsg[ret] = ' ';\r
+       noticeMsg[ret] = 0;\r
+\r
+       notice_msg_time = plat_get_ticks_ms();\r
+}\r
+\r
+static const char * const biosfiles_us[] = { "us_scd1_9210", "us_scd2_9306", "SegaCDBIOS9303" };\r
+static const char * const biosfiles_eu[] = { "eu_mcd1_9210", "eu_mcd2_9306", "eu_mcd2_9303"   };\r
+static const char * const biosfiles_jp[] = { "jp_mcd1_9112", "jp_mcd1_9111" };\r
 \r
 static int find_bios(int region, char **bios_file)\r
 {\r
        static char bios_path[1024];\r
        int i, count;\r
-       const char **files;\r
+       const char * const *files;\r
        FILE *f = NULL;\r
 \r
        if (region == 4) { // US\r
@@ -155,12 +176,13 @@ static unsigned char id_header[0x100];
 \r
 /* checks if fname points to valid MegaCD image\r
  * if so, checks for suitable BIOS */\r
-int emu_cd_check(int *pregion, char *fname_in)\r
+static int emu_cd_check(int *pregion, const char *fname_in)\r
 {\r
+       const char *fname = fname_in;\r
        unsigned char buf[32];\r
        pm_file *cd_f;\r
        int region = 4; // 1: Japan, 4: US, 8: Europe\r
-       char ext[5], *fname = fname_in;\r
+       char ext[5];\r
        cue_track_type type = CT_UNKNOWN;\r
        cue_data_t *cue_data = NULL;\r
 \r
@@ -384,8 +406,8 @@ int emu_reload_rom(char *rom_fname)
                // valid CD image, check for BIOS..\r
 \r
                // we need to have config loaded at this point\r
-               ret = emu_read_config(1, 1);\r
-               if (!ret) emu_read_config(0, 1);\r
+               ret = emu_read_config(1, 0);\r
+               if (!ret) emu_read_config(0, 0);\r
                cfg_loaded = 1;\r
 \r
                if (PicoRegionOverride) {\r
@@ -442,8 +464,8 @@ int emu_reload_rom(char *rom_fname)
        if (!(PicoAHW & PAHW_MCD))\r
                memcpy(id_header, rom_data + 0x100, sizeof(id_header));\r
        if (!cfg_loaded) {\r
-               ret = emu_read_config(1, 1);\r
-               if (!ret) emu_read_config(0, 1);\r
+               ret = emu_read_config(1, 0);\r
+               if (!ret) emu_read_config(0, 0);\r
        }\r
 \r
        lprintf("PicoCartInsert(%p, %d);\n", rom_data, rom_size);\r
@@ -485,21 +507,22 @@ int emu_reload_rom(char *rom_fname)
                        // TODO: bits 6 & 5\r
                }\r
                movie_data[0x18+30] = 0;\r
-               plat_status_msg("MOVIE: %s", (char *) &movie_data[0x18]);\r
+               emu_status_msg("MOVIE: %s", (char *) &movie_data[0x18]);\r
        }\r
        else\r
        {\r
                PicoOpt &= ~POPT_DIS_VDP_FIFO;\r
-               plat_status_msg(Pico.m.pal ? "PAL SYSTEM / 50 FPS" : "NTSC SYSTEM / 60 FPS");\r
+               emu_status_msg(Pico.m.pal ? "PAL SYSTEM / 50 FPS" : "NTSC SYSTEM / 60 FPS");\r
        }\r
 \r
+       strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);\r
+       rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;\r
+       rom_loaded = 1;\r
+\r
        // load SRAM for this ROM\r
        if (currentConfig.EmuOpt & EOPT_EN_SRAM)\r
                emu_save_load_game(1, 1);\r
 \r
-       strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);\r
-       rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;\r
-       rom_loaded = 1;\r
        return 1;\r
 \r
 fail2:\r
@@ -509,6 +532,24 @@ fail:
        return 0;\r
 }\r
 \r
+int emu_swap_cd(const char *fname)\r
+{\r
+       cd_img_type cd_type;\r
+       int ret = -1;\r
+\r
+       cd_type = emu_cd_check(NULL, fname);\r
+       if (cd_type != CIT_NOT_CD)\r
+               ret = Insert_CD(fname, cd_type);\r
+       if (ret != 0) {\r
+               me_update_msg("Load failed, invalid CD image?");\r
+               return 0;\r
+       }\r
+\r
+       strncpy(rom_fname_loaded, fname, sizeof(rom_fname_loaded)-1);\r
+       rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;\r
+       return 1;\r
+}\r
+\r
 static void romfname_ext(char *dst, const char *prefix, const char *ext)\r
 {\r
        char *p;\r
@@ -557,7 +598,7 @@ static void make_config_cfg(char *cfg_buff_512)
        cfg_buff_512[511] = 0;\r
 }\r
 \r
-static void emu_setDefaultConfig(void)\r
+void emu_set_defconfig(void)\r
 {\r
        memcpy(&currentConfig, &defaultConfig, sizeof(currentConfig));\r
        PicoOpt = currentConfig.s_PicoOpt;\r
@@ -572,10 +613,11 @@ int emu_read_config(int game, int no_defaults)
        char cfg[512];\r
        int ret;\r
 \r
+       if (!no_defaults)\r
+               emu_set_defconfig();\r
+\r
        if (!game)\r
        {\r
-               if (!no_defaults)\r
-                       emu_setDefaultConfig();\r
                make_config_cfg(cfg);\r
                ret = config_readsect(cfg, NULL);\r
        }\r
@@ -593,7 +635,7 @@ int emu_read_config(int game, int no_defaults)
                {\r
                        // read user's config\r
                        int vol = currentConfig.volume;\r
-                       emu_setDefaultConfig();\r
+                       emu_set_defconfig();\r
                        ret = config_readsect(cfg, sect);\r
                        currentConfig.volume = vol; // make vol global (bah)\r
                }\r
@@ -602,7 +644,8 @@ int emu_read_config(int game, int no_defaults)
                        // read global config, and apply game_def.cfg on top\r
                        make_config_cfg(cfg);\r
                        config_readsect(cfg, NULL);\r
-                       ret = config_readsect("game_def.cfg", sect);\r
+                       emu_make_path(cfg, "game_def.cfg", sizeof(cfg));\r
+                       ret = config_readsect(cfg, sect);\r
                }\r
 \r
                if (ret == 0)\r
@@ -662,32 +705,36 @@ int emu_write_config(int is_game)
 \r
 /* always using built-in font */\r
 \r
-#define mk_text_out(name, type, val) \\r
+#define mk_text_out(name, type, val, topleft, step_x, step_y) \\r
 void name(int x, int y, const char *text)                              \\r
 {                                                                      \\r
        int i, l, len = strlen(text);                                   \\r
-       type *screen = (type *)g_screen_ptr + x + y * g_screen_width;   \\r
+       type *screen = (type *)(topleft) + x * step_x + y * step_y;     \\r
                                                                        \\r
-       for (i = 0; i < len; i++, screen += 8)                          \\r
+       for (i = 0; i < len; i++, screen += 8 * step_x)                 \\r
        {                                                               \\r
                for (l = 0; l < 8; l++)                                 \\r
                {                                                       \\r
                        unsigned char fd = fontdata8x8[text[i] * 8 + l];\\r
-                       type *s = screen + l * g_screen_width;          \\r
-                       if (fd&0x80) s[0] = val;                        \\r
-                       if (fd&0x40) s[1] = val;                        \\r
-                       if (fd&0x20) s[2] = val;                        \\r
-                       if (fd&0x10) s[3] = val;                        \\r
-                       if (fd&0x08) s[4] = val;                        \\r
-                       if (fd&0x04) s[5] = val;                        \\r
-                       if (fd&0x02) s[6] = val;                        \\r
-                       if (fd&0x01) s[7] = val;                        \\r
+                       type *s = screen + l * step_y;                  \\r
+                       if (fd&0x80) s[step_x * 0] = val;               \\r
+                       if (fd&0x40) s[step_x * 1] = val;               \\r
+                       if (fd&0x20) s[step_x * 2] = val;               \\r
+                       if (fd&0x10) s[step_x * 3] = val;               \\r
+                       if (fd&0x08) s[step_x * 4] = val;               \\r
+                       if (fd&0x04) s[step_x * 5] = val;               \\r
+                       if (fd&0x02) s[step_x * 6] = val;               \\r
+                       if (fd&0x01) s[step_x * 7] = val;               \\r
                }                                                       \\r
        }                                                               \\r
 }\r
 \r
-mk_text_out(emu_textOut8, unsigned char, 0xf0)\r
-mk_text_out(emu_textOut16, unsigned short, 0xffff)\r
+mk_text_out(emu_text_out8,      unsigned char,    0xf0, g_screen_ptr, 1, g_screen_width)\r
+mk_text_out(emu_text_out16,     unsigned short, 0xffff, g_screen_ptr, 1, g_screen_width)\r
+mk_text_out(emu_text_out8_rot,  unsigned char,    0xf0,\r
+       (char *)g_screen_ptr  + (g_screen_width - 1) * g_screen_height, -g_screen_height, 1)\r
+mk_text_out(emu_text_out16_rot, unsigned short, 0xffff,\r
+       (short *)g_screen_ptr + (g_screen_width - 1) * g_screen_height, -g_screen_height, 1)\r
 \r
 #undef mk_text_out\r
 \r
@@ -698,7 +745,7 @@ void update_movie(void)
        if (offs+3 > movie_size) {\r
                free(movie_data);\r
                movie_data = 0;\r
-               plat_status_msg("END OF MOVIE.");\r
+               emu_status_msg("END OF MOVIE.");\r
                lprintf("END OF MOVIE.\n");\r
        } else {\r
                // MXYZ SACB RLDU\r
@@ -719,18 +766,6 @@ void update_movie(void)
        }\r
 }\r
 \r
-\r
-static size_t gzRead2(void *p, size_t _size, size_t _n, void *file)\r
-{\r
-       return gzread(file, p, _n);\r
-}\r
-\r
-\r
-static size_t gzWrite2(void *p, size_t _size, size_t _n, void *file)\r
-{\r
-       return gzwrite(file, p, _n);\r
-}\r
-\r
 static int try_ropen_file(const char *fname)\r
 {\r
        FILE *f;\r
@@ -750,7 +785,8 @@ char *emu_get_save_fname(int load, int is_sram, int slot)
 \r
        if (is_sram)\r
        {\r
-               romfname_ext(saveFname, (PicoAHW&1) ? "brm"PATH_SEP : "srm"PATH_SEP, (PicoAHW&1) ? ".brm" : ".srm");\r
+               romfname_ext(saveFname, (PicoAHW & PAHW_MCD) ? "brm"PATH_SEP : "srm"PATH_SEP,\r
+                               (PicoAHW & PAHW_MCD) ? ".brm" : ".srm");\r
                if (load) {\r
                        if (try_ropen_file(saveFname)) return saveFname;\r
                        // try in current dir..\r
@@ -794,23 +830,6 @@ int emu_check_save_file(int slot)
        return emu_get_save_fname(1, 0, slot) ? 1 : 0;\r
 }\r
 \r
-void emu_setSaveStateCbs(int gz)\r
-{\r
-       if (gz) {\r
-               areaRead  = gzRead2;\r
-               areaWrite = gzWrite2;\r
-               areaEof   = (areaeof *) gzeof;\r
-               areaSeek  = (areaseek *) gzseek;\r
-               areaClose = (areaclose *) gzclose;\r
-       } else {\r
-               areaRead  = (arearw *) fread;\r
-               areaWrite = (arearw *) fwrite;\r
-               areaEof   = (areaeof *) feof;\r
-               areaSeek  = (areaseek *) fseek;\r
-               areaClose = (areaclose *) fclose;\r
-       }\r
-}\r
-\r
 int emu_save_load_game(int load, int sram)\r
 {\r
        int ret = 0;\r
@@ -820,7 +839,7 @@ int emu_save_load_game(int load, int sram)
        saveFname = emu_get_save_fname(load, sram, state_slot);\r
        if (saveFname == NULL) {\r
                if (!sram)\r
-                       plat_status_msg(load ? "LOAD FAILED (missing file)" : "SAVE FAILED");\r
+                       emu_status_msg(load ? "LOAD FAILED (missing file)" : "SAVE FAILED");\r
                return -1;\r
        }\r
 \r
@@ -881,35 +900,14 @@ int emu_save_load_game(int load, int sram)
        }\r
        else\r
        {\r
-               void *PmovFile = NULL;\r
-               if (strcmp(saveFname + strlen(saveFname) - 3, ".gz") == 0)\r
-               {\r
-                       if( (PmovFile = gzopen(saveFname, load ? "rb" : "wb")) ) {\r
-                               emu_setSaveStateCbs(1);\r
-                               if (!load) gzsetparams(PmovFile, 9, Z_DEFAULT_STRATEGY);\r
-                       }\r
-               }\r
-               else\r
-               {\r
-                       if( (PmovFile = fopen(saveFname, load ? "rb" : "wb")) ) {\r
-                               emu_setSaveStateCbs(0);\r
-                       }\r
-               }\r
-               if(PmovFile) {\r
-                       ret = PmovState(load ? 6 : 5, PmovFile);\r
-                       areaClose(PmovFile);\r
-                       PmovFile = 0;\r
-                       if (load) Pico.m.dirtyPal=1;\r
+               ret = PicoState(saveFname, !load);\r
+               if (!ret) {\r
 #ifndef NO_SYNC\r
-                       else sync();\r
+                       if (!load) sync();\r
 #endif\r
-               }\r
-               else    ret = -1;\r
-               if (!ret)\r
-                       plat_status_msg(load ? "GAME LOADED" : "GAME SAVED");\r
-               else\r
-               {\r
-                       plat_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");\r
+                       emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");\r
+               } else {\r
+                       emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");\r
                        ret = -1;\r
                }\r
 \r
@@ -931,7 +929,7 @@ void emu_set_fastforward(int set_on)
                currentConfig.EmuOpt &= ~4;\r
                currentConfig.EmuOpt |= 0x40000;\r
                is_on = 1;\r
-               plat_status_msg("FAST FORWARD");\r
+               emu_status_msg("FAST FORWARD");\r
        }\r
        else if (!set_on && is_on) {\r
                PsndOut = set_PsndOut;\r
@@ -942,9 +940,14 @@ void emu_set_fastforward(int set_on)
        }\r
 }\r
 \r
-static void emu_msg_tray_open(void)\r
+static void emu_tray_open(void)\r
+{\r
+       engineState = PGS_TrayMenu;\r
+}\r
+\r
+static void emu_tray_close(void)\r
 {\r
-       plat_status_msg("CD tray opened");\r
+       emu_status_msg("CD tray closed.");\r
 }\r
 \r
 void emu_reset_game(void)\r
@@ -962,9 +965,9 @@ void run_events_pico(unsigned int events)
                if (pico_inp_mode > 2)\r
                        pico_inp_mode = 0;\r
                switch (pico_inp_mode) {\r
-                       case 2: plat_status_msg("Input: Pen on Pad"); break;\r
-                       case 1: plat_status_msg("Input: Pen on Storyware"); break;\r
-                       case 0: plat_status_msg("Input: Joystick");\r
+                       case 2: emu_status_msg("Input: Pen on Pad"); break;\r
+                       case 1: emu_status_msg("Input: Pen on Storyware"); break;\r
+                       case 0: emu_status_msg("Input: Joystick");\r
                                PicoPicohw.pen_pos[0] = PicoPicohw.pen_pos[1] = 0x8000;\r
                                break;\r
                }\r
@@ -973,13 +976,13 @@ void run_events_pico(unsigned int events)
                PicoPicohw.page--;\r
                if (PicoPicohw.page < 0)\r
                        PicoPicohw.page = 0;\r
-               plat_status_msg("Page %i", PicoPicohw.page);\r
+               emu_status_msg("Page %i", PicoPicohw.page);\r
        }\r
        if (events & PEV_PICO_PNEXT) {\r
                PicoPicohw.page++;\r
                if (PicoPicohw.page > 6)\r
                        PicoPicohw.page = 6;\r
-               plat_status_msg("Page %i", PicoPicohw.page);\r
+               emu_status_msg("Page %i", PicoPicohw.page);\r
        }\r
 \r
        if (pico_inp_mode == 0)\r
@@ -1068,7 +1071,7 @@ static void run_events_ui(unsigned int which)
                        in_set_blocking(0);\r
                }\r
                if (do_it) {\r
-                       plat_status_msg_busy_first((which & PEV_STATE_LOAD) ? "LOADING GAME" : "SAVING GAME");\r
+                       plat_status_msg_busy_first((which & PEV_STATE_LOAD) ? "LOADING STATE" : "SAVING STATE");\r
                        PicoStateProgressCB = plat_status_msg_busy_next;\r
                        emu_save_load_game((which & PEV_STATE_LOAD) ? 1 : 0, 0);\r
                        PicoStateProgressCB = NULL;\r
@@ -1076,7 +1079,7 @@ static void run_events_ui(unsigned int which)
        }\r
        if (which & PEV_SWITCH_RND)\r
        {\r
-               plat_video_toggle_renderer();\r
+               plat_video_toggle_renderer(1, 0);\r
        }\r
        if (which & (PEV_SSLOT_PREV|PEV_SSLOT_NEXT))\r
        {\r
@@ -1090,7 +1093,7 @@ static void run_events_ui(unsigned int which)
                                state_slot = 0;\r
                }\r
 \r
-               plat_status_msg("SAVE SLOT %i [%s]", state_slot,\r
+               emu_status_msg("SAVE SLOT %i [%s]", state_slot,\r
                        emu_check_save_file(state_slot) ? "USED" : "FREE");\r
        }\r
        if (which & PEV_MENU)\r
@@ -1166,8 +1169,8 @@ void emu_init(void)
 \r
        PicoInit();\r
        PicoMessage = plat_status_msg_busy_next;\r
-       PicoMCDopenTray = emu_msg_tray_open;\r
-       PicoMCDcloseTray = menu_loop_tray;\r
+       PicoMCDopenTray = emu_tray_open;\r
+       PicoMCDcloseTray = emu_tray_close;\r
 }\r
 \r
 void emu_finish(void)\r
@@ -1190,3 +1193,208 @@ void emu_finish(void)
        PicoExit();\r
 }\r
 \r
+static void skip_frame(int do_audio)\r
+{\r
+       PicoSkipFrame = do_audio ? 1 : 2;\r
+       PicoFrame();\r
+       PicoSkipFrame = 0;\r
+}\r
+\r
+/* our tick here is 1 us right now */\r
+#define ms_to_ticks(x) (unsigned int)(x * 1000)\r
+#define get_ticks() plat_get_ticks_us()\r
+\r
+void emu_loop(void)\r
+{\r
+       int pframes_done;               /* "period" frames, used for sync */\r
+       int frames_done, frames_shown;  /* actual frames for fps counter */\r
+       int oldmodes, target_fps, target_frametime;\r
+       unsigned int timestamp_base = 0, timestamp_fps;\r
+       char *notice_msg = NULL;\r
+       char fpsbuff[24];\r
+       int i;\r
+\r
+       fpsbuff[0] = 0;\r
+\r
+       /* make sure we are in correct mode */\r
+       oldmodes = ((Pico.video.reg[12]&1)<<2) ^ 0xc;\r
+       Pico.m.dirtyPal = 1;\r
+\r
+       /* number of ticks per frame */\r
+       if (Pico.m.pal) {\r
+               target_fps = 50;\r
+               target_frametime = ms_to_ticks(1000) / 50;\r
+       } else {\r
+               target_fps = 60;\r
+               target_frametime = ms_to_ticks(1000) / 60 + 1;\r
+       }\r
+\r
+       // prepare CD buffer\r
+       if (PicoAHW & PAHW_MCD)\r
+               PicoCDBufferInit();\r
+\r
+       pemu_loop_prep();\r
+\r
+       timestamp_fps = get_ticks();\r
+       reset_timing = 1;\r
+\r
+       frames_done = frames_shown = pframes_done = 0;\r
+\r
+       plat_video_wait_vsync();\r
+\r
+       /* loop with resync every 1 sec. */\r
+       while (engineState == PGS_Running)\r
+       {\r
+               unsigned int timestamp;\r
+               int diff, diff_lim;\r
+               int modes;\r
+\r
+               timestamp = get_ticks();\r
+               if (reset_timing) {\r
+                       reset_timing = 0;\r
+                       timestamp_base = timestamp;\r
+                       pframes_done = 0;\r
+               }\r
+\r
+               // show notice_msg message?\r
+               if (notice_msg_time != 0)\r
+               {\r
+                       static int noticeMsgSum;\r
+                       if (timestamp - ms_to_ticks(notice_msg_time) > ms_to_ticks(STATUS_MSG_TIMEOUT)) {\r
+                               notice_msg_time = 0;\r
+                               plat_status_msg_clear();\r
+                               notice_msg = NULL;\r
+                       } else {\r
+                               int sum = noticeMsg[0] + noticeMsg[1] + noticeMsg[2];\r
+                               if (sum != noticeMsgSum) {\r
+                                       plat_status_msg_clear();\r
+                                       noticeMsgSum = sum;\r
+                               }\r
+                               notice_msg = noticeMsg;\r
+                       }\r
+               }\r
+\r
+               // check for mode changes\r
+               modes = ((Pico.video.reg[12]&1)<<2) | (Pico.video.reg[1]&8);\r
+               if (modes != oldmodes) {\r
+                       oldmodes = modes;\r
+                       pemu_video_mode_change(!(modes & 4), (modes & 8));\r
+               }\r
+\r
+               // second changed?\r
+               if (timestamp - timestamp_fps >= ms_to_ticks(1000))\r
+               {\r
+#ifdef BENCHMARK\r
+                       static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];\r
+                       if (++bench == 10) {\r
+                               bench = 0;\r
+                               bench_fps_s = bench_fps;\r
+                               bf[bfp++ & 3] = bench_fps;\r
+                               bench_fps = 0;\r
+                       }\r
+                       bench_fps += frames_shown;\r
+                       sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);\r
+#else\r
+                       if (currentConfig.EmuOpt & EOPT_SHOW_FPS) {\r
+                               sprintf(fpsbuff, "%02i/%02i", frames_shown, frames_done);\r
+                               if (fpsbuff[5] == 0) { fpsbuff[5] = fpsbuff[6] = ' '; fpsbuff[7] = 0; }\r
+                       }\r
+#endif\r
+                       frames_shown = frames_done = 0;\r
+                       timestamp_fps += ms_to_ticks(1000);\r
+               }\r
+#ifdef PFRAMES\r
+               sprintf(fpsbuff, "%i", Pico.m.frame_count);\r
+#endif\r
+\r
+               if (timestamp - timestamp_base >= ms_to_ticks(1000))\r
+               {\r
+                       if ((currentConfig.EmuOpt & EOPT_NO_FRMLIMIT) && currentConfig.Frameskip >= 0)\r
+                               pframes_done = 0;\r
+                       else {\r
+                               pframes_done -= target_fps;\r
+                               /* don't allow it to drift during heavy slowdowns */\r
+                               if (pframes_done < -5) {\r
+                                       reset_timing = 1;\r
+                                       continue;\r
+                               }\r
+                               if (pframes_done < -2)\r
+                                       pframes_done = -2;\r
+                       }\r
+                       timestamp_base += ms_to_ticks(1000);\r
+               }\r
+\r
+               diff = timestamp - timestamp_base;\r
+               diff_lim = (pframes_done + 1) * target_frametime;\r
+\r
+               if (currentConfig.Frameskip >= 0) // frameskip enabled\r
+               {\r
+                       for (i = 0; i < currentConfig.Frameskip; i++) {\r
+                               emu_update_input();\r
+                               skip_frame(1);\r
+                               pframes_done++; frames_done++;\r
+                               diff_lim += target_frametime;\r
+\r
+                               if (!(currentConfig.EmuOpt & EOPT_NO_FRMLIMIT)) {\r
+                                       timestamp = get_ticks();\r
+                                       diff = timestamp - timestamp_base;\r
+                                       if (diff < diff_lim) // we are too fast\r
+                                               plat_wait_till_us(timestamp_base + diff_lim);\r
+                               }\r
+                       }\r
+               }\r
+               else if (diff > diff_lim)\r
+               {\r
+                       /* no time left for this frame - skip */\r
+                       if (diff - diff_lim >= ms_to_ticks(200)) {\r
+                               /* if too much behind, reset instead */\r
+                               reset_timing = 1;\r
+                               continue;\r
+                       }\r
+                       emu_update_input();\r
+                       skip_frame(diff < diff_lim + target_frametime * 2);\r
+                       pframes_done++; frames_done++;\r
+                       continue;\r
+               }\r
+\r
+               emu_update_input();\r
+               PicoFrame();\r
+\r
+               /* frame limiter */\r
+               if (!reset_timing && !(currentConfig.EmuOpt & EOPT_NO_FRMLIMIT))\r
+               {\r
+                       timestamp = get_ticks();\r
+                       diff = timestamp - timestamp_base;\r
+\r
+                       // sleep or vsync if we are still too fast\r
+                       if (diff < diff_lim)\r
+                       {\r
+                               // we are too fast\r
+                               plat_wait_till_us(timestamp_base + diff_lim - target_frametime / 4);\r
+                               if (currentConfig.EmuOpt & EOPT_VSYNC)\r
+                                       plat_video_wait_vsync();\r
+                       }\r
+               }\r
+\r
+               pemu_update_display(fpsbuff, notice_msg);\r
+\r
+               pframes_done++; frames_done++; frames_shown++;\r
+       }\r
+\r
+       emu_set_fastforward(0);\r
+\r
+       // save SRAM\r
+       if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && SRam.changed) {\r
+               plat_status_msg_busy_first("Writing SRAM/BRAM...");\r
+               emu_save_load_game(0, 1);\r
+               SRam.changed = 0;\r
+       }\r
+\r
+       pemu_loop_end();\r
+\r
+       // pemu_loop_end() might want to do 1 frame for bg image,\r
+       // so free CD buffer here\r
+       if (PicoAHW & PAHW_MCD)\r
+               PicoCDBufferFree();\r
+}\r
+\r