From 59fddf11f204d6c68140aef9618a2212d29cad9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9lestin=20Matte?= Date: Thu, 8 Jan 2026 11:40:12 +0100 Subject: [PATCH 1/6] Save inputs from all savestates during save --- src/program/movie/MovieFile.cpp | 8 ++++++++ src/program/movie/MovieFileInputs.cpp | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/program/movie/MovieFile.cpp b/src/program/movie/MovieFile.cpp index 837aa3c4..7ce340c8 100644 --- a/src/program/movie/MovieFile.cpp +++ b/src/program/movie/MovieFile.cpp @@ -24,6 +24,7 @@ #include #include +#include #include // O_RDONLY, O_WRONLY, O_CREAT #include #include @@ -188,6 +189,13 @@ int MovieFile::saveMovie(const std::string& moviefile, uint64_t nb_frames) oss << "\" -C "; oss << context->config.tempmoviedir; oss << " inputs config.ini editor.ini annotations.txt"; + // Add only savestate input files that exist + for (int i = 1; i <= 10; i++) { + std::filesystem::path p = std::filesystem::path(context->config.tempmoviedir) / ("inputs" + std::to_string(i)); + if (std::filesystem::exists(p)) { + oss << " inputs" << i; + } + } /* Execute the tar command */ // std::cout << oss.str() << std::endl; diff --git a/src/program/movie/MovieFileInputs.cpp b/src/program/movie/MovieFileInputs.cpp index 970498f1..af878e24 100644 --- a/src/program/movie/MovieFileInputs.cpp +++ b/src/program/movie/MovieFileInputs.cpp @@ -25,6 +25,8 @@ #include "MovieActionInsertFrames.h" #include "MovieActionPaint.h" #include "MovieActionRemoveFrames.h" +#include "SaveStateList.h" +#include "SaveState.h" #include "utils.h" #include "Context.h" @@ -105,8 +107,19 @@ void MovieFileInputs::save() std::ofstream input_stream(input_file, std::ofstream::trunc); InputSerialization::writeInputs(input_stream, input_list); - input_stream.close(); + + // Save branches inputs + for (int i = 0; i <= 10; i++) { + SaveState& s = SaveStateList::get(i); + if (s.framecount != 0 && s.movie && s.movie->inputs) { + std::string branch_input_file = context->config.tempmoviedir + "/inputs" + std::to_string(i); + std::ofstream branch_stream(branch_input_file, std::ofstream::trunc); + InputSerialization::writeInputs(branch_stream, s.movie->inputs->input_list); + branch_stream.close(); + } + } + } uint64_t MovieFileInputs::nbFrames() From fcbe1d95b3848c62cb003940aa6b068590d80b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9lestin=20Matte?= Date: Thu, 8 Jan 2026 20:35:04 +0100 Subject: [PATCH 2/6] Load inputs from savestate input file instead of temp folder --- src/program/GameEvents.cpp | 22 +++++++++++++++------- src/program/SaveState.cpp | 26 +++++++++----------------- src/program/movie/MovieFile.cpp | 18 ++++++++++++++++++ src/program/movie/MovieFileInputs.cpp | 5 ++++- src/program/movie/MovieFileInputs.h | 2 +- 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/program/GameEvents.cpp b/src/program/GameEvents.cpp index 407b126d..75bbb142 100644 --- a/src/program/GameEvents.cpp +++ b/src/program/GameEvents.cpp @@ -211,6 +211,7 @@ bool GameEvents::processEvent(GameEvents::EventType type, const HotKey &hk) * Prompting a alert window must be done by the UI thread, so we are * using std::future/std::promise mechanism. */ + int frame_to_seek; std::promise answer; std::future future = answer.get_future(); emit askToShow(QString("There is a savestate in that slot from a previous game iteration. Do you want to load the associated movie?"), &answer); @@ -220,17 +221,24 @@ bool GameEvents::processEvent(GameEvents::EventType type, const HotKey &hk) return false; } - /* Loading the movie */ - movie->loadSavestateMovie(SaveStateList::get(statei).getMoviePath()); + SaveState& s = SaveStateList::get(statei); + movie->inputs = s.movie->inputs; - /* Return if we already are on the correct frame */ - if (context->framecount == movie->header->savestate_framecount) - return false; + /* seek to either the current frame, or the max frame + * of the saved inputs */ + if (s.movie->inputs->nbFrames() < context->framecount) { + frame_to_seek = context->framecount; + } else { + frame_to_seek = s.movie->inputs->nbFrames(); + } + + /* Find where movies diverge, load state and seek back there */ + // todo /* Fast-forward to savestate frame */ context->config.sc.recording = SharedConfig::RECORDING_READ; - context->config.sc.movie_framecount = movie->inputs->nbFrames(); - context->seek_frame = movie->header->savestate_framecount; + context->config.sc.movie_framecount = s.movie->inputs->nbFrames(); + context->seek_frame = frame_to_seek; context->config.sc.running = true; context->config.sc_modified = true; diff --git a/src/program/SaveState.cpp b/src/program/SaveState.cpp index 019d23da..71f64745 100644 --- a/src/program/SaveState.cpp +++ b/src/program/SaveState.cpp @@ -120,26 +120,18 @@ int SaveState::load(Context* context, const MovieFile& m, bool branch, bool inpu * forked savestate of previous execution). */ if ((access(pagemap_path.c_str(), F_OK) != 0) || (access(pages_path.c_str(), F_OK) != 0) || (framecount == 0)) { - /* If there is no savestate but a movie file, offer to load - * the movie and fast-forward to the savestate movie frame. + /* If there is no savestate but inputs are saved in the save + * file, offer to load the movie and fast-forward to the + * savestate movie frame. */ - if ((context->config.sc.recording != SharedConfig::NO_RECORDING) && - (access(movie_path.c_str(), F_OK) == 0)) { - - /* Load the savestate movie from disk */ - MovieFile savedmovie(context); - int ret = savedmovie.loadSavestateMovie(movie_path); - - /* Checking if our movie is a prefix of the savestate movie */ - if ((ret == 0) && savedmovie.inputs->isEqual(m.inputs, 0, context->framecount)) { - return ENOSTATEMOVIEPREFIX; - } + if (movie->inputs->nbFrames() > 0) { + return ENOSTATEMOVIEPREFIX; + } else { + sendMessage(MSGN_OSD_MSG); + sendString(no_state_msg); + return ENOSTATE; } - - sendMessage(MSGN_OSD_MSG); - sendString(no_state_msg); - return ENOSTATE; } /* Send the savestate index */ diff --git a/src/program/movie/MovieFile.cpp b/src/program/movie/MovieFile.cpp index 7ce340c8..b31281e9 100644 --- a/src/program/movie/MovieFile.cpp +++ b/src/program/movie/MovieFile.cpp @@ -21,10 +21,14 @@ #include "../shared/inputs/AllInputs.h" #include "Context.h" +#include "SaveStateList.h" +#include "SaveState.h" #include #include #include +#include +#include #include // O_RDONLY, O_WRONLY, O_CREAT #include #include @@ -144,6 +148,20 @@ int MovieFile::loadMovie(const std::string& moviefile) context->config.sc.movie_framecount = inputs->nbFrames(); } + /* Load savestate inputs */ + for (int i = 1; i <= 10; i++) { + std::filesystem::path p = std::filesystem::path(context->config.tempmoviedir) / ("inputs" + std::to_string(i)); + + if (std::filesystem::exists(p)) { + SaveState& s = SaveStateList::get(i); + if (!s.movie) + s.movie = std::make_unique(context); + + s.movie->inputs->load(i); + s.framecount = s.movie->inputs->nbFrames(); + } + } + return 0; } diff --git a/src/program/movie/MovieFileInputs.cpp b/src/program/movie/MovieFileInputs.cpp index af878e24..fb25cc8c 100644 --- a/src/program/movie/MovieFileInputs.cpp +++ b/src/program/movie/MovieFileInputs.cpp @@ -76,7 +76,7 @@ void MovieFileInputs::clear() emit inputsReset(); } -void MovieFileInputs::load() +void MovieFileInputs::load(int savestate) { emit inputsToBeReset(); @@ -89,6 +89,9 @@ void MovieFileInputs::load() /* Open the input file and parse each line to fill our input list */ std::string input_file = context->config.tempmoviedir + "/inputs"; + if (savestate > 0) { + input_file += std::to_string(savestate); + } std::ifstream input_stream(input_file); InputSerialization::readInputs(input_stream, input_list); diff --git a/src/program/movie/MovieFileInputs.h b/src/program/movie/MovieFileInputs.h index 48b94699..7f84a176 100644 --- a/src/program/movie/MovieFileInputs.h +++ b/src/program/movie/MovieFileInputs.h @@ -73,7 +73,7 @@ class MovieFileInputs : public QObject { /* Import the inputs into a list, and all the parameters. * Returns 0 if no error, or a negative value if an error occured */ - void load(); + void load(int savestate = 0); /* Write the inputs into a file and compress to the whole moviefile */ void save(); From 64075a5ac742e656a055a8173767746834d82334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9lestin=20Matte?= Date: Thu, 8 Jan 2026 20:51:21 +0100 Subject: [PATCH 3/6] Reproduce earlier behaviour of only allowing to load saved inputs state if it's a prefix of the current movie --- src/program/SaveState.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/program/SaveState.cpp b/src/program/SaveState.cpp index 71f64745..b7fae853 100644 --- a/src/program/SaveState.cpp +++ b/src/program/SaveState.cpp @@ -125,7 +125,9 @@ int SaveState::load(Context* context, const MovieFile& m, bool branch, bool inpu * savestate movie frame. */ - if (movie->inputs->nbFrames() > 0) { + /* Checking if our movie is a prefix of the savestate movie + * and if savestatefile input exists */ + if (movie->inputs->nbFrames() > 0 && movie->inputs->isEqual(m.inputs, 0, context->framecount)) { return ENOSTATEMOVIEPREFIX; } else { sendMessage(MSGN_OSD_MSG); From ee0340a1702f38682d9766bca1b9ebe3edd05863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9lestin=20Matte?= Date: Thu, 8 Jan 2026 20:58:58 +0100 Subject: [PATCH 4/6] Add a clearer error message when not able to load savestate inputs from save file --- src/program/GameEvents.cpp | 6 ++++++ src/program/SaveState.cpp | 4 ++++ src/program/SaveState.h | 1 + 3 files changed, 11 insertions(+) diff --git a/src/program/GameEvents.cpp b/src/program/GameEvents.cpp index 75bbb142..766cd980 100644 --- a/src/program/GameEvents.cpp +++ b/src/program/GameEvents.cpp @@ -247,6 +247,12 @@ bool GameEvents::processEvent(GameEvents::EventType type, const HotKey &hk) return false; } + if (error == SaveState::ESAVESTATEINPUTMISMATCH) { + if (!(context->config.sc.osd)) + emit alertToShow(QString("Cannot load inputs from this savestate as earlier inputs mismatch. Please seek to an earlier state for this.")); + return false; + } + if (error == SaveState::ENOSTATE) { if (!(context->config.sc.osd)) emit alertToShow(QString("There is no savestate to load in this slot")); diff --git a/src/program/SaveState.cpp b/src/program/SaveState.cpp index b7fae853..41802117 100644 --- a/src/program/SaveState.cpp +++ b/src/program/SaveState.cpp @@ -129,6 +129,10 @@ int SaveState::load(Context* context, const MovieFile& m, bool branch, bool inpu * and if savestatefile input exists */ if (movie->inputs->nbFrames() > 0 && movie->inputs->isEqual(m.inputs, 0, context->framecount)) { return ENOSTATEMOVIEPREFIX; + } else if (movie->inputs->nbFrames() > 0) { + sendMessage(MSGN_OSD_MSG); + sendString(std::string("Cannot load inputs from this savestate as earlier inputs mismatch")); + return ESAVESTATEINPUTMISMATCH; } else { sendMessage(MSGN_OSD_MSG); sendString(no_state_msg); diff --git a/src/program/SaveState.h b/src/program/SaveState.h index 38d2a2d3..f4dc2dc4 100644 --- a/src/program/SaveState.h +++ b/src/program/SaveState.h @@ -41,6 +41,7 @@ class SaveState { ENOMOVIE = -3, // Could not moad movie EINPUTMISMATCH = -4, // Mistmatch inputs ENOLOAD = -5, // State loading failed + ESAVESTATEINPUTMISMATCH = -6, // Mismatch inputs with savestate file }; /* Savestate number */ From 0813fd8d93bc6a51e44dd3546eceaf4bb53d892d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9lestin=20Matte?= Date: Sat, 17 Jan 2026 22:42:36 +0100 Subject: [PATCH 5/6] Create an actual savestate after loading one from input files --- src/program/GameEvents.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/program/GameEvents.cpp b/src/program/GameEvents.cpp index 766cd980..45a6a26a 100644 --- a/src/program/GameEvents.cpp +++ b/src/program/GameEvents.cpp @@ -223,6 +223,7 @@ bool GameEvents::processEvent(GameEvents::EventType type, const HotKey &hk) SaveState& s = SaveStateList::get(statei); movie->inputs = s.movie->inputs; + s.save(context, *movie); /* seek to either the current frame, or the max frame * of the saved inputs */ From df6980959f5d388a4fd5e42c39e415d4e6194f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9lestin=20Matte?= Date: Sun, 18 Jan 2026 22:15:14 +0100 Subject: [PATCH 6/6] Properly clean all input files when extracting movie --- src/program/movie/MovieFile.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/program/movie/MovieFile.cpp b/src/program/movie/MovieFile.cpp index b31281e9..6dac9d6e 100644 --- a/src/program/movie/MovieFile.cpp +++ b/src/program/movie/MovieFile.cpp @@ -89,6 +89,10 @@ int MovieFile::extractMovie(const std::string& moviefile) unlink(configfile.c_str()); unlink(editorfile.c_str()); unlink(inputfile.c_str()); + for (int i = 1; i <= 10; i++) { + std::filesystem::path p = std::filesystem::path(context->config.tempmoviedir) / ("inputs" + std::to_string(i)); + unlink(p.c_str()); + } unlink(annotationsfile.c_str()); /* Build the tar command */