Feature: Added loading savestates from command line (fixes #1125)

This commit is contained in:
Vicki Pfau 2018-07-14 14:18:16 -07:00
parent 182efc916e
commit f8fb86ef79
9 changed files with 83 additions and 16 deletions

View File

@ -69,6 +69,7 @@ Misc:
- GB Video: Darken colors in GBA mode - GB Video: Darken colors in GBA mode
- FFmpeg: Support libswresample (fixes mgba.io/i/1120, mgba.io/b/123) - FFmpeg: Support libswresample (fixes mgba.io/i/1120, mgba.io/b/123)
- FFmpeg: Support lossless h.264 encoding - FFmpeg: Support lossless h.264 encoding
- Feature: Added loading savestates from command line
0.6.3: (2017-04-14) 0.6.3: (2017-04-14)
Bugfixes: Bugfixes:

View File

@ -18,7 +18,7 @@ struct mArguments {
char* fname; char* fname;
char* patch; char* patch;
char* cheatsFile; char* cheatsFile;
char* movie; char* savestate;
char* bios; char* bios;
int logLevel; int logLevel;
int frameskip; int frameskip;

View File

@ -39,7 +39,7 @@ static const struct option _options[] = {
#endif #endif
{ "help", no_argument, 0, 'h' }, { "help", no_argument, 0, 'h' },
{ "log-level", required_argument, 0, 'l' }, { "log-level", required_argument, 0, 'l' },
{ "movie", required_argument, 0, 'v' }, { "savestate", required_argument, 0, 't' },
{ "patch", required_argument, 0, 'p' }, { "patch", required_argument, 0, 'p' },
{ "version", no_argument, 0, '\0' }, { "version", no_argument, 0, '\0' },
{ 0, 0, 0, 0 } { 0, 0, 0, 0 }
@ -68,7 +68,7 @@ static void _tableApply(const char* key, void* value, void* user) {
bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) { bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) {
int ch; int ch;
char options[64] = char options[64] =
"b:c:C:hl:p:s:v:" "b:c:C:hl:p:s:t:"
#ifdef USE_EDITLINE #ifdef USE_EDITLINE
"d" "d"
#endif #endif
@ -132,8 +132,8 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct
case 's': case 's':
args->frameskip = atoi(optarg); args->frameskip = atoi(optarg);
break; break;
case 'v': case 't':
args->movie = strdup(optarg); args->savestate = strdup(optarg);
break; break;
default: default:
if (subparser) { if (subparser) {
@ -179,8 +179,8 @@ void freeArguments(struct mArguments* args) {
free(args->patch); free(args->patch);
args->patch = 0; args->patch = 0;
free(args->movie); free(args->savestate);
args->movie = 0; args->savestate = 0;
free(args->cheatsFile); free(args->cheatsFile);
args->cheatsFile = 0; args->cheatsFile = 0;
@ -244,7 +244,7 @@ void usage(const char* arg0, const char* extraOptions) {
puts(" -g, --gdb Start GDB session (default port 2345)"); puts(" -g, --gdb Start GDB session (default port 2345)");
#endif #endif
puts(" -l, --log-level N Log level mask"); 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(" -p, --patch FILE Apply a specified patch file when running");
puts(" -s, --frameskip N Skip every N frames"); puts(" -s, --frameskip N Skip every N frames");
puts(" --version Print version and exit"); puts(" --version Print version and exit");

View File

@ -465,6 +465,26 @@ void CoreController::loadState(int slot) {
}); });
} }
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) { void CoreController::saveState(int slot) {
if (slot > 0) { if (slot > 0) {
m_stateSlot = slot; m_stateSlot = slot;
@ -481,6 +501,25 @@ void CoreController::saveState(int slot) {
}); });
} }
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);
});
}
void CoreController::loadBackupState() { void CoreController::loadBackupState() {
if (!m_backupLoadState.isOpen()) { if (!m_backupLoadState.isOpen()) {
return; return;

View File

@ -103,7 +103,9 @@ public slots:
void forceFastForward(bool); void forceFastForward(bool);
void loadState(int slot = 0); void loadState(int slot = 0);
void loadState(const QString& path);
void saveState(int slot = 0); void saveState(int slot = 0);
void saveState(const QString& path);
void loadBackupState(); void loadBackupState();
void saveBackupState(); void saveBackupState();
@ -189,6 +191,7 @@ private:
VFileDevice m_backupLoadState; VFileDevice m_backupLoadState;
QByteArray m_backupSaveState{nullptr}; QByteArray m_backupSaveState{nullptr};
int m_stateSlot = 1; int m_stateSlot = 1;
QString m_statePath;
int m_loadStateFlags; int m_loadStateFlags;
int m_saveStateFlags; int m_saveStateFlags;

View File

@ -170,6 +170,14 @@ Window::~Window() {
void Window::argumentsPassed(mArguments* args) { void Window::argumentsPassed(mArguments* args) {
loadConfig(); loadConfig();
if (args->patch) {
m_pendingPatch = args->patch;
}
if (args->savestate) {
m_pendingState = args->savestate;
}
if (args->fname) { if (args->fname) {
setController(m_manager->loadGame(args->fname), args->fname); setController(m_manager->loadGame(args->fname), args->fname);
} }
@ -1911,6 +1919,11 @@ void Window::setController(CoreController* controller, const QString& fname) {
m_controller->loadConfig(m_config); m_controller->loadConfig(m_config);
m_controller->start(); m_controller->start();
if (!m_pendingState.isEmpty()) {
m_controller->loadState(m_pendingState);
m_pendingState = QString();
}
} }
WindowBackground::WindowBackground(QWidget* parent) WindowBackground::WindowBackground(QWidget* parent)

View File

@ -206,6 +206,7 @@ private:
bool m_autoresume = false; bool m_autoresume = false;
bool m_wasOpened = false; bool m_wasOpened = false;
QString m_pendingPatch; QString m_pendingPatch;
QString m_pendingState;
bool m_hitUnimplementedBiosCall; bool m_hitUnimplementedBiosCall;

View File

@ -25,6 +25,7 @@
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include <mgba/core/config.h> #include <mgba/core/config.h>
#include <mgba/core/input.h> #include <mgba/core/input.h>
#include <mgba/core/serialize.h>
#include <mgba/core/thread.h> #include <mgba/core/thread.h>
#include <mgba/internal/gba/input.h> #include <mgba/internal/gba/input.h>
@ -43,6 +44,12 @@ static void mSDLDeinit(struct mSDLRenderer* renderer);
static int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args); 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) { int main(int argc, char** argv) {
struct mSDLRenderer renderer = {0}; struct mSDLRenderer renderer = {0};
@ -232,6 +239,15 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
mSDLSuspendScreensaver(&renderer->events); mSDLSuspendScreensaver(&renderer->events);
#endif #endif
if (mSDLInitAudio(&renderer->audio, &thread)) { 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); renderer->runloop(renderer, &thread);
mSDLPauseAudio(&renderer->audio); mSDLPauseAudio(&renderer->audio);
if (mCoreThreadHasCrashed(&thread)) { if (mCoreThreadHasCrashed(&thread)) {

View File

@ -26,14 +26,12 @@
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \ " -F FRAMES Run for the specified number of FRAMES before exiting\n" \
" -N Disable video rendering entirely\n" \ " -N Disable video rendering entirely\n" \
" -O OFFSET Offset to apply savestate overlay\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" \ " -V FILE Overlay a second savestate over the loaded savestate\n" \
struct FuzzOpts { struct FuzzOpts {
bool noVideo; bool noVideo;
int frames; int frames;
size_t overlayOffset; size_t overlayOffset;
char* savestate;
char* ssOverlay; char* ssOverlay;
}; };
@ -108,9 +106,8 @@ int main(int argc, char** argv) {
struct VFile* savestateOverlay = 0; struct VFile* savestateOverlay = 0;
size_t overlayOffset; size_t overlayOffset;
if (fuzzOpts.savestate) { if (args.savestate) {
savestate = VFileOpen(fuzzOpts.savestate, O_RDONLY); savestate = VFileOpen(args.savestate, O_RDONLY);
free(fuzzOpts.savestate);
} }
if (fuzzOpts.ssOverlay) { if (fuzzOpts.ssOverlay) {
overlayOffset = fuzzOpts.overlayOffset; overlayOffset = fuzzOpts.overlayOffset;
@ -200,9 +197,6 @@ static bool _parseFuzzOpts(struct mSubParser* parser, int option, const char* ar
case 'O': case 'O':
opts->overlayOffset = strtoul(arg, 0, 10); opts->overlayOffset = strtoul(arg, 0, 10);
return !errno; return !errno;
case 'S':
opts->savestate = strdup(arg);
return true;
case 'V': case 'V':
opts->ssOverlay = strdup(arg); opts->ssOverlay = strdup(arg);
return true; return true;