diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index ebd5def156..de6363d8a1 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -88,7 +88,8 @@ static u32 s_total_drawn_frames = 0; bool GSRunner::InitializeConfig() { - if (!EmuFolders::InitializeCriticalFolders()) + EmuFolders::SetAppRoot(); + if (!EmuFolders::SetResourcesDirectory() || !EmuFolders::SetDataDirectory(nullptr)) return false; const char* error; diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index e9dc2519dd..64d4fa945f 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -34,6 +34,7 @@ #include "common/Assertions.h" #include "common/Console.h" #include "common/CrashHandler.h" +#include "common/Error.h" #include "common/FileSystem.h" #include "common/HTTPDownloader.h" #include "common/Path.h" @@ -1197,23 +1198,41 @@ void Host::OnCaptureStopped() bool QtHost::InitializeConfig() { - if (!EmuFolders::InitializeCriticalFolders()) + Error error; + + EmuFolders::SetAppRoot(); + + if (!EmuFolders::SetResourcesDirectory()) { QMessageBox::critical(nullptr, QStringLiteral("PCSX2"), - QStringLiteral("One or more critical directories are missing, your installation may be incomplete.")); + QStringLiteral("Resources directory is missing, your installation is incomplete.")); + return false; + } + + if (!EmuFolders::SetDataDirectory(&error)) + { + // no point translating, config isn't loaded + QMessageBox::critical( + nullptr, QStringLiteral("PCSX2"), + QStringLiteral("Failed to create data directory at path\n\n%1\n\n" + "The error was: %2\n" + "Please ensure this directory is writable. You can also try portable mode " + "by creating portable.txt in the same directory you installed PCSX2 into.") + .arg(QString::fromStdString(EmuFolders::DataRoot)) + .arg(QString::fromStdString(error.GetDescription()))); return false; } // Write crash dumps to the data directory, since that'll be accessible for certain. CrashHandler::SetWriteDirectory(EmuFolders::DataRoot); - const std::string path(Path::Combine(EmuFolders::Settings, "PCSX2.ini")); - s_run_setup_wizard = s_run_setup_wizard || !FileSystem::FileExists(path.c_str()); - Console.WriteLn("Loading config from %s.", path.c_str()); + const std::string path = Path::Combine(EmuFolders::Settings, "PCSX2.ini"); + const bool settings_exists = FileSystem::FileExists(path.c_str()); + Console.WriteLnFmt("Loading config from {}.", path); s_base_settings_interface = std::make_unique(std::move(path)); Host::Internal::SetBaseSettingsLayer(s_base_settings_interface.get()); - if (!s_base_settings_interface->Load() || !VMManager::Internal::CheckSettingsVersion()) + if (!settings_exists || !s_base_settings_interface->Load() || !VMManager::Internal::CheckSettingsVersion()) { // If the config file doesn't exist, assume this is a new install and don't prompt to overwrite. if (FileSystem::FileExists(s_base_settings_interface->GetFileName().c_str()) && @@ -1226,6 +1245,22 @@ bool QtHost::InitializeConfig() VMManager::SetDefaultSettings(*s_base_settings_interface, true, true, true, 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_base_settings_interface->SetBoolValue("UI", "SetupWizardIncomplete", true); + + // Make sure we can actually save the config, and the user doesn't have some permission issue. + if (!s_base_settings_interface->Save(&error)) + { + QMessageBox::critical( + nullptr, QStringLiteral("PCSX2"), + 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 PCSX2 into.") + .arg(QString::fromStdString(s_base_settings_interface->GetFileName())) + .arg(QString::fromStdString(error.GetDescription()))); + return false; + } + // Don't save if we're running the setup wizard. We want to run it next time if they don't finish it. if (!s_run_setup_wizard) SaveSettings(); @@ -1379,8 +1414,7 @@ std::optional QtHost::DownloadFile(QWidget* parent, const QString& title, const std::string::size_type url_file_part_pos = url.rfind('/'); QtModalProgressCallback progress(parent); progress.GetDialog().setLabelText( - qApp->translate("EmuThread", "Downloading %1...").arg(QtUtils::StringViewToQString( - std::string_view(url).substr((url_file_part_pos != std::string::npos) ? (url_file_part_pos + 1) : 0)))); + qApp->translate("EmuThread", "Downloading %1...").arg(QtUtils::StringViewToQString(std::string_view(url).substr((url_file_part_pos != std::string::npos) ? (url_file_part_pos + 1) : 0)))); progress.GetDialog().setWindowTitle(title); progress.GetDialog().setWindowIcon(GetAppIcon()); progress.SetCancellable(true); @@ -1403,8 +1437,8 @@ std::optional QtHost::DownloadFile(QWidget* parent, const QString& title, QMessageBox::critical(parent, qApp->translate("EmuThread", "Error"), qApp->translate("EmuThread", "Download failed: Data is empty.").arg(status_code)); - download_result = false; - return; + download_result = false; + return; } *data = std::move(hdata); @@ -1851,10 +1885,6 @@ void QtHost::RegisterTypes() bool QtHost::RunSetupWizard() { - // Set a flag in the config so that even though we created the ini, we'll run the wizard next time. - Host::SetBaseBoolSettingValue("UI", "SetupWizardIncomplete", true); - Host::CommitBaseSettingChanges(); - SetupWizardDialog dialog; if (dialog.exec() == QDialog::Rejected) return false; diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 81c8a12e36..3604fc3c3b 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once @@ -24,6 +24,7 @@ } \ ; +class Error; class SettingsInterface; class SettingsWrapper; @@ -1226,7 +1227,9 @@ namespace EmuFolders extern std::string Videos; /// Initializes critical folders (AppRoot, DataRoot, Settings). Call once on startup. - bool InitializeCriticalFolders(); + void SetAppRoot(); + bool SetResourcesDirectory(); + bool SetDataDirectory(Error* error); // Assumes that AppRoot and DataRoot have been initialized. void SetDefaults(SettingsInterface& si); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 8f4d86b581..7647d46b3a 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -167,10 +167,7 @@ namespace EmuFolders std::string InputProfiles; std::string Videos; - static void SetAppRoot(); - static void SetResourcesDirectory(); static bool ShouldUsePortableMode(); - static void SetDataDirectory(); } // namespace EmuFolders TraceFiltersEE::TraceFiltersEE() @@ -1845,26 +1842,28 @@ void Pcsx2Config::ClearConfiguration(SettingsInterface* dest_si) temp.LoadSaveCore(wrapper); } -bool EmuFolders::InitializeCriticalFolders() +void EmuFolders::SetAppRoot() { - SetAppRoot(); - SetResourcesDirectory(); - SetDataDirectory(); + const std::string program_path = FileSystem::GetProgramPath(); + Console.WriteLnFmt("Program Path: {}", program_path); + + AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); // logging of directories in case something goes wrong super early - Console.WriteLn("AppRoot Directory: %s", AppRoot.c_str()); - Console.WriteLn("DataRoot Directory: %s", DataRoot.c_str()); - Console.WriteLn("Resources Directory: %s", Resources.c_str()); + Console.WriteLnFmt("AppRoot Directory: {}", AppRoot); +} - // allow SetDataDirectory() to change settings directory (if we want to split config later on) - if (Settings.empty()) - { - Settings = Path::Combine(DataRoot, "inis"); +bool EmuFolders::SetResourcesDirectory() +{ +#ifndef __APPLE__ + // On Windows/Linux, these are in the binary directory. + Resources = Path::Combine(AppRoot, "resources"); +#else + // On macOS, this is in the bundle resources directory. + Resources = Path::Canonicalize(Path::Combine(AppRoot, "../Resources")); +#endif - // Create settings directory if it doesn't exist. If we're not using portable mode, it won't. - if (!FileSystem::DirectoryExists(Settings.c_str())) - FileSystem::CreateDirectoryPath(Settings.c_str(), false); - } + Console.WriteLnFmt("Resources Directory: {}", Resources); // the resources directory should exist, bail out if not if (!FileSystem::DirectoryExists(Resources.c_str())) @@ -1876,87 +1875,65 @@ bool EmuFolders::InitializeCriticalFolders() return true; } -void EmuFolders::SetAppRoot() -{ - const std::string program_path = FileSystem::GetProgramPath(); - Console.WriteLn("Program Path: %s", program_path.c_str()); - - AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); -} - -void EmuFolders::SetResourcesDirectory() -{ -#ifndef __APPLE__ - // On Windows/Linux, these are in the binary directory. - Resources = Path::Combine(AppRoot, "resources"); -#else - // On macOS, this is in the bundle resources directory. - Resources = Path::Canonicalize(Path::Combine(AppRoot, "../Resources")); -#endif -} - bool EmuFolders::ShouldUsePortableMode() { // Check whether portable.ini exists in the program directory. return FileSystem::FileExists(Path::Combine(AppRoot, "portable.ini").c_str()) || FileSystem::FileExists(Path::Combine(AppRoot, "portable.txt").c_str()); } -void EmuFolders::SetDataDirectory() +bool EmuFolders::SetDataDirectory(Error* error) { - if (ShouldUsePortableMode()) + if (!ShouldUsePortableMode()) { - DataRoot = AppRoot; - return; - } - #if defined(_WIN32) - // On Windows, use My Documents\PCSX2 to match old installs. - PWSTR documents_directory; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &documents_directory))) - { - if (std::wcslen(documents_directory) > 0) - DataRoot = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "PCSX2"); - CoTaskMemFree(documents_directory); - } + // On Windows, use My Documents\PCSX2 to match old installs. + PWSTR documents_directory; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &documents_directory))) + { + if (std::wcslen(documents_directory) > 0) + DataRoot = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "PCSX2"); + CoTaskMemFree(documents_directory); + } #elif defined(__linux__) || defined(__FreeBSD__) - // Use $XDG_CONFIG_HOME/PCSX2 if it exists. - const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); - if (xdg_config_home && Path::IsAbsolute(xdg_config_home)) - { - DataRoot = Path::RealPath(Path::Combine(xdg_config_home, "PCSX2")); - } - else - { - // Use ~/PCSX2 for non-XDG, and ~/.config/PCSX2 for XDG. + // Use $XDG_CONFIG_HOME/PCSX2 if it exists. + const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); + if (xdg_config_home && Path::IsAbsolute(xdg_config_home)) + { + DataRoot = Path::RealPath(Path::Combine(xdg_config_home, "PCSX2")); + } + else + { + // Use ~/PCSX2 for non-XDG, and ~/.config/PCSX2 for XDG. + const char* home_dir = getenv("HOME"); + if (home_dir) + { + // ~/.config should exist, but just in case it doesn't and this is a fresh profile.. + const std::string config_dir(Path::Combine(home_dir, ".config")); + if (!FileSystem::DirectoryExists(config_dir.c_str())) + FileSystem::CreateDirectoryPath(config_dir.c_str(), false); + + DataRoot = Path::RealPath(Path::Combine(config_dir, "PCSX2")); + } + } +#elif defined(__APPLE__) + static constexpr char MAC_DATA_DIR[] = "Library/Application Support/PCSX2"; const char* home_dir = getenv("HOME"); if (home_dir) - { - // ~/.config should exist, but just in case it doesn't and this is a fresh profile.. - const std::string config_dir(Path::Combine(home_dir, ".config")); - if (!FileSystem::DirectoryExists(config_dir.c_str())) - FileSystem::CreateDirectoryPath(config_dir.c_str(), false); - - DataRoot = Path::RealPath(Path::Combine(config_dir, "PCSX2")); - } - } -#elif defined(__APPLE__) - static constexpr char MAC_DATA_DIR[] = "Library/Application Support/PCSX2"; - const char* home_dir = getenv("HOME"); - if (home_dir) - DataRoot = Path::RealPath(Path::Combine(home_dir, MAC_DATA_DIR)); + DataRoot = Path::RealPath(Path::Combine(home_dir, MAC_DATA_DIR)); #endif - - // make sure it exists - if (!DataRoot.empty() && !FileSystem::DirectoryExists(DataRoot.c_str())) - { - // we're in trouble if we fail to create this directory... but try to hobble on with portable - if (!FileSystem::CreateDirectoryPath(DataRoot.c_str(), false)) - DataRoot.clear(); } - // couldn't determine the data directory? fallback to portable. + // couldn't determine the data directory, or using portable mode? fallback to portable. if (DataRoot.empty()) DataRoot = AppRoot; + + // inis is always below the data root + Settings = Path::Combine(DataRoot, "inis"); + + // make sure it exists + Console.WriteLnFmt("DataRoot Directory: {}", DataRoot); + return (FileSystem::EnsureDirectoryExists(DataRoot.c_str(), false, error) && + FileSystem::EnsureDirectoryExists(Settings.c_str(), false, error)); } void EmuFolders::SetDefaults(SettingsInterface& si)