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.
pull/3687/head
Stenzek 7 months ago
parent 322320f816
commit 06897cd733
No known key found for this signature in database

@ -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`.

@ -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;

@ -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
}

@ -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();

@ -1,13 +1,18 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// 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<std::mutex> 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<std::mutex>& lock)
{
s_locals.layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_GAME, sif);

@ -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<std::mutex>& lock);
@ -20,3 +42,17 @@ void SetGameSettingsLayer(SettingsInterface* sif, std::unique_lock<std::mutex>&
void SetInputSettingsLayer(SettingsInterface* sif, std::unique_lock<std::mutex>& 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

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

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// 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."));
});
}

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

@ -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 <ctime>
#include <thread>
#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<SystemBootParameters>& 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<WindowInfo> 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);

@ -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 += "</ul>";
@ -507,13 +503,6 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, const Error& error,
"before installing this update or you will lose progress.</p>"));
}
if (update_increases_settings_version)
{
changes_html.prepend(
tr("<h2>Settings Warning</h2><p>Installing this update will reset your program configuration. Please note "
"that you will have to reconfigure your settings after this update.</p>"));
}
m_ui.updateNotes->setText(changes_html);
return;
}

@ -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();

@ -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();

@ -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<QTimer> settings_save_timer;
std::vector<QTranslator*> 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<INISettingsInterface*>(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<std::mutex>& 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()))
{

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

@ -301,7 +301,7 @@ void SettingsWindow::onRestoreDefaultsClicked()
return;
}
g_core_thread->setDefaultSettings(true, false);
g_core_thread->setDefaultSettings(true, true, false);
}
void SettingsWindow::onCopyGlobalSettingsClicked()

@ -53,10 +53,9 @@ namespace RegTestHost {
static bool ParseCommandLineParameters(int argc, char* argv[], std::optional<SystemBootParameters>& 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<u32>(InputSourceType::Count); i++)
si.SetBoolValue("InputSources", InputManager::InputSourceToString(static_cast<InputSourceType>(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<s32>(upscale));
Core::SetBaseIntSettingValue("GPU", "ResolutionScale", static_cast<s32>(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<SystemBootParameters> 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))

Loading…
Cancel
Save