Core: Add autosave/-load cheats

This commit is contained in:
Vicki Pfau 2017-11-11 12:30:04 -08:00
parent fe354097f2
commit 764acb7d63
17 changed files with 195 additions and 8 deletions

View File

@ -8,6 +8,7 @@ Features:
- Customizable autofire speed - Customizable autofire speed
- Ability to set default Game Boy model - Ability to set default Game Boy model
- Map viewer - Map viewer
- Automatic cheat loading and saving
Bugfixes: Bugfixes:
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
- GB Serialize: Fix audio state loading - GB Serialize: Fix audio state loading

View File

@ -79,6 +79,7 @@ struct mCheatDevice {
struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name); struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name);
struct mCheatSets cheats; struct mCheatSets cheats;
bool autosave;
}; };
struct VFile; struct VFile;
@ -98,6 +99,7 @@ void mCheatRemoveSet(struct mCheatDevice*, struct mCheatSet*);
bool mCheatParseFile(struct mCheatDevice*, struct VFile*); bool mCheatParseFile(struct mCheatDevice*, struct VFile*);
bool mCheatSaveFile(struct mCheatDevice*, struct VFile*); bool mCheatSaveFile(struct mCheatDevice*, struct VFile*);
void mCheatAutosave(struct mCheatDevice*);
void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*); void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*);

View File

@ -51,6 +51,7 @@ struct mCoreOptions {
char* savestatePath; char* savestatePath;
char* screenshotPath; char* screenshotPath;
char* patchPath; char* patchPath;
char* cheatsPath;
int volume; int volume;
bool mute; bool mute;

View File

@ -168,6 +168,7 @@ bool mCorePreloadFile(struct mCore* core, const char* path);
bool mCoreAutoloadSave(struct mCore* core); bool mCoreAutoloadSave(struct mCore* core);
bool mCoreAutoloadPatch(struct mCore* core); bool mCoreAutoloadPatch(struct mCore* core);
bool mCoreAutoloadCheats(struct mCore* core);
bool mCoreSaveState(struct mCore* core, int slot, int flags); bool mCoreSaveState(struct mCore* core, int slot, int flags);
bool mCoreLoadState(struct mCore* core, int slot, int flags); bool mCoreLoadState(struct mCore* core, int slot, int flags);

View File

@ -21,6 +21,7 @@ struct mDirectorySet {
struct VDir* patch; struct VDir* patch;
struct VDir* state; struct VDir* state;
struct VDir* screenshot; struct VDir* screenshot;
struct VDir* cheats;
}; };
void mDirectorySetInit(struct mDirectorySet* dirs); void mDirectorySetInit(struct mDirectorySet* dirs);

View File

