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))