Qt: Add ability to customize savestate data, add rewind duration box
@@ -19,6 +19,7 @@ - Cleaner, unified settings window
- Added a setting for pausing when the emulator is not in focus - Customizable paths for save games, save states, screenshots and patches - Controller hotplugging + - Ability to store save games and active cheats within savestates Bugfixes: - Util: Fix PowerPC PNG read/write pixel order - VFS: Fix VFileReadline and remove _vfdReadline@@ -67,6 +68,7 @@ - Debugger: Convert breakpoints and watchpoints from linked-lists to vectors
- Qt: Added button for breaking into the GDB debugger - GBA BIOS: Finish implementing RegisterRamReset - GBA: Allow jumping to OAM and palette RAM + - Qt: Add box for showing duration of rewind 0.3.2: (2015-12-16) Bugfixes:
@@ -907,6 +907,14 @@ QMetaObject::invokeMethod(m_audioProcessor, "start");
} } +void GameController::setSaveStateExtdata(int flags) { + m_saveStateFlags = flags; +} + +void GameController::setLoadStateExtdata(int flags) { + m_loadStateFlags = flags; +} + void GameController::setLuminanceValue(uint8_t value) { m_luxValue = value; value = std::max<int>(value - 0x16, 0);
@@ -138,6 +138,8 @@ void setTurboSpeed(float ratio = -1);
void setAVStream(GBAAVStream*); void clearAVStream(); void reloadAudioDriver(); + void setSaveStateExtdata(int flags); + void setLoadStateExtdata(int flags); #ifdef USE_PNG void screenshot();
@@ -13,6 +13,10 @@ #include "GBAKeyEditor.h"
#include "InputController.h" #include "ShortcutView.h" +extern "C" { +#include "gba/serialize.h" +} + using namespace QGBA; SettingsView::SettingsView(ConfigController* controller, InputController* inputController, ShortcutController* shortcutController, QWidget* parent)@@ -21,29 +25,7 @@ , m_controller(controller)
{ m_ui.setupUi(this); - loadSetting("bios", m_ui.bios); - loadSetting("useBios", m_ui.useBios); - loadSetting("skipBios", m_ui.skipBios); - loadSetting("audioBuffers", m_ui.audioBufferSize); - loadSetting("sampleRate", m_ui.sampleRate); - loadSetting("videoSync", m_ui.videoSync); - loadSetting("audioSync", m_ui.audioSync); - loadSetting("frameskip", m_ui.frameskip); - loadSetting("fpsTarget", m_ui.fpsTarget); - loadSetting("lockAspectRatio", m_ui.lockAspectRatio); - loadSetting("volume", m_ui.volume); - loadSetting("mute", m_ui.mute); - loadSetting("rewindEnable", m_ui.rewind); - loadSetting("rewindBufferInterval", m_ui.rewindInterval); - loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); - loadSetting("resampleVideo", m_ui.resampleVideo); - loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); - loadSetting("suspendScreensaver", m_ui.suspendScreensaver); - loadSetting("pauseOnFocusLost", m_ui.pauseOnFocusLost); - loadSetting("savegamePath", m_ui.savegamePath); - loadSetting("savestatePath", m_ui.savestatePath); - loadSetting("screenshotPath", m_ui.screenshotPath); - loadSetting("patchPath", m_ui.patchPath); + reloadConfig(); if (m_ui.savegamePath->text().isEmpty()) { m_ui.savegameSameDir->setChecked(true);@@ -109,28 +91,7 @@ m_ui.patchPath->setText(path);
} }); - double fastForwardRatio = loadSetting("fastForwardRatio").toDouble(); - if (fastForwardRatio <= 0) { - m_ui.fastForwardUnbounded->setChecked(true); - m_ui.fastForwardRatio->setEnabled(false); - } else { - m_ui.fastForwardUnbounded->setChecked(false); - m_ui.fastForwardRatio->setEnabled(true); - m_ui.fastForwardRatio->setValue(fastForwardRatio); - } - connect(m_ui.fastForwardUnbounded, &QAbstractButton::toggled, [this](bool checked) { - m_ui.fastForwardRatio->setEnabled(!checked); - }); - - QString idleOptimization = loadSetting("idleOptimization"); - if (idleOptimization == "ignore") { - m_ui.idleOptimization->setCurrentIndex(0); - } else if (idleOptimization == "remove") { - m_ui.idleOptimization->setCurrentIndex(1); - } else if (idleOptimization == "detect") { - m_ui.idleOptimization->setCurrentIndex(2); - } - + // TODO: Move to reloadConfig() QVariant audioDriver = m_controller->getQtOption("audioDriver"); #ifdef BUILD_QT_MULTIMEDIA m_ui.audioDriver->addItem(tr("Qt Multimedia"), static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA));@@ -146,6 +107,7 @@ m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1);
} #endif + // TODO: Move to reloadConfig() QVariant displayDriver = m_controller->getQtOption("displayDriver"); m_ui.displayDriver->addItem(tr("Software (Qt)"), static_cast<int>(Display::Driver::QT)); if (!displayDriver.isNull() && displayDriver.toInt() == static_cast<int>(Display::Driver::QT)) {@@ -195,6 +157,14 @@ m_ui.bios->setText(filename);
} } +void SettingsView::recalculateRewind() { + int interval = m_ui.rewindInterval->value(); + int capacity = m_ui.rewindCapacity->value(); + double duration = m_ui.fpsTarget->value(); + m_ui.rewindDuration->setValue(interval * capacity / duration); + +} + void SettingsView::updateConfig() { saveSetting("bios", m_ui.bios); saveSetting("useBios", m_ui.useBios);@@ -238,6 +208,18 @@ saveSetting("idleOptimization", "detect");
break; } + int loadState = 0; + loadState |= m_ui.loadStateScreenshot->isChecked() ? SAVESTATE_SCREENSHOT : 0; + loadState |= m_ui.loadStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0; + loadState |= m_ui.loadStateCheats->isChecked() ? SAVESTATE_CHEATS : 0; + saveSetting("loadStateExtdata", loadState); + + int saveState = 0; + saveState |= m_ui.saveStateScreenshot->isChecked() ? SAVESTATE_SCREENSHOT : 0; + saveState |= m_ui.saveStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0; + saveState |= m_ui.saveStateCheats->isChecked() ? SAVESTATE_CHEATS : 0; + saveSetting("saveStateExtdata", saveState); + QVariant audioDriver = m_ui.audioDriver->itemData(m_ui.audioDriver->currentIndex()); if (audioDriver != m_controller->getQtOption("audioDriver")) { m_controller->setQtOption("audioDriver", audioDriver);@@ -256,6 +238,72 @@ m_controller->write();
emit pathsChanged(); emit biosLoaded(m_ui.bios->text()); +} + +void SettingsView::reloadConfig() { + loadSetting("bios", m_ui.bios); + loadSetting("useBios", m_ui.useBios); + loadSetting("skipBios", m_ui.skipBios); + loadSetting("audioBuffers", m_ui.audioBufferSize); + loadSetting("sampleRate", m_ui.sampleRate); + loadSetting("videoSync", m_ui.videoSync); + loadSetting("audioSync", m_ui.audioSync); + loadSetting("frameskip", m_ui.frameskip); + loadSetting("fpsTarget", m_ui.fpsTarget); + loadSetting("lockAspectRatio", m_ui.lockAspectRatio); + loadSetting("volume", m_ui.volume); + loadSetting("mute", m_ui.mute); + loadSetting("rewindEnable", m_ui.rewind); + loadSetting("rewindBufferInterval", m_ui.rewindInterval); + loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); + loadSetting("resampleVideo", m_ui.resampleVideo); + loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); + loadSetting("suspendScreensaver", m_ui.suspendScreensaver); + loadSetting("pauseOnFocusLost", m_ui.pauseOnFocusLost); + loadSetting("savegamePath", m_ui.savegamePath); + loadSetting("savestatePath", m_ui.savestatePath); + loadSetting("screenshotPath", m_ui.screenshotPath); + loadSetting("patchPath", m_ui.patchPath); + + double fastForwardRatio = loadSetting("fastForwardRatio").toDouble(); + if (fastForwardRatio <= 0) { + m_ui.fastForwardUnbounded->setChecked(true); + m_ui.fastForwardRatio->setEnabled(false); + } else { + m_ui.fastForwardUnbounded->setChecked(false); + m_ui.fastForwardRatio->setEnabled(true); + m_ui.fastForwardRatio->setValue(fastForwardRatio); + } + + connect(m_ui.rewindInterval, SIGNAL(valueChanged(int)), this, SLOT(recalculateRewind())); + connect(m_ui.rewindCapacity, SIGNAL(valueChanged(int)), this, SLOT(recalculateRewind())); + connect(m_ui.fpsTarget, SIGNAL(valueChanged(double)), this, SLOT(recalculateRewind())); + + QString idleOptimization = loadSetting("idleOptimization"); + if (idleOptimization == "ignore") { + m_ui.idleOptimization->setCurrentIndex(0); + } else if (idleOptimization == "remove") { + m_ui.idleOptimization->setCurrentIndex(1); + } else if (idleOptimization == "detect") { + m_ui.idleOptimization->setCurrentIndex(2); + } + + bool ok; + int loadState = loadSetting("loadStateExtdata").toInt(&ok); + if (!ok) { + loadState = SAVESTATE_SCREENSHOT; + } + m_ui.loadStateScreenshot->setChecked(loadState & SAVESTATE_SCREENSHOT); + m_ui.loadStateSave->setChecked(loadState & SAVESTATE_SAVEDATA); + m_ui.loadStateCheats->setChecked(loadState & SAVESTATE_CHEATS); + + int saveState = loadSetting("saveStateExtdata").toInt(&ok); + if (!ok) { + saveState = SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS; + } + m_ui.saveStateScreenshot->setChecked(saveState & SAVESTATE_SCREENSHOT); + m_ui.saveStateSave->setChecked(saveState & SAVESTATE_SAVEDATA); + m_ui.saveStateCheats->setChecked(saveState & SAVESTATE_CHEATS); } void SettingsView::saveSetting(const char* key, const QAbstractButton* field) {
@@ -30,7 +30,9 @@ void pathsChanged();
private slots: void selectBios(); + void recalculateRewind(); void updateConfig(); + void reloadConfig(); private: Ui::SettingsView m_ui;
@@ -6,8 +6,8 @@ <property name="geometry">
<rect> <x>0</x> <y>0</y> - <width>568</width> - <height>451</height> + <width>544</width> + <height>425</height> </rect> </property> <property name="sizePolicy">@@ -23,10 +23,56 @@ <layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint"> <enum>QLayout::SetFixedSize</enum> </property> + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QListWidget" name="tabs"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>140</width> + <height>16777215</height> + </size> + </property> + <property name="currentRow"> + <number>0</number> + </property> + <item> + <property name="text"> + <string>Audio/Video</string> + </property> + </item> + <item> + <property name="text"> + <string>Emulation</string> + </property> + </item> + <item> + <property name="text"> + <string>Savestates</string> + </property> + </item> + <item> + <property name="text"> + <string>Paths</string> + </property> + </item> + </widget> + </item> <item row="1" column="1"> <widget class="QStackedWidget" name="stackedWidget"> <property name="currentIndex"> - <number>0</number> + <number>2</number> </property> <widget class="QWidget" name="stackedWidgetPage1"> <layout class="QFormLayout" name="formLayout">@@ -374,77 +420,35 @@ </item>
<item row="1" column="1"> <widget class="QCheckBox" name="useBios"> <property name="text"> - <string>Use BIOS file</string> + <string>Use BIOS file if found</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_4"> + <item row="2" column="1"> + <widget class="QCheckBox" name="skipBios"> <property name="text"> - <string>Create rewind state:</string> + <string>Skip BIOS intro</string> </property> </widget> </item> - <item row="4" column="1"> - <widget class="QCheckBox" name="rewind"> - <property name="text"> - <string>Enable rewind</string> + <item row="3" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="5" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_12"> - <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Every</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="rewindInterval"/> - </item> - <item> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>frames</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_8"> - <property name="text"> - <string>Rewind history:</string> - </property> - </widget> - </item> - <item row="6" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_13"> - <item> - <widget class="QSpinBox" name="rewindCapacity"/> - </item> - <item> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>states</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="8" column="0"> + <item row="4" column="0"> <widget class="QLabel" name="label_18"> <property name="text"> <string>Fast forward speed</string> </property> </widget> </item> - <item row="8" column="1"> + <item row="4" column="1"> <widget class="QDoubleSpinBox" name="fastForwardRatio"> <property name="enabled"> <bool>false</bool>@@ -466,7 +470,7 @@ <double>5.000000000000000</double>
</property> </widget> </item> - <item row="9" column="1"> + <item row="5" column="1"> <widget class="QCheckBox" name="fastForwardUnbounded"> <property name="text"> <string>Unbounded</string>@@ -476,14 +480,21 @@ <bool>true</bool>
</property> </widget> </item> - <item row="11" column="1"> + <item row="6" column="0" colspan="2"> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="7" column="1"> <widget class="QCheckBox" name="allowOpposingDirections"> <property name="text"> <string>Allow opposing input directions</string> </property> </widget> </item> - <item row="12" column="1"> + <item row="8" column="1"> <widget class="QCheckBox" name="suspendScreensaver"> <property name="text"> <string>Suspend screensaver</string>@@ -493,14 +504,21 @@ <bool>true</bool>
</property> </widget> </item> - <item row="14" column="0"> + <item row="9" column="1"> + <widget class="QCheckBox" name="pauseOnFocusLost"> + <property name="text"> + <string>Pause when inactive</string> + </property> + </widget> + </item> + <item row="10" column="0"> <widget class="QLabel" name="label_15"> <property name="text"> <string>Idle loops</string> </property> </widget> </item> - <item row="14" column="1"> + <item row="10" column="1"> <widget class="QComboBox" name="idleOptimization"> <item> <property name="text">@@ -519,41 +537,172 @@ </property>
</item> </widget> </item> + </layout> + </widget> + <widget class="QWidget" name="page_2"> + <layout class="QFormLayout" name="formLayout_4"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::FieldsStayAtSizeHint</enum> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label_24"> + <property name="text"> + <string>Save extra data</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="saveStateScreenshot"> + <property name="text"> + <string>Screenshot</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="saveStateSave"> + <property name="text"> + <string>Save data</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> <item row="2" column="1"> - <widget class="QCheckBox" name="skipBios"> + <widget class="QCheckBox" name="saveStateCheats"> <property name="text"> - <string>Skip BIOS intro</string> + <string>Cheat codes</string> + </property> + <property name="checked"> + <bool>true</bool> </property> </widget> </item> - <item row="3" column="0" colspan="2"> - <widget class="Line" name="line"> + <item row="4" column="0" colspan="2"> + <widget class="Line" name="line_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="7" column="0" colspan="2"> + <item row="5" column="0"> + <widget class="QLabel" name="label_25"> + <property name="text"> + <string>Load extra data</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="loadStateScreenshot"> + <property name="text"> + <string>Screenshot</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QCheckBox" name="loadStateSave"> + <property name="text"> + <string>Save data</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QCheckBox" name="loadStateCheats"> + <property name="text"> + <string>Cheat codes</string> + </property> + </widget> + </item> + <item row="8" column="0" colspan="2"> <widget class="Line" name="line_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="10" column="0" colspan="2"> - <widget class="Line" name="line_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <item row="9" column="1"> + <widget class="QCheckBox" name="rewind"> + <property name="text"> + <string>Enable rewind</string> </property> </widget> </item> - <item row="13" column="1"> - <widget class="QCheckBox" name="pauseOnFocusLost"> + <item row="10" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Create rewind state:</string> + </property> + </widget> + </item> + <item row="10" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Every</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="rewindInterval"/> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>frames</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="11" column="0"> + <widget class="QLabel" name="label_8"> <property name="text"> - <string>Pause when inactive</string> + <string>Rewind history:</string> </property> </widget> </item> + <item row="11" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <item> + <widget class="QSpinBox" name="rewindCapacity"/> + </item> + <item> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>states</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="12" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QDoubleSpinBox" name="rewindDuration"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maximum"> + <double>999.990000000000009</double> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_26"> + <property name="text"> + <string>seconds</string> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> <widget class="QWidget" name="page">@@ -750,44 +899,6 @@ </layout>
</widget> </widget> </item> - <item row="1" column="0"> - <widget class="QListWidget" name="tabs"> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>140</width> - <height>16777215</height> - </size> - </property> - <item> - <property name="text"> - <string>Audio/Video</string> - </property> - </item> - <item> - <property name="text"> - <string>Emulation</string> - </property> - </item> - <item> - <property name="text"> - <string>Paths</string> - </property> - </item> - </widget> - </item> - <item row="2" column="0" colspan="2"> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> </layout> </widget> <resources/>@@ -901,6 +1012,22 @@ </hint>
<hint type="destinationlabel"> <x>340</x> <y>285</y> + </hint> + </hints> + </connection> + <connection> + <sender>fastForwardUnbounded</sender> + <signal>toggled(bool)</signal> + <receiver>fastForwardRatio</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>338</x> + <y>163</y> + </hint> + <hint type="destinationlabel"> + <x>327</x> + <y>135</y> </hint> </hints> </connection>
@@ -241,6 +241,19 @@ const GBAOptions* opts = m_config->options();
m_log.setLevels(opts->logLevel); + QString saveStateExtdata = m_config->getOption("saveStateExtdata"); + bool ok; + int flags = saveStateExtdata.toInt(&ok); + if (ok) { + m_controller->setSaveStateExtdata(flags); + } + + QString loadStateExtdata = m_config->getOption("loadStateExtdata"); + flags = loadStateExtdata.toInt(&ok); + if (ok) { + m_controller->setLoadStateExtdata(flags); + } + m_controller->setOptions(opts); m_display->lockAspectRatio(opts->lockAspectRatio); m_display->filter(opts->resampleVideo);@@ -1237,6 +1250,16 @@
ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections"); allowOpposingDirections->connect([this](const QVariant& value) { m_inputController.setAllowOpposing(value.toBool()); + }, this); + + ConfigOption* saveStateExtdata = m_config->addOption("saveStateExtdata"); + saveStateExtdata->connect([this](const QVariant& value) { + m_controller->setSaveStateExtdata(value.toInt()); + }, this); + + ConfigOption* loadStateExtdata = m_config->addOption("loadStateExtdata"); + loadStateExtdata->connect([this](const QVariant& value) { + m_controller->setLoadStateExtdata(value.toInt()); }, this); QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu);