Skip to content

Commit

Permalink
Qt: Support for multiple saves per game using .sa2, .sa3, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
endrift committed Jan 31, 2022
1 parent 16df8fe commit 667dffe
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion src/core/core.c
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand Down
82 changes: 81 additions & 1 deletion src/platform/qt/CoreController.cpp
Expand Up @@ -11,7 +11,9 @@
#include "MultiplayerController.h"
#include "Override.h"

#include <QAbstractButton>
#include <QDateTime>
#include <QMessageBox>
#include <QMutexLocker>

#include <mgba/core/serialize.h>
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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<int>()) {
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<color_t*>(m_activeBuffer.data()), sizeBefore.width());
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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):
Expand Down
5 changes: 5 additions & 0 deletions src/platform/qt/CoreController.h
Expand Up @@ -142,6 +142,8 @@ public slots:
void setFastForward(bool);
void forceFastForward(bool);

void changePlayer(int id);

void overrideMute(bool);

void loadState(int slot = 0);
Expand All @@ -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&);
Expand Down Expand Up @@ -227,6 +230,8 @@ public slots:
int updateAutofire();
void finishFrame();

void updatePlayerSave();

void updateFastForward();

void updateROMInfo();
Expand Down
17 changes: 17 additions & 0 deletions src/platform/qt/Window.cpp
Expand Up @@ -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
Expand Down

0 comments on commit 667dffe

Please sign in to comment.