Feature: Added loading savestates from command line (fixes #1125)
@@ -69,6 +69,7 @@ - GB: Fix VRAM/palette locking (fixes mgba.io/i/1109)
- GB Video: Darken colors in GBA mode - FFmpeg: Support libswresample (fixes mgba.io/i/1120, mgba.io/b/123) - FFmpeg: Support lossless h.264 encoding + - Feature: Added loading savestates from command line 0.6.3: (2017-04-14) Bugfixes:
@@ -18,7 +18,7 @@ struct mArguments {
char* fname; char* patch; char* cheatsFile; - char* movie; + char* savestate; char* bios; int logLevel; int frameskip;
@@ -39,7 +39,7 @@ { "gdb", no_argument, 0, 'g' },
#endif { "help", no_argument, 0, 'h' }, { "log-level", required_argument, 0, 'l' }, - { "movie", required_argument, 0, 'v' }, + { "savestate", required_argument, 0, 't' }, { "patch", required_argument, 0, 'p' }, { "version", no_argument, 0, '\0' }, { 0, 0, 0, 0 }@@ -68,7 +68,7 @@
bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) { int ch; char options[64] = - "b:c:C:hl:p:s:v:" + "b:c:C:hl:p:s:t:" #ifdef USE_EDITLINE "d" #endif@@ -132,8 +132,8 @@ break;
case 's': args->frameskip = atoi(optarg); break; - case 'v': - args->movie = strdup(optarg); + case 't': + args->savestate = strdup(optarg); break; default: if (subparser) {@@ -179,8 +179,8 @@
free(args->patch); args->patch = 0; - free(args->movie); - args->movie = 0; + free(args->savestate); + args->savestate = 0; free(args->cheatsFile); args->cheatsFile = 0;@@ -244,7 +244,7 @@ #ifdef USE_GDB_STUB
puts(" -g, --gdb Start GDB session (default port 2345)"); #endif puts(" -l, --log-level N Log level mask"); - puts(" -v, --movie FILE Play back a movie of recorded input"); + puts(" -t, --savestate FILE Load savestate when starting"); puts(" -p, --patch FILE Apply a specified patch file when running"); puts(" -s, --frameskip N Skip every N frames"); puts(" --version Print version and exit");
@@ -465,6 +465,26 @@ }
}); } +void CoreController::loadState(const QString& path) { + m_statePath = path; + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY); + if (!vf) { + return; + } + if (!controller->m_backupLoadState.isOpen()) { + controller->m_backupLoadState = VFileMemChunk(nullptr, 0); + } + mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); + if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) { + emit controller->frameAvailable(); + emit controller->stateLoaded(); + } + vf->close(vf); + }); +} + void CoreController::saveState(int slot) { if (slot > 0) { m_stateSlot = slot;@@ -478,6 +498,25 @@ vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
vf->close(vf); } mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags); + }); +} + +void CoreController::saveState(const QString& path) { + m_statePath = path; + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY); + if (vf) { + controller->m_backupSaveState.resize(vf->size(vf)); + vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); + vf->close(vf); + } + vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } + mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags); + vf->close(vf); }); }
@@ -103,7 +103,9 @@ void setFastForward(bool);
void forceFastForward(bool); void loadState(int slot = 0); + void loadState(const QString& path); void saveState(int slot = 0); + void saveState(const QString& path); void loadBackupState(); void saveBackupState();@@ -189,6 +191,7 @@
VFileDevice m_backupLoadState; QByteArray m_backupSaveState{nullptr}; int m_stateSlot = 1; + QString m_statePath; int m_loadStateFlags; int m_saveStateFlags;
@@ -170,6 +170,14 @@
void Window::argumentsPassed(mArguments* args) { loadConfig(); + if (args->patch) { + m_pendingPatch = args->patch; + } + + if (args->savestate) { + m_pendingState = args->savestate; + } + if (args->fname) { setController(m_manager->loadGame(args->fname), args->fname); }@@ -1911,6 +1919,11 @@ }
m_controller->loadConfig(m_config); m_controller->start(); + + if (!m_pendingState.isEmpty()) { + m_controller->loadState(m_pendingState); + m_pendingState = QString(); + } } WindowBackground::WindowBackground(QWidget* parent)
@@ -206,6 +206,7 @@ QTimer m_focusCheck;
bool m_autoresume = false; bool m_wasOpened = false; QString m_pendingPatch; + QString m_pendingState; bool m_hitUnimplementedBiosCall;
@@ -25,6 +25,7 @@ #include <mgba/core/cheats.h>
#include <mgba/core/core.h> #include <mgba/core/config.h> #include <mgba/core/input.h> +#include <mgba/core/serialize.h> #include <mgba/core/thread.h> #include <mgba/internal/gba/input.h>@@ -42,6 +43,12 @@ static bool mSDLInit(struct mSDLRenderer* renderer);
static void mSDLDeinit(struct mSDLRenderer* renderer); static int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args); + +static struct VFile* _state = NULL; + +static void _loadState(struct mCoreThread* thread) { + mCoreLoadStateNamed(thread->core, _state, SAVESTATE_RTC); +} int main(int argc, char** argv) { struct mSDLRenderer renderer = {0};@@ -232,6 +239,15 @@ mSDLSetScreensaverSuspendable(&renderer->events, renderer->core->opts.suspendScreensaver);
mSDLSuspendScreensaver(&renderer->events); #endif if (mSDLInitAudio(&renderer->audio, &thread)) { + if (args->savestate) { + struct VFile* state = VFileOpen(args->savestate, O_RDONLY); + if (state) { + _state = state; + mCoreThreadRunFunction(&thread, _loadState); + _state = NULL; + state->close(state); + } + } renderer->runloop(renderer, &thread); mSDLPauseAudio(&renderer->audio); if (mCoreThreadHasCrashed(&thread)) {
@@ -26,14 +26,12 @@ "\nAdditional options:\n" \
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \ " -N Disable video rendering entirely\n" \ " -O OFFSET Offset to apply savestate overlay\n" \ - " -S FILE Load a savestate when starting the test\n" \ " -V FILE Overlay a second savestate over the loaded savestate\n" \ struct FuzzOpts { bool noVideo; int frames; size_t overlayOffset; - char* savestate; char* ssOverlay; };@@ -108,9 +106,8 @@ struct VFile* savestate = 0;
struct VFile* savestateOverlay = 0; size_t overlayOffset; - if (fuzzOpts.savestate) { - savestate = VFileOpen(fuzzOpts.savestate, O_RDONLY); - free(fuzzOpts.savestate); + if (args.savestate) { + savestate = VFileOpen(args.savestate, O_RDONLY); } if (fuzzOpts.ssOverlay) { overlayOffset = fuzzOpts.overlayOffset;@@ -200,9 +197,6 @@ return true;
case 'O': opts->overlayOffset = strtoul(arg, 0, 10); return !errno; - case 'S': - opts->savestate = strdup(arg); - return true; case 'V': opts->ssOverlay = strdup(arg); return true;