@ -51,6 +51,7 @@ void mCheatDeviceCreate(struct mCheatDevice* device) {
device->d.id = M_CHEAT_DEVICE_ID; device->d.id = M_CHEAT_DEVICE_ID;
device->d.init = mCheatDeviceInit; device->d.init = mCheatDeviceInit;
device->d.deinit = mCheatDeviceDeinit; device->d.deinit = mCheatDeviceDeinit;
device->autosave = false;
mCheatSetsInit(&device->cheats, 4); mCheatSetsInit(&device->cheats, 4);
} }
@ -252,6 +253,15 @@ bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
return true; return true;
} }
void mCheatAutosave(struct mCheatDevice* device) {
if (!device->autosave) {
return;
}
struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
mCheatSaveFile(device, vf);
vf->close(vf);
}
void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
if (!cheats->enabled) { if (!cheats->enabled) {
return; return;

View File

@ -379,6 +379,7 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts)
_lookupCharValue(config, "savestatePath", &opts->savestatePath); _lookupCharValue(config, "savestatePath", &opts->savestatePath);
_lookupCharValue(config, "screenshotPath", &opts->screenshotPath); _lookupCharValue(config, "screenshotPath", &opts->screenshotPath);
_lookupCharValue(config, "patchPath", &opts->patchPath); _lookupCharValue(config, "patchPath", &opts->patchPath);
_lookupCharValue(config, "cheatsPath", &opts->cheatsPath);
} }
void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) { void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) {
@ -443,10 +444,12 @@ void mCoreConfigFreeOpts(struct mCoreOptions* opts) {
free(opts->savestatePath); free(opts->savestatePath);
free(opts->screenshotPath); free(opts->screenshotPath);
free(opts->patchPath); free(opts->patchPath);
free(opts->cheatsPath);
opts->bios = 0; opts->bios = 0;
opts->shader = 0; opts->shader = 0;
opts->savegamePath = 0; opts->savegamePath = 0;
opts->savestatePath = 0; opts->savestatePath = 0;
opts->screenshotPath = 0; opts->screenshotPath = 0;
opts->patchPath = 0; opts->patchPath = 0;
opts->cheatsPath = 0;
} }

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include <mgba/core/cheats.h>
#include <mgba/core/log.h> #include <mgba/core/log.h>
#include <mgba/core/serialize.h> #include <mgba/core/serialize.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
@ -165,6 +166,24 @@ bool mCoreAutoloadPatch(struct mCore* core) {
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY)); core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY));
} }
bool mCoreAutoloadCheats(struct mCore* core) {
bool success = true;
int cheatAuto;
if (!mCoreConfigGetIntValue(&core->config, "cheatAutoload", &cheatAuto) || cheatAuto) {
struct VFile* vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.cheats, ".cheats", O_RDONLY);
if (vf) {
struct mCheatDevice* device = core->cheatDevice(core);
success = mCheatParseFile(device, vf);
vf->close(vf);
}
}
if (!mCoreConfigGetIntValue(&core->config, "cheatAutosave", &cheatAuto) || cheatAuto) {
struct mCheatDevice* device = core->cheatDevice(core);
device->autosave = true;
}
return success;
}
bool mCoreSaveState(struct mCore* core, int slot, int flags) { bool mCoreSaveState(struct mCore* core, int slot, int flags) {
struct VFile* vf = mCoreGetState(core, slot, true); struct VFile* vf = mCoreGetState(core, slot, true);
if (!vf) { if (!vf) {
@ -271,6 +290,10 @@ void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config
if (core->opts.audioBuffers) { if (core->opts.audioBuffers) {
core->setAudioBufferSize(core, core->opts.audioBuffers); core->setAudioBufferSize(core, core->opts.audioBuffers);
} }
mCoreConfigCopyValue(&core->config, config, "cheatAutosave");
mCoreConfigCopyValue(&core->config, config, "cheatAutoload");
core->loadConfig(core, config); core->loadConfig(core, config);
} }

View File

@ -16,6 +16,7 @@ void mDirectorySetInit(struct mDirectorySet* dirs) {
dirs->patch = 0; dirs->patch = 0;
dirs->state = 0; dirs->state = 0;
dirs->screenshot = 0; dirs->screenshot = 0;
dirs->cheats = 0;
} }
void mDirectorySetDeinit(struct mDirectorySet* dirs) { void mDirectorySetDeinit(struct mDirectorySet* dirs) {
@ -34,6 +35,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
if (dirs->archive == dirs->screenshot) { if (dirs->archive == dirs->screenshot) {
dirs->screenshot = NULL; dirs->screenshot = NULL;
} }
if (dirs->archive == dirs->cheats) {
dirs->cheats = NULL;
}
dirs->archive->close(dirs->archive); dirs->archive->close(dirs->archive);
dirs->archive = NULL; dirs->archive = NULL;
} }
@ -48,6 +52,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
if (dirs->save == dirs->screenshot) { if (dirs->save == dirs->screenshot) {
dirs->screenshot = NULL; dirs->screenshot = NULL;
} }
if (dirs->save == dirs->cheats) {
dirs->cheats = NULL;
}
dirs->save->close(dirs->save); dirs->save->close(dirs->save);
dirs->save = NULL; dirs->save = NULL;
} }
@ -59,6 +66,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
if (dirs->patch == dirs->screenshot) { if (dirs->patch == dirs->screenshot) {
dirs->screenshot = NULL; dirs->screenshot = NULL;
} }
if (dirs->patch == dirs->cheats) {
dirs->cheats = NULL;
}
dirs->patch->close(dirs->patch); dirs->patch->close(dirs->patch);
dirs->patch = NULL; dirs->patch = NULL;
} }
@ -67,14 +77,25 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
if (dirs->state == dirs->screenshot) { if (dirs->state == dirs->screenshot) {
dirs->state = NULL; dirs->state = NULL;
} }
if (dirs->state == dirs->cheats) {
dirs->cheats = NULL;
}
dirs->state->close(dirs->state); dirs->state->close(dirs->state);
dirs->state = NULL; dirs->state = NULL;
} }
if (dirs->screenshot) { if (dirs->screenshot) {
if (dirs->screenshot == dirs->cheats) {
dirs->cheats = NULL;
}
dirs->screenshot->close(dirs->screenshot); dirs->screenshot->close(dirs->screenshot);
dirs->screenshot = NULL; dirs->screenshot = NULL;
} }
if (dirs->cheats) {
dirs->cheats->close(dirs->cheats);
dirs->cheats = NULL;
}
} }
void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) { void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
@ -91,6 +112,9 @@ void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
if (!dirs->screenshot) { if (!dirs->screenshot) {
dirs->screenshot = dirs->base; dirs->screenshot = dirs->base;
} }
if (!dirs->cheats) {
dirs->cheats = dirs->base;
}
} }
void mDirectorySetDetachBase(struct mDirectorySet* dirs) { void mDirectorySetDetachBase(struct mDirectorySet* dirs) {
@ -106,6 +130,9 @@ void mDirectorySetDetachBase(struct mDirectorySet* dirs) {
if (dirs->screenshot == dirs->base) { if (dirs->screenshot == dirs->base) {
dirs->screenshot = NULL; dirs->screenshot = NULL;
} }
if (dirs->cheats == dirs->base) {
dirs->cheats = NULL;
}
if (dirs->base) { if (dirs->base) {
dirs->base->close(dirs->base); dirs->base->close(dirs->base);
@ -183,5 +210,15 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio
dirs->patch = dir; dirs->patch = dir;
} }
} }
if (opts->cheatsPath) {
struct VDir* dir = VDirOpen(opts->cheatsPath);
if (dir) {
if (dirs->cheats && dirs->cheats != dirs->base) {
dirs->cheats->close(dirs->cheats);
}
dirs->cheats = dir;
}
}
} }
#endif #endif

