From 06897cd733b5f138808cd8120705e9afa5ae5cc1 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 19 Jan 2026 20:37:22 +1000 Subject: [PATCH] Qt: Move windows data location from Documents to AppData OneDrive shenanigans, Windows Defender preventing access, the list of issues goes on. Existing setups with a DuckStation directory in Documents will continue to use Documents. New installs will use AppData\Local. --- README.md | 2 +- src/common/crash_handler.cpp | 2 +- src/common/file_system.cpp | 18 +- src/common/file_system.h | 2 +- src/core/core.cpp | 241 +++++++++++++++-- src/core/core_private.h | 38 ++- src/core/fullscreenui.h | 3 - src/core/fullscreenui_settings.cpp | 7 +- src/core/system.cpp | 14 +- src/duckstation-mini/mini_host.cpp | 210 +++------------ src/duckstation-qt/autoupdaterdialog.cpp | 11 - .../controllersettingswindow.cpp | 2 +- src/duckstation-qt/logwindow.cpp | 5 +- src/duckstation-qt/qthost.cpp | 242 ++++++------------ src/duckstation-qt/qthost.h | 7 +- src/duckstation-qt/settingswindow.cpp | 2 +- src/duckstation-regtest/regtest_host.cpp | 99 +++---- 17 files changed, 451 insertions(+), 454 deletions(-) diff --git a/README.md b/README.md index fa10ce26b..06cdce906 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ An optional [SDL game controller database file](#sdl-game-controller-database) c This is located in the following places depending on the platform you're using: -- Windows: My Documents\DuckStation +- Windows: `AppData\Local\DuckStation` - Linux: `$XDG_DATA_HOME/duckstation`, or `~/.local/share/duckstation`. - macOS: `~/Library/Application Support/DuckStation`. diff --git a/src/common/crash_handler.cpp b/src/common/crash_handler.cpp index 84c0c5d5c..029750abe 100644 --- a/src/common/crash_handler.cpp +++ b/src/common/crash_handler.cpp @@ -439,7 +439,7 @@ void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx) bool CrashHandler::Install(CleanupHandler cleanup_handler) { - const std::string progpath = FileSystem::GetProgramPath(); + const std::string progpath = FileSystem::GetProgramPath(nullptr); s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr); if (!s_backtrace_state) return false; diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index f7d1bfa97..a0483dbe0 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -2388,7 +2388,7 @@ bool FileSystem::DeleteDirectory(const char* path, Error* error) return true; } -std::string FileSystem::GetProgramPath() +std::string FileSystem::GetProgramPath(Error* error) { std::wstring buffer; buffer.resize(MAX_PATH); @@ -2407,6 +2407,12 @@ std::string FileSystem::GetProgramPath() continue; } + if (nChars == 0) + { + Error::SetWin32(error, "GetModuleFileNameW() failed: ", GetLastError()); + return {}; + } + buffer.resize(nChars); break; } @@ -2905,7 +2911,7 @@ bool FileSystem::DeleteDirectory(const char* path, Error* error) return true; } -std::string FileSystem::GetProgramPath() +std::string FileSystem::GetProgramPath(Error* error) { #if defined(__linux__) static const char* exe_path = "/proc/self/exe"; @@ -2917,6 +2923,7 @@ std::string FileSystem::GetProgramPath() int len = readlink(exe_path, buffer, curSize); if (len < 0) { + Error::SetErrno(error, "readlink() failed: ", errno); std::free(buffer); return {}; } @@ -2945,8 +2952,9 @@ std::string FileSystem::GetProgramPath() buffer[nChars] = 0; char* resolvedBuffer = realpath(buffer, nullptr); - if (resolvedBuffer == nullptr) + if (!resolvedBuffer) { + Error::SetErrno(error, "realpath() failed: ", errno); std::free(buffer); return {}; } @@ -2966,11 +2974,15 @@ std::string FileSystem::GetProgramPath() size_t cb = sizeof(buffer) - 1; int res = sysctl(mib, std::size(mib), buffer, &cb, nullptr, 0); if (res != 0) + { + Error::SetErrno(error, "readlink() failed: ", errno); return {}; + } buffer[cb] = '\0'; return buffer; #else +#error Not implemented. return {}; #endif } diff --git a/src/common/file_system.h b/src/common/file_system.h index 4394b5fe4..3df625dc5 100644 --- a/src/common/file_system.h +++ b/src/common/file_system.h @@ -244,7 +244,7 @@ bool RecursiveDeleteDirectory(const char* path, Error* error = nullptr); bool CopyFilePath(const char* source, const char* destination, bool replace, Error* error = nullptr); /// Returns the path to the current executable. -std::string GetProgramPath(); +std::string GetProgramPath(Error* error); /// Retrieves the current working directory. std::string GetWorkingDirectory(); diff --git a/src/core/core.cpp b/src/core/core.cpp index 293a9f1eb..f517390c5 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -1,13 +1,18 @@ -// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "core.h" #include "core_private.h" #include "settings.h" +#include "system.h" #include "scmversion/scmversion.h" +#include "util/ini_settings_interface.h" +#include "util/input_manager.h" + #include "common/assert.h" +#include "common/crash_handler.h" #include "common/error.h" #include "common/file_system.h" #include "common/layered_settings_interface.h" @@ -30,11 +35,19 @@ LOG_CHANNEL(Core); namespace Core { +static bool SetAppRootAndResources(const char* resources_subdir, Error* error); +static bool SetDataRoot(Error* error); +static void SetDefaultSettings(SettingsInterface& si, bool host, bool system, bool controller); + namespace { struct CoreLocals { std::mutex settings_mutex; LayeredSettingsInterface layered_settings_interface; + +#ifndef __ANDROID__ + INISettingsInterface base_settings_interface; +#endif }; } // namespace @@ -42,10 +55,55 @@ ALIGN_TO_CACHE_LINE static CoreLocals s_locals; } // namespace Core -std::string Core::ComputeDataDirectory() +bool Core::SetCriticalFolders(const char* resources_subdir, Error* error) +{ + if (!SetAppRootAndResources(resources_subdir, error)) + return false; + + if (!SetDataRoot(error)) + return false; + + // logging of directories in case something goes wrong super early + DEV_LOG("AppRoot Directory: {}", EmuFolders::AppRoot); + DEV_LOG("DataRoot Directory: {}", EmuFolders::DataRoot); + DEV_LOG("Resources Directory: {}", EmuFolders::Resources); + + // Write crash dumps to the data directory, since that'll be accessible for certain. + CrashHandler::SetWriteDirectory(EmuFolders::DataRoot); + + return true; +} + +bool Core::SetAppRootAndResources(const char* resources_subdir, Error* error) { - std::string ret; + const std::string program_path = FileSystem::GetProgramPath(error); + if (program_path.empty()) + return false; + INFO_LOG("Program Path: {}", program_path); + + EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); + + // MacOS resources are inside the app bundle, so canonicalize them. + EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, resources_subdir); +#ifdef __APPLE__ + EmuFolders::Resources = Path::Canonicalize(EmuFolders::Resources); +#endif + + if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str())) + { + Error::SetStringFmt(error, + "Resources directory does not exist at expected path:\n\n{}\n\nYour installation is not " + "complete. Please delete and re-download the application from https://www.duckstation.org/.", + EmuFolders::Resources); + return false; + } + + return true; +} + +bool Core::SetDataRoot(Error* error) +{ #ifndef __ANDROID__ // This bullshit here because AppImage mounts in /tmp, so we need to check the "real" appimage location. std::string_view real_approot = EmuFolders::AppRoot; @@ -59,26 +117,56 @@ std::string Core::ComputeDataDirectory() if (FileSystem::FileExists(Path::Combine(real_approot, "portable.txt").c_str()) || FileSystem::FileExists(Path::Combine(real_approot, "settings.ini").c_str())) { - ret = real_approot; - return ret; + // no need to check that it exists, if it's where the executable is it definitely will + EmuFolders::DataRoot = std::string(real_approot); + return true; } #endif // __ANDROID__ #if defined(_WIN32) - // On Windows, use My Documents\DuckStation. + + // On Windows, we want to use %APPDATA%\DuckStation for data, and %LOCALAPPDATA%\DuckStation for cache. + // Old installs use Documents\DuckStation for everything. Check this first. PWSTR documents_directory; if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &documents_directory))) { if (std::wcslen(documents_directory) > 0) - ret = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "DuckStation"); + { + std::string path = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "DuckStation"); + if (FileSystem::DirectoryExists(path.c_str())) + { + WARNING_LOG("Using Documents directory for data root: {}", path); + EmuFolders::DataRoot = std::move(path); + } + } + CoTaskMemFree(documents_directory); } + + if (EmuFolders::DataRoot.empty()) + { + PWSTR appdata_directory; + HRESULT hr; + if (SUCCEEDED((hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &appdata_directory)))) + { + if (std::wcslen(appdata_directory) > 0) + EmuFolders::DataRoot = Path::Combine(StringUtil::WideStringToUTF8String(appdata_directory), "DuckStation"); + + CoTaskMemFree(appdata_directory); + } + else + { + Error::SetHResult(error, "SHGetKnownFolderPath(FOLDERID_LocalAppData) failed: ", hr); + } + } + #elif (defined(__linux__) || defined(__FreeBSD__)) && !defined(__ANDROID__) + // Use $XDG_CONFIG_HOME/duckstation if it exists. const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home && Path::IsAbsolute(xdg_config_home)) { - ret = Path::RealPath(Path::Combine(xdg_config_home, "duckstation")); + EmuFolders::DataRoot = Path::RealPath(Path::Combine(xdg_config_home, "duckstation")); } else { @@ -87,27 +175,146 @@ std::string Core::ComputeDataDirectory() if (home_dir) { // ~/.local/share should exist, but just in case it doesn't and this is a fresh profile.. - const std::string local_dir(Path::Combine(home_dir, ".local")); - const std::string share_dir(Path::Combine(local_dir, "share")); + const std::string local_dir = Path::Combine(home_dir, ".local"); + const std::string share_dir = Path::Combine(local_dir, "share"); FileSystem::EnsureDirectoryExists(local_dir.c_str(), false); FileSystem::EnsureDirectoryExists(share_dir.c_str(), false); - ret = Path::RealPath(Path::Combine(share_dir, "duckstation")); + EmuFolders::DataRoot = Path::RealPath(Path::Combine(share_dir, "duckstation")); + } + else + { + Error::SetStringView(error, "HOME environment variable is not set."); } } + #elif defined(__APPLE__) + static constexpr char MAC_DATA_DIR[] = "Library/Application Support/DuckStation"; const char* home_dir = getenv("HOME"); if (home_dir) - ret = Path::RealPath(Path::Combine(home_dir, MAC_DATA_DIR)); + EmuFolders::DataRoot = Path::RealPath(Path::Combine(home_dir, MAC_DATA_DIR)); + else + Error::SetStringView(error, "HOME environment variable is not set."); + #endif // Couldn't find anything? Fall back to portable. - if (ret.empty()) - ret = EmuFolders::AppRoot; + if (EmuFolders::DataRoot.empty()) + { + Error::AddPrefix( + error, + "Failed to set data directory. Please ensure your system is configured correctly. You can also try portable mode " + "by creating portable.txt in the same directory you installed DuckStation into.\n\nThe error was:\n"); + return false; + } + + // Ensure the directories exist. + if (!FileSystem::EnsureDirectoryExists(EmuFolders::DataRoot.c_str(), false, error)) + { + Error::AddPrefixFmt(error, + "Failed to create data directory at path:\n\n{}\n\n.Please ensure this directory is " + "writable. You can also try portable mode by creating portable.txt in the same directory you " + "installed DuckStation into.\n\nThe error was:\n", + EmuFolders::AppRoot); + return false; + } - return ret; + return true; } +#ifndef __ANDROID__ + +std::string Core::GetBaseSettingsPath() +{ + return Path::Combine(EmuFolders::DataRoot, "settings.ini"); +} + +bool Core::InitializeBaseSettingsLayer(std::string settings_path, Error* error) +{ + INISettingsInterface& si = s_locals.base_settings_interface; + s_locals.layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_BASE, &si); + + if (!settings_path.empty()) + { + const bool settings_exists = FileSystem::FileExists(settings_path.c_str()); + INFO_LOG("Loading config from {}.", settings_path); + si.SetPath(std::move(settings_path)); + + if (settings_exists) + { + if (!si.Load(error)) + { + Error::AddPrefix(error, "Failed to load settings: "); + return false; + } + } + else + { + SetDefaultSettings(si, true, true, true); + if (!si.Save(error)) + { + Error::AddPrefix(error, "Failed to save settings: "); + return false; + } + } + } + else + { + // Running settings-file-less, use defaults. + SetDefaultSettings(si, true, true, true); + } + + EmuFolders::LoadConfig(si); + EmuFolders::EnsureFoldersExist(); + + // We need to create the console window early, otherwise it appears in front of the main window. + if (!Log::IsConsoleOutputEnabled() && si.GetBoolValue("Logging", "LogToConsole", false)) + Log::SetConsoleOutputParams(true, si.GetBoolValue("Logging", "LogTimestamps", true)); + + return true; +} + +bool Core::SaveBaseSettingsLayer(Error* error) +{ + INISettingsInterface& si = s_locals.base_settings_interface; + if (si.IsDirty() && !si.Save(error)) + return false; + + return true; +} + +void Core::SetDefaultSettings(bool host, bool system, bool controller) +{ + { + const auto lock = GetSettingsLock(); + SetDefaultSettings(s_locals.base_settings_interface, host, system, controller); + } + + Host::OnSettingsResetToDefault(host, system, controller); +} + +void Core::SetDefaultSettings(SettingsInterface& si, bool host, bool system, bool controller) +{ + if (host) + Host::SetDefaultSettings(si); + + if (system) + { + System::SetDefaultSettings(si); + EmuFolders::SetDefaults(); + EmuFolders::Save(si); + } + + if (controller) + { + InputManager::SetDefaultSourceConfig(si); + Settings::SetDefaultControllerConfig(si); + Settings::SetDefaultHotkeyConfig(si); + } +} + +#endif // __ANDROID__ + std::unique_lock Core::GetSettingsLock() { return std::unique_lock(s_locals.settings_mutex); @@ -317,6 +524,8 @@ SettingsInterface* Core::GetInputSettingsLayer() return s_locals.layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_INPUT); } +#ifdef __ANDROID__ + void Core::SetBaseSettingsLayer(SettingsInterface* sif) { AssertMsg(s_locals.layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE) == nullptr, @@ -324,6 +533,8 @@ void Core::SetBaseSettingsLayer(SettingsInterface* sif) s_locals.layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_BASE, sif); } +#endif // __ANDROID__ + void Core::SetGameSettingsLayer(SettingsInterface* sif, std::unique_lock& lock) { s_locals.layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_GAME, sif); diff --git a/src/core/core_private.h b/src/core/core_private.h index 6e84fcdae..b320dbb80 100644 --- a/src/core/core_private.h +++ b/src/core/core_private.h @@ -5,14 +5,36 @@ #include "core.h" +class Error; + namespace Core { /// Based on the current configuration, determines what the data directory is. -std::string ComputeDataDirectory(); +bool SetCriticalFolders(const char* resources_subdir, Error* error); + +#ifndef __ANDROID__ + +/// Returns the path to the configuration file. +/// We split this out so it can be retrieved by the host for error message purposes, +/// and so that regtest can override with no-config. +std::string GetBaseSettingsPath(); + +/// Loads the configuration file. +bool InitializeBaseSettingsLayer(std::string settings_path, Error* error); + +/// Saves the configuration file. +bool SaveBaseSettingsLayer(Error* error); + +/// Restores default settings. +void SetDefaultSettings(bool host, bool system, bool controller); + +#else /// Sets the base settings layer. Should be called by the host at initialization time. void SetBaseSettingsLayer(SettingsInterface* sif); +#endif // __ANDROID__ + /// Sets the game settings layer. Called by System when the game changes. void SetGameSettingsLayer(SettingsInterface* sif, std::unique_lock& lock); @@ -20,3 +42,17 @@ void SetGameSettingsLayer(SettingsInterface* sif, std::unique_lock& void SetInputSettingsLayer(SettingsInterface* sif, std::unique_lock& lock); } // namespace Core + +namespace Host { + +#ifndef __ANDROID__ + +/// Sets host-specific default settings. +void SetDefaultSettings(SettingsInterface& si); + +/// Called when settings have been reset. +void OnSettingsResetToDefault(bool host, bool system, bool controller); + +#endif // __ANDROID__ + +} // namespace Host diff --git a/src/core/fullscreenui.h b/src/core/fullscreenui.h index 3bb2e62ec..1979f7670 100644 --- a/src/core/fullscreenui.h +++ b/src/core/fullscreenui.h @@ -109,9 +109,6 @@ namespace Host { #ifndef __ANDROID__ -/// Requests settings reset. -void RequestResetSettings(bool system, bool controller); - /// Requests shut down and exit of the hosting application. This may not actually exit, /// if the user cancels the shutdown confirmation. void RequestExitApplication(bool allow_confirm); diff --git a/src/core/fullscreenui_settings.cpp b/src/core/fullscreenui_settings.cpp index c58b58636..0153a1e2b 100644 --- a/src/core/fullscreenui_settings.cpp +++ b/src/core/fullscreenui_settings.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "achievements.h" @@ -6,6 +6,7 @@ #include "cheats.h" #include "controller.h" #include "core.h" +#include "core_private.h" #include "fullscreenui_private.h" #include "game_database.h" #include "game_list.h" @@ -1752,7 +1753,7 @@ void FullscreenUI::BeginResetSettings() if (!result) return; - Host::RequestResetSettings(true, false); + Core::SetDefaultSettings(true, true, false); ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Settings reset to default.")); }); } @@ -3151,7 +3152,7 @@ void FullscreenUI::BeginResetControllerSettings() if (!result) return; - Host::RequestResetSettings(false, true); + Core::SetDefaultSettings(false, false, true); ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Controller settings reset to default.")); }); } diff --git a/src/core/system.cpp b/src/core/system.cpp index 87fb5c30c..a80945831 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1373,16 +1373,6 @@ void System::SetDefaultSettings(SettingsInterface& si) { Settings temp; - // we don't want to reset some things (e.g. OSD) - temp.display_show_messages = g_settings.display_show_messages; - temp.display_show_fps = g_settings.display_show_fps; - temp.display_show_speed = g_settings.display_show_speed; - temp.display_show_gpu_stats = g_settings.display_show_gpu_stats; - temp.display_show_resolution = g_settings.display_show_resolution; - temp.display_show_cpu_usage = g_settings.display_show_cpu_usage; - temp.display_show_gpu_usage = g_settings.display_show_gpu_usage; - temp.display_show_frame_times = g_settings.display_show_frame_times; - // keep controller, we reset it elsewhere for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) temp.controller_types[i] = g_settings.controller_types[i]; @@ -1394,6 +1384,10 @@ void System::SetDefaultSettings(SettingsInterface& si) Settings::SetDefaultLogConfig(si); + PostProcessing::Config::ClearStages(si, PostProcessing::Config::DISPLAY_CHAIN_SECTION); + PostProcessing::Config::ClearStages(si, PostProcessing::Config::INTERNAL_CHAIN_SECTION); + si.ClearSection("BorderOverlay"); + #ifndef __ANDROID__ si.SetStringValue("MediaCapture", "Backend", MediaCapture::GetBackendName(Settings::DEFAULT_MEDIA_CAPTURE_BACKEND)); si.SetStringValue("MediaCapture", "Container", Settings::DEFAULT_MEDIA_CAPTURE_CONTAINER); diff --git a/src/duckstation-mini/mini_host.cpp b/src/duckstation-mini/mini_host.cpp index c68ba6183..c635a6d98 100644 --- a/src/duckstation-mini/mini_host.cpp +++ b/src/duckstation-mini/mini_host.cpp @@ -23,7 +23,6 @@ #include "util/cd_image.h" #include "util/gpu_device.h" #include "util/imgui_manager.h" -#include "util/ini_settings_interface.h" #include "util/input_manager.h" #include "util/sdl_input_source.h" #include "util/translation.h" @@ -54,6 +53,10 @@ #include #include +#ifdef _WIN32 +#include "common/windows_headers.h" +#endif + LOG_CHANNEL(Host); namespace MiniHost { @@ -66,7 +69,6 @@ static constexpr u32 NUM_ASYNC_WORKER_THREADS = 2; static constexpr u32 DEFAULT_WINDOW_WIDTH = 1920; static constexpr u32 DEFAULT_WINDOW_HEIGHT = 1080; -static constexpr u32 SETTINGS_VERSION = 3; static constexpr auto CORE_THREAD_POLL_INTERVAL = std::chrono::milliseconds(8); // how often we'll poll controllers when paused @@ -74,14 +76,9 @@ static bool ParseCommandLineParametersAndInitializeConfig(int argc, char* argv[] std::optional& autoboot); static void PrintCommandLineVersion(); static void PrintCommandLineHelp(const char* progname); -static bool InitializeConfig(); +static bool InitializeFoldersAndConfig(Error* error); static void InitializeEarlyConsole(); static void HookSignals(); -static void SetAppRoot(); -static void SetResourcesDirectory(); -static bool SetDataDirectory(); -static bool SetCriticalFolders(); -static void SetDefaultSettings(SettingsInterface& si, bool system, bool controller); static std::string GetResourcePath(std::string_view name, bool allow_override); static bool PerformEarlyHardwareChecks(); static bool EarlyProcessStartup(); @@ -103,8 +100,7 @@ static void SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height); struct SDLHostState { // UI thread state - ALIGN_TO_CACHE_LINE INISettingsInterface base_settings_interface; - bool batch_mode = false; + ALIGN_TO_CACHE_LINE bool batch_mode = false; bool start_fullscreen_ui_fullscreen = false; bool was_paused_by_focus_loss = false; bool ui_thread_running = false; @@ -197,139 +193,34 @@ bool MiniHost::EarlyProcessStartup() return true; } -bool MiniHost::SetCriticalFolders() -{ - SetAppRoot(); - SetResourcesDirectory(); - if (!SetDataDirectory()) - return false; - - // logging of directories in case something goes wrong super early - DEV_LOG("AppRoot Directory: {}", EmuFolders::AppRoot); - DEV_LOG("DataRoot Directory: {}", EmuFolders::DataRoot); - DEV_LOG("Resources Directory: {}", EmuFolders::Resources); - - // Write crash dumps to the data directory, since that'll be accessible for certain. - CrashHandler::SetWriteDirectory(EmuFolders::DataRoot); - - // the resources directory should exist, bail out if not - if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str())) - { - Host::ReportFatalError("Error", "Resources directory is missing, your installation is incomplete."); - return false; - } - - return true; -} - -void MiniHost::SetAppRoot() -{ - const std::string program_path = FileSystem::GetProgramPath(); - INFO_LOG("Program Path: {}", program_path); - - EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); -} - -void MiniHost::SetResourcesDirectory() +bool MiniHost::InitializeFoldersAndConfig(Error* error) { + // Path to the resources directory relative to the application binary. +// On Windows/Linux, these are in the binary directory. +// On macOS, this is in the bundle resources directory. #ifndef __APPLE__ - // On Windows/Linux, these are in the binary directory. - EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources"); + static constexpr const char* RESOURCES_RELATIVE_PATH = "resources"; #else - // On macOS, this is in the bundle resources directory. - EmuFolders::Resources = Path::Canonicalize(Path::Combine(EmuFolders::AppRoot, "../Resources")); + static constexpr const char* RESOURCES_RELATIVE_PATH = "../Resources"; #endif -} - -bool MiniHost::SetDataDirectory() -{ - EmuFolders::DataRoot = Core::ComputeDataDirectory(); - // make sure it exists - if (!EmuFolders::DataRoot.empty() && !FileSystem::DirectoryExists(EmuFolders::DataRoot.c_str())) - { - // we're in trouble if we fail to create this directory... but try to hobble on with portable - Error error; - if (!FileSystem::EnsureDirectoryExists(EmuFolders::DataRoot.c_str(), false, &error)) - { - Host::ReportFatalError("Error", - TinyString::from_format("Failed to create data directory: {}", error.GetDescription())); - return false; - } - } + if (!Core::SetCriticalFolders(RESOURCES_RELATIVE_PATH, error)) + return false; - // couldn't determine the data directory? fallback to portable. - if (EmuFolders::DataRoot.empty()) - EmuFolders::DataRoot = EmuFolders::AppRoot; + Error config_error; + if (!Core::InitializeBaseSettingsLayer(Core::GetBaseSettingsPath(), &config_error)) + return false; return true; } -bool MiniHost::InitializeConfig() +void Host::SetDefaultSettings(SettingsInterface& si) { - if (!SetCriticalFolders()) - return false; - - std::string settings_path = Path::Combine(EmuFolders::DataRoot, "settings.ini"); - const bool settings_exists = FileSystem::FileExists(settings_path.c_str()); - INFO_LOG("Loading config from {}.", settings_path); - s_state.base_settings_interface.SetPath(std::move(settings_path)); - Core::SetBaseSettingsLayer(&s_state.base_settings_interface); - - u32 settings_version; - if (!settings_exists || !s_state.base_settings_interface.Load() || - !s_state.base_settings_interface.GetUIntValue("Main", "SettingsVersion", &settings_version) || - settings_version != SETTINGS_VERSION) - { - if (s_state.base_settings_interface.ContainsValue("Main", "SettingsVersion")) - { - // NOTE: No point translating this, because there's no config loaded, so no language loaded. - Host::ReportErrorAsync("Error", fmt::format("Settings version {} does not match expected version {}, resetting.", - settings_version, SETTINGS_VERSION)); - } - - s_state.base_settings_interface.SetUIntValue("Main", "SettingsVersion", SETTINGS_VERSION); - SetDefaultSettings(s_state.base_settings_interface, true, true); - - // Make sure we can actually save the config, and the user doesn't have some permission issue. - Error error; - if (!s_state.base_settings_interface.Save(&error)) - { - Host::ReportFatalError( - "Error", - fmt::format( - "Failed to save configuration to\n\n{}\n\nThe error was: {}\n\nPlease ensure this directory is writable. You " - "can also try portable mode by creating portable.txt in the same directory you installed DuckStation into.", - s_state.base_settings_interface.GetPath(), error.GetDescription())); - return false; - } - } - - EmuFolders::LoadConfig(s_state.base_settings_interface); - EmuFolders::EnsureFoldersExist(); - - // We need to create the console window early, otherwise it appears in front of the main window. - if (!Log::IsConsoleOutputEnabled() && s_state.base_settings_interface.GetBoolValue("Logging", "LogToConsole", false)) - Log::SetConsoleOutputParams(true, s_state.base_settings_interface.GetBoolValue("Logging", "LogTimestamps", true)); - - return true; } -void MiniHost::SetDefaultSettings(SettingsInterface& si, bool system, bool controller) +void Host::OnSettingsResetToDefault(bool host, bool system, bool controller) { - if (system) - { - System::SetDefaultSettings(si); - EmuFolders::SetDefaults(); - EmuFolders::Save(si); - } - - if (controller) - { - InputManager::SetDefaultSourceConfig(si); - Settings::SetDefaultControllerConfig(si); - Settings::SetDefaultHotkeyConfig(si); - } + Host::RunOnCoreThread([]() { System::ApplySettings(false); }); } void Host::ReportDebuggerEvent(CPU::DebuggerEvent event, std::string_view message) @@ -450,7 +341,7 @@ void Host::CommitBaseSettingChanges() { const auto lock = Core::GetSettingsLock(); Error error; - if (!MiniHost::s_state.base_settings_interface.Save(&error)) + if (!Core::SaveBaseSettingsLayer(&error)) ERROR_LOG("Failed to save settings: {}", error.GetDescription()); } @@ -720,20 +611,22 @@ bool MiniHost::GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* h { const auto lock = Core::GetSettingsLock(); - bool result = s_state.base_settings_interface.GetIntValue("UI", "MainWindowX", x); - result = result && s_state.base_settings_interface.GetIntValue("UI", "MainWindowY", y); - result = result && s_state.base_settings_interface.GetIntValue("UI", "MainWindowWidth", width); - result = result && s_state.base_settings_interface.GetIntValue("UI", "MainWindowHeight", height); + SettingsInterface* si = Core::GetBaseSettingsLayer(); + bool result = si->GetIntValue("UI", "MainWindowX", x); + result = result && si->GetIntValue("UI", "MainWindowY", y); + result = result && si->GetIntValue("UI", "MainWindowWidth", width); + result = result && si->GetIntValue("UI", "MainWindowHeight", height); return result; } void MiniHost::SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height) { const auto lock = Core::GetSettingsLock(); - s_state.base_settings_interface.SetIntValue("UI", "MainWindowX", x); - s_state.base_settings_interface.SetIntValue("UI", "MainWindowY", y); - s_state.base_settings_interface.SetIntValue("UI", "MainWindowWidth", width); - s_state.base_settings_interface.SetIntValue("UI", "MainWindowHeight", height); + SettingsInterface* si = Core::GetBaseSettingsLayer(); + si->SetIntValue("UI", "MainWindowX", x); + si->SetIntValue("UI", "MainWindowY", y); + si->SetIntValue("UI", "MainWindowWidth", width); + si->SetIntValue("UI", "MainWindowHeight", height); } void MiniHost::UIThreadMainLoop() @@ -1293,32 +1186,6 @@ std::optional Host::GetTopLevelWindowInfo() return MiniHost::TranslateSDLWindowInfo(MiniHost::s_state.sdl_window, nullptr); } -void Host::RequestResetSettings(bool system, bool controller) -{ - using namespace MiniHost; - - const auto lock = Core::GetSettingsLock(); - { - SettingsInterface& si = s_state.base_settings_interface; - - if (system) - { - System::SetDefaultSettings(si); - EmuFolders::SetDefaults(); - EmuFolders::Save(si); - } - - if (controller) - { - InputManager::SetDefaultSourceConfig(si); - Settings::SetDefaultControllerConfig(si); - Settings::SetDefaultHotkeyConfig(si); - } - } - - System::ApplySettings(false); -} - void Host::RequestExitApplication(bool allow_confirm) { Host::RunOnCoreThread([]() { @@ -1348,6 +1215,7 @@ void Host::ReportFatalError(std::string_view title, std::string_view message) // Depending on the platform, this may not be available. std::fputs(SmallString::from_format("Fatal error: {}: {}\n", title, message).c_str(), stderr); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, TinyString(title).c_str(), SmallString(message).c_str(), nullptr); + std::abort(); } void Host::ReportErrorAsync(std::string_view title, std::string_view message) @@ -1760,10 +1628,11 @@ bool MiniHost::ParseCommandLineParametersAndInitializeConfig(int argc, char* arg } // To do anything useful, we need the config initialized. - if (!InitializeConfig()) + Error error; + if (!InitializeFoldersAndConfig(&error)) { // NOTE: No point translating this, because no config means the language won't be loaded anyway. - Host::ReportFatalError("Error", "Failed to initialize config."); + Host::ReportFatalError("DuckStation", error.GetDescription()); return EXIT_FAILURE; } @@ -1772,7 +1641,7 @@ bool MiniHost::ParseCommandLineParametersAndInitializeConfig(int argc, char* arg if (autoboot && !autoboot->path.empty() && !FileSystem::FileExists(autoboot->path.c_str()) && !CDImage::IsDeviceName(autoboot->path.c_str())) { - Host::ReportFatalError("Error", fmt::format("File '{}' does not exist.", autoboot->path)); + Host::ReportFatalError("DuckStation", fmt::format("File '{}' does not exist.", autoboot->path)); return false; } @@ -1797,7 +1666,7 @@ bool MiniHost::ParseCommandLineParametersAndInitializeConfig(int argc, char* arg if (autoboot->save_state.empty() || !FileSystem::FileExists(autoboot->save_state.c_str())) { - Host::ReportFatalError("Error", "The specified save state does not exist."); + Host::ReportFatalError("DuckStation", "The specified save state does not exist."); return false; } } @@ -1813,7 +1682,7 @@ bool MiniHost::ParseCommandLineParametersAndInitializeConfig(int argc, char* arg { if (!autoboot) { - Host::ReportFatalError("Error", "Cannot use batch mode, because no boot filename was specified."); + Host::ReportFatalError("DuckStation", "Cannot use batch mode, because no boot filename was specified."); return false; } @@ -1884,8 +1753,7 @@ int main(int argc, char* argv[]) // Ensure log is flushed. Log::SetFileOutputParams(false, nullptr); - if (s_state.base_settings_interface.IsDirty()) - s_state.base_settings_interface.Save(); + Core::SaveBaseSettingsLayer(nullptr); SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS); diff --git a/src/duckstation-qt/autoupdaterdialog.cpp b/src/duckstation-qt/autoupdaterdialog.cpp index 67ac49294..e7a8fb94b 100644 --- a/src/duckstation-qt/autoupdaterdialog.cpp +++ b/src/duckstation-qt/autoupdaterdialog.cpp @@ -474,7 +474,6 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, const Error& error, const QJsonArray commits(doc_object["commits"].toArray()); bool update_will_break_save_states = false; - bool update_increases_settings_version = false; for (const QJsonValue& commit : commits) { @@ -493,9 +492,6 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, const Error& error, if (message.contains(QStringLiteral("[SAVEVERSION+]"))) update_will_break_save_states = true; - - if (message.contains(QStringLiteral("[SETTINGSVERSION+]"))) - update_increases_settings_version = true; } changes_html += ""; @@ -507,13 +503,6 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, const Error& error, "before installing this update or you will lose progress.

")); } - if (update_increases_settings_version) - { - changes_html.prepend( - tr("

Settings Warning

Installing this update will reset your program configuration. Please note " - "that you will have to reconfigure your settings after this update.

")); - } - m_ui.updateNotes->setText(changes_html); return; } diff --git a/src/duckstation-qt/controllersettingswindow.cpp b/src/duckstation-qt/controllersettingswindow.cpp index 5dff3fd09..2669d1de0 100644 --- a/src/duckstation-qt/controllersettingswindow.cpp +++ b/src/duckstation-qt/controllersettingswindow.cpp @@ -298,7 +298,7 @@ void ControllerSettingsWindow::onRestoreDefaultsClicked() } // actually restore it - g_core_thread->setDefaultSettings(false, true); + g_core_thread->setDefaultSettings(false, false, true); // reload all settings createWidgets(); diff --git a/src/duckstation-qt/logwindow.cpp b/src/duckstation-qt/logwindow.cpp index bbf04184f..c81d4508c 100644 --- a/src/duckstation-qt/logwindow.cpp +++ b/src/duckstation-qt/logwindow.cpp @@ -380,12 +380,9 @@ void LogWindow::setLogLevel(Log::Level level) void LogWindow::populateFilterMenu(QMenu* filter_menu) { - const auto settings_Lock = Core::GetSettingsLock(); - const INISettingsInterface* si = QtHost::GetBaseSettingsInterface(); - for (const char* channel_name : Log::GetChannelNames()) { - const bool enabled = si->GetBoolValue("Logging", channel_name, true); + const bool enabled = Core::GetBaseBoolSettingValue("Logging", channel_name, true); QAction* const action = filter_menu->addAction(QString::fromUtf8(channel_name), [channel_name](bool checked) { Core::SetBaseBoolSettingValue("Logging", channel_name, checked); Host::CommitBaseSettingChanges(); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index d45d3ca86..a2deb4021 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -86,6 +86,8 @@ #include "moc_qthost.cpp" +using namespace Qt::Literals::StringLiterals; + LOG_CHANNEL(Host); #if 0 @@ -99,7 +101,6 @@ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Quit %1") QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "About %1") #endif -static constexpr u32 SETTINGS_VERSION = 3; static constexpr u32 SETTINGS_SAVE_DELAY = 1000; /// Use two async worker threads, should be enough for most tasks. @@ -125,13 +126,8 @@ static bool EarlyProcessStartup(); static void ProcessShutdown(); static void MessageOutputHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); static void RegisterTypes(); -static bool InitializeConfig(); -static void SetAppRoot(); -static void SetResourcesDirectory(); -static bool SetDataDirectory(); -static bool SetCriticalFolders(); -static void LoadResources(); -static void SetDefaultSettings(SettingsInterface& si, bool system, bool controller); +static bool InitializeFoldersAndConfig(Error* error); +static bool LoadResources(Error* error); static void SaveSettings(); static bool RunSetupWizard(); static void UpdateFontOrder(std::string_view language); @@ -153,7 +149,6 @@ static void ApplyWaylandWorkarounds(); namespace { struct State { - INISettingsInterface base_settings_interface; std::unique_ptr settings_save_timer; std::vector translators; QIcon app_icon; @@ -420,11 +415,6 @@ QString QtHost::GetResourcesBasePath() return QString::fromStdString(EmuFolders::Resources); } -INISettingsInterface* QtHost::GetBaseSettingsInterface() -{ - return &s_state.base_settings_interface; -} - bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty) { INISettingsInterface* ini = static_cast(sif); @@ -537,141 +527,81 @@ void QtHost::DownloadFile(QWidget* parent, std::string url, std::string path, }); } -bool QtHost::InitializeConfig() +bool QtHost::InitializeFoldersAndConfig(Error* error) { - if (!SetCriticalFolders()) - return false; + // Path to the resources directory relative to the application binary. +// On Windows/Linux, these are in the binary directory. +// On macOS, this is in the bundle resources directory. +#ifndef __APPLE__ + static constexpr const char* RESOURCES_RELATIVE_PATH = "resources"; +#else + static constexpr const char* RESOURCES_RELATIVE_PATH = "../Resources"; +#endif - std::string settings_path = Path::Combine(EmuFolders::DataRoot, "settings.ini"); - const bool settings_exists = FileSystem::FileExists(settings_path.c_str()); - INFO_LOG("Loading config from {}.", settings_path); - s_state.base_settings_interface.SetPath(std::move(settings_path)); - Core::SetBaseSettingsLayer(&s_state.base_settings_interface); + if (!Core::SetCriticalFolders(RESOURCES_RELATIVE_PATH, error)) + return false; - uint settings_version; - if (!settings_exists || !s_state.base_settings_interface.Load() || - !s_state.base_settings_interface.GetUIntValue("Main", "SettingsVersion", &settings_version) || - settings_version != SETTINGS_VERSION) + Error config_error; + if (!Core::InitializeBaseSettingsLayer(Core::GetBaseSettingsPath(), &config_error)) { - if (s_state.base_settings_interface.ContainsValue("Main", "SettingsVersion")) + if (QMessageBox::question( + nullptr, "DuckStation"_L1, + "Failed to load configuration. The error was:\n\n%1\n\nThe settings file may be corrupted. Do you want to " + "delete the settings file and try again? Note that any currently-configured settings will be lost."_L1.arg( + QString::fromStdString(config_error.GetDescription()))) == QMessageBox::Yes) { - // NOTE: No point translating this, because there's no config loaded, so no language loaded. - Host::ReportErrorAsync("Error", fmt::format("Settings version {} does not match expected version {}, resetting.", - settings_version, SETTINGS_VERSION)); + if (!FileSystem::DeleteFile(Core::GetBaseSettingsPath().c_str(), &config_error)) + { + QMessageBox::critical(nullptr, QStringLiteral("DuckStation"), + QStringLiteral("Failed to delete settings file:\n\n%1") + .arg(QString::fromStdString(config_error.GetDescription()))); + } } - s_state.base_settings_interface.SetUIntValue("Main", "SettingsVersion", SETTINGS_VERSION); - SetDefaultSettings(s_state.base_settings_interface, true, true); - - // Flag for running the setup wizard if this is our first run. We want to run it next time if they don't finish it. - s_state.base_settings_interface.SetBoolValue("Main", "SetupWizardIncomplete", true); - - // Make sure we can actually save the config, and the user doesn't have some permission issue. - Error error; - if (!s_state.base_settings_interface.Save(&error)) + // Try again after deleting. + if (!Core::InitializeBaseSettingsLayer(Core::GetBaseSettingsPath(), &config_error)) { - QMessageBox::critical( - nullptr, QStringLiteral("DuckStation"), - QStringLiteral( - "Failed to save configuration to\n\n%1\n\nThe error was: %2\n\nPlease ensure this directory is writable. You " - "can also try portable mode by creating portable.txt in the same directory you installed DuckStation into.") - .arg(QString::fromStdString(s_state.base_settings_interface.GetPath())) - .arg(QString::fromStdString(error.GetDescription()))); + Error::SetStringFmt(error, + "Failed to load configuration. The error was:\n\n{}\n\nPlease ensure that the data directory " + "is writable. The data directory is located at:\n\n{}\n\nYou can also try portable mode by " + "creating portable.txt in the same directory you installed DuckStation into.", + config_error.GetDescription(), EmuFolders::DataRoot); return false; } } + // Very old installations pre-setup-wizard won't have the "SetupWizardIncomplete" key. + // Instead, we rely on "SettingsVersion" there as a signal that setup has been completed. + if (!Core::ContainsBaseSettingValue("Main", "SetupWizardIncomplete") && + !Core::ContainsBaseSettingValue("Main", "SettingsVersion")) + { + // Flag for running the setup wizard if this is our first run. We want to run it next time if they don't finish it. + Core::SetBaseBoolSettingValue("Main", "SetupWizardIncomplete", true); + } + // Setup wizard was incomplete last time? s_state.run_setup_wizard = - s_state.run_setup_wizard || s_state.base_settings_interface.GetBoolValue("Main", "SetupWizardIncomplete", false); - - EmuFolders::LoadConfig(s_state.base_settings_interface); - EmuFolders::EnsureFoldersExist(); - - // We need to create the console window early, otherwise it appears in front of the main window. - if (!Log::IsConsoleOutputEnabled() && s_state.base_settings_interface.GetBoolValue("Logging", "LogToConsole", false)) - Log::SetConsoleOutputParams(true, s_state.base_settings_interface.GetBoolValue("Logging", "LogTimestamps", true)); + s_state.run_setup_wizard || Core::GetBaseBoolSettingValue("Main", "SetupWizardIncomplete", false); UpdateApplicationLanguage(nullptr); return true; } -bool QtHost::SetCriticalFolders() +bool QtHost::LoadResources(Error* error) { - SetAppRoot(); - SetResourcesDirectory(); - if (!SetDataDirectory()) - return false; - - // logging of directories in case something goes wrong super early - DEV_LOG("AppRoot Directory: {}", EmuFolders::AppRoot); - DEV_LOG("DataRoot Directory: {}", EmuFolders::DataRoot); - DEV_LOG("Resources Directory: {}", EmuFolders::Resources); - - // Write crash dumps to the data directory, since that'll be accessible for certain. - CrashHandler::SetWriteDirectory(EmuFolders::DataRoot); - // the resources directory should exist, bail out if not const std::string rcc_path = Path::Combine(EmuFolders::Resources, "duckstation-qt.rcc"); - if (!FileSystem::FileExists(rcc_path.c_str()) || !QResource::registerResource(QString::fromStdString(rcc_path)) || - !FileSystem::DirectoryExists(EmuFolders::Resources.c_str())) + if (!FileSystem::FileExists(rcc_path.c_str()) || !QResource::registerResource(QString::fromStdString(rcc_path))) { - QMessageBox::critical(nullptr, QStringLiteral("Error"), - QStringLiteral("Resources are missing, your installation is incomplete.")); + Error::SetStringFmt(error, + "{} could not be loaded. Your installation is not complete. Please delete and re-download the " + "application from https://www.duckstation.org/.", + Path::GetFileName(rcc_path)); return false; } - return true; -} - -void QtHost::SetAppRoot() -{ - const std::string program_path = FileSystem::GetProgramPath(); - INFO_LOG("Program Path: {}", program_path); - - EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); -} - -void QtHost::SetResourcesDirectory() -{ -#ifndef __APPLE__ - // On Windows/Linux, these are in the binary directory. - EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources"); -#else - // On macOS, this is in the bundle resources directory. - EmuFolders::Resources = Path::Canonicalize(Path::Combine(EmuFolders::AppRoot, "../Resources")); -#endif -} - -bool QtHost::SetDataDirectory() -{ - EmuFolders::DataRoot = Core::ComputeDataDirectory(); - - // make sure it exists - if (!FileSystem::DirectoryExists(EmuFolders::DataRoot.c_str())) - { - // we're in trouble if we fail to create this directory... but try to hobble on with portable - Error error; - if (!FileSystem::EnsureDirectoryExists(EmuFolders::DataRoot.c_str(), false, &error)) - { - // no point translating, config isn't loaded - QMessageBox::critical( - nullptr, QStringLiteral("DuckStation"), - QStringLiteral("Failed to create data directory at path\n\n%1\n\nThe error was: %2\nPlease ensure this " - "directory is writable. You can also try portable mode by creating portable.txt in the same " - "directory you installed DuckStation into.") - .arg(QString::fromStdString(EmuFolders::DataRoot)) - .arg(QString::fromStdString(error.GetDescription()))); - return false; - } - } - - return true; -} - -void QtHost::LoadResources() -{ s_state.app_icon = QIcon(GetResourceQPath("images/duck.png", true)); + return true; } void Host::LoadSettings(const SettingsInterface& si, std::unique_lock& lock) @@ -685,41 +615,37 @@ void Host::CheckForSettingsChanges(const Settings& old_settings) QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection); } -void CoreThread::setDefaultSettings(bool system /* = true */, bool controller /* = true */) +void CoreThread::setDefaultSettings(bool host, bool system, bool controller) { if (!isCurrentThread()) { - QMetaObject::invokeMethod(this, &CoreThread::setDefaultSettings, Qt::QueuedConnection, system, controller); + QMetaObject::invokeMethod(this, &CoreThread::setDefaultSettings, Qt::QueuedConnection, host, system, controller); return; } - { - const auto lock = Core::GetSettingsLock(); - QtHost::SetDefaultSettings(s_state.base_settings_interface, system, controller); - QtHost::QueueSettingsSave(); - } + Core::SetDefaultSettings(host, system, controller); +} - applySettings(false); +void Host::SetDefaultSettings(SettingsInterface& si) +{ +#ifdef _WIN32 + si.SetBoolValue("Main", "DisableWindowRoundedCorners", false); +#endif - if (system) - emit settingsResetToDefault(system, controller); + si.SetBoolValue("Main", "DisableWindowResize", false); + si.SetBoolValue("Main", "HideCursorInFullscreen", false); + si.SetBoolValue("Main", "RenderToSeparateWindow", false); + si.SetBoolValue("Main", "HideMainWindowWhenRunning", false); + + // TODO: We could include stuff like game list here, but meh... } -void QtHost::SetDefaultSettings(SettingsInterface& si, bool system, bool controller) +void Host::OnSettingsResetToDefault(bool host, bool system, bool controller) { - if (system) - { - System::SetDefaultSettings(si); - EmuFolders::SetDefaults(); - EmuFolders::Save(si); - } + g_core_thread->applySettings(false); - if (controller) - { - InputManager::SetDefaultSourceConfig(si); - Settings::SetDefaultControllerConfig(si); - Settings::SetDefaultHotkeyConfig(si); - } + if (system) + emit g_core_thread->settingsResetToDefault(host, system, controller); } void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height) @@ -3016,8 +2942,13 @@ void QtHost::SaveSettings() { Error error; const auto lock = Core::GetSettingsLock(); - if (s_state.base_settings_interface.IsDirty() && !s_state.base_settings_interface.Save(&error)) + if (!Core::SaveBaseSettingsLayer(&error)) + { ERROR_LOG("Failed to save settings: {}", error.GetDescription()); + QtUtils::AsyncMessageBox( + g_main_window, QMessageBox::Critical, QStringLiteral("DuckStation"), + QStringLiteral("Failed to save settings: %1").arg(QString::fromStdString(error.GetDescription()))); + } } if (s_state.settings_save_timer) @@ -3058,11 +2989,6 @@ void Host::RequestSystemShutdown(bool allow_confirm, bool save_state, bool check save_state, check_memcard_busy, true, false, false); } -void Host::RequestResetSettings(bool system, bool controller) -{ - g_core_thread->setDefaultSettings(system, controller); -} - void Host::RequestExitApplication(bool allow_confirm) { QMetaObject::invokeMethod(g_main_window, &MainWindow::requestExit, Qt::QueuedConnection, allow_confirm); @@ -3193,8 +3119,8 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app, { if (!no_more_args) { -#define CHECK_ARG(str) (args[i] == QStringLiteral(str)) -#define CHECK_ARG_PARAM(str) (args[i] == QStringLiteral(str) && ((i + 1) < args.size())) +#define CHECK_ARG(str) (args[i] == str##_L1) +#define CHECK_ARG_PARAM(str) (args[i] == str##_L1 && ((i + 1) < args.size())) if (CHECK_ARG("-help")) { @@ -3302,7 +3228,7 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app, } else if (args[i][0] == QChar('-')) { - QMessageBox::critical(nullptr, QStringLiteral("Error"), QStringLiteral("Unknown parameter: %1").arg(args[i])); + QMessageBox::critical(nullptr, "DuckStation"_L1, QString("Unknown parameter: %1"_L1).arg(args[i])); return false; } @@ -3316,16 +3242,14 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app, } // To do anything useful, we need the config initialized. - if (!InitializeConfig()) + Error error; + if (!InitializeFoldersAndConfig(&error) || !LoadResources(&error)) { // NOTE: No point translating this, because no config means the language won't be loaded anyway. - QMessageBox::critical(nullptr, QStringLiteral("Error"), QStringLiteral("Failed to initialize config.")); + QMessageBox::critical(nullptr, "DuckStation"_L1, QString::fromStdString(error.GetDescription())); return false; } - // Not the best location for this, but early enough. - LoadResources(); - // Check the file we're starting actually exists. if (autoboot && !autoboot->path.empty() && !CDImage::IsDeviceName(autoboot->path.c_str())) { diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 92710ee26..492c5bdea 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -105,7 +105,7 @@ public: Q_SIGNALS: void errorReported(const QString& title, const QString& message); void statusMessage(const QString& message); - void settingsResetToDefault(bool system, bool controller); + void settingsResetToDefault(bool host, bool system, bool controller); void systemStarting(); void systemStarted(); void systemStopping(); @@ -136,7 +136,7 @@ Q_SIGNALS: void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size); public: - void setDefaultSettings(bool system = true, bool controller = true); + void setDefaultSettings(bool host, bool system, bool controller); void applySettings(bool display_osd_messages = false); void reloadGameSettings(bool display_osd_messages = false); void reloadInputProfile(bool display_osd_messages = false); @@ -354,9 +354,6 @@ const QStringList& GetRobotoFontFamilies(); /// Returns the font for the bundled fixed-width font. const QFont& GetFixedFont(); -/// Returns the base settings interface. Should lock before manipulating. -INISettingsInterface* GetBaseSettingsInterface(); - /// Saves a game settings interface. bool SaveGameSettings(SettingsInterface* sif, bool delete_if_empty); diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp index fac60344a..e1cc8c754 100644 --- a/src/duckstation-qt/settingswindow.cpp +++ b/src/duckstation-qt/settingswindow.cpp @@ -301,7 +301,7 @@ void SettingsWindow::onRestoreDefaultsClicked() return; } - g_core_thread->setDefaultSettings(true, false); + g_core_thread->setDefaultSettings(true, true, false); } void SettingsWindow::onCopyGlobalSettingsClicked() diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 203449c60..e4441338d 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -53,10 +53,9 @@ namespace RegTestHost { static bool ParseCommandLineParameters(int argc, char* argv[], std::optional& autoboot); static void PrintCommandLineVersion(); static void PrintCommandLineHelp(const char* progname); -static bool InitializeConfig(); +static bool InitializeFoldersAndConfig(Error* error); static void InitializeEarlyConsole(); static void HookSignals(); -static bool SetFolders(); static bool SetNewDataRoot(const std::string& filename); static void DumpSystemStateHashes(); static std::string GetFrameDumpPath(u32 frame); @@ -76,7 +75,6 @@ ALIGN_TO_CACHE_LINE static TaskQueue s_async_task_queue; } // namespace RegTestHost -static MemorySettingsInterface s_base_settings_interface; static Threading::Thread s_gpu_thread; static u32 s_frames_to_run = 60 * 60; @@ -84,48 +82,17 @@ static u32 s_frames_remaining = 0; static u32 s_frame_dump_interval = 0; static std::string s_dump_base_directory; -bool RegTestHost::SetFolders() +bool RegTestHost::InitializeFoldersAndConfig(Error* error) { - std::string program_path(FileSystem::GetProgramPath()); - DEV_LOG("Program Path: {}", program_path); - - EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); - EmuFolders::DataRoot = Core::ComputeDataDirectory(); - EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources"); - - DEV_LOG("AppRoot Directory: {}", EmuFolders::AppRoot); - DEV_LOG("DataRoot Directory: {}", EmuFolders::DataRoot); - DEV_LOG("Resources Directory: {}", EmuFolders::Resources); - - // Write crash dumps to the data directory, since that'll be accessible for certain. - CrashHandler::SetWriteDirectory(EmuFolders::DataRoot); - - // the resources directory should exist, bail out if not - if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str())) - { - ERROR_LOG("Resources directory is missing, your installation is incomplete."); + if (!Core::SetCriticalFolders("resources", error)) return false; - } - if (EmuFolders::DataRoot.empty() || !FileSystem::EnsureDirectoryExists(EmuFolders::DataRoot.c_str(), false)) - { - ERROR_LOG("Failed to create data directory '{}'", EmuFolders::DataRoot); + if (!Core::InitializeBaseSettingsLayer({}, error)) return false; - } - - return true; -} - -bool RegTestHost::InitializeConfig() -{ - SetFolders(); - - Core::SetBaseSettingsLayer(&s_base_settings_interface); // default settings for runner - SettingsInterface& si = s_base_settings_interface; - g_settings.Load(si, si); - g_settings.Save(si, false); + const auto lock = Core::GetSettingsLock(); + SettingsInterface& si = *Core::GetBaseSettingsLayer(); si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(GPURenderer::Software)); si.SetBoolValue("GPU", "DisableShaderCache", true); si.SetStringValue("Pad1", "Type", Controller::GetControllerInfo(ControllerType::AnalogController).name); @@ -145,9 +112,6 @@ bool RegTestHost::InitializeConfig() for (u32 i = 0; i < static_cast(InputSourceType::Count); i++) si.SetBoolValue("InputSources", InputManager::InputSourceToString(static_cast(i)), false); - EmuFolders::LoadConfig(s_base_settings_interface); - EmuFolders::EnsureFoldersExist(); - return true; } @@ -422,7 +386,12 @@ void Host::RequestResizeHostDisplay(s32 width, s32 height) // } -void Host::RequestResetSettings(bool system, bool controller) +void Host::SetDefaultSettings(SettingsInterface& si) +{ + // +} + +void Host::OnSettingsResetToDefault(bool host, bool system, bool controller) { // } @@ -898,13 +867,13 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option } Log::SetLogLevel(level.value()); - s_base_settings_interface.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(level.value())); + Core::SetBaseStringSettingValue("Logging", "LogLevel", Settings::GetLogLevelName(level.value())); continue; } else if (CHECK_ARG("-console")) { Log::SetConsoleOutputParams(true); - s_base_settings_interface.SetBoolValue("Logging", "LogToConsole", true); + Core::SetBaseBoolSettingValue("Logging", "LogToConsole", true); continue; } else if (CHECK_ARG_PARAM("-renderer")) @@ -916,7 +885,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option return false; } - s_base_settings_interface.SetStringValue("GPU", "Renderer", Settings::GetRendererName(renderer.value())); + Core::SetBaseStringSettingValue("GPU", "Renderer", Settings::GetRendererName(renderer.value())); continue; } else if (CHECK_ARG_PARAM("-upscale")) @@ -929,7 +898,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option } INFO_LOG("Setting upscale to {}.", upscale); - s_base_settings_interface.SetIntValue("GPU", "ResolutionScale", static_cast(upscale)); + Core::SetBaseIntSettingValue("GPU", "ResolutionScale", static_cast(upscale)); continue; } else if (CHECK_ARG_PARAM("-cpu")) @@ -942,21 +911,20 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option } INFO_LOG("Setting CPU execution mode to {}.", Settings::GetCPUExecutionModeName(cpu.value())); - s_base_settings_interface.SetStringValue("CPU", "ExecutionMode", - Settings::GetCPUExecutionModeName(cpu.value())); + Core::SetBaseStringSettingValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(cpu.value())); continue; } else if (CHECK_ARG("-pgxp")) { INFO_LOG("Enabling PGXP."); - s_base_settings_interface.SetBoolValue("GPU", "PGXPEnable", true); + Core::SetBaseBoolSettingValue("GPU", "PGXPEnable", true); continue; } else if (CHECK_ARG("-pgxp-cpu")) { INFO_LOG("Enabling PGXP CPU mode."); - s_base_settings_interface.SetBoolValue("GPU", "PGXPEnable", true); - s_base_settings_interface.SetBoolValue("GPU", "PGXPCPU", true); + Core::SetBaseBoolSettingValue("GPU", "PGXPEnable", true); + Core::SetBaseBoolSettingValue("GPU", "PGXPCPU", true); continue; } else if (CHECK_ARG("--")) @@ -999,10 +967,13 @@ bool RegTestHost::SetNewDataRoot(const std::string& filename) // Switch to file logging. INFO_LOG("Dumping frames to '{}'...", dump_directory); + + const auto lock = Core::GetSettingsLock(); EmuFolders::DataRoot = std::move(dump_directory); - s_base_settings_interface.SetBoolValue("Logging", "LogToFile", true); - s_base_settings_interface.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(Log::Level::Dev)); - Settings::UpdateLogConfig(s_base_settings_interface); + SettingsInterface& si = *Core::GetBaseSettingsLayer(); + si.SetBoolValue("Logging", "LogToFile", true); + si.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(Log::Level::Dev)); + Settings::UpdateLogConfig(si); } return true; @@ -1017,17 +988,18 @@ int main(int argc, char* argv[]) { CrashHandler::Install(&Bus::CleanupMemoryMap); - Error startup_error; - if (!System::PerformEarlyHardwareChecks(&startup_error) || !System::ProcessStartup(&startup_error)) + Error error; + if (!System::PerformEarlyHardwareChecks(&error) || !System::ProcessStartup(&error)) { - ERROR_LOG("ProcessStartup() failed: {}", startup_error.GetDescription()); + std::fprintf(stderr, "ERROR: ProcessStartup() failed: %s\n", error.GetDescription().c_str()); return EXIT_FAILURE; } - RegTestHost::InitializeEarlyConsole(); - - if (!RegTestHost::InitializeConfig()) + if (!RegTestHost::InitializeFoldersAndConfig(&error)) + { + std::fprintf(stderr, "ERROR: Failed to initialize config: %s\n", error.GetDescription().c_str()); return EXIT_FAILURE; + } std::optional autoboot; if (!RegTestHost::ParseCommandLineParameters(argc, argv, autoboot)) @@ -1042,9 +1014,9 @@ int main(int argc, char* argv[]) if (!RegTestHost::SetNewDataRoot(autoboot->path)) return EXIT_FAILURE; - if (!System::CoreThreadInitialize(&startup_error)) + if (!System::CoreThreadInitialize(&error)) { - ERROR_LOG("CoreThreadInitialize() failed: {}", startup_error.GetDescription()); + ERROR_LOG("CoreThreadInitialize() failed: {}", error.GetDescription()); return EXIT_FAILURE; } @@ -1054,7 +1026,6 @@ int main(int argc, char* argv[]) RegTestHost::HookSignals(); s_gpu_thread.Start(&RegTestHost::GPUThreadEntryPoint); - Error error; int result = -1; INFO_LOG("Trying to boot '{}'...", autoboot->path); if (!System::BootSystem(std::move(autoboot.value()), &error))