From 45b9ea797f628b3f13a44c3f91864dd6de0acf15 Mon Sep 17 00:00:00 2001 From: bardeci Date: Sat, 31 Jul 2021 12:36:02 +0200 Subject: [PATCH] Added savestate autoload/autosave feature --- dist/manual.txt | 20 ++- gambatte_sdl/builddate.h | 2 +- gambatte_sdl/libmenu.cpp | 66 +++++++++- gambatte_sdl/libmenu.h | 5 +- gambatte_sdl/menu.cpp | 195 ++++++++++++++++++++++-------- gambatte_sdl/src/gambatte_sdl.cpp | 4 + 6 files changed, 231 insertions(+), 61 deletions(-) diff --git a/dist/manual.txt b/dist/manual.txt index 22cff9e..a750e02 100644 --- a/dist/manual.txt +++ b/dist/manual.txt @@ -39,8 +39,11 @@ saving states. The default slot is always Slot 0. The default slot will always be selected when -starting the emulator. Selected savestate slot -will not be saved on emulator exit. +starting the emulator. The slot selection will +not be saved on emulator exit. + +The default slot will be used by the savestate +autosave/autoload feature when enabled. Emulator Options @@ -74,9 +77,15 @@ Here is a list of the available config options: with the same name as the ROM file, and it will load the default border if it fails. -- System: - Allows to select the system priority when +- System Priority: + Allows to select the system to use when a ROM supports both DMG and GBC modes. + Default is "GBC". + +- Savestates: + Allows to set savestate related options, + like autoload on game boot and autosave + on game exit. - Boot Logos: Allows to use GB/GBC BIOS files to display @@ -88,7 +97,8 @@ Here is a list of the available config options: Default is "DMG only". - Controls: - Allows the user to tune the controls. + Allows the user to tune the control related + settings. - Sound: Allows the user to select between Mono and diff --git a/gambatte_sdl/builddate.h b/gambatte_sdl/builddate.h index 499c345..dd9af09 100644 --- a/gambatte_sdl/builddate.h +++ b/gambatte_sdl/builddate.h @@ -1 +1 @@ -#define BUILDDATE "20210421-010751" +#define BUILDDATE "20210731-123313" diff --git a/gambatte_sdl/libmenu.cpp b/gambatte_sdl/libmenu.cpp index ad50ac4..fe1a5b1 100644 --- a/gambatte_sdl/libmenu.cpp +++ b/gambatte_sdl/libmenu.cpp @@ -56,7 +56,7 @@ Mix_Chunk *menusound_move = NULL; Mix_Chunk *menusound_ok = NULL; // Default config values -int showfps = 0, ghosting = 1, biosenabled = 0, colorfilter = 0, gameiscgb = 0, buttonlayout = 0, stereosound = 1, prefercgb = 0, ffwhotkey = 1; +int showfps = 0, ghosting = 1, biosenabled = 0, colorfilter = 0, gameiscgb = 0, buttonlayout = 0, stereosound = 1, prefercgb = 1, ffwhotkey = 1, stateautoload = 0, stateautosave = 0; uint32_t menupalblack = 0x000000, menupaldark = 0x505450, menupallight = 0xA8A8A8, menupalwhite = 0xF8FCF8; int filtervalue[12] = {135, 20, 0, 25, 0, 125, 20, 25, 0, 20, 105, 30}; std::string selectedscaler= "No Scaling", dmgbordername = "DEFAULT", gbcbordername = "DEFAULT", palname = "DEFAULT", filtername = "NONE", currgamename = "default"; @@ -441,6 +441,60 @@ int textanim_reset(){ return 0; } +void stateload_dms(int saveslot) { + gambatte_p->selectState_NoOsd(saveslot); + char overlaytext[20]; + if(gambatte_p->loadState_NoOsd()){ + can_reset = 1;//allow user to reset or save state once a savestate is loaded + sprintf(overlaytext, "State %d loaded", gambatte_p->currentState()); + printOverlay(overlaytext);//print overlay text + } else { + sprintf(overlaytext, "State %d empty", gambatte_p->currentState()); + printOverlay(overlaytext);//print overlay text + } +} + +void statesave_dms(int saveslot) { + gambatte_p->selectState_NoOsd(saveslot); + if(can_reset == 1){//boot logo already ended, can save state safely + if(gameiscgb == 0){ //set palette to greyscale + Uint32 value; + for (int i = 0; i < 3; ++i) { + for (int k = 0; k < 4; ++k) { + if(k == 0) + value = 0xF8FCF8; + if(k == 1) + value = 0xA8A8A8; + if(k == 2) + value = 0x505450; + if(k == 3) + value = 0x000000; + gambatte_p->setDmgPaletteColor(i, k, value); + } + } + } else { // disable color filter + gambatte_p->setColorFilter(0, filtervalue); + } + //run the emulator for 1 frame, so the screen buffer is updated without color palettes + std::size_t fakesamples = 35112; + Array const fakeBuf(35112 + 2064); + gambatte_p->runFor(blitter_p->inBuf().pixels, blitter_p->inBuf().pitch, fakeBuf, fakesamples); + //save state. the snapshot will now be in greyscale + gambatte_p->saveState_NoOsd(blitter_p->inBuf().pixels, blitter_p->inBuf().pitch); + if(gameiscgb == 0){ + loadPalette(palname); //restore palette + } else { + loadFilter(filtername); //restore color filter + } + + char overlaytext[14]; + sprintf(overlaytext, "State %d saved", gambatte_p->currentState()); + printOverlay(overlaytext);//print overlay text + } else if (can_reset == 0){//boot logo is still running, can't save state + printOverlay("Unable to save");//print overlay text + } +} + int menu_main(menu_t *menu) { SDL_Event event; int dirty, loop, i; @@ -2208,6 +2262,8 @@ void saveConfig(int pergame){ "DMGBORDERNAME %s\n" "GBCBORDERNAME %s\n" "PREFERCGB %d\n" + "STATEAUTOLOAD %d\n" + "STATEAUTOSAVE %d\n" "BIOSENABLED %d\n" "GHOSTING %d\n" "BUTTONLAYOUT %d\n" @@ -2220,6 +2276,8 @@ void saveConfig(int pergame){ dmgbordername.c_str(), gbcbordername.c_str(), prefercgb, + stateautoload, + stateautosave, biosenabled, ghosting, buttonlayout, @@ -2324,6 +2382,12 @@ void loadConfig(){ } else if (!strcmp(line, "PREFERCGB")) { sscanf(arg, "%d", &value); prefercgb = value; + } else if (!strcmp(line, "STATEAUTOLOAD")) { + sscanf(arg, "%d", &value); + stateautoload = value; + } else if (!strcmp(line, "STATEAUTOSAVE")) { + sscanf(arg, "%d", &value); + stateautosave = value; } else if (!strcmp(line, "BIOSENABLED")) { sscanf(arg, "%d", &value); biosenabled = value; diff --git a/gambatte_sdl/libmenu.h b/gambatte_sdl/libmenu.h index eeda32a..b890cac 100644 --- a/gambatte_sdl/libmenu.h +++ b/gambatte_sdl/libmenu.h @@ -109,7 +109,7 @@ extern SDL_Surface *menuscreen; extern SDL_Surface *surface_menuinout; extern SDL_Surface *textoverlay; extern SDL_Surface *textoverlaycolored; -extern int showfps, ghosting, biosenabled, colorfilter, gameiscgb, buttonlayout, stereosound, prefercgb, ffwhotkey; +extern int showfps, ghosting, biosenabled, colorfilter, gameiscgb, buttonlayout, stereosound, prefercgb, ffwhotkey, stateautoload, stateautosave; extern uint32_t menupalblack, menupaldark, menupallight, menupalwhite; extern int filtervalue[12]; extern std::string selectedscaler, dmgbordername, gbcbordername, palname, filtername, currgamename, homedir, ipuscaling; @@ -185,6 +185,9 @@ void apply_cfilter(SDL_Surface *surface); void printOverlay(const char *text); void clearAllCheats(); +void stateload_dms(int saveslot); +void statesave_dms(int saveslot); + #ifdef MIYOO_BATTERY_WARNING void checkBatt(); #endif diff --git a/gambatte_sdl/menu.cpp b/gambatte_sdl/menu.cpp index 623e804..16117d7 100644 --- a/gambatte_sdl/menu.cpp +++ b/gambatte_sdl/menu.cpp @@ -338,6 +338,9 @@ void main_menu() { static void callback_quit(menu_t *caller_menu) { playMenuSound_ok(); SDL_Delay(500); + if (stateautosave == 1) { + statesave_dms(0); //autosave state 0 + } printf("exiting...\n"); forcemenuexit = 0; gambatte_p->saveSavedata(); @@ -468,6 +471,9 @@ static void callback_loaddmggame(menu_t *caller_menu) { static void callback_selecteddmggame(menu_t *caller_menu) { playMenuSound_ok(); SDL_Delay(250); + if (stateautosave == 1) { + statesave_dms(0); //autosave state 0 + } gamename = gamelist[caller_menu->selected_entry]->d_name; currgamename = strip_Extension(gamename); loadConfig(); @@ -486,6 +492,9 @@ static void callback_selecteddmggame(menu_t *caller_menu) { } printOverlay("Game loaded");//print overlay text firstframe = 0; //reset the frame counter + if (stateautoload == 1) { + stateload_dms(0); //autoload state 0 + } forcemenuexit = 2; caller_menu->quit = 1; } @@ -540,6 +549,9 @@ static void callback_loadgbcgame(menu_t *caller_menu) { static void callback_selectedgbcgame(menu_t *caller_menu) { playMenuSound_ok(); SDL_Delay(250); + if (stateautosave == 1) { + statesave_dms(0); //autosave state 0 + } gamename = gamelist[caller_menu->selected_entry]->d_name; currgamename = strip_Extension(gamename); loadConfig(); @@ -558,6 +570,9 @@ static void callback_selectedgbcgame(menu_t *caller_menu) { } printOverlay("Game loaded");//print overlay text firstframe = 0; //reset the frame counter + if (stateautoload == 1) { + stateload_dms(0); //autoload state 0 + } forcemenuexit = 2; caller_menu->quit = 1; } @@ -602,18 +617,9 @@ static void callback_selectstateload(menu_t *caller_menu) { } static void callback_selectedstateload(menu_t *caller_menu) { - gambatte_p->selectState_NoOsd(caller_menu->selected_entry); playMenuSound_ok(); SDL_Delay(250); - char overlaytext[20]; - if(gambatte_p->loadState_NoOsd()){ - can_reset = 1;//allow user to reset or save state once a savestate is loaded - sprintf(overlaytext, "State %d loaded", gambatte_p->currentState()); - printOverlay(overlaytext);//print overlay text - } else { - sprintf(overlaytext, "State %d empty", gambatte_p->currentState()); - printOverlay(overlaytext);//print overlay text - } + stateload_dms(caller_menu->selected_entry); forcemenuexit = 2; caller_menu->quit = 1; } @@ -657,46 +663,9 @@ static void callback_selectstatesave(menu_t *caller_menu) { } static void callback_selectedstatesave(menu_t *caller_menu) { - gambatte_p->selectState_NoOsd(caller_menu->selected_entry); playMenuSound_ok(); SDL_Delay(250); - if(can_reset == 1){//boot logo already ended, can save state safely - if(gameiscgb == 0){ //set palette to greyscale - Uint32 value; - for (int i = 0; i < 3; ++i) { - for (int k = 0; k < 4; ++k) { - if(k == 0) - value = 0xF8FCF8; - if(k == 1) - value = 0xA8A8A8; - if(k == 2) - value = 0x505450; - if(k == 3) - value = 0x000000; - gambatte_p->setDmgPaletteColor(i, k, value); - } - } - } else { // disable color filter - gambatte_p->setColorFilter(0, filtervalue); - } - //run the emulator for 1 frame, so the screen buffer is updated without color palettes - std::size_t fakesamples = 35112; - Array const fakeBuf(35112 + 2064); - gambatte_p->runFor(blitter_p->inBuf().pixels, blitter_p->inBuf().pitch, fakeBuf, fakesamples); - //save state. the snapshot will now be in greyscale - gambatte_p->saveState_NoOsd(blitter_p->inBuf().pixels, blitter_p->inBuf().pitch); - if(gameiscgb == 0){ - loadPalette(palname); //restore palette - } else { - loadFilter(filtername); //restore color filter - } - - char overlaytext[14]; - sprintf(overlaytext, "State %d saved", gambatte_p->currentState()); - printOverlay(overlaytext);//print overlay text - } else if (can_reset == 0){//boot logo is still running, can't save state - printOverlay("Unable to save");//print overlay text - } + statesave_dms(caller_menu->selected_entry); forcemenuexit = 2; caller_menu->quit = 1; } @@ -710,6 +679,7 @@ static void callback_colorfilter(menu_t *caller_menu); static void callback_dmgborderimage(menu_t *caller_menu); static void callback_gbcborderimage(menu_t *caller_menu); static void callback_system(menu_t *caller_menu); +static void callback_savestatesettings(menu_t *caller_menu); static void callback_usebios(menu_t *caller_menu); static void callback_ghosting(menu_t *caller_menu); static void callback_controls(menu_t *caller_menu); @@ -764,10 +734,15 @@ static void callback_settings(menu_t *caller_menu) { } menu_entry = new_menu_entry(0); - menu_entry_set_text(menu_entry, "System"); + menu_entry_set_text(menu_entry, "System Priority"); menu_add_entry(menu, menu_entry); menu_entry->callback = callback_system; + menu_entry = new_menu_entry(0); + menu_entry_set_text(menu_entry, "Savestates"); + menu_add_entry(menu, menu_entry); + menu_entry->callback = callback_savestatesettings; + menu_entry = new_menu_entry(0); menu_entry_set_text(menu_entry, "Boot logos"); menu_add_entry(menu, menu_entry); @@ -1730,7 +1705,7 @@ static void callback_selectedgbcborder(menu_t *caller_menu) { } } -/* ==================== SYSTEM MENU =========================== */ +/* ==================== SYSTEM PRIORITY MENU =========================== */ static void callback_selectedsystem(menu_t *caller_menu); @@ -1742,16 +1717,16 @@ static void callback_system(menu_t *caller_menu) { menu = new_menu(); menu_set_header(menu, menu_main_title.c_str()); - menu_set_title(menu, "System"); + menu_set_title(menu, "System Priority"); menu->back_callback = callback_back; menu_entry = new_menu_entry(0); - menu_entry_set_text(menu_entry, "Priority DMG"); + menu_entry_set_text(menu_entry, "DMG"); menu_add_entry(menu, menu_entry); menu_entry->callback = callback_selectedsystem; menu_entry = new_menu_entry(0); - menu_entry_set_text(menu_entry, "Priority GBC"); + menu_entry_set_text(menu_entry, "GBC"); menu_add_entry(menu, menu_entry); menu_entry->callback = callback_selectedsystem; @@ -1769,6 +1744,120 @@ static void callback_selectedsystem(menu_t *caller_menu) { caller_menu->quit = 1; } +/* ==================== SAVESTATES (SETTINGS) MENU ================================ */ + +static void callback_autoloadstate(menu_t *caller_menu); +static void callback_autosavestate(menu_t *caller_menu); + +static void callback_savestatesettings(menu_t *caller_menu) { + menu_t *menu; + menu_entry_t *menu_entry; + (void) caller_menu; + menu = new_menu(); + + menu_set_header(menu, menu_main_title.c_str()); + menu_set_title(menu, "Savestates"); + menu->back_callback = callback_back; + + menu_entry = new_menu_entry(0); + menu_entry_set_text(menu_entry, "Autoload State 0"); + menu_add_entry(menu, menu_entry); + menu_entry->callback = callback_autoloadstate; + + menu_entry = new_menu_entry(0); + menu_entry_set_text(menu_entry, "Autosave State 0"); + menu_add_entry(menu, menu_entry); + menu_entry->callback = callback_autosavestate; + + playMenuSound_in(); + menu_main(menu); + + delete_menu(menu); + + if(forcemenuexit > 0) { + menuout = 0; + caller_menu->quit = 1; + } +} + +/* ==================== AUTO-LOAD MENU =========================== */ + +static void callback_selectedautoloadstate(menu_t *caller_menu); + +static void callback_autoloadstate(menu_t *caller_menu) { + + menu_t *menu; + menu_entry_t *menu_entry; + (void) caller_menu; + menu = new_menu(); + + menu_set_header(menu, menu_main_title.c_str()); + menu_set_title(menu, "Autoload State 0"); + menu->back_callback = callback_back; + + menu_entry = new_menu_entry(0); + menu_entry_set_text(menu_entry, "OFF"); + menu_add_entry(menu, menu_entry); + menu_entry->callback = callback_selectedautoloadstate; + + menu_entry = new_menu_entry(0); + menu_entry_set_text(menu_entry, "ON"); + menu_add_entry(menu, menu_entry); + menu_entry->callback = callback_selectedautoloadstate; + + menu->selected_entry = stateautoload; + + playMenuSound_in(); + menu_main(menu); + + delete_menu(menu); +} + +static void callback_selectedautoloadstate(menu_t *caller_menu) { + playMenuSound_ok(); + stateautoload = caller_menu->selected_entry; + caller_menu->quit = 1; +} + +/* ==================== AUTO-SAVE MENU =========================== */ + +static void callback_selectedautosavestate(menu_t *caller_menu); + +static void callback_autosavestate(menu_t *caller_menu) { + + menu_t *menu; + menu_entry_t *menu_entry; + (void) caller_menu; + menu = new_menu(); + + menu_set_header(menu, menu_main_title.c_str()); + menu_set_title(menu, "Autosave State 0"); + menu->back_callback = callback_back; + + menu_entry = new_menu_entry(0); + menu_entry_set_text(menu_entry, "OFF"); + menu_add_entry(menu, menu_entry); + menu_entry->callback = callback_selectedautosavestate; + + menu_entry = new_menu_entry(0); + menu_entry_set_text(menu_entry, "ON"); + menu_add_entry(menu, menu_entry); + menu_entry->callback = callback_selectedautosavestate; + + menu->selected_entry = stateautosave; + + playMenuSound_in(); + menu_main(menu); + + delete_menu(menu); +} + +static void callback_selectedautosavestate(menu_t *caller_menu) { + playMenuSound_ok(); + stateautosave = caller_menu->selected_entry; + caller_menu->quit = 1; +} + /* ==================== BOOT LOGOS MENU =========================== */ static void callback_selectedbios(menu_t *caller_menu); diff --git a/gambatte_sdl/src/gambatte_sdl.cpp b/gambatte_sdl/src/gambatte_sdl.cpp index 9765ab7..e999251 100644 --- a/gambatte_sdl/src/gambatte_sdl.cpp +++ b/gambatte_sdl/src/gambatte_sdl.cpp @@ -988,6 +988,10 @@ int GambatteSdl::exec(int const argc, char const *const argv[]) { loadPalette(palname); //load palette on startup } + if (stateautoload == 1) { + stateload_dms(0); //autoload state 0 + } + return run(rateOption.rate(), latencyOption.latency(), periodsOption.periods(), resamplerOption.resampler(), blitter); }