diff --git a/CHANGES b/CHANGES index 0cedffb76..7a6a551ce 100644 --- a/CHANGES +++ b/CHANGES @@ -61,6 +61,7 @@ Bugfixes: - GBA BIOS: Fix BitUnPack final byte - GB I/O: DMA register is R/W - GB Video: Fix SCX timing + - GBA Video: Improve sprite cycle counting (fixes mgba.io/i/1126) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) @@ -88,6 +89,9 @@ Misc: - 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 + - Qt: Allow pausing game at load (fixes mgba.io/i/1129) 0.6.3: (2017-04-14) Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index d067ec24c..7bd1a0da3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -521,17 +521,17 @@ if(USE_FFMPEG) list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c") string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION}) - string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION}) - string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION}) string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION}) - math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1") list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}") if(USE_LIBSWRESAMPLE) + string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION}) + math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_DEBIAN}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_DEBIAN}") else() + string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavresample${LIBAVRESAMPLE_VERSION_MAJOR}|libavresample-ffmpeg${LIBAVRESAMPLE_VERSION_MAJOR}") endif() set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavutil${LIBAVUTIL_VERSION_MAJOR}|libavutil-ffmpeg${LIBAVUTIL_VERSION_MAJOR}") diff --git a/include/mgba/feature/commandline.h b/include/mgba/feature/commandline.h index 854afdb77..c543652e9 100644 --- a/include/mgba/feature/commandline.h +++ b/include/mgba/feature/commandline.h @@ -18,7 +18,7 @@ struct mArguments { char* fname; char* patch; char* cheatsFile; - char* movie; + char* savestate; char* bios; int logLevel; int frameskip; diff --git a/src/core/flags.h.in b/src/core/flags.h.in index 8aa37ccf5..ddd3cd949 100644 --- a/src/core/flags.h.in +++ b/src/core/flags.h.in @@ -83,6 +83,14 @@ #cmakedefine USE_LIBAV #endif +#ifndef USE_LIBAVRESAMPLE +#cmakedefine USE_LIBAVRESAMPLE +#endif + +#ifndef USE_LIBSWRESAMPLE +#cmakedefine USE_LIBSWRESAMPLE +#endif + #ifndef USE_LIBZIP #cmakedefine USE_LIBZIP #endif diff --git a/src/core/thread.c b/src/core/thread.c index 69183d072..20cbd83cc 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -201,16 +201,19 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { while (impl->state > THREAD_MAX_RUNNING && impl->state < THREAD_EXITING) { deferred = impl->state; - if (impl->state == THREAD_INTERRUPTING) { + switch (deferred) { + case THREAD_INTERRUPTING: impl->state = THREAD_INTERRUPTED; ConditionWake(&impl->stateCond); - } - - if (impl->state == THREAD_PAUSING) { + break; + case THREAD_PAUSING: impl->state = THREAD_PAUSED; - } - if (impl->state == THREAD_RESETING) { + break; + case THREAD_RESETING: impl->state = THREAD_RUNNING; + break; + default: + break; } if (deferred >= THREAD_MIN_DEFERRED && deferred <= THREAD_MAX_DEFERRED) { @@ -218,6 +221,9 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { } deferred = impl->state; + if (deferred == THREAD_INTERRUPTED) { + deferred = impl->savedState; + } while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) { ConditionWait(&impl->stateCond, &impl->stateMutex); diff --git a/src/feature/commandline.c b/src/feature/commandline.c index ccb44f105..2d3f3e357 100644 --- a/src/feature/commandline.c +++ b/src/feature/commandline.c @@ -39,7 +39,7 @@ static const struct option _options[] = { #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 @@ static void _tableApply(const char* key, void* value, void* user) { 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 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct 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 @@ void freeArguments(struct mArguments* args) { 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 @@ void usage(const char* arg0, const char* extraOptions) { 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"); diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 1ec9d6b8e..64f96fa00 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -318,6 +318,15 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER; #endif } + + if (encoder->video->codec->id == AV_CODEC_ID_H264 && + (strcasecmp(encoder->containerFormat, "mp4") || + strcasecmp(encoder->containerFormat, "m4v") || + strcasecmp(encoder->containerFormat, "mov"))) { + // QuickTime and a few other things require YUV420 + encoder->video->pix_fmt = AV_PIX_FMT_YUV420P; + } + if (strcmp(vcodec->name, "libx264") == 0) { // Try to adaptively figure out when you can use a slower encoder if (encoder->width * encoder->height > 1000000) { @@ -327,16 +336,12 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { } else { av_opt_set(encoder->video->priv_data, "preset", "faster", 0); } - av_opt_set(encoder->video->priv_data, "tune", "zerolatency", 0); + if (encoder->videoBitrate == 0) { + av_opt_set(encoder->video->priv_data, "crf", "0", 0); + encoder->video->pix_fmt = AV_PIX_FMT_YUV444P; + } } - if (encoder->video->codec->id == AV_CODEC_ID_H264 && - (strcasecmp(encoder->containerFormat, "mp4") || - strcasecmp(encoder->containerFormat, "m4v") || - strcasecmp(encoder->containerFormat, "mov"))) { - // QuickTime and a few other things require YUV420 - encoder->video->pix_fmt = AV_PIX_FMT_YUV420P; - } avcodec_open2(encoder->video, vcodec, 0); #if LIBAVCODEC_VERSION_MAJOR >= 55 encoder->videoFrame = av_frame_alloc(); diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 703ec4e3f..fee944c03 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -446,6 +446,7 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { address &= 0x1FF; memory->sramBank[(address >> 1)] &= 0xF0 >> shift; memory->sramBank[(address >> 1)] |= (value & 0xF) << shift; + break; default: // TODO mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value); @@ -640,6 +641,7 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { break; case 0x5: _GBMBC7Write(&gb->memory, address, value); + break; default: // TODO mLOG(GB_MBC, STUB, "MBC7 unknown address: %04X:%02X", address, value); diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 9a7c95bc7..538f48c68 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -294,7 +294,6 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re if (GBAObjAttributesAIsTransformed(sprite->a)) { int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a); - renderer->spriteCyclesRemaining -= 10; struct GBAOAMMatrix mat; LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b); @@ -354,6 +353,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re if (outX < start || outX >= condition) { return 0; } + renderer->spriteCyclesRemaining -= 10; if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) { int alpha = GBAObjAttributesCGetPalette(sprite->c); @@ -393,7 +393,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re SPRITE_TRANSFORMED_LOOP(256, NORMAL); } } - if (x + totalWidth > renderer->masterEnd) { + if (end == renderer->masterEnd && x + totalWidth > renderer->masterEnd) { renderer->spriteCyclesRemaining -= (x + totalWidth - renderer->masterEnd) * 2; } } else { @@ -485,7 +485,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } } - if (x + width > renderer->masterEnd) { + if (end = renderer->masterEnd && x + width > renderer->masterEnd) { renderer->spriteCyclesRemaining -= x + width - renderer->masterEnd; } } diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 8ea6de88d..ca2151450 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -470,6 +470,26 @@ void CoreController::loadState(int slot) { }); } +void CoreController::loadState(const QString& path) { + m_statePath = path; + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast(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; @@ -486,6 +506,25 @@ void CoreController::saveState(int slot) { }); } +void CoreController::saveState(const QString& path) { + m_statePath = path; + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast(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() { if (!m_backupLoadState.isOpen()) { return; @@ -559,7 +598,7 @@ void CoreController::replaceGame(const QString& path) { QString fname = info.canonicalFilePath(); Interrupter interrupter(this); mDirectorySetDetachBase(&m_threadContext.core->dirs); - mCoreLoadFile(m_threadContext.core, fname.toLocal8Bit().constData()); + mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData()); } void CoreController::yankPak() { diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index fa3a1f771..898eba66e 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -104,7 +104,9 @@ public slots: 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(); @@ -185,6 +187,7 @@ private: VFileDevice m_backupLoadState; QByteArray m_backupSaveState{nullptr}; int m_stateSlot = 1; + QString m_statePath; int m_loadStateFlags; int m_saveStateFlags; diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index e92fc5604..ea842ba67 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -99,7 +99,7 @@ VideoView::VideoView(QWidget* parent) setPreset({ .container = "MKV", - .vcodec = "PNG", + .vcodec = "h.264", .acodec = "FLAC", .vbr = 0, .abr = 0, @@ -179,7 +179,7 @@ void VideoView::updatePresets() { if (m_nativeWidth && m_nativeHeight) { addPreset(m_ui.presetLossless, { .container = "MKV", - .vcodec = "PNG", + .vcodec = "h.264", .acodec = "FLAC", .vbr = 0, .abr = 0, diff --git a/src/platform/qt/VideoView.ui b/src/platform/qt/VideoView.ui index 4bdcefe75..eb42fd4f5 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -254,11 +254,6 @@ true - - - PNG - - h.264 @@ -353,9 +348,6 @@ - - 200 - 10000 @@ -523,7 +515,7 @@ - + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index d6c56db76..cfa0a1498 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -200,6 +200,14 @@ Window::~Window() { 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); } @@ -1252,12 +1260,15 @@ void Window::setupMenu(QMenuBar* menubar) { pause->setCheckable(true); pause->setShortcut(tr("Ctrl+P")); connect(pause, &QAction::triggered, [this](bool paused) { - m_controller->setPaused(paused); + if (m_controller) { + m_controller->setPaused(paused); + } else { + m_pendingPause = paused; + } }); connect(this, &Window::paused, [pause](bool paused) { pause->setChecked(paused); }); - m_gameActions.append(pause); addControlledAction(emulationMenu, pause, "pause"); QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu); @@ -1911,6 +1922,16 @@ void Window::setController(CoreController* controller, const QString& fname) { m_controller->loadConfig(m_config); m_controller->start(); + + if (!m_pendingState.isEmpty()) { + m_controller->loadState(m_pendingState); + m_pendingState = QString(); + } + + if (m_pendingPause) { + m_controller->setPaused(true); + m_pendingPause = false; + } } WindowBackground::WindowBackground(QWidget* parent) diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 7f5b1d05a..3b49b2bc3 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -202,6 +202,8 @@ private: bool m_autoresume = false; bool m_wasOpened = false; QString m_pendingPatch; + QString m_pendingState; + bool m_pendingPause = false; bool m_hitUnimplementedBiosCall; diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 5a8a5707f..7da3e8243 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,12 @@ 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 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { 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)) { diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 215e7f01f..d7e807f8f 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -407,7 +407,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer* if (!event->keysym.mod) { key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym); } - if (key != -1 && !event->repeat) { + if (key != -1) { mCoreThreadInterrupt(context); if (event->type == SDL_KEYDOWN) { context->core->addKeys(context->core, 1 << key); @@ -485,7 +485,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer* case SDLK_F8: case SDLK_F9: mCoreThreadInterrupt(context); - mCoreSaveState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SAVEDATA | SAVESTATE_SCREENSHOT); + mCoreSaveState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SAVEDATA | SAVESTATE_SCREENSHOT | SAVESTATE_RTC); mCoreThreadContinue(context); break; default: @@ -503,7 +503,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer* case SDLK_F8: case SDLK_F9: mCoreThreadInterrupt(context); - mCoreLoadState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT); + mCoreLoadState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); mCoreThreadContinue(context); break; default: diff --git a/src/platform/test/fuzz-main.c b/src/platform/test/fuzz-main.c index fb27bce57..044450099 100644 --- a/src/platform/test/fuzz-main.c +++ b/src/platform/test/fuzz-main.c @@ -26,14 +26,12 @@ " -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 @@ int main(int argc, char** argv) { 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 @@ static bool _parseFuzzOpts(struct mSubParser* parser, int option, const char* ar 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;