Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions workspace/all/minarch/ma_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,7 @@ struct Config config = {
.shortcuts = (ButtonMapping[]){
[SHORTCUT_SAVE_STATE] = {"Save State", -1, BTN_ID_NONE, 0},
[SHORTCUT_LOAD_STATE] = {"Load State", -1, BTN_ID_NONE, 0},
[SHORTCUT_UNDO_LOAD_STATE] = {"Undo Load State", -1, BTN_ID_NONE, 0},
[SHORTCUT_RESET_GAME] = {"Reset Game", -1, BTN_ID_NONE, 0},
[SHORTCUT_SAVE_QUIT] = {"Save & Quit", -1, BTN_ID_NONE, 0},
[SHORTCUT_CYCLE_SCALE] = {"Cycle Scaling", -1, BTN_ID_NONE, 0},
Expand Down
4 changes: 4 additions & 0 deletions workspace/all/minarch/ma_game.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "ma_internal.h"
#include "ma_game.h"
#include "ma_saves.h"

struct Game game;
struct retro_disk_control_ext_callback disk_control_ext;
Expand Down Expand Up @@ -158,6 +159,9 @@ void Game_changeDisc(char* path) {

disk_control_ext.replace_image_index(0, &game_info);
putFile(CHANGE_DISC_PATH, path); // NextUI still needs to know this to update recents.txt

// any undo snapshot belongs to the disc we just ejected
State_invalidateUndo();
}

int extract_zip(char** extensions)
Expand Down
1 change: 1 addition & 0 deletions workspace/all/minarch/ma_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ void input_poll_callback(void) {
Menu_saveState();
break;
case SHORTCUT_LOAD_STATE: Menu_loadState(); break;
case SHORTCUT_UNDO_LOAD_STATE: Menu_undoLoadState(); break;
case SHORTCUT_SCREENSHOT:
Menu_screenshot();
break;
Expand Down
1 change: 1 addition & 0 deletions workspace/all/minarch/ma_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ enum {
enum {
SHORTCUT_SAVE_STATE,
SHORTCUT_LOAD_STATE,
SHORTCUT_UNDO_LOAD_STATE,
SHORTCUT_RESET_GAME,
SHORTCUT_SAVE_QUIT,
SHORTCUT_CYCLE_SCALE,
Expand Down
41 changes: 36 additions & 5 deletions workspace/all/minarch/ma_menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,7 @@ void Menu_loadState(void) {
Menu_updateState();

if (menu.save_exists) {
int disc_changed = 0;
if (menu.total_discs) {
char slot_disc_name[256];
getFile(menu.txt_path, slot_disc_name, 256);
Expand All @@ -1699,12 +1700,22 @@ void Menu_loadState(void) {
char* disc_path = menu.disc_paths[menu.disc];
if (!exactMatch(slot_disc_path, disc_path)) {
Game_changeDisc(slot_disc_path);
disc_changed = 1;
}
}

state_slot = menu.slot;
putInt(menu.slot_path, menu.slot);
int success = State_read();
int success;
if (disc_changed) {
// the state we'd be undoing back to belongs to the disc that was just
// ejected, so there's nothing safe to offer an undo for
State_invalidateUndo();
success = State_read();
}
else {
success = State_readWithUndo();
}
Rewind_on_state_change();

// Show notification if enabled
Expand All @@ -1716,6 +1727,15 @@ void Menu_loadState(void) {
}
}
}
void Menu_undoLoadState(void) {
int success = State_undoLoad();

// Show notification if enabled (hardcore mode pushes its own message)
if (CFG_getNotifyLoad() && !RA_isHardcoreModeActive()) {
Notification_push(NOTIFICATION_LOAD_STATE,
success ? "Load State Undone" : "Nothing To Undo", NULL);
}
}

void Menu_loop(void) {

Expand Down Expand Up @@ -1838,6 +1858,13 @@ void Menu_loop(void) {
status = STATUS_CONT;
show_menu = 0;
}
else if (PAD_justPressed(BTN_X)) {
if (selected==ITEM_LOAD && State_hasUndo()) {
Menu_undoLoadState();
status = STATUS_LOAD;
show_menu = 0;
}
}
else if (PAD_justPressed(BTN_A)) {
switch(selected) {
case ITEM_CONT:
Expand Down Expand Up @@ -1933,14 +1960,19 @@ void Menu_loop(void) {

if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting);
else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?"POWER":"MENU","SLEEP", NULL }, 0, screen, 0);
GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OKAY", NULL }, 1, screen, 1);
if (selected==ITEM_LOAD && State_hasUndo()) {
GFX_blitButtonGroup((char*[]){ "X","UNDO LOAD", "B","BACK", "A","LOAD", NULL }, 1, screen, 1);
}
else {
GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OKAY", NULL }, 1, screen, 1);
}

// list
oy = (((DEVICE_HEIGHT / FIXED_SCALE) - PADDING * 2) - (MENU_ITEM_COUNT * PILL_SIZE)) / 2;
for (int i=0; i<MENU_ITEM_COUNT; i++) {
char* item = menu.items[i];
SDL_Color text_color = COLOR_WHITE;

if (i==selected) {
text_color = uintToColour(THEME_COLOR5_255);

Expand Down Expand Up @@ -1971,8 +2003,7 @@ void Menu_loop(void) {
SCALE1(PILL_SIZE)
});
}



// text
text = TTF_RenderUTF8_Blended(font.large, item, text_color);
SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){
Expand Down
1 change: 1 addition & 0 deletions workspace/all/minarch/ma_menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ int Menu_options(MenuList* list);
void Menu_screenshot(void);
void Menu_saveState(void);
void Menu_loadState(void);
void Menu_undoLoadState(void);
void OptionSaveChanges_updateDesc(void);
void OptionAchievements_updateDesc(void);
bool getAlias(char* path, char* alias);
Expand Down
94 changes: 94 additions & 0 deletions workspace/all/minarch/ma_saves.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,100 @@ int State_write(void) { // from picoarch
return success;
}

///////////////////////////////////////

// Undo load state keeps the state from immediately before the last load in memory.
// Undoing swaps that snapshot with the current state, so undoing twice returns to
// the state that was loaded.
static uint8_t* undo_state = NULL;
static size_t undo_state_size = 0;
static int undo_state_valid = 0;

static int State_captureUndo(void) {
size_t state_size = core.serialize_size();
if (!state_size) return 0;

if (state_size != undo_state_size) {
uint8_t* buf = realloc(undo_state, state_size);
if (!buf) {
LOG_error("Couldn't allocate memory for undo state\n");
return 0;
}
undo_state = buf;
undo_state_size = state_size;
}

if (!core.serialize(undo_state, undo_state_size)) {
LOG_error("Error serializing undo state\n");
return 0;
}
return 1;
}

int State_readWithUndo(void) {
int captured = State_captureUndo();
int success = State_read();
undo_state_valid = captured && success;
return success;
}

int State_hasUndo(void) {
// the core can report a different size later on (eg. after a disc change),
// which would make the snapshot unusable
return undo_state_valid && undo_state_size==core.serialize_size();
}

int State_undoLoad(void) {
// Block load states in RetroAchievements hardcore mode
if (RA_isHardcoreModeActive()) {
LOG_info("Undo load blocked - hardcore mode active\n");
Notification_push(NOTIFICATION_ACHIEVEMENT, "Load states disabled in Hardcore mode", NULL);
return 0;
}

if (!State_hasUndo()) return 0;

int was_ff = fast_forward;
fast_forward = 0;

// hold on to the current state so undoing again returns to it
uint8_t* current = malloc(undo_state_size);
if (current && !core.serialize(current, undo_state_size)) {
free(current);
current = NULL;
}

int success = core.unserialize(undo_state, undo_state_size);
if (!success) {
LOG_error("Error restoring undo state\n");
undo_state_valid = 0;
}
else if (current) {
memcpy(undo_state, current, undo_state_size);
}
else {
undo_state_valid = 0; // nothing left to undo back to
}
if (current) free(current);

fast_forward = was_ff;
if (success) Rewind_on_state_change();
return success;
}

void State_invalidateUndo(void) {
undo_state_valid = 0;
}

void State_freeUndo(void) {
if (undo_state) free(undo_state);
undo_state = NULL;
undo_state_size = 0;
undo_state_valid = 0;
}

///////////////////////////////////////

void State_autosave(void) {
int last_state_slot = state_slot;
state_slot = AUTO_RESUME_SLOT;
Expand Down
5 changes: 5 additions & 0 deletions workspace/all/minarch/ma_saves.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ void RTC_read(void);
void RTC_write(void);
void State_getPath(char* filename);
int State_read(void);
int State_readWithUndo(void);
int State_write(void);
int State_hasUndo(void);
int State_undoLoad(void);
void State_invalidateUndo(void);
void State_freeUndo(void);
void State_autosave(void);
void State_resume(void);
1 change: 1 addition & 0 deletions workspace/all/minarch/minarch.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ int main(int argc , char* argv[]) {
Notification_quit();

Game_close();
State_freeUndo();
Rewind_free();
Core_unload();
Core_quit();
Expand Down