From 394388af4ed8a7d17372f69943c11ce426ce0269 Mon Sep 17 00:00:00 2001 From: spycrab Date: Thu, 1 Mar 2018 13:12:31 +0100 Subject: [PATCH] Qt/PropertiesDialog: Add "Game Config" tab --- Source/Core/DolphinQt2/CMakeLists.txt | 1 + .../DolphinQt2/Config/GameConfigWidget.cpp | 408 ++++++++++++++++++ .../Core/DolphinQt2/Config/GameConfigWidget.h | 70 +++ .../DolphinQt2/Config/PropertiesDialog.cpp | 5 +- Source/Core/DolphinQt2/DolphinQt2.vcxproj | 6 +- 5 files changed, 488 insertions(+), 2 deletions(-) create mode 100644 Source/Core/DolphinQt2/Config/GameConfigWidget.cpp create mode 100644 Source/Core/DolphinQt2/Config/GameConfigWidget.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 69217a4ca3..36dfa6d599 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRCS Config/CheatWarningWidget.cpp Config/ControllersWindow.cpp Config/FilesystemWidget.cpp + Config/GameConfigWidget.cpp Config/GeckoCodeWidget.cpp Config/Graphics/AdvancedWidget.cpp Config/Graphics/EnhancementsWidget.cpp diff --git a/Source/Core/DolphinQt2/Config/GameConfigWidget.cpp b/Source/Core/DolphinQt2/Config/GameConfigWidget.cpp new file mode 100644 index 0000000000..2421d056f9 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/GameConfigWidget.cpp @@ -0,0 +1,408 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Config/GameConfigWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonPaths.h" +#include "Common/FileUtil.h" +#include "Core/ConfigLoaders/GameConfigLoader.h" +#include "Core/ConfigManager.h" +#include "DolphinQt2/Config/Graphics/GraphicsSlider.h" +#include "DolphinQt2/GameList/GameFile.h" + +constexpr int DETERMINISM_NOT_SET_INDEX = 0; +constexpr int DETERMINISM_AUTO_INDEX = 1; +constexpr int DETERMINISM_NONE_INDEX = 2; +constexpr int DETERMINISM_FAKE_COMPLETION_INDEX = 3; + +constexpr const char* DETERMINISM_NOT_SET_STRING = ""; +constexpr const char* DETERMINISM_AUTO_STRING = "auto"; +constexpr const char* DETERMINISM_NONE_STRING = "none"; +constexpr const char* DETERMINISM_FAKE_COMPLETION_STRING = "fake-completion"; + +GameConfigWidget::GameConfigWidget(const GameFile& game) : m_game(game) +{ + m_game_id = m_game.GetGameID().toStdString(); + m_gameini_local_path = + QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); + m_gameini_local = SConfig::LoadLocalGameIni(m_game_id, m_game.GetRevision()); + m_gameini_default = SConfig::LoadDefaultGameIni(m_game_id, m_game.GetRevision()); + + CreateWidgets(); + LoadSettings(); + ConnectWidgets(); +} + +void GameConfigWidget::CreateWidgets() +{ + m_refresh_config = new QPushButton(tr("Refresh")); + m_edit_user_config = new QPushButton(tr("Edit User Config")); + m_view_default_config = new QPushButton(tr("View Default Config")); + + // Core + auto* core_box = new QGroupBox(tr("Core")); + auto* core_layout = new QGridLayout; + core_box->setLayout(core_layout); + + m_enable_dual_core = new QCheckBox(tr("Enable Dual Core")); + m_enable_mmu = new QCheckBox(tr("Enable MMU")); + m_skip_dcbz = new QCheckBox(tr("Skip DCBZ clearing")); + m_enable_fprf = new QCheckBox(tr("Enable FPRF")); + m_sync_gpu = new QCheckBox(tr("Synchronize GPU thread")); + m_enable_fast_disc = new QCheckBox(tr("Speed up Disc Transfer Rate")); + m_use_dsp_hle = new QCheckBox(tr("DSP HLE Emulation (fast)")); + m_deterministic_dual_core = new QComboBox; + + for (const auto& item : {tr("Not Set"), tr("auto"), tr("none"), tr("fake-completion")}) + m_deterministic_dual_core->addItem(item); + + m_enable_mmu->setToolTip(tr( + "Enables the Memory Management Unit, needed for some games. (ON = Compatible, OFF = Fast)")); + m_skip_dcbz->setToolTip(tr("Bypass the clearing of the data cache by the DCBZ instruction. " + "Usually leave this option disabled.")); + m_enable_fprf->setToolTip(tr("Enables Floating Point Result Flag calculation, needed for a few " + "games. (ON = Compatible, OFF = Fast)")); + m_sync_gpu->setToolTip(tr("Synchronizes the GPU and CPU threads to help prevent random freezes " + "in Dual core mode. (ON = Compatible, OFF = Fast)")); + m_enable_fast_disc->setToolTip(tr("Enable fast disc access. This can cause crashes and other " + "problems in some games. (ON = Fast, OFF = Compatible)")); + + core_layout->addWidget(m_enable_dual_core, 0, 0); + core_layout->addWidget(m_enable_mmu, 1, 0); + core_layout->addWidget(m_skip_dcbz, 2, 0); + core_layout->addWidget(m_enable_fprf, 3, 0); + core_layout->addWidget(m_sync_gpu, 4, 0); + core_layout->addWidget(m_enable_fast_disc, 5, 0); + core_layout->addWidget(m_use_dsp_hle, 6, 0); + core_layout->addWidget(new QLabel(tr("Deterministic dual core:")), 7, 0); + core_layout->addWidget(m_deterministic_dual_core, 7, 1); + + // Stereoscopy + auto* stereoscopy_box = new QGroupBox(tr("Stereoscopy")); + auto* stereoscopy_layout = new QGridLayout; + stereoscopy_box->setLayout(stereoscopy_layout); + + m_depth_slider = new QSlider(Qt::Horizontal); + + m_depth_slider->setMinimum(100); + m_depth_slider->setMaximum(200); + + m_convergence_spin = new QSpinBox; + m_convergence_spin->setMinimum(0); + m_convergence_spin->setMaximum(INT32_MAX); + m_use_monoscopic_shadows = new QCheckBox(tr("Monoscopic Shadows")); + + m_depth_slider->setToolTip( + tr("This value is multiplied with the depth set in the graphics configuration.")); + m_convergence_spin->setToolTip( + tr("This value is added to the convergence value set in the graphics configuration.")); + m_use_monoscopic_shadows->setToolTip( + tr("Use a single depth buffer for both eyes. Needed for a few games.")); + + stereoscopy_layout->addWidget(new QLabel(tr("Depth Percentage:")), 0, 0); + stereoscopy_layout->addWidget(m_depth_slider, 0, 1); + stereoscopy_layout->addWidget(new QLabel(tr("Convergence:")), 1, 0); + stereoscopy_layout->addWidget(m_convergence_spin, 1, 1); + stereoscopy_layout->addWidget(m_use_monoscopic_shadows, 2, 0); + + auto* settings_box = new QGroupBox(tr("Game-Specific Settings")); + auto* settings_layout = new QVBoxLayout; + settings_box->setLayout(settings_layout); + + settings_layout->addWidget( + new QLabel(tr("These settings override core Dolphin settings.\nUndetermined means the game " + "uses Dolphin's setting."))); + settings_layout->addWidget(core_box); + settings_layout->addWidget(stereoscopy_box); + + m_state_combo = new QComboBox; + + for (const auto& item : + {tr("Not Set"), tr("Broken"), tr("Intro"), tr("In Game"), tr("Playable"), tr("Perfect")}) + m_state_combo->addItem(item); + + m_state_comment_edit = new QLineEdit; + + auto* layout = new QGridLayout; + + auto* emulation_state = new QLabel(tr("Emulation State:")); + + layout->addWidget(settings_box, 0, 0, 1, -1); + layout->addWidget(emulation_state, 1, 0); + layout->addWidget(m_state_combo, 1, 1); + layout->addWidget(m_state_comment_edit, 1, 2, 1, -1); + + auto* button_layout = new QHBoxLayout; + button_layout->setMargin(0); + + layout->addLayout(button_layout, 2, 0, 1, -1); + + button_layout->addWidget(m_refresh_config); + button_layout->addWidget(m_edit_user_config); + button_layout->addWidget(m_view_default_config); + + for (QCheckBox* item : {m_enable_dual_core, m_enable_mmu, m_skip_dcbz, m_enable_fprf, m_sync_gpu, + m_enable_fast_disc, m_use_dsp_hle, m_use_monoscopic_shadows}) + item->setTristate(true); + + emulation_state->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_state_combo->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + setLayout(layout); +} + +void GameConfigWidget::ConnectWidgets() +{ + // Buttons + connect(m_refresh_config, &QPushButton::pressed, this, &GameConfigWidget::LoadSettings); + connect(m_edit_user_config, &QPushButton::pressed, this, &GameConfigWidget::EditUserConfig); + connect(m_view_default_config, &QPushButton::pressed, this, &GameConfigWidget::ViewDefaultConfig); + + // Settings + connect(m_state_combo, static_cast(&QComboBox::currentIndexChanged), + this, &GameConfigWidget::SaveSettings); + connect(m_state_comment_edit, &QLineEdit::editingFinished, this, &GameConfigWidget::SaveSettings); + + for (QCheckBox* box : {m_enable_dual_core, m_enable_mmu, m_skip_dcbz, m_enable_fprf, m_sync_gpu, + m_enable_fast_disc, m_use_dsp_hle, m_use_monoscopic_shadows}) + connect(box, &QCheckBox::toggled, this, &GameConfigWidget::SaveSettings); + + connect(m_deterministic_dual_core, + static_cast(&QComboBox::currentIndexChanged), this, + &GameConfigWidget::SaveSettings); + connect(m_depth_slider, static_cast(&QSlider::valueChanged), this, + &GameConfigWidget::SaveSettings); + connect(m_convergence_spin, static_cast(&QSpinBox::valueChanged), this, + &GameConfigWidget::SaveSettings); +} + +void GameConfigWidget::LoadCheckBox(QCheckBox* checkbox, const std::string& section, + const std::string& key) +{ + bool checked; + + if (m_gameini_local.GetOrCreateSection(section)->Get(key, &checked)) + return checkbox->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + + if (m_gameini_default.GetOrCreateSection(section)->Get(key, &checked)) + return checkbox->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + + checkbox->setCheckState(Qt::PartiallyChecked); +} + +void GameConfigWidget::SaveCheckBox(QCheckBox* checkbox, const std::string& section, + const std::string& key) +{ + // Delete any existing entries from the local gameini if checkbox is undetermined. + // Otherwise, write the current value to the local gameini if the value differs from the default + // gameini values. + // Delete any existing entry from the local gameini if the value does not differ from the default + // gameini value. + + if (checkbox->checkState() == Qt::PartiallyChecked) + { + m_gameini_local.DeleteKey(section, key); + return; + } + + bool checked = checkbox->checkState() == Qt::Checked; + + if (m_gameini_default.Exists(section, key)) + { + bool default_value; + m_gameini_default.GetOrCreateSection(section)->Get(key, &default_value); + + if (default_value != checked) + m_gameini_local.GetOrCreateSection(section)->Set(key, checked); + else + m_gameini_local.DeleteKey(section, key); + + return; + } + + m_gameini_local.GetOrCreateSection(section)->Set(key, checked); +} + +void GameConfigWidget::LoadSettings() +{ + // Load state information + m_state_combo->setCurrentIndex(m_game.GetRating()); + m_state_comment_edit->setText(m_game.GetIssues()); + + // Load game-specific settings + + // Core + LoadCheckBox(m_enable_dual_core, "Core", "CPUThread"); + LoadCheckBox(m_enable_mmu, "Core", "MMU"); + LoadCheckBox(m_skip_dcbz, "Core", "DCBZ"); + LoadCheckBox(m_enable_fprf, "Core", "FPRF"); + LoadCheckBox(m_sync_gpu, "Core", "SyncGPU"); + LoadCheckBox(m_enable_fast_disc, "Core", "FastDiscSpeed"); + LoadCheckBox(m_use_dsp_hle, "Core", "DSPHLE"); + + std::string determinism_mode; + + int determinism_index = DETERMINISM_NOT_SET_INDEX; + + m_gameini_default.GetIfExists("Core", "GPUDeterminismMode", &determinism_mode); + m_gameini_local.GetIfExists("Core", "GPUDeterminismMode", &determinism_mode); + + if (determinism_mode == DETERMINISM_AUTO_STRING) + { + determinism_index = DETERMINISM_AUTO_INDEX; + } + else if (determinism_mode == DETERMINISM_NONE_STRING) + { + determinism_index = DETERMINISM_NONE_INDEX; + } + else if (determinism_mode == DETERMINISM_FAKE_COMPLETION_STRING) + { + determinism_index = DETERMINISM_FAKE_COMPLETION_INDEX; + } + + m_deterministic_dual_core->setCurrentIndex(determinism_index); + + // Stereoscopy + int depth_percentage = 100; + + m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage", &depth_percentage); + m_gameini_local.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage", &depth_percentage); + + m_depth_slider->setValue(depth_percentage); + + int convergence = 0; + + m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoConvergence", &convergence); + m_gameini_local.GetIfExists("Video_Stereoscopy", "StereoConvergence", &convergence); + + m_convergence_spin->setValue(convergence); + + LoadCheckBox(m_use_monoscopic_shadows, "Video_Stereoscopy", "StereoEFBMonoDepth"); +} + +void GameConfigWidget::SaveSettings() +{ + // Save state information + QString comment = m_state_comment_edit->text(); + int state = m_state_combo->currentIndex(); + + if (comment != m_game.GetIssues()) + m_gameini_local.GetOrCreateSection("EmuState")->Set("EmulationIssues", comment.toStdString()); + + if (state != m_game.GetRating()) + m_gameini_local.GetOrCreateSection("EmuState")->Set("EmulationStateId", state); + + // Save game-specific settings + + // Core + SaveCheckBox(m_enable_dual_core, "Core", "CPUThread"); + SaveCheckBox(m_enable_mmu, "Core", "MMU"); + SaveCheckBox(m_skip_dcbz, "Core", "DCBZ"); + SaveCheckBox(m_enable_fprf, "Core", "FPRF"); + SaveCheckBox(m_sync_gpu, "Core", "SyncGPU"); + SaveCheckBox(m_enable_fast_disc, "Core", "FastDiscSpeed"); + SaveCheckBox(m_use_dsp_hle, "Core", "DSPHLE"); + + int determinism_num = m_deterministic_dual_core->currentIndex(); + + std::string determinism_mode = DETERMINISM_NOT_SET_STRING; + + switch (determinism_num) + { + case DETERMINISM_AUTO_INDEX: + determinism_mode = DETERMINISM_AUTO_STRING; + break; + case DETERMINISM_NONE_INDEX: + determinism_mode = DETERMINISM_NONE_STRING; + break; + case DETERMINISM_FAKE_COMPLETION_INDEX: + determinism_mode = DETERMINISM_FAKE_COMPLETION_STRING; + break; + } + + if (determinism_mode != DETERMINISM_NOT_SET_STRING) + { + std::string default_mode = DETERMINISM_NOT_SET_STRING; + if (!(m_gameini_default.GetIfExists("Core", "GPUDeterminismMode", &default_mode) && + default_mode == determinism_mode)) + { + m_gameini_local.GetOrCreateSection("Core")->Set("GPUDeterminismMode", determinism_mode); + } + } + + // Stereoscopy + int depth_percentage = m_depth_slider->value(); + + if (depth_percentage != 100) + { + int default_value = 0; + if (!(m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage", + &default_value) && + default_value == depth_percentage)) + { + m_gameini_local.GetOrCreateSection("Video_Stereoscopy") + ->Set("StereoDepthPercentage", depth_percentage); + } + } + + int convergence = m_convergence_spin->value(); + if (convergence != 0) + { + int default_value = 0; + if (!(m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoConvergence", &default_value) && + default_value == convergence)) + { + m_gameini_local.GetOrCreateSection("Video_Stereoscopy") + ->Set("StereoConvergence", convergence); + } + } + + SaveCheckBox(m_use_monoscopic_shadows, "Video_Stereoscopy", "StereoEFBMonoDepth"); + + bool success = m_gameini_local.Save(m_gameini_local_path.toStdString()); + + // If the resulting file is empty, delete it. Kind of a hack, but meh. + if (success && File::GetSize(m_gameini_local_path.toStdString()) == 0) + File::Delete(m_gameini_local_path.toStdString()); +} + +void GameConfigWidget::EditUserConfig() +{ + QFile file(m_gameini_local_path); + + if (!file.exists()) + { + file.open(QIODevice::WriteOnly); + file.close(); + } + + QDesktopServices::openUrl(QUrl::fromLocalFile(m_gameini_local_path)); +} + +void GameConfigWidget::ViewDefaultConfig() +{ + for (const std::string& filename : + ConfigLoaders::GetGameIniFilenames(m_game_id, m_game.GetRevision())) + { + QString path = + QString::fromStdString(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename); + + if (QFile(path).exists()) + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + } +} diff --git a/Source/Core/DolphinQt2/Config/GameConfigWidget.h b/Source/Core/DolphinQt2/Config/GameConfigWidget.h new file mode 100644 index 0000000000..2062d55224 --- /dev/null +++ b/Source/Core/DolphinQt2/Config/GameConfigWidget.h @@ -0,0 +1,70 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include +#include + +#include "Common/IniFile.h" + +class GameFile; +class QCheckBox; +class QComboBox; +class QGroupBox; +class QLineEdit; +class QPushButton; +class QSlider; +class QSpinBox; +class QVBoxLayout; + +class GameConfigWidget : public QWidget +{ + Q_OBJECT +public: + explicit GameConfigWidget(const GameFile& game); + +private: + void CreateWidgets(); + void ConnectWidgets(); + void LoadSettings(); + void SaveSettings(); + + void EditUserConfig(); + void ViewDefaultConfig(); + + void LoadCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key); + void SaveCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key); + + QComboBox* m_state_combo; + QLineEdit* m_state_comment_edit; + QPushButton* m_refresh_config; + QPushButton* m_edit_user_config; + QPushButton* m_view_default_config; + + // Core + QCheckBox* m_enable_dual_core; + QCheckBox* m_enable_mmu; + QCheckBox* m_skip_dcbz; + QCheckBox* m_enable_fprf; + QCheckBox* m_sync_gpu; + QCheckBox* m_enable_fast_disc; + QCheckBox* m_use_dsp_hle; + QComboBox* m_deterministic_dual_core; + + // Stereoscopy + QSlider* m_depth_slider; + QSpinBox* m_convergence_spin; + QCheckBox* m_use_monoscopic_shadows; + + QString m_gameini_local_path; + + IniFile m_gameini_local; + IniFile m_gameini_default; + + const GameFile& m_game; + std::string m_game_id; +}; diff --git a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp index 36f2aef707..cd2355d5d7 100644 --- a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp +++ b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp @@ -8,6 +8,7 @@ #include "DolphinQt2/Config/ARCodeWidget.h" #include "DolphinQt2/Config/FilesystemWidget.h" +#include "DolphinQt2/Config/GameConfigWidget.h" #include "DolphinQt2/Config/GeckoCodeWidget.h" #include "DolphinQt2/Config/InfoWidget.h" #include "DolphinQt2/Config/PatchesWidget.h" @@ -25,16 +26,18 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia ARCodeWidget* ar = new ARCodeWidget(game); GeckoCodeWidget* gecko = new GeckoCodeWidget(game); PatchesWidget* patches = new PatchesWidget(game); + GameConfigWidget* game_config = new GameConfigWidget(game); connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings); connect(ar, &ARCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings); - tab_widget->addTab(info, tr("Info")); + tab_widget->addTab(game_config, tr("Game Config")); tab_widget->addTab(patches, tr("Patches")); tab_widget->addTab(ar, tr("AR Codes")); tab_widget->addTab(gecko, tr("Gecko Codes")); + tab_widget->addTab(info, tr("Info")); if (DiscIO::IsDisc(game.GetPlatformID())) { diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index fd55e37ca2..7931dfa998 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -65,6 +65,7 @@ + @@ -109,7 +110,7 @@ - + @@ -144,6 +145,7 @@ + @@ -196,6 +198,7 @@ + @@ -308,6 +311,7 @@ +