From 667dffe515e1b36384bc06a9cf88281e99aec0eb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Jan 2022 17:23:36 -0800 Subject: [PATCH 1/4] Qt: Support for multiple saves per game using .sa2, .sa3, etc. --- CHANGES | 1 + src/core/core.c | 9 +++- src/platform/qt/CoreController.cpp | 82 +++++++++++++++++++++++++++++- src/platform/qt/CoreController.h | 5 ++ src/platform/qt/Window.cpp | 17 +++++++ 5 files changed, 112 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index ca220e25e..ea90d3629 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Features: - Discord Rich Presence now supports time elapsed - Additional scaling shaders - Support for GameShark Advance SP (.gsv) save file importing + - Support for multiple saves per game using .sa2, .sa3, etc. Emulation fixes: - ARM7: Fix unsigned multiply timing - GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032) diff --git a/src/core/core.c b/src/core/core.c index 0f56edc08..b673c2b2e 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -223,7 +223,13 @@ bool mCoreAutoloadSave(struct mCore* core) { if (!core->dirs.save) { return false; } - return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR)); + int savePlayerId = 0; + char sav[16] = ".sav"; + mCoreConfigGetIntValue(&core->config, "savePlayerId", &savePlayerId); + if (savePlayerId > 1) { + snprintf(sav, sizeof(sav), ".sa%i", savePlayerId); + } + return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, sav, O_CREAT | O_RDWR)); } bool mCoreAutoloadPatch(struct mCore* core) { @@ -365,6 +371,7 @@ void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config mCoreConfigCopyValue(&core->config, config, "cheatAutosave"); mCoreConfigCopyValue(&core->config, config, "cheatAutoload"); + mCoreConfigCopyValue(&core->config, config, "savePlayerId"); core->loadConfig(core, config); } diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 86736e911..ac1613036 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -11,7 +11,9 @@ #include "MultiplayerController.h" #include "Override.h" +#include #include +#include #include #include @@ -70,6 +72,7 @@ CoreController::CoreController(mCore* core, QObject* parent) if (controller->m_multiplayer) { controller->m_multiplayer->attachGame(controller); + controller->updatePlayerSave(); } QMetaObject::invokeMethod(controller, "started"); @@ -285,6 +288,13 @@ void CoreController::loadConfig(ConfigController* config) { mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume"); mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute"); + int playerId = m_multiplayer->playerId(this) + 1; + QVariant savePlayerId = config->getOption("savePlayerId"); + if (m_multiplayer->attached() < 2 && savePlayerId.canConvert()) { + playerId = savePlayerId.toInt(); + } + mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", playerId); + QSize sizeBefore = screenDimensions(); m_activeBuffer.resize(256 * 224 * sizeof(color_t)); m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer.data()), sizeBefore.width()); @@ -537,6 +547,41 @@ void CoreController::forceFastForward(bool enable) { emit fastForwardChanged(enable || m_fastForward); } +void CoreController::changePlayer(int id) { + Interrupter interrupter(this); + int playerId = 0; + mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &playerId); + if (id == playerId) { + return; + } + interrupter.resume(); + + QMessageBox* resetPrompt = new QMessageBox(QMessageBox::Question, tr("Reset the game?"), + tr("Most games will require a reset to load the new save. Do you want to reset now?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + connect(resetPrompt, &QMessageBox::buttonClicked, this, [this, resetPrompt, id](QAbstractButton* button) { + Interrupter interrupter(this); + switch (resetPrompt->standardButton(button)) { + default: + return; + case QMessageBox::Yes: + mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", id); + m_resetActions.append([this]() { + updatePlayerSave(); + }); + interrupter.resume(); + reset(); + break; + case QMessageBox::No: + mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", id); + updatePlayerSave(); + break; + } + }); + resetPrompt->setAttribute(Qt::WA_DeleteOnClose); + resetPrompt->show(); +} + void CoreController::overrideMute(bool override) { m_mute = override; @@ -734,7 +779,22 @@ void CoreController::loadSave(const QString& path, bool temporary) { m_threadContext.core->loadSave(m_threadContext.core, vf); } }); - reset(); + if (hasStarted()) { + reset(); + } +} + +void CoreController::loadSave(VFile* vf, bool temporary) { + m_resetActions.append([this, vf, temporary]() { + if (temporary) { + m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + } else { + m_threadContext.core->loadSave(m_threadContext.core, vf); + } + }); + if (hasStarted()) { + reset(); + } } void CoreController::loadPatch(const QString& patchPath) { @@ -1098,6 +1158,26 @@ void CoreController::finishFrame() { QMetaObject::invokeMethod(this, "frameAvailable"); } +void CoreController::updatePlayerSave() { + int savePlayerId = 0; + mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId); + if (savePlayerId == 0 || m_multiplayer->attached() > 1) { + savePlayerId = m_multiplayer->playerId(this) + 1; + } + + QString saveSuffix; + if (savePlayerId < 2) { + saveSuffix = QLatin1String(".sav"); + } else { + saveSuffix = QString(".sa%1").arg(savePlayerId); + } + QByteArray saveSuffixBin(saveSuffix.toUtf8()); + VFile* save = mDirectorySetOpenSuffix(&m_threadContext.core->dirs, m_threadContext.core->dirs.save, saveSuffixBin.constData(), O_CREAT | O_RDWR); + if (save) { + m_threadContext.core->loadSave(m_threadContext.core, save); + } +} + void CoreController::updateFastForward() { // If we have "Fast forward" checked in the menu (m_fastForwardForced) // or are holding the fast forward button (m_fastForward): diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index ae7c37269..be96f15f9 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -142,6 +142,8 @@ public slots: void setFastForward(bool); void forceFastForward(bool); + void changePlayer(int id); + void overrideMute(bool); void loadState(int slot = 0); @@ -154,6 +156,7 @@ public slots: void saveBackupState(); void loadSave(const QString&, bool temporary); + void loadSave(VFile*, bool temporary); void loadPatch(const QString&); void scanCard(const QString&); void replaceGame(const QString&); @@ -227,6 +230,8 @@ private: int updateAutofire(); void finishFrame(); + void updatePlayerSave(); + void updateFastForward(); void updateROMInfo(); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index cba4ef95c..32de87dbe 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1218,6 +1218,23 @@ void Window::setupMenu(QMenuBar* menubar) { m_platformActions.insert(mPLATFORM_GBA, exportShark); #endif + m_actions.addSeparator("saves"); + Action* savePlayerAction; + ConfigOption* savePlayer = m_config->addOption("savePlayerId"); + savePlayerAction = savePlayer->addValue(tr("Automatically determine"), 0, &m_actions, "saves"); + m_nonMpActions.append(savePlayerAction); + + for (int i = 1; i < 5; ++i) { + savePlayerAction = savePlayer->addValue(tr("Use player %0 save game").arg(i), i, &m_actions, "saves"); + m_nonMpActions.append(savePlayerAction); + } + savePlayer->connect([this](const QVariant& value) { + if (m_controller) { + m_controller->changePlayer(value.toInt()); + } + }, this); + m_config->updateOption("savePlayerId"); + m_actions.addAction(tr("Load &patch..."), "loadPatch", this, &Window::selectPatch, "file"); #ifdef M_CORE_GBA From cdc753516798882a805db1d2042dbce8313382bf Mon Sep 17 00:00:00 2001 From: Ryan Tandy Date: Thu, 3 Feb 2022 19:02:52 -0800 Subject: [PATCH 2/4] FFmpeg: Support FFmpeg 5.0 --- src/feature/ffmpeg/ffmpeg-decoder.c | 3 ++- src/feature/ffmpeg/ffmpeg-encoder.c | 25 ++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/feature/ffmpeg/ffmpeg-decoder.c b/src/feature/ffmpeg/ffmpeg-decoder.c index c3bb6d1c5..daa47fbf2 100644 --- a/src/feature/ffmpeg/ffmpeg-decoder.c +++ b/src/feature/ffmpeg/ffmpeg-decoder.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ffmpeg-decoder.h" +#include #include void FFmpegDecoderInit(struct FFmpegDecoder* decoder) { @@ -38,7 +39,7 @@ bool FFmpegDecoderOpen(struct FFmpegDecoder* decoder, const char* infile) { #else enum AVMediaType type = decoder->context->streams[i]->codec->codec_type; #endif - struct AVCodec* codec; + const struct AVCodec* codec; struct AVCodecContext* context = NULL; if (type == AVMEDIA_TYPE_VIDEO && decoder->videoStream < 0) { decoder->video = avcodec_alloc_context3(NULL); diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index ad76ca57b..be6bd3af5 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -12,6 +12,9 @@ #include #include +#if LIBAVCODEC_VERSION_MAJOR >= 58 +#include +#endif #include #include @@ -121,7 +124,7 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un return true; } - AVCodec* codec = avcodec_find_encoder_by_name(acodec); + const AVCodec* codec = avcodec_find_encoder_by_name(acodec); if (!codec) { return false; } @@ -193,7 +196,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, in return true; } - AVCodec* codec = avcodec_find_encoder_by_name(vcodec); + const AVCodec* codec = avcodec_find_encoder_by_name(vcodec); if (!codec) { return false; } @@ -213,7 +216,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, in if (encoder->pixFormat == AV_PIX_FMT_NONE) { return false; } - if (vbr < 0 && !av_opt_find(&codec->priv_class, "crf", NULL, 0, 0)) { + if (vbr < 0 && !av_opt_find((void*) &codec->priv_class, "crf", NULL, 0, 0)) { return false; } encoder->videoCodec = vcodec; @@ -223,7 +226,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, in } bool FFmpegEncoderSetContainer(struct FFmpegEncoder* encoder, const char* container) { - AVOutputFormat* oformat = av_guess_format(container, 0, 0); + const AVOutputFormat* oformat = av_guess_format(container, 0, 0); if (!oformat) { return false; } @@ -241,9 +244,9 @@ void FFmpegEncoderSetLooping(struct FFmpegEncoder* encoder, bool loop) { } bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) { - AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); - AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); - AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); + const AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); + const AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); + const AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); if ((encoder->audioCodec && !acodec) || (encoder->videoCodec && !vcodec) || !oformat || (!acodec && !vcodec)) { return false; } @@ -257,8 +260,8 @@ bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) { } bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { - AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); - AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); + const AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); + const AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); if ((encoder->audioCodec && !acodec) || (encoder->videoCodec && !vcodec) || !FFmpegEncoderVerifyContainer(encoder)) { return false; } @@ -272,9 +275,9 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { encoder->currentVideoFrame = 0; encoder->skipResidue = 0; - AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); + const AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); #ifndef USE_LIBAV - avformat_alloc_output_context2(&encoder->context, oformat, 0, outfile); + avformat_alloc_output_context2(&encoder->context, (AVOutputFormat*) oformat, 0, outfile); #else encoder->context = avformat_alloc_context(); strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename) - 1); From 73efec2a0202d87830ae3b616b8496edee4ee9a3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Jan 2022 19:21:27 -0800 Subject: [PATCH 3/4] GBA Video: Fix rare crash in modes 3-5 --- CHANGES | 1 + src/gba/renderers/software-bg.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index ea90d3629..09ed6f0cb 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Emulation fixes: - GBA Video: Fix OpenGL rendering on M1 Macs - GBA Video: Ignore horizontally off-screen sprite timing (fixes mgba.io/i/2391) - GBA Video: Fix Hblank timing (fixes mgba.io/i/2131, mgba.io/i/2310) + - GBA Video: Fix rare crash in modes 3-5 Other fixes: - Core: Don't attempt to restore rewind diffs past start of rewind - FFmpeg: Fix crash when encoding audio with some containers diff --git a/src/gba/renderers/software-bg.c b/src/gba/renderers/software-bg.c index 620f11e24..8c48b41ad 100644 --- a/src/gba/renderers/software-bg.c +++ b/src/gba/renderers/software-bg.c @@ -108,7 +108,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer BACKGROUND_BITMAP_INIT; uint32_t color = renderer->normalPalette[0]; - if (mosaicWait && localX >= 0 && localY >= 0) { + if (mosaicWait && localX >= 0 && localY >= 0 && (localX >> 8) < GBA_VIDEO_HORIZONTAL_PIXELS && (localY >> 8) < GBA_VIDEO_VERTICAL_PIXELS) { LOAD_16(color, ((localX >> 8) + (localY >> 8) * GBA_VIDEO_HORIZONTAL_PIXELS) << 1, renderer->d.vram); color = mColorFrom555(color); } @@ -151,7 +151,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { offset = 0xA000; } - if (mosaicWait && localX >= 0 && localY >= 0) { + if (mosaicWait && localX >= 0 && localY >= 0 && (localX >> 8) < GBA_VIDEO_HORIZONTAL_PIXELS && (localY >> 8) < GBA_VIDEO_VERTICAL_PIXELS) { color = ((uint8_t*)renderer->d.vram)[offset + (localX >> 8) + (localY >> 8) * GBA_VIDEO_HORIZONTAL_PIXELS]; } @@ -192,7 +192,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { offset = 0xA000; } - if (mosaicWait && localX >= 0 && localY >= 0) { + if (mosaicWait && localX >= 0 && localY >= 0 && (localX >> 8) < 160 && (localY >> 8) < 128) { LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram); color = mColorFrom555(color); } From e2040146ea06c765cd019f754d9236f5d8516818 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 2 Feb 2022 20:24:39 -0800 Subject: [PATCH 4/4] Qt: Enable -b for Boot BIOS menu option (fixes #2074) --- CHANGES | 1 + src/platform/qt/Window.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 09ed6f0cb..c35632def 100644 --- a/CHANGES +++ b/CHANGES @@ -46,6 +46,7 @@ Misc: - Qt: Add optional frame counter to OSD (closes mgba.io/i/1728) - Qt: Add optional emulation-related information on reset (closes mgba.io/i/1780) - Qt: Add QOpenGLWidget cross-thread codepath for macOS (fixes mgba.io/i/1754) + - Qt: Enable -b for Boot BIOS menu option (fixes mgba.io/i/2074) - Windows: Attach to console if present 0.9.3: (2021-12-17) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 32de87dbe..76bcacebc 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -348,7 +348,11 @@ void Window::selectROM() { } void Window::bootBIOS() { - setController(m_manager->loadBIOS(mPLATFORM_GBA, m_config->getOption("gba.bios")), QString()); + QString bios(m_config->getOption("gba.bios")); + if (bios.isEmpty()) { + bios = m_config->getOption("bios"); + } + setController(m_manager->loadBIOS(mPLATFORM_GBA, bios), QString()); } #ifdef USE_SQLITE3