From 1cdfca155d8ac5efcccd99bc9f4e656104058b60 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 25 Apr 2024 14:02:16 +1000 Subject: [PATCH] Qt: Clean and remove empty game settings --- dep/simpleini/include/SimpleIni.h | 16 +++++++++ src/common/file_system.cpp | 32 ++++++++++++++--- src/common/file_system.h | 2 +- src/common/layered_settings_interface.cpp | 15 ++++++++ src/common/layered_settings_interface.h | 4 +++ src/common/memory_settings_interface.cpp | 28 +++++++++++++++ src/common/memory_settings_interface.h | 4 +++ src/common/settings_interface.h | 3 ++ src/core/fullscreen_ui.cpp | 34 +++++++++++++++++- .../controllerbindingwidgets.cpp | 2 +- .../controllersettingswindow.cpp | 16 +++++---- src/duckstation-qt/controllersettingswindow.h | 1 + .../controllersettingwidgetbinder.h | 8 ++--- src/duckstation-qt/inputbindingdialog.cpp | 2 +- src/duckstation-qt/inputbindingwidgets.cpp | 4 +-- .../postprocessingsettingswidget.cpp | 6 ++-- src/duckstation-qt/qthost.cpp | 36 +++++++++++++++++++ src/duckstation-qt/qthost.h | 3 ++ src/duckstation-qt/settingswindow.cpp | 28 +++++++-------- src/duckstation-qt/settingswindow.h | 1 + src/duckstation-qt/settingwidgetbinder.h | 18 +++++----- src/util/ini_settings_interface.cpp | 28 +++++++++++++++ src/util/ini_settings_interface.h | 3 ++ 23 files changed, 247 insertions(+), 47 deletions(-) diff --git a/dep/simpleini/include/SimpleIni.h b/dep/simpleini/include/SimpleIni.h index 1f0c0ae38..9681487db 100644 --- a/dep/simpleini/include/SimpleIni.h +++ b/dep/simpleini/include/SimpleIni.h @@ -797,6 +797,11 @@ public: /** @} @{ @name Accessing INI Data */ + /** Retrieve the number keys across all sections. + @return number of keys currently present. + */ + size_t GetKeyCount() const; + /** Retrieve all section names. The list is returned as an STL vector of names and can be iterated or searched as necessary. Note that the sort order of the returned strings is NOT DEFINED. You can sort @@ -2440,6 +2445,17 @@ CSimpleIniTempl::GetSection( return 0; } +template +size_t +CSimpleIniTempl::GetKeyCount() const +{ + size_t count = 0; + typename TSection::const_iterator i = m_data.begin(); + for (; i != m_data.end(); ++i) + count += i->second.size(); + return count; +} + template void CSimpleIniTempl::GetAllSections( diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index 8d5fc02a1..55b0394a7 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -1736,17 +1736,29 @@ bool FileSystem::CreateDirectory(const char* Path, bool Recursive, Error* error) } } -bool FileSystem::DeleteFile(const char* path) +bool FileSystem::DeleteFile(const char* path, Error* error) { if (path[0] == '\0') + { + Error::SetStringView(error, "Path is empty."); return false; + } const std::wstring wpath = GetWin32Path(path); const DWORD fileAttributes = GetFileAttributesW(wpath.c_str()); if (fileAttributes == INVALID_FILE_ATTRIBUTES || fileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + Error::SetStringView(error, "File does not exist."); return false; + } - return (DeleteFileW(wpath.c_str()) == TRUE); + if (!DeleteFileW(wpath.c_str())) + { + Error::SetWin32(error, "DeleteFileW() failed: ", GetLastError()); + return false; + } + + return true; } bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* error) @@ -2241,16 +2253,28 @@ bool FileSystem::CreateDirectory(const char* path, bool recursive, Error* error) } } -bool FileSystem::DeleteFile(const char* path) +bool FileSystem::DeleteFile(const char* path, Error* error) { if (path[0] == '\0') + { + Error::SetStringView(error, "Path is empty."); return false; + } struct stat sysStatData; if (stat(path, &sysStatData) != 0 || S_ISDIR(sysStatData.st_mode)) + { + Error::SetStringView(error, "File does not exist."); return false; + } - return (unlink(path) == 0); + if (unlink(path) != 0) + { + Error::SetErrno(error, "unlink() failed: ", errno); + return false; + } + + return true; } bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* error) diff --git a/src/common/file_system.h b/src/common/file_system.h index 046b5cdb5..53e8185a0 100644 --- a/src/common/file_system.h +++ b/src/common/file_system.h @@ -86,7 +86,7 @@ bool DirectoryExists(const char* path); bool DirectoryIsEmpty(const char* path); /// Delete file -bool DeleteFile(const char* path); +bool DeleteFile(const char* path, Error* error = nullptr); /// Rename file bool RenamePath(const char* OldPath, const char* NewPath, Error* error = nullptr); diff --git a/src/common/layered_settings_interface.cpp b/src/common/layered_settings_interface.cpp index 92fc3e930..53ea6c6a7 100644 --- a/src/common/layered_settings_interface.cpp +++ b/src/common/layered_settings_interface.cpp @@ -19,6 +19,11 @@ void LayeredSettingsInterface::Clear() Panic("Attempting to clear layered settings interface"); } +bool LayeredSettingsInterface::IsEmpty() +{ + return false; +} + bool LayeredSettingsInterface::GetIntValue(const char* section, const char* key, s32* value) const { for (u32 layer = FIRST_LAYER; layer <= LAST_LAYER; layer++) @@ -170,6 +175,16 @@ void LayeredSettingsInterface::ClearSection(const char* section) Panic("Attempt to call ClearSection() on layered settings interface"); } +void LayeredSettingsInterface::RemoveSection(const char* section) +{ + Panic("Attempt to call RemoveSection() on layered settings interface"); +} + +void LayeredSettingsInterface::RemoveEmptySections() +{ + Panic("Attempt to call RemoveEmptySections() on layered settings interface"); +} + std::vector LayeredSettingsInterface::GetStringList(const char* section, const char* key) const { std::vector ret; diff --git a/src/common/layered_settings_interface.h b/src/common/layered_settings_interface.h index 611a2a68a..3a89b5960 100644 --- a/src/common/layered_settings_interface.h +++ b/src/common/layered_settings_interface.h @@ -27,6 +27,8 @@ public: void Clear() override; + bool IsEmpty() override; + bool GetIntValue(const char* section, const char* key, s32* value) const override; bool GetUIntValue(const char* section, const char* key, u32* value) const override; bool GetFloatValue(const char* section, const char* key, float* value) const override; @@ -44,6 +46,8 @@ public: bool ContainsValue(const char* section, const char* key) const override; void DeleteValue(const char* section, const char* key) override; void ClearSection(const char* section) override; + void RemoveSection(const char* section) override; + void RemoveEmptySections() override; std::vector GetStringList(const char* section, const char* key) const override; void SetStringList(const char* section, const char* key, const std::vector& items) override; diff --git a/src/common/memory_settings_interface.cpp b/src/common/memory_settings_interface.cpp index a8edc3a94..732ec20c2 100644 --- a/src/common/memory_settings_interface.cpp +++ b/src/common/memory_settings_interface.cpp @@ -22,6 +22,11 @@ void MemorySettingsInterface::Clear() m_sections.clear(); } +bool MemorySettingsInterface::IsEmpty() +{ + return m_sections.empty(); +} + bool MemorySettingsInterface::GetIntValue(const char* section, const char* key, s32* value) const { const auto sit = m_sections.find(section); @@ -315,3 +320,26 @@ void MemorySettingsInterface::ClearSection(const char* section) m_sections.erase(sit); } + +void MemorySettingsInterface::RemoveSection(const char* section) +{ + auto sit = m_sections.find(section); + if (sit == m_sections.end()) + return; + + m_sections.erase(sit); +} + +void MemorySettingsInterface::RemoveEmptySections() +{ + for (auto sit = m_sections.begin(); sit != m_sections.end();) + { + if (sit->second.size() > 0) + { + ++sit; + continue; + } + + sit = m_sections.erase(sit); + } +} diff --git a/src/common/memory_settings_interface.h b/src/common/memory_settings_interface.h index 90ee47f46..baa071a1d 100644 --- a/src/common/memory_settings_interface.h +++ b/src/common/memory_settings_interface.h @@ -16,6 +16,8 @@ public: void Clear() override; + bool IsEmpty() override; + bool GetIntValue(const char* section, const char* key, s32* value) const override; bool GetUIntValue(const char* section, const char* key, u32* value) const override; bool GetFloatValue(const char* section, const char* key, float* value) const override; @@ -37,6 +39,8 @@ public: bool ContainsValue(const char* section, const char* key) const override; void DeleteValue(const char* section, const char* key) override; void ClearSection(const char* section) override; + void RemoveSection(const char* section) override; + void RemoveEmptySections(); std::vector GetStringList(const char* section, const char* key) const override; void SetStringList(const char* section, const char* key, const std::vector& items) override; diff --git a/src/common/settings_interface.h b/src/common/settings_interface.h index 5efd6a976..dbaf0dc6b 100644 --- a/src/common/settings_interface.h +++ b/src/common/settings_interface.h @@ -19,6 +19,7 @@ public: virtual bool Save(Error* error = nullptr) = 0; virtual void Clear() = 0; + virtual bool IsEmpty() = 0; virtual bool GetIntValue(const char* section, const char* key, s32* value) const = 0; virtual bool GetUIntValue(const char* section, const char* key, u32* value) const = 0; @@ -46,6 +47,8 @@ public: virtual bool ContainsValue(const char* section, const char* key) const = 0; virtual void DeleteValue(const char* section, const char* key) = 0; virtual void ClearSection(const char* section) = 0; + virtual void RemoveSection(const char* section) = 0; + virtual void RemoveEmptySections() = 0; ALWAYS_INLINE s32 GetIntValue(const char* section, const char* key, s32 default_value = 0) const { diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 9ebe9858e..156ac3bff 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -836,7 +836,29 @@ void FullscreenUI::Render() { if (s_game_settings_interface) { - s_game_settings_interface->Save(); + Error error; + s_game_settings_interface->RemoveEmptySections(); + + if (s_game_settings_interface->IsEmpty()) + { + if (FileSystem::FileExists(s_game_settings_interface->GetFileName().c_str()) && + !FileSystem::DeleteFile(s_game_settings_interface->GetFileName().c_str(), &error)) + { + ImGuiFullscreen::OpenInfoMessageDialog( + FSUI_STR("Error"), fmt::format(FSUI_FSTR("An error occurred while deleting empty game settings:\n{}"), + error.GetDescription())); + } + } + else + { + if (!s_game_settings_interface->Save(&error)) + { + ImGuiFullscreen::OpenInfoMessageDialog( + FSUI_STR("Error"), + fmt::format(FSUI_FSTR("An error occurred while saving game settings:\n{}"), error.GetDescription())); + } + } + if (System::IsValid()) Host::RunOnCPUThread([]() { System::ReloadGameSettings(false); }); } @@ -7052,6 +7074,8 @@ TRANSLATE_NOOP("FullscreenUI", "Advanced Settings"); TRANSLATE_NOOP("FullscreenUI", "All Time: {}"); TRANSLATE_NOOP("FullscreenUI", "Allow Booting Without SBI File"); TRANSLATE_NOOP("FullscreenUI", "Allows loading protected games without subchannel information."); +TRANSLATE_NOOP("FullscreenUI", "An error occurred while deleting empty game settings:\n{}"); +TRANSLATE_NOOP("FullscreenUI", "An error occurred while saving game settings:\n{}"); TRANSLATE_NOOP("FullscreenUI", "Applies modern dithering techniques to further smooth out gradients when true color is enabled."); TRANSLATE_NOOP("FullscreenUI", "Apply Image Patches"); TRANSLATE_NOOP("FullscreenUI", "Apply Per-Game Settings"); @@ -7220,6 +7244,7 @@ TRANSLATE_NOOP("FullscreenUI", "Enhancements"); TRANSLATE_NOOP("FullscreenUI", "Ensures every frame generated is displayed for optimal pacing. Enable for variable refresh displays, such as GSync/FreeSync. Disable if you are having speed or sound issues."); TRANSLATE_NOOP("FullscreenUI", "Enter Value"); TRANSLATE_NOOP("FullscreenUI", "Enter the name of the input profile you wish to create."); +TRANSLATE_NOOP("FullscreenUI", "Error"); TRANSLATE_NOOP("FullscreenUI", "Execution Mode"); TRANSLATE_NOOP("FullscreenUI", "Exit"); TRANSLATE_NOOP("FullscreenUI", "Exit And Save State"); @@ -7330,6 +7355,7 @@ TRANSLATE_NOOP("FullscreenUI", "Macro {} Buttons"); TRANSLATE_NOOP("FullscreenUI", "Macro {} Frequency"); TRANSLATE_NOOP("FullscreenUI", "Macro {} Trigger"); TRANSLATE_NOOP("FullscreenUI", "Makes games run closer to their console framerate, at a small cost to performance."); +TRANSLATE_NOOP("FullscreenUI", "Memory Card Busy"); TRANSLATE_NOOP("FullscreenUI", "Memory Card Directory"); TRANSLATE_NOOP("FullscreenUI", "Memory Card Port {}"); TRANSLATE_NOOP("FullscreenUI", "Memory Card Settings"); @@ -7351,6 +7377,7 @@ TRANSLATE_NOOP("FullscreenUI", "No input profiles available."); TRANSLATE_NOOP("FullscreenUI", "No resume save state found."); TRANSLATE_NOOP("FullscreenUI", "No save present in this slot."); TRANSLATE_NOOP("FullscreenUI", "No save states found."); +TRANSLATE_NOOP("FullscreenUI", "No, resume the game."); TRANSLATE_NOOP("FullscreenUI", "None (Double Speed)"); TRANSLATE_NOOP("FullscreenUI", "None (Normal Speed)"); TRANSLATE_NOOP("FullscreenUI", "Not Logged In"); @@ -7599,6 +7626,7 @@ TRANSLATE_NOOP("FullscreenUI", "Uses perspective-correct interpolation for textu TRANSLATE_NOOP("FullscreenUI", "Uses screen positions to resolve PGXP data. May improve visuals in some games."); TRANSLATE_NOOP("FullscreenUI", "Value: {} | Default: {} | Minimum: {} | Maximum: {}"); TRANSLATE_NOOP("FullscreenUI", "Vertical Sync (VSync)"); +TRANSLATE_NOOP("FullscreenUI", "WARNING: Your game is still saving to the memory card. Continuing to {0} may IRREVERSIBLY DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to finish saving.\n\nDo you want to {0} anyway?"); TRANSLATE_NOOP("FullscreenUI", "When enabled and logged in, DuckStation will scan for achievements on startup."); TRANSLATE_NOOP("FullscreenUI", "When enabled, DuckStation will assume all achievements are locked and not send any unlock notifications to the server."); TRANSLATE_NOOP("FullscreenUI", "When enabled, DuckStation will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements."); @@ -7611,8 +7639,12 @@ TRANSLATE_NOOP("FullscreenUI", "When this option is chosen, the clock speed set TRANSLATE_NOOP("FullscreenUI", "Widescreen Hack"); TRANSLATE_NOOP("FullscreenUI", "Wireframe Rendering"); TRANSLATE_NOOP("FullscreenUI", "Writes textures which can be replaced to the dump directory."); +TRANSLATE_NOOP("FullscreenUI", "Yes, {} now and risk memory card corruption."); TRANSLATE_NOOP("FullscreenUI", "\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."); TRANSLATE_NOOP("FullscreenUI", "\"PlayStation\" and \"PSX\" are registered trademarks of Sony Interactive Entertainment Europe Limited. This software is not affiliated in any way with Sony Interactive Entertainment."); +TRANSLATE_NOOP("FullscreenUI", "change disc"); +TRANSLATE_NOOP("FullscreenUI", "reset"); +TRANSLATE_NOOP("FullscreenUI", "shut down"); TRANSLATE_NOOP("FullscreenUI", "{:%H:%M}"); TRANSLATE_NOOP("FullscreenUI", "{:%Y-%m-%d %H:%M:%S}"); TRANSLATE_NOOP("FullscreenUI", "{} Frames"); diff --git a/src/duckstation-qt/controllerbindingwidgets.cpp b/src/duckstation-qt/controllerbindingwidgets.cpp index 9d45f1228..fc0259c20 100644 --- a/src/duckstation-qt/controllerbindingwidgets.cpp +++ b/src/duckstation-qt/controllerbindingwidgets.cpp @@ -336,7 +336,7 @@ void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device) else { result = InputManager::MapController(*m_dialog->getProfileSettingsInterface(), m_port_number, mapping); - m_dialog->getProfileSettingsInterface()->Save(); + QtHost::SaveGameSettings(m_dialog->getProfileSettingsInterface(), false); g_emu_thread->reloadInputBindings(); } diff --git a/src/duckstation-qt/controllersettingswindow.cpp b/src/duckstation-qt/controllersettingswindow.cpp index d0b080447..c54035add 100644 --- a/src/duckstation-qt/controllersettingswindow.cpp +++ b/src/duckstation-qt/controllersettingswindow.cpp @@ -278,8 +278,7 @@ void ControllerSettingsWindow::setBoolValue(const char* section, const char* key if (m_profile_interface) { m_profile_interface->SetBoolValue(section, key, value); - m_profile_interface->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -294,8 +293,7 @@ void ControllerSettingsWindow::setIntValue(const char* section, const char* key, if (m_profile_interface) { m_profile_interface->SetIntValue(section, key, value); - m_profile_interface->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -310,8 +308,7 @@ void ControllerSettingsWindow::setStringValue(const char* section, const char* k if (m_profile_interface) { m_profile_interface->SetStringValue(section, key, value); - m_profile_interface->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -321,6 +318,13 @@ void ControllerSettingsWindow::setStringValue(const char* section, const char* k } } +void ControllerSettingsWindow::saveAndReloadGameSettings() +{ + DebugAssert(m_profile_interface); + QtHost::SaveGameSettings(m_profile_interface.get(), false); + g_emu_thread->reloadGameSettings(false); +} + void ControllerSettingsWindow::clearSettingValue(const char* section, const char* key) { if (m_profile_interface) diff --git a/src/duckstation-qt/controllersettingswindow.h b/src/duckstation-qt/controllersettingswindow.h index ae268b54d..5e0a6909f 100644 --- a/src/duckstation-qt/controllersettingswindow.h +++ b/src/duckstation-qt/controllersettingswindow.h @@ -64,6 +64,7 @@ public: void setIntValue(const char* section, const char* key, s32 value); void setStringValue(const char* section, const char* key, const char* value); void clearSettingValue(const char* section, const char* key); + void saveAndReloadGameSettings(); Q_SIGNALS: void inputProfileSwitched(); diff --git a/src/duckstation-qt/controllersettingwidgetbinder.h b/src/duckstation-qt/controllersettingwidgetbinder.h index e2f7834b4..4f3f328f9 100644 --- a/src/duckstation-qt/controllersettingwidgetbinder.h +++ b/src/duckstation-qt/controllersettingwidgetbinder.h @@ -37,7 +37,7 @@ static void BindWidgetToInputProfileBool(SettingsInterface* sif, WidgetType* wid Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() { const bool new_value = Accessor::getBoolValue(widget); sif->SetBoolValue(section.c_str(), key.c_str(), new_value); - sif->Save(); + QtHost::SaveGameSettings(sif, false); g_emu_thread->reloadGameSettings(); }); } @@ -70,7 +70,7 @@ static void BindWidgetToInputProfileFloat(SettingsInterface* sif, WidgetType* wi Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() { const float new_value = Accessor::getFloatValue(widget); sif->SetFloatValue(section.c_str(), key.c_str(), new_value); - sif->Save(); + QtHost::SaveGameSettings(sif, false); g_emu_thread->reloadGameSettings(); }); } @@ -103,7 +103,7 @@ static void BindWidgetToInputProfileNormalized(SettingsInterface* sif, WidgetTyp Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), range]() { const int new_value = Accessor::getIntValue(widget); sif->SetFloatValue(section.c_str(), key.c_str(), static_cast(new_value) / range); - sif->Save(); + QtHost::SaveGameSettings(sif, false); g_emu_thread->reloadGameSettings(); }); } @@ -142,7 +142,7 @@ static void BindWidgetToInputProfileString(SettingsInterface* sif, WidgetType* w else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, false); g_emu_thread->reloadGameSettings(); }); } diff --git a/src/duckstation-qt/inputbindingdialog.cpp b/src/duckstation-qt/inputbindingdialog.cpp index bc64b6581..dfcdc0fbc 100644 --- a/src/duckstation-qt/inputbindingdialog.cpp +++ b/src/duckstation-qt/inputbindingdialog.cpp @@ -234,7 +234,7 @@ void InputBindingDialog::saveListToSettings() m_sif->SetStringList(m_section_name.c_str(), m_key_name.c_str(), m_bindings); else m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str()); - m_sif->Save(); + QtHost::SaveGameSettings(m_sif, false); g_emu_thread->reloadGameSettings(); } else diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index c9edbdeb7..636c65d35 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -218,7 +218,7 @@ void InputBindingWidget::setNewBinding() if (m_sif) { m_sif->SetStringValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str()); - m_sif->Save(); + QtHost::SaveGameSettings(m_sif, false); g_emu_thread->reloadGameSettings(); } else @@ -239,7 +239,7 @@ void InputBindingWidget::clearBinding() if (m_sif) { m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str()); - m_sif->Save(); + QtHost::SaveGameSettings(m_sif, false); g_emu_thread->reloadGameSettings(); } else diff --git a/src/duckstation-qt/postprocessingsettingswidget.cpp b/src/duckstation-qt/postprocessingsettingswidget.cpp index 8b842c8b3..0942e22df 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.cpp +++ b/src/duckstation-qt/postprocessingsettingswidget.cpp @@ -41,15 +41,13 @@ void PostProcessingSettingsWidget::commitSettingsUpdate() { if (m_dialog->isPerGameSettings()) { - m_dialog->getSettingsInterface()->Save(); - g_emu_thread->reloadGameSettings(false); + m_dialog->saveAndReloadGameSettings(); } else { Host::CommitBaseSettingChanges(); + g_emu_thread->updatePostProcessingSettings(); } - - g_emu_thread->updatePostProcessingSettings(); } void PostProcessingSettingsWidget::connectUi() diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 64ba20d56..35fa6a41e 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -171,6 +171,42 @@ INISettingsInterface* QtHost::GetBaseSettingsInterface() return s_base_settings_interface.get(); } +bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty) +{ + INISettingsInterface* ini = static_cast(sif); + Error error; + + // if there's no keys, just toss the whole thing out + if (delete_if_empty && ini->IsEmpty()) + { + Log_InfoFmt("Removing empty gamesettings ini {}", Path::GetFileName(ini->GetFileName())); + if (FileSystem::FileExists(ini->GetFileName().c_str()) && + !FileSystem::DeleteFile(ini->GetFileName().c_str(), &error)) + { + Host::ReportErrorAsync( + TRANSLATE_SV("QtHost", "Error"), + fmt::format(TRANSLATE_FS("QtHost", "An error occurred while deleting empty game settings:\n{}"), + error.GetDescription())); + return false; + } + + return true; + } + + // clean unused sections, stops the file being bloated + sif->RemoveEmptySections(); + + if (!sif->Save(&error)) + { + Host::ReportErrorAsync( + TRANSLATE_SV("QtHost", "Error"), + fmt::format(TRANSLATE_FS("QtHost", "An error occurred while saving game settings:\n{}"), error.GetDescription())); + return false; + } + + return true; +} + QIcon QtHost::GetAppIcon() { return QIcon(QStringLiteral(":/icons/duck.png")); diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index d10abc3ef..7a08b8071 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -280,6 +280,9 @@ QString GetResourcesBasePath(); /// Returns the base settings interface. Should lock before manipulating. INISettingsInterface* GetBaseSettingsInterface(); +/// Saves a game settings interface. +bool SaveGameSettings(SettingsInterface* sif, bool delete_if_empty); + /// Downloads the specified URL to the provided path. bool DownloadFile(QWidget* parent, const QString& title, std::string url, const char* path); diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp index 03df1b445..db78f1950 100644 --- a/src/duckstation-qt/settingswindow.cpp +++ b/src/duckstation-qt/settingswindow.cpp @@ -289,8 +289,7 @@ void SettingsWindow::onCopyGlobalSettingsClicked() temp.Load(*Host::Internal::GetBaseSettingsLayer()); temp.Save(*m_sif.get(), true); } - m_sif->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); reloadPages(); @@ -311,8 +310,7 @@ void SettingsWindow::onClearSettingsClicked() } Settings::Clear(*m_sif.get()); - m_sif->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); reloadPages(); @@ -524,8 +522,7 @@ void SettingsWindow::setBoolSettingValue(const char* section, const char* key, s if (m_sif) { value.has_value() ? m_sif->SetBoolValue(section, key, value.value()) : m_sif->DeleteValue(section, key); - m_sif->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -541,8 +538,7 @@ void SettingsWindow::setIntSettingValue(const char* section, const char* key, st if (m_sif) { value.has_value() ? m_sif->SetIntValue(section, key, value.value()) : m_sif->DeleteValue(section, key); - m_sif->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -558,8 +554,7 @@ void SettingsWindow::setFloatSettingValue(const char* section, const char* key, if (m_sif) { value.has_value() ? m_sif->SetFloatValue(section, key, value.value()) : m_sif->DeleteValue(section, key); - m_sif->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -575,8 +570,7 @@ void SettingsWindow::setStringSettingValue(const char* section, const char* key, if (m_sif) { value.has_value() ? m_sif->SetStringValue(section, key, value.value()) : m_sif->DeleteValue(section, key); - m_sif->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -600,8 +594,7 @@ void SettingsWindow::removeSettingValue(const char* section, const char* key) if (m_sif) { m_sif->DeleteValue(section, key); - m_sif->Save(); - g_emu_thread->reloadGameSettings(); + saveAndReloadGameSettings(); } else { @@ -611,6 +604,13 @@ void SettingsWindow::removeSettingValue(const char* section, const char* key) } } +void SettingsWindow::saveAndReloadGameSettings() +{ + DebugAssert(m_sif); + QtHost::SaveGameSettings(m_sif.get(), true); + g_emu_thread->reloadGameSettings(false); +} + void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& serial, DiscRegion region) { const GameDatabase::Entry* dentry = nullptr; diff --git a/src/duckstation-qt/settingswindow.h b/src/duckstation-qt/settingswindow.h index 4a30b6372..c49bdd140 100644 --- a/src/duckstation-qt/settingswindow.h +++ b/src/duckstation-qt/settingswindow.h @@ -88,6 +88,7 @@ public: void setStringSettingValue(const char* section, const char* key, std::optional value); bool containsSettingValue(const char* section, const char* key) const; void removeSettingValue(const char* section, const char* key); + void saveAndReloadGameSettings(); Q_SIGNALS: void settingsResetToDefaults(); diff --git a/src/duckstation-qt/settingwidgetbinder.h b/src/duckstation-qt/settingwidgetbinder.h index 149ae89d1..63908578c 100644 --- a/src/duckstation-qt/settingwidgetbinder.h +++ b/src/duckstation-qt/settingwidgetbinder.h @@ -710,7 +710,7 @@ static void BindWidgetToBoolSetting(SettingsInterface* sif, WidgetType* widget, else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -753,7 +753,7 @@ static void BindWidgetToIntSetting(SettingsInterface* sif, WidgetType* widget, s else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -828,7 +828,7 @@ static inline void BindWidgetAndLabelToIntSetting(SettingsInterface* sif, Widget } } - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -876,7 +876,7 @@ static void BindWidgetToFloatSetting(SettingsInterface* sif, WidgetType* widget, else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -917,7 +917,7 @@ static void BindWidgetToNormalizedSetting(SettingsInterface* sif, WidgetType* wi else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -959,7 +959,7 @@ static void BindWidgetToStringSetting(SettingsInterface* sif, WidgetType* widget else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -1024,7 +1024,7 @@ static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, sif->DeleteValue(section.c_str(), key.c_str()); } - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -1092,7 +1092,7 @@ static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } @@ -1160,7 +1160,7 @@ static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, else sif->DeleteValue(section.c_str(), key.c_str()); - sif->Save(); + QtHost::SaveGameSettings(sif, true); g_emu_thread->reloadGameSettings(); }); } diff --git a/src/util/ini_settings_interface.cpp b/src/util/ini_settings_interface.cpp index dbcf550bf..83a737da5 100644 --- a/src/util/ini_settings_interface.cpp +++ b/src/util/ini_settings_interface.cpp @@ -135,6 +135,11 @@ void INISettingsInterface::Clear() m_ini.Reset(); } +bool INISettingsInterface::IsEmpty() +{ + return (m_ini.GetKeyCount() == 0); +} + bool INISettingsInterface::GetIntValue(const char* section, const char* key, s32* value) const { const char* str_value = m_ini.GetValue(section, key); @@ -279,6 +284,29 @@ void INISettingsInterface::ClearSection(const char* section) m_ini.SetValue(section, nullptr, nullptr); } +void INISettingsInterface::RemoveSection(const char* section) +{ + if (!m_ini.GetSection(section)) + return; + + m_dirty = true; + m_ini.Delete(section, nullptr); +} + +void INISettingsInterface::RemoveEmptySections() +{ + std::list entries; + m_ini.GetAllSections(entries); + for (const CSimpleIniA::Entry& entry : entries) + { + if (m_ini.GetSectionSize(entry.pItem) > 0) + continue; + + m_dirty = true; + m_ini.Delete(entry.pItem, nullptr); + } +} + std::vector INISettingsInterface::GetStringList(const char* section, const char* key) const { std::list entries; diff --git a/src/util/ini_settings_interface.h b/src/util/ini_settings_interface.h index a18bc69eb..eba6f13dc 100644 --- a/src/util/ini_settings_interface.h +++ b/src/util/ini_settings_interface.h @@ -22,6 +22,7 @@ public: bool Save(Error* error = nullptr) override; void Clear() override; + bool IsEmpty() override; bool GetIntValue(const char* section, const char* key, s32* value) const override; bool GetUIntValue(const char* section, const char* key, u32* value) const override; @@ -40,6 +41,8 @@ public: bool ContainsValue(const char* section, const char* key) const override; void DeleteValue(const char* section, const char* key) override; void ClearSection(const char* section) override; + void RemoveSection(const char* section) override; + void RemoveEmptySections() override; std::vector GetStringList(const char* section, const char* key) const override; void SetStringList(const char* section, const char* key, const std::vector& items) override;