// Copyright 2018 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "DolphinQt/Config/GameConfigWidget.h" #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 "DolphinQt/Config/ConfigControls/ConfigSlider.h" #include "DolphinQt/Config/GameConfigEdit.h" #include "UICommon/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"; static void PopulateTab(QTabWidget* tab, const std::string& path, std::string& game_id, u16 revision, bool read_only) { for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision)) { const std::string ini_path = path + filename; if (File::Exists(ini_path)) { auto* edit = new GameConfigEdit(nullptr, QString::fromStdString(ini_path), read_only); tab->addTab(edit, QString::fromStdString(filename)); } } } GameConfigWidget::GameConfigWidget(const UICommon::GameFile& game) : m_game(game) { m_game_id = m_game.GetGameID(); m_gameini_local_path = QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); CreateWidgets(); LoadSettings(); ConnectWidgets(); PopulateTab(m_default_tab, File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP, m_game_id, m_game.GetRevision(), true); PopulateTab(m_local_tab, File::GetUserPath(D_GAMESETTINGS_IDX), m_game_id, m_game.GetRevision(), false); // Always give the user the opportunity to create a new INI if (m_local_tab->count() == 0) { auto* edit = new GameConfigEdit( nullptr, QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"), false); m_local_tab->addTab(edit, QString::fromStdString(m_game_id + ".ini")); } } void GameConfigWidget::CreateWidgets() { // General m_refresh_config = new QPushButton(tr("Refresh")); // 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_enable_fprf = new QCheckBox(tr("Enable FPRF")); m_sync_gpu = new QCheckBox(tr("Synchronize GPU thread")); m_emulate_disc_speed = new QCheckBox(tr("Emulate Disc Speed")); m_use_dsp_hle = new QCheckBox(tr("DSP HLE (fast)")); m_manual_texture_sampling = new QCheckBox(tr("Manual Texture Sampling")); 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_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_emulate_disc_speed->setToolTip( tr("Enable emulated disc speed. Disabling this can cause crashes " "and other problems in some games. " "(ON = Compatible, OFF = Unlocked)")); core_layout->addWidget(m_enable_dual_core, 0, 0); core_layout->addWidget(m_enable_mmu, 1, 0); core_layout->addWidget(m_enable_fprf, 2, 0); core_layout->addWidget(m_sync_gpu, 3, 0); core_layout->addWidget(m_emulate_disc_speed, 4, 0); core_layout->addWidget(m_use_dsp_hle, 5, 0); core_layout->addWidget(m_manual_texture_sampling, 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); auto* general_layout = new QGridLayout; general_layout->addWidget(settings_box, 0, 0, 1, -1); general_layout->addWidget(m_refresh_config, 1, 0, 1, -1); for (QCheckBox* item : {m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu, m_emulate_disc_speed, m_use_dsp_hle, m_manual_texture_sampling, m_use_monoscopic_shadows}) item->setTristate(true); auto* general_widget = new QWidget; general_widget->setLayout(general_layout); // Advanced auto* advanced_layout = new QVBoxLayout; auto* default_group = new QGroupBox(tr("Default Config (Read Only)")); auto* default_layout = new QVBoxLayout; m_default_tab = new QTabWidget; default_group->setLayout(default_layout); default_layout->addWidget(m_default_tab); auto* local_group = new QGroupBox(tr("User Config")); auto* local_layout = new QVBoxLayout; m_local_tab = new QTabWidget; local_group->setLayout(local_layout); local_layout->addWidget(m_local_tab); advanced_layout->addWidget(default_group); advanced_layout->addWidget(local_group); auto* advanced_widget = new QWidget; advanced_widget->setLayout(advanced_layout); auto* layout = new QVBoxLayout; auto* tab_widget = new QTabWidget; tab_widget->addTab(general_widget, tr("General")); tab_widget->addTab(advanced_widget, tr("Editor")); layout->addWidget(tab_widget); setLayout(layout); } void GameConfigWidget::ConnectWidgets() { // Buttons connect(m_refresh_config, &QPushButton::clicked, this, &GameConfigWidget::LoadSettings); for (QCheckBox* box : {m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu, m_emulate_disc_speed, m_use_dsp_hle, m_manual_texture_sampling, m_use_monoscopic_shadows}) connect(box, &QCheckBox::stateChanged, this, &GameConfigWidget::SaveSettings); connect(m_deterministic_dual_core, qOverload(&QComboBox::currentIndexChanged), this, &GameConfigWidget::SaveSettings); connect(m_depth_slider, qOverload(&QSlider::valueChanged), this, &GameConfigWidget::SaveSettings); connect(m_convergence_spin, qOverload(&QSpinBox::valueChanged), this, &GameConfigWidget::SaveSettings); } void GameConfigWidget::LoadCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key, bool reverse) { bool checked; if (m_gameini_local.GetOrCreateSection(section)->Get(key, &checked)) return checkbox->setCheckState(checked ^ reverse ? Qt::Checked : Qt::Unchecked); if (m_gameini_default.GetOrCreateSection(section)->Get(key, &checked)) return checkbox->setCheckState(checked ^ reverse ? Qt::Checked : Qt::Unchecked); checkbox->setCheckState(Qt::PartiallyChecked); } void GameConfigWidget::SaveCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key, bool reverse) { // 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) ^ reverse; 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() { // Reload config m_gameini_local = SConfig::LoadLocalGameIni(m_game_id, m_game.GetRevision()); m_gameini_default = SConfig::LoadDefaultGameIni(m_game_id, m_game.GetRevision()); // Load game-specific settings // Core LoadCheckBox(m_enable_dual_core, "Core", "CPUThread"); LoadCheckBox(m_enable_mmu, "Core", "MMU"); LoadCheckBox(m_enable_fprf, "Core", "FPRF"); LoadCheckBox(m_sync_gpu, "Core", "SyncGPU"); LoadCheckBox(m_emulate_disc_speed, "Core", "FastDiscSpeed", true); LoadCheckBox(m_use_dsp_hle, "Core", "DSPHLE"); LoadCheckBox(m_manual_texture_sampling, "Video_Hacks", "FastTextureSampling", true); 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() { // Core SaveCheckBox(m_enable_dual_core, "Core", "CPUThread"); SaveCheckBox(m_enable_mmu, "Core", "MMU"); SaveCheckBox(m_enable_fprf, "Core", "FPRF"); SaveCheckBox(m_sync_gpu, "Core", "SyncGPU"); SaveCheckBox(m_emulate_disc_speed, "Core", "FastDiscSpeed", true); SaveCheckBox(m_use_dsp_hle, "Core", "DSPHLE"); SaveCheckBox(m_manual_texture_sampling, "Video_Hacks", "FastTextureSampling", true); 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); } } else { m_gameini_local.DeleteKey("Core", "GPUDeterminismMode"); } // 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()); }