mirror of https://github.com/mgba-emu/mgba.git
Core: Add autosave/-load cheats
This commit is contained in:
parent
fe354097f2
commit
764acb7d63
1
CHANGES
1
CHANGES
|
@ -8,6 +8,7 @@ Features:
|
|||
- Customizable autofire speed
|
||||
- Ability to set default Game Boy model
|
||||
- Map viewer
|
||||
- Automatic cheat loading and saving
|
||||
Bugfixes:
|
||||
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
|
||||
- GB Serialize: Fix audio state loading
|
||||
|
|
|
@ -79,6 +79,7 @@ struct mCheatDevice {
|
|||
struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name);
|
||||
|
||||
struct mCheatSets cheats;
|
||||
bool autosave;
|
||||
};
|
||||
|
||||
struct VFile;
|
||||
|
@ -98,6 +99,7 @@ void mCheatRemoveSet(struct mCheatDevice*, struct mCheatSet*);
|
|||
|
||||
bool mCheatParseFile(struct mCheatDevice*, struct VFile*);
|
||||
bool mCheatSaveFile(struct mCheatDevice*, struct VFile*);
|
||||
void mCheatAutosave(struct mCheatDevice*);
|
||||
|
||||
void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*);
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ struct mCoreOptions {
|
|||
char* savestatePath;
|
||||
char* screenshotPath;
|
||||
char* patchPath;
|
||||
char* cheatsPath;
|
||||
|
||||
int volume;
|
||||
bool mute;
|
||||
|
|
|
@ -168,6 +168,7 @@ bool mCorePreloadFile(struct mCore* core, const char* path);
|
|||
|
||||
bool mCoreAutoloadSave(struct mCore* core);
|
||||
bool mCoreAutoloadPatch(struct mCore* core);
|
||||
bool mCoreAutoloadCheats(struct mCore* core);
|
||||
|
||||
bool mCoreSaveState(struct mCore* core, int slot, int flags);
|
||||
bool mCoreLoadState(struct mCore* core, int slot, int flags);
|
||||
|
|
|
@ -21,6 +21,7 @@ struct mDirectorySet {
|
|||
struct VDir* patch;
|
||||
struct VDir* state;
|
||||
struct VDir* screenshot;
|
||||
struct VDir* cheats;
|
||||
};
|
||||
|
||||
void mDirectorySetInit(struct mDirectorySet* dirs);
|
||||
|
|
|
@ -51,6 +51,7 @@ void mCheatDeviceCreate(struct mCheatDevice* device) {
|
|||
device->d.id = M_CHEAT_DEVICE_ID;
|
||||
device->d.init = mCheatDeviceInit;
|
||||
device->d.deinit = mCheatDeviceDeinit;
|
||||
device->autosave = false;
|
||||
mCheatSetsInit(&device->cheats, 4);
|
||||
}
|
||||
|
||||
|
@ -252,6 +253,15 @@ bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
|
|||
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) {
|
||||
if (!cheats->enabled) {
|
||||
return;
|
||||
|
|
|
@ -379,6 +379,7 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts)
|
|||
_lookupCharValue(config, "savestatePath", &opts->savestatePath);
|
||||
_lookupCharValue(config, "screenshotPath", &opts->screenshotPath);
|
||||
_lookupCharValue(config, "patchPath", &opts->patchPath);
|
||||
_lookupCharValue(config, "cheatsPath", &opts->cheatsPath);
|
||||
}
|
||||
|
||||
void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) {
|
||||
|
@ -443,10 +444,12 @@ void mCoreConfigFreeOpts(struct mCoreOptions* opts) {
|
|||
free(opts->savestatePath);
|
||||
free(opts->screenshotPath);
|
||||
free(opts->patchPath);
|
||||
free(opts->cheatsPath);
|
||||
opts->bios = 0;
|
||||
opts->shader = 0;
|
||||
opts->savegamePath = 0;
|
||||
opts->savestatePath = 0;
|
||||
opts->screenshotPath = 0;
|
||||
opts->patchPath = 0;
|
||||
opts->cheatsPath = 0;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/core/core.h>
|
||||
|
||||
#include <mgba/core/cheats.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/core/serialize.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));
|
||||
}
|
||||
|
||||
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) {
|
||||
struct VFile* vf = mCoreGetState(core, slot, true);
|
||||
if (!vf) {
|
||||
|
@ -271,6 +290,10 @@ void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config
|
|||
if (core->opts.audioBuffers) {
|
||||
core->setAudioBufferSize(core, core->opts.audioBuffers);
|
||||
}
|
||||
|
||||
mCoreConfigCopyValue(&core->config, config, "cheatAutosave");
|
||||
mCoreConfigCopyValue(&core->config, config, "cheatAutoload");
|
||||
|
||||
core->loadConfig(core, config);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ void mDirectorySetInit(struct mDirectorySet* dirs) {
|
|||
dirs->patch = 0;
|
||||
dirs->state = 0;
|
||||
dirs->screenshot = 0;
|
||||
dirs->cheats = 0;
|
||||
}
|
||||
|
||||
void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
||||
|
@ -34,6 +35,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
|||
if (dirs->archive == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->archive->close(dirs->archive);
|
||||
dirs->archive = NULL;
|
||||
}
|
||||
|
@ -48,6 +52,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
|||
if (dirs->save == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->save == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->save->close(dirs->save);
|
||||
dirs->save = NULL;
|
||||
}
|
||||
|
@ -59,6 +66,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
|||
if (dirs->patch == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->patch == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->patch->close(dirs->patch);
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
|
@ -67,14 +77,25 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
|||
if (dirs->state == dirs->screenshot) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->state == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->state->close(dirs->state);
|
||||
dirs->state = NULL;
|
||||
}
|
||||
|
||||
if (dirs->screenshot) {
|
||||
if (dirs->screenshot == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->screenshot->close(dirs->screenshot);
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
|
||||
if (dirs->cheats) {
|
||||
dirs->cheats->close(dirs->cheats);
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
|
||||
|
@ -91,6 +112,9 @@ void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
|
|||
if (!dirs->screenshot) {
|
||||
dirs->screenshot = dirs->base;
|
||||
}
|
||||
if (!dirs->cheats) {
|
||||
dirs->cheats = dirs->base;
|
||||
}
|
||||
}
|
||||
|
||||
void mDirectorySetDetachBase(struct mDirectorySet* dirs) {
|
||||
|
@ -106,6 +130,9 @@ void mDirectorySetDetachBase(struct mDirectorySet* dirs) {
|
|||
if (dirs->screenshot == dirs->base) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->cheats == dirs->base) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
|
||||
if (dirs->base) {
|
||||
dirs->base->close(dirs->base);
|
||||
|
@ -183,5 +210,15 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio
|
|||
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
|
||||
|
|
|
@ -306,6 +306,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
|
|||
|
||||
mLOG(GUI_RUNNER, DEBUG, "Loading save...");
|
||||
mCoreAutoloadSave(runner->core);
|
||||
mCoreAutoloadCheats(runner->core);
|
||||
if (runner->setup) {
|
||||
mLOG(GUI_RUNNER, DEBUG, "Setting up runner...");
|
||||
runner->setup(runner);
|
||||
|
|
|
@ -182,6 +182,9 @@ class Core(object):
|
|||
def autoloadPatch(self):
|
||||
return bool(lib.mCoreAutoloadPatch(self._core))
|
||||
|
||||
def autoloadCheats(self):
|
||||
return bool(lib.mCoreAutoloadCheats(self._core))
|
||||
|
||||
def platform(self):
|
||||
return self._core.platform(self._core)
|
||||
|
||||
|
|
|
@ -70,10 +70,12 @@ bool CheatsModel::setData(const QModelIndex& index, const QVariant& value, int r
|
|||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
mCheatSetRename(cheats, value.toString().toUtf8().constData());
|
||||
mCheatAutosave(m_device);
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
case Qt::CheckStateRole:
|
||||
cheats->enabled = value == Qt::Checked;
|
||||
mCheatAutosave(m_device);
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
default:
|
||||
|
@ -154,7 +156,8 @@ void CheatsModel::removeAt(const QModelIndex& index) {
|
|||
beginRemoveRows(QModelIndex(), row, row);
|
||||
mCheatRemoveSet(m_device, set);
|
||||
mCheatSetDeinit(set);
|
||||
endInsertRows();
|
||||
endRemoveRows();
|
||||
mCheatAutosave(m_device);
|
||||
}
|
||||
|
||||
QString CheatsModel::toString(const QModelIndexList& indices) const {
|
||||
|
@ -201,6 +204,7 @@ void CheatsModel::beginAppendRow(const QModelIndex& index) {
|
|||
|
||||
void CheatsModel::endAppendRow() {
|
||||
endInsertRows();
|
||||
mCheatAutosave(m_device);
|
||||
}
|
||||
|
||||
void CheatsModel::loadFile(const QString& path) {
|
||||
|
@ -232,6 +236,7 @@ void CheatsModel::addSet(mCheatSet* set) {
|
|||
}
|
||||
mCheatAddSet(m_device, set);
|
||||
endInsertRows();
|
||||
mCheatAutosave(m_device);
|
||||
}
|
||||
|
||||
void CheatsModel::invalidated() {
|
||||
|
|
|
@ -109,14 +109,14 @@ bool CheatsView::eventFilter(QObject* object, QEvent* event) {
|
|||
}
|
||||
|
||||
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()) {
|
||||
m_model.loadFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
m_model.saveFile(filename);
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr
|
|||
bytes = info.dir().canonicalPath().toUtf8();
|
||||
mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData()));
|
||||
mCoreAutoloadSave(core);
|
||||
mCoreAutoloadCheats(core);
|
||||
|
||||
CoreController* cc = new CoreController(core);
|
||||
if (m_multiplayer) {
|
||||
|
|
|
@ -106,6 +106,22 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
|
|||
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);
|
||||
|
||||
// TODO: Move to reloadConfig()
|
||||
|
@ -334,10 +350,13 @@ void SettingsView::updateConfig() {
|
|||
saveSetting("savestatePath", m_ui.savestatePath);
|
||||
saveSetting("screenshotPath", m_ui.screenshotPath);
|
||||
saveSetting("patchPath", m_ui.patchPath);
|
||||
saveSetting("cheatsPath", m_ui.cheatsPath);
|
||||
saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex());
|
||||
saveSetting("showLibrary", m_ui.showLibrary);
|
||||
saveSetting("preload", m_ui.preload);
|
||||
saveSetting("showFps", m_ui.showFps);
|
||||
saveSetting("cheatAutoload", m_ui.cheatAutoload);
|
||||
saveSetting("cheatAutosave", m_ui.cheatAutosave);
|
||||
|
||||
if (m_ui.fastForwardUnbounded->isChecked()) {
|
||||
saveSetting("fastForwardRatio", "-1");
|
||||
|
@ -453,9 +472,12 @@ void SettingsView::reloadConfig() {
|
|||
loadSetting("savestatePath", m_ui.savestatePath);
|
||||
loadSetting("screenshotPath", m_ui.screenshotPath);
|
||||
loadSetting("patchPath", m_ui.patchPath);
|
||||
loadSetting("cheatsPath", m_ui.cheatsPath);
|
||||
loadSetting("showLibrary", m_ui.showLibrary);
|
||||
loadSetting("preload", m_ui.preload);
|
||||
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());
|
||||
|
||||
|
|
|
@ -419,6 +419,13 @@
|
|||
</item>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
|
@ -488,17 +495,37 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="Line" name="line_10">
|
||||
<item row="9" column="1">
|
||||
<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">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="showFps">
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="cheatAutosave">
|
||||
<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 name="checked">
|
||||
<bool>true</bool>
|
||||
|
@ -1068,6 +1095,54 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<widget class="QWidget" name="gb">
|
||||
|
|
|
@ -181,6 +181,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
|
|||
return 1;
|
||||
}
|
||||
mCoreAutoloadSave(renderer->core);
|
||||
mCoreAutoloadCheats(renderer->core);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
struct mScriptBridge* bridge = mScriptBridgeCreate();
|
||||
#ifdef ENABLE_PYTHON
|
||||
|
|
Loading…
Reference in New Issue