duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

settingswindow.cpp (23436B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "settingswindow.h"
      5 #include "advancedsettingswidget.h"
      6 #include "audiosettingswidget.h"
      7 #include "biossettingswidget.h"
      8 #include "consolesettingswidget.h"
      9 
     10 #include "achievementsettingswidget.h"
     11 #include "emulationsettingswidget.h"
     12 #include "foldersettingswidget.h"
     13 #include "gamelistsettingswidget.h"
     14 #include "gamesummarywidget.h"
     15 #include "graphicssettingswidget.h"
     16 #include "interfacesettingswidget.h"
     17 #include "mainwindow.h"
     18 #include "memorycardsettingswidget.h"
     19 #include "postprocessingsettingswidget.h"
     20 #include "qthost.h"
     21 
     22 #include "core/achievements.h"
     23 #include "core/host.h"
     24 
     25 #include "util/ini_settings_interface.h"
     26 
     27 #include "common/assert.h"
     28 #include "common/error.h"
     29 #include "common/file_system.h"
     30 #include "common/log.h"
     31 
     32 #include <QtGui/QWheelEvent>
     33 #include <QtWidgets/QMessageBox>
     34 #include <QtWidgets/QScrollBar>
     35 #include <QtWidgets/QTextEdit>
     36 
     37 Log_SetChannel(SettingsWindow);
     38 
     39 static QList<SettingsWindow*> s_open_game_properties_dialogs;
     40 
     41 SettingsWindow::SettingsWindow() : QWidget()
     42 {
     43   m_ui.setupUi(this);
     44   setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
     45   addPages();
     46   connectUi();
     47 }
     48 
     49 SettingsWindow::SettingsWindow(const std::string& path, const std::string& serial, DiscRegion region,
     50                                const GameDatabase::Entry* entry, std::unique_ptr<INISettingsInterface> sif)
     51   : QWidget(), m_sif(std::move(sif)), m_database_entry(entry)
     52 {
     53   m_ui.setupUi(this);
     54   setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
     55 
     56   addWidget(new GameSummaryWidget(path, serial, region, entry, this, m_ui.settingsContainer), tr("Summary"),
     57             QStringLiteral("file-list-line"),
     58             tr("<strong>Summary</strong><hr>This page shows information about the selected game, and allows you to "
     59                "validate your disc was dumped correctly."));
     60   addPages();
     61   connectUi();
     62 
     63   s_open_game_properties_dialogs.push_back(this);
     64 }
     65 
     66 SettingsWindow::~SettingsWindow()
     67 {
     68   if (isPerGameSettings())
     69     s_open_game_properties_dialogs.removeOne(this);
     70 }
     71 
     72 void SettingsWindow::closeEvent(QCloseEvent* event)
     73 {
     74   // we need to clean up ourselves, since we're not modal
     75   if (isPerGameSettings())
     76     deleteLater();
     77 }
     78 
     79 void SettingsWindow::addPages()
     80 {
     81   addWidget(
     82     m_interface_settings = new InterfaceSettingsWidget(this, m_ui.settingsContainer), tr("Interface"),
     83     QStringLiteral("settings-3-line"),
     84     tr("<strong>Interface Settings</strong><hr>These options control how the emulator looks and "
     85        "behaves.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
     86 
     87   if (!isPerGameSettings())
     88   {
     89     addWidget(
     90       m_game_list_settings = new GameListSettingsWidget(this, m_ui.settingsContainer), tr("Game List"),
     91       QStringLiteral("folder-open-line"),
     92       tr("<strong>Game List Settings</strong><hr>The list above shows the directories which will be searched by "
     93          "DuckStation to populate the game list. Search directories can be added, removed, and switched to "
     94          "recursive/non-recursive."));
     95   }
     96 
     97   addWidget(
     98     m_bios_settings = new BIOSSettingsWidget(this, m_ui.settingsContainer), tr("BIOS"), QStringLiteral("chip-line"),
     99     tr("<strong>BIOS Settings</strong><hr>These options control which BIOS is used and how it will be "
    100        "patched.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
    101   addWidget(
    102     m_console_settings = new ConsoleSettingsWidget(this, m_ui.settingsContainer), tr("Console"),
    103     QStringLiteral("chip-2-line"),
    104     tr("<strong>Console Settings</strong><hr>These options determine the configuration of the simulated "
    105        "console.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
    106   addWidget(
    107     m_emulation_settings = new EmulationSettingsWidget(this, m_ui.settingsContainer), tr("Emulation"),
    108     QStringLiteral("emulation-line"),
    109     tr("<strong>Emulation Settings</strong><hr>These options determine the speed and runahead behavior of the "
    110        "system.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
    111   addWidget(
    112     m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"),
    113     QStringLiteral("memcard-line"),
    114     tr("<strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will "
    115        "function in, and where the images for these cards will be stored on disk."));
    116   addWidget(m_graphics_settings = new GraphicsSettingsWidget(this, m_ui.settingsContainer), tr("Graphics"),
    117             QStringLiteral("image-fill"),
    118             tr("<strong>Graphics Settings</strong><hr>These options control how the graphics of the emulated console "
    119                "are rendered. Not all options are available for the software renderer. Mouse over each option for "
    120                "additional information, and Shift+Wheel to scroll this panel."));
    121   addWidget(
    122     m_post_processing_settings = new PostProcessingSettingsWidget(this, m_ui.settingsContainer), tr("Post-Processing"),
    123     QStringLiteral("sun-fill"),
    124     tr("<strong>Post-Processing Settings</strong><hr>Post processing allows you to alter the appearance of the image "
    125        "displayed on the screen with various filters. Shaders will be executed in sequence."));
    126   addWidget(
    127     m_audio_settings = new AudioSettingsWidget(this, m_ui.settingsContainer), tr("Audio"),
    128     QStringLiteral("volume-up-line"),
    129     tr("<strong>Audio Settings</strong><hr>These options control the audio output of the console. Mouse over an option "
    130        "for additional information."));
    131   {
    132     QString title(tr("Achievements"));
    133     QString icon_text(QStringLiteral("trophy-line"));
    134     QString help_text(
    135       tr("<strong>Achievement Settings</strong><hr>DuckStation uses RetroAchievements as an achievement database and "
    136          "for tracking progress. To use achievements, please sign up for an account at retroachievements.org. To view "
    137          "the achievement list in-game, press the hotkey for <strong>Open Pause Menu</strong> and select "
    138          "<strong>Achievements</strong> from the menu. Mouse over an option for additional information, and "
    139          "Shift+Wheel to scroll this panel."));
    140 
    141     if (!Achievements::IsUsingRAIntegration())
    142     {
    143       addWidget(m_achievement_settings = new AchievementSettingsWidget(this, m_ui.settingsContainer), std::move(title),
    144                 std::move(icon_text), std::move(help_text));
    145     }
    146     else
    147     {
    148       QLabel* placeholder_label =
    149         new QLabel(QStringLiteral("RAIntegration is being used, built-in RetroAchievements support is disabled."),
    150                    m_ui.settingsContainer);
    151       placeholder_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    152 
    153       addWidget(placeholder_label, std::move(title), std::move(icon_text), std::move(help_text));
    154     }
    155   }
    156 
    157   if (!isPerGameSettings())
    158   {
    159     addWidget(
    160       m_folder_settings = new FolderSettingsWidget(this, m_ui.settingsContainer), tr("Folders"),
    161       QStringLiteral("folder-settings-line"),
    162       tr("<strong>Folder Settings</strong><hr>These options control where DuckStation will save runtime data files."));
    163   }
    164 
    165   addWidget(m_advanced_settings = new AdvancedSettingsWidget(this, m_ui.settingsContainer), tr("Advanced"),
    166             QStringLiteral("alert-line"),
    167             tr("<strong>Advanced Settings</strong><hr>These options control logging and internal behavior of the "
    168                "emulator. Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
    169 
    170   connect(m_advanced_settings, &AdvancedSettingsWidget::onShowDebugOptionsChanged, m_graphics_settings,
    171           &GraphicsSettingsWidget::onShowDebugSettingsChanged);
    172 }
    173 
    174 void SettingsWindow::reloadPages()
    175 {
    176   const int min_count = isPerGameSettings() ? 1 : 0;
    177   while (m_ui.settingsContainer->count() > min_count)
    178   {
    179     const int row = m_ui.settingsContainer->count() - 1;
    180 
    181     delete m_ui.settingsCategory->takeItem(row);
    182 
    183     QWidget* widget = m_ui.settingsContainer->widget(row);
    184     m_ui.settingsContainer->removeWidget(widget);
    185     delete widget;
    186   }
    187 
    188   addPages();
    189 }
    190 
    191 void SettingsWindow::connectUi()
    192 {
    193   if (isPerGameSettings())
    194   {
    195     m_ui.footerLayout->removeWidget(m_ui.restoreDefaults);
    196     m_ui.restoreDefaults->deleteLater();
    197     m_ui.restoreDefaults = nullptr;
    198   }
    199   else
    200   {
    201     m_ui.footerLayout->removeWidget(m_ui.copyGlobalSettings);
    202     m_ui.copyGlobalSettings->deleteLater();
    203     m_ui.copyGlobalSettings = nullptr;
    204     m_ui.footerLayout->removeWidget(m_ui.clearGameSettings);
    205     m_ui.clearGameSettings->deleteLater();
    206     m_ui.clearGameSettings = nullptr;
    207   }
    208 
    209   m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
    210   m_ui.settingsCategory->setCurrentRow(0);
    211   m_ui.settingsContainer->setCurrentIndex(0);
    212   m_ui.helpText->setText(m_category_help_text[0]);
    213   connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &SettingsWindow::onCategoryCurrentRowChanged);
    214   connect(m_ui.close, &QPushButton::clicked, this, &SettingsWindow::close);
    215   if (m_ui.restoreDefaults)
    216     connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &SettingsWindow::onRestoreDefaultsClicked);
    217   if (m_ui.copyGlobalSettings)
    218     connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this, &SettingsWindow::onCopyGlobalSettingsClicked);
    219   if (m_ui.clearGameSettings)
    220     connect(m_ui.clearGameSettings, &QPushButton::clicked, this, &SettingsWindow::onClearSettingsClicked);
    221 }
    222 
    223 void SettingsWindow::addWidget(QWidget* widget, QString title, QString icon, QString help_text)
    224 {
    225   const int index = m_ui.settingsCategory->count();
    226 
    227   QListWidgetItem* item = new QListWidgetItem(m_ui.settingsCategory);
    228   item->setText(title);
    229   if (!icon.isEmpty())
    230     item->setIcon(QIcon::fromTheme(icon));
    231 
    232   m_ui.settingsContainer->addWidget(widget);
    233 
    234   m_category_help_text[index] = std::move(help_text);
    235 }
    236 
    237 void SettingsWindow::setCategory(const char* category)
    238 {
    239   // the titles in the category list will be translated.
    240   const QString translated_category(tr(category));
    241 
    242   for (int i = 0; i < m_ui.settingsCategory->count(); i++)
    243   {
    244     if (translated_category == m_ui.settingsCategory->item(i)->text())
    245     {
    246       // will also update the visible widget
    247       m_ui.settingsCategory->setCurrentRow(i);
    248       break;
    249     }
    250   }
    251 }
    252 
    253 int SettingsWindow::getCategoryRow() const
    254 {
    255   return m_ui.settingsCategory->currentRow();
    256 }
    257 
    258 void SettingsWindow::setCategoryRow(int index)
    259 {
    260   m_ui.settingsCategory->setCurrentRow(index);
    261 }
    262 
    263 void SettingsWindow::onCategoryCurrentRowChanged(int row)
    264 {
    265   DebugAssert(row < static_cast<int>(MAX_SETTINGS_WIDGETS));
    266   m_ui.settingsContainer->setCurrentIndex(row);
    267   m_ui.helpText->setText(m_category_help_text[row]);
    268 }
    269 
    270 void SettingsWindow::onRestoreDefaultsClicked()
    271 {
    272   if (QMessageBox::question(this, tr("Confirm Restore Defaults"),
    273                             tr("Are you sure you want to restore the default settings? Any preferences will be lost."),
    274                             QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
    275   {
    276     return;
    277   }
    278 
    279   g_emu_thread->setDefaultSettings(true, false);
    280 }
    281 
    282 void SettingsWindow::onCopyGlobalSettingsClicked()
    283 {
    284   if (!isPerGameSettings())
    285     return;
    286 
    287   if (QMessageBox::question(
    288         this, tr("DuckStation Settings"),
    289         tr("The configuration for this game will be replaced by the current global settings.\n\nAny current setting "
    290            "values will be overwritten.\n\nDo you want to continue?"),
    291         QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
    292   {
    293     return;
    294   }
    295 
    296   {
    297     auto lock = Host::GetSettingsLock();
    298     Settings temp;
    299     temp.Load(*Host::Internal::GetBaseSettingsLayer(), *Host::Internal::GetBaseSettingsLayer());
    300     temp.Save(*m_sif.get(), true);
    301   }
    302   saveAndReloadGameSettings();
    303 
    304   reloadPages();
    305 
    306   QMessageBox::information(this, tr("DuckStation Settings"), tr("Per-game configuration copied from global settings."));
    307 }
    308 
    309 void SettingsWindow::onClearSettingsClicked()
    310 {
    311   if (!isPerGameSettings())
    312     return;
    313 
    314   if (QMessageBox::question(this, tr("DuckStation Settings"),
    315                             tr("The configuration for this game will be cleared.\n\nAny current setting values will be "
    316                                "lost.\n\nDo you want to continue?"),
    317                             QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
    318   {
    319     return;
    320   }
    321 
    322   Settings::Clear(*m_sif.get());
    323   saveAndReloadGameSettings();
    324 
    325   reloadPages();
    326 
    327   QMessageBox::information(this, tr("DuckStation Settings"), tr("Per-game configuration cleared."));
    328 }
    329 
    330 void SettingsWindow::registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text)
    331 {
    332   // construct rich text with formatted description
    333   QString full_text;
    334   full_text += "<table width='100%' cellpadding='0' cellspacing='0'><tr><td><strong>";
    335   full_text += title;
    336   full_text += "</strong></td><td align='right'><strong>";
    337   full_text += tr("Recommended Value");
    338   full_text += ": </strong>";
    339   full_text += recommended_value;
    340   full_text += "</td></table><hr>";
    341   full_text += text;
    342 
    343   m_widget_help_text_map[object] = std::move(full_text);
    344   object->installEventFilter(this);
    345 }
    346 
    347 bool SettingsWindow::eventFilter(QObject* object, QEvent* event)
    348 {
    349   if (event->type() == QEvent::Enter)
    350   {
    351     auto iter = m_widget_help_text_map.constFind(object);
    352     if (iter != m_widget_help_text_map.end())
    353     {
    354       m_current_help_widget = object;
    355       m_ui.helpText->setText(iter.value());
    356     }
    357   }
    358   else if (event->type() == QEvent::Leave)
    359   {
    360     if (m_current_help_widget)
    361     {
    362       m_current_help_widget = nullptr;
    363       m_ui.helpText->setText(m_category_help_text[m_ui.settingsCategory->currentRow()]);
    364     }
    365   }
    366   else if (event->type() == QEvent::Wheel)
    367   {
    368     if (handleWheelEvent(static_cast<QWheelEvent*>(event)))
    369       return true;
    370   }
    371 
    372   return QWidget::eventFilter(object, event);
    373 }
    374 
    375 bool SettingsWindow::handleWheelEvent(QWheelEvent* event)
    376 {
    377   if (!(event->modifiers() & Qt::ShiftModifier))
    378     return false;
    379 
    380   const int amount = event->hasPixelDelta() ? event->pixelDelta().y() : (event->angleDelta().y() / 20);
    381 
    382   QScrollBar* sb = m_ui.helpText->verticalScrollBar();
    383   if (!sb)
    384     return false;
    385 
    386   sb->setSliderPosition(std::max(sb->sliderPosition() - amount, 0));
    387   return true;
    388 }
    389 
    390 void SettingsWindow::wheelEvent(QWheelEvent* event)
    391 {
    392   if (handleWheelEvent(event))
    393     return;
    394 
    395   QWidget::wheelEvent(event);
    396 }
    397 
    398 bool SettingsWindow::getEffectiveBoolValue(const char* section, const char* key, bool default_value) const
    399 {
    400   bool value;
    401   if (m_sif && m_sif->GetBoolValue(section, key, &value))
    402     return value;
    403   else
    404     return Host::GetBaseBoolSettingValue(section, key, default_value);
    405 }
    406 
    407 int SettingsWindow::getEffectiveIntValue(const char* section, const char* key, int default_value) const
    408 {
    409   int value;
    410   if (m_sif && m_sif->GetIntValue(section, key, &value))
    411     return value;
    412   else
    413     return Host::GetBaseIntSettingValue(section, key, default_value);
    414 }
    415 
    416 float SettingsWindow::getEffectiveFloatValue(const char* section, const char* key, float default_value) const
    417 {
    418   float value;
    419   if (m_sif && m_sif->GetFloatValue(section, key, &value))
    420     return value;
    421   else
    422     return Host::GetBaseFloatSettingValue(section, key, default_value);
    423 }
    424 
    425 std::string SettingsWindow::getEffectiveStringValue(const char* section, const char* key,
    426                                                     const char* default_value) const
    427 {
    428   std::string value;
    429   if (!m_sif || !m_sif->GetStringValue(section, key, &value))
    430     value = Host::GetBaseStringSettingValue(section, key, default_value);
    431   return value;
    432 }
    433 
    434 Qt::CheckState SettingsWindow::getCheckState(const char* section, const char* key, bool default_value)
    435 {
    436   bool value;
    437   if (m_sif)
    438   {
    439     if (!m_sif->GetBoolValue(section, key, &value))
    440       return Qt::PartiallyChecked;
    441   }
    442   else
    443   {
    444     value = Host::GetBaseBoolSettingValue(section, key, default_value);
    445   }
    446 
    447   return value ? Qt::Checked : Qt::Unchecked;
    448 }
    449 
    450 std::optional<bool> SettingsWindow::getBoolValue(const char* section, const char* key,
    451                                                  std::optional<bool> default_value) const
    452 {
    453   std::optional<bool> value;
    454   if (m_sif)
    455   {
    456     bool bvalue;
    457     if (m_sif->GetBoolValue(section, key, &bvalue))
    458       value = bvalue;
    459     else
    460       value = default_value;
    461   }
    462   else
    463   {
    464     value = Host::GetBaseBoolSettingValue(section, key, default_value.value_or(false));
    465   }
    466 
    467   return value;
    468 }
    469 
    470 std::optional<int> SettingsWindow::getIntValue(const char* section, const char* key,
    471                                                std::optional<int> default_value) const
    472 {
    473   std::optional<int> value;
    474   if (m_sif)
    475   {
    476     int ivalue;
    477     if (m_sif->GetIntValue(section, key, &ivalue))
    478       value = ivalue;
    479     else
    480       value = default_value;
    481   }
    482   else
    483   {
    484     value = Host::GetBaseIntSettingValue(section, key, default_value.value_or(0));
    485   }
    486 
    487   return value;
    488 }
    489 
    490 std::optional<float> SettingsWindow::getFloatValue(const char* section, const char* key,
    491                                                    std::optional<float> default_value) const
    492 {
    493   std::optional<float> value;
    494   if (m_sif)
    495   {
    496     float fvalue;
    497     if (m_sif->GetFloatValue(section, key, &fvalue))
    498       value = fvalue;
    499     else
    500       value = default_value;
    501   }
    502   else
    503   {
    504     value = Host::GetBaseFloatSettingValue(section, key, default_value.value_or(0.0f));
    505   }
    506 
    507   return value;
    508 }
    509 
    510 std::optional<std::string> SettingsWindow::getStringValue(const char* section, const char* key,
    511                                                           std::optional<const char*> default_value) const
    512 {
    513   std::optional<std::string> value;
    514   if (m_sif)
    515   {
    516     std::string svalue;
    517     if (m_sif->GetStringValue(section, key, &svalue))
    518       value = std::move(svalue);
    519     else if (default_value.has_value())
    520       value = default_value.value();
    521   }
    522   else
    523   {
    524     value = Host::GetBaseStringSettingValue(section, key, default_value.value_or(""));
    525   }
    526 
    527   return value;
    528 }
    529 
    530 void SettingsWindow::setBoolSettingValue(const char* section, const char* key, std::optional<bool> value)
    531 {
    532   if (m_sif)
    533   {
    534     value.has_value() ? m_sif->SetBoolValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
    535     saveAndReloadGameSettings();
    536   }
    537   else
    538   {
    539     value.has_value() ? Host::SetBaseBoolSettingValue(section, key, value.value()) :
    540                         Host::DeleteBaseSettingValue(section, key);
    541     Host::CommitBaseSettingChanges();
    542     g_emu_thread->applySettings();
    543   }
    544 }
    545 
    546 void SettingsWindow::setIntSettingValue(const char* section, const char* key, std::optional<int> value)
    547 {
    548   if (m_sif)
    549   {
    550     value.has_value() ? m_sif->SetIntValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
    551     saveAndReloadGameSettings();
    552   }
    553   else
    554   {
    555     value.has_value() ? Host::SetBaseIntSettingValue(section, key, value.value()) :
    556                         Host::DeleteBaseSettingValue(section, key);
    557     Host::CommitBaseSettingChanges();
    558     g_emu_thread->applySettings();
    559   }
    560 }
    561 
    562 void SettingsWindow::setFloatSettingValue(const char* section, const char* key, std::optional<float> value)
    563 {
    564   if (m_sif)
    565   {
    566     value.has_value() ? m_sif->SetFloatValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
    567     saveAndReloadGameSettings();
    568   }
    569   else
    570   {
    571     value.has_value() ? Host::SetBaseFloatSettingValue(section, key, value.value()) :
    572                         Host::DeleteBaseSettingValue(section, key);
    573     Host::CommitBaseSettingChanges();
    574     g_emu_thread->applySettings();
    575   }
    576 }
    577 
    578 void SettingsWindow::setStringSettingValue(const char* section, const char* key, std::optional<const char*> value)
    579 {
    580   if (m_sif)
    581   {
    582     value.has_value() ? m_sif->SetStringValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
    583     saveAndReloadGameSettings();
    584   }
    585   else
    586   {
    587     value.has_value() ? Host::SetBaseStringSettingValue(section, key, value.value()) :
    588                         Host::DeleteBaseSettingValue(section, key);
    589     Host::CommitBaseSettingChanges();
    590     g_emu_thread->applySettings();
    591   }
    592 }
    593 
    594 bool SettingsWindow::containsSettingValue(const char* section, const char* key) const
    595 {
    596   if (m_sif)
    597     return m_sif->ContainsValue(section, key);
    598   else
    599     return Host::ContainsBaseSettingValue(section, key);
    600 }
    601 
    602 void SettingsWindow::removeSettingValue(const char* section, const char* key)
    603 {
    604   if (m_sif)
    605   {
    606     m_sif->DeleteValue(section, key);
    607     saveAndReloadGameSettings();
    608   }
    609   else
    610   {
    611     Host::DeleteBaseSettingValue(section, key);
    612     Host::CommitBaseSettingChanges();
    613     g_emu_thread->applySettings();
    614   }
    615 }
    616 
    617 void SettingsWindow::saveAndReloadGameSettings()
    618 {
    619   DebugAssert(m_sif);
    620   QtHost::SaveGameSettings(m_sif.get(), true);
    621   g_emu_thread->reloadGameSettings(false);
    622 }
    623 
    624 bool SettingsWindow::hasGameTrait(GameDatabase::Trait trait)
    625 {
    626   return (m_database_entry && m_database_entry->HasTrait(trait) &&
    627           m_sif->GetBoolValue("Main", "ApplyCompatibilitySettings", true));
    628 }
    629 
    630 void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& title,
    631                                               const std::string& serial, DiscRegion region)
    632 {
    633   const GameDatabase::Entry* dentry = nullptr;
    634   if (!System::IsExeFileName(path) && !System::IsPsfFileName(path))
    635   {
    636     // Need to resolve hash games.
    637     Error error;
    638     std::unique_ptr<CDImage> image = CDImage::Open(path.c_str(), false, &error);
    639     if (image)
    640       dentry = GameDatabase::GetEntryForDisc(image.get());
    641     else
    642       ERROR_LOG("Failed to open '{}' for game properties: {}", path, error.GetDescription());
    643 
    644     if (!dentry)
    645     {
    646       // Use the serial and hope for the best...
    647       dentry = GameDatabase::GetEntryForSerial(serial);
    648     }
    649   }
    650 
    651   const std::string& real_serial = dentry ? dentry->serial : serial;
    652   std::string ini_filename = System::GetGameSettingsPath(real_serial);
    653 
    654   // check for an existing dialog with this crc
    655   for (SettingsWindow* dialog : s_open_game_properties_dialogs)
    656   {
    657     if (dialog->isPerGameSettings() &&
    658         static_cast<INISettingsInterface*>(dialog->getSettingsInterface())->GetFileName() == ini_filename)
    659     {
    660       dialog->show();
    661       dialog->raise();
    662       dialog->activateWindow();
    663       dialog->setFocus();
    664       return;
    665     }
    666   }
    667 
    668   std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(ini_filename));
    669   if (FileSystem::FileExists(sif->GetFileName().c_str()))
    670     sif->Load();
    671 
    672   SettingsWindow* dialog = new SettingsWindow(path, real_serial, region, dentry, std::move(sif));
    673   dialog->show();
    674 }
    675 
    676 void SettingsWindow::closeGamePropertiesDialogs()
    677 {
    678   for (SettingsWindow* dialog : s_open_game_properties_dialogs)
    679   {
    680     dialog->close();
    681     dialog->deleteLater();
    682   }
    683 }
    684 
    685 bool SettingsWindow::setGameSettingsBoolForSerial(const std::string& serial, const char* section, const char* key,
    686                                                   bool value)
    687 {
    688   std::string ini_filename = System::GetGameSettingsPath(serial);
    689   if (ini_filename.empty())
    690     return false;
    691 
    692   INISettingsInterface sif(std::move(ini_filename));
    693   if (FileSystem::FileExists(sif.GetFileName().c_str()))
    694     sif.Load();
    695 
    696   sif.SetBoolValue(section, key, value);
    697   return sif.Save();
    698 }