View File

@ -306,6 +306,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
mLOG(GUI_RUNNER, DEBUG, "Loading save..."); mLOG(GUI_RUNNER, DEBUG, "Loading save...");
mCoreAutoloadSave(runner->core); mCoreAutoloadSave(runner->core);
mCoreAutoloadCheats(runner->core);
if (runner->setup) { if (runner->setup) {
mLOG(GUI_RUNNER, DEBUG, "Setting up runner..."); mLOG(GUI_RUNNER, DEBUG, "Setting up runner...");
runner->setup(runner); runner->setup(runner);

View File

@ -182,6 +182,9 @@ class Core(object):
def autoloadPatch(self): def autoloadPatch(self):
return bool(lib.mCoreAutoloadPatch(self._core)) return bool(lib.mCoreAutoloadPatch(self._core))
def autoloadCheats(self):
return bool(lib.mCoreAutoloadCheats(self._core))
def platform(self): def platform(self):
return self._core.platform(self._core) return self._core.platform(self._core)

View File

@ -70,10 +70,12 @@ bool CheatsModel::setData(const QModelIndex& index, const QVariant& value, int r
case Qt::DisplayRole: case Qt::DisplayRole:
case Qt::EditRole: case Qt::EditRole:
mCheatSetRename(cheats, value.toString().toUtf8().constData()); mCheatSetRename(cheats, value.toString().toUtf8().constData());
mCheatAutosave(m_device);
emit dataChanged(index, index); emit dataChanged(index, index);
return true; return true;
case Qt::CheckStateRole: case Qt::CheckStateRole:
cheats->enabled = value == Qt::Checked; cheats->enabled = value == Qt::Checked;
mCheatAutosave(m_device);
emit dataChanged(index, index); emit dataChanged(index, index);
return true; return true;
default: default:
@ -154,7 +156,8 @@ void CheatsModel::removeAt(const QModelIndex& index) {
beginRemoveRows(QModelIndex(), row, row); beginRemoveRows(QModelIndex(), row, row);
mCheatRemoveSet(m_device, set); mCheatRemoveSet(m_device, set);
mCheatSetDeinit(set); mCheatSetDeinit(set);
endInsertRows(); endRemoveRows();
mCheatAutosave(m_device);
} }
QString CheatsModel::toString(const QModelIndexList& indices) const { QString CheatsModel::toString(const QModelIndexList& indices) const {
@ -201,6 +204,7 @@ void CheatsModel::beginAppendRow(const QModelIndex& index) {
void CheatsModel::endAppendRow() { void CheatsModel::endAppendRow() {
endInsertRows(); endInsertRows();
mCheatAutosave(m_device);
} }
void CheatsModel::loadFile(const QString& path) { void CheatsModel::loadFile(const QString& path) {
@ -232,6 +236,7 @@ void CheatsModel::addSet(mCheatSet* set) {
} }
mCheatAddSet(m_device, set); mCheatAddSet(m_device, set);
endInsertRows(); endInsertRows();
mCheatAutosave(m_device);
} }
void CheatsModel::invalidated() { void CheatsModel::invalidated() {

View File

@ -109,14 +109,14 @@ bool CheatsView::eventFilter(QObject* object, QEvent* event) {
} }
void CheatsView::load() { void CheatsView::load() {
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file")); QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file"), tr(("Cheats file (*.cheats *.cht *.clt)")));
if (!filename.isEmpty()) { if (!filename.isEmpty()) {
m_model.loadFile(filename); m_model.loadFile(filename);
} }
} }
void CheatsView::save() { void CheatsView::save() {
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file")); QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file"), tr(("Cheats file (*.cheats *.cht *.clt)")));
if (!filename.isEmpty()) { if (!filename.isEmpty()) {
m_model.saveFile(filename); m_model.saveFile(filename);
} }

View File

@ -109,6 +109,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr
bytes = info.dir().canonicalPath().toUtf8(); bytes = info.dir().canonicalPath().toUtf8();
mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData()));
mCoreAutoloadSave(core); mCoreAutoloadSave(core);
mCoreAutoloadCheats(core);
CoreController* cc = new CoreController(core); CoreController* cc = new CoreController(core);
if (m_multiplayer) { if (m_multiplayer) {

View File

@ -106,6 +106,22 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
m_ui.patchPath->setText(path); m_ui.patchPath->setText(path);
} }
}); });
if (m_ui.cheatsPath->text().isEmpty()) {
m_ui.cheatsSameDir->setChecked(true);
}
connect(m_ui.cheatsSameDir, &QAbstractButton::toggled, [this] (bool e) {
if (e) {
m_ui.cheatsPath->clear();
}
});
connect(m_ui.cheatsBrowse, &QAbstractButton::pressed, [this] () {
QString path = GBAApp::app()->getOpenDirectoryName(this, "Select directory");
if (!path.isNull()) {
m_ui.cheatsSameDir->setChecked(false);
m_ui.cheatsPath->setText(path);
}
});
connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared);
// TODO: Move to reloadConfig() // TODO: Move to reloadConfig()
@ -334,10 +350,13 @@ void SettingsView::updateConfig() {
saveSetting("savestatePath", m_ui.savestatePath); saveSetting("savestatePath", m_ui.savestatePath);
saveSetting("screenshotPath", m_ui.screenshotPath); saveSetting("screenshotPath", m_ui.screenshotPath);
saveSetting("patchPath", m_ui.patchPath); saveSetting("patchPath", m_ui.patchPath);
saveSetting("cheatsPath", m_ui.cheatsPath);
saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex()); saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex());
saveSetting("showLibrary", m_ui.showLibrary); saveSetting("showLibrary", m_ui.showLibrary);
saveSetting("preload", m_ui.preload); saveSetting("preload", m_ui.preload);
saveSetting("showFps", m_ui.showFps); saveSetting("showFps", m_ui.showFps);
saveSetting("cheatAutoload", m_ui.cheatAutoload);
saveSetting("cheatAutosave", m_ui.cheatAutosave);
if (m_ui.fastForwardUnbounded->isChecked()) { if (m_ui.fastForwardUnbounded->isChecked()) {
saveSetting("fastForwardRatio", "-1"); saveSetting("fastForwardRatio", "-1");
@ -453,9 +472,12 @@ void SettingsView::reloadConfig() {
loadSetting("savestatePath", m_ui.savestatePath); loadSetting("savestatePath", m_ui.savestatePath);
loadSetting("screenshotPath", m_ui.screenshotPath); loadSetting("screenshotPath", m_ui.screenshotPath);
loadSetting("patchPath", m_ui.patchPath); loadSetting("patchPath", m_ui.patchPath);
loadSetting("cheatsPath", m_ui.cheatsPath);
loadSetting("showLibrary", m_ui.showLibrary); loadSetting("showLibrary", m_ui.showLibrary);
loadSetting("preload", m_ui.preload); loadSetting("preload", m_ui.preload);
loadSetting("showFps", m_ui.showFps, true); loadSetting("showFps", m_ui.showFps, true);
loadSetting("cheatAutoload", m_ui.cheatAutoload, true);
loadSetting("cheatAutosave", m_ui.cheatAutosave, true);
m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());

View File

@ -419,6 +419,13 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="2">
<widget class="Line" name="line_10">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
@ -488,17 +495,37 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="2"> <item row="9" column="1">
<widget class="Line" name="line_10"> <widget class="QCheckBox" name="showFps">
<property name="text">
<string>Show FPS in title bar</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<widget class="Line" name="line_13">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="11" column="1">
<widget class="QCheckBox" name="showFps"> <widget class="QCheckBox" name="cheatAutosave">
<property name="text"> <property name="text">
<string>Show FPS in title bar</string> <string>Automatically save cheats</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QCheckBox" name="cheatAutoload">
<property name="text">
<string>Automatically load cheats</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -1068,6 +1095,54 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="0" colspan="2">
<widget class="Line" name="line_14">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_48">
<property name="text">
<string>Cheats</string>
</property>
</widget>
</item>
<item row="13" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_27">
<item>
<widget class="QLineEdit" name="cheatsPath">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cheatsBrowse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="14" column="1">
<widget class="QCheckBox" name="cheatsSameDir">
<property name="text">
<string>Same directory as the ROM</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="gb"> <widget class="QWidget" name="gb">

View File

@ -181,6 +181,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
return 1; return 1;
} }
mCoreAutoloadSave(renderer->core); mCoreAutoloadSave(renderer->core);
mCoreAutoloadCheats(renderer->core);
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
struct mScriptBridge* bridge = mScriptBridgeCreate(); struct mScriptBridge* bridge = mScriptBridgeCreate();
#ifdef ENABLE_PYTHON #ifdef ENABLE_PYTHON