Qt: Ensure settings are writable before running setup wizard

This commit is contained in:
Stenzek 2024-04-01 21:15:52 +10:00 committed by Connor McLaughlin
parent 332be6c771
commit f8b18d406f
4 changed files with 110 additions and 99 deletions

View File

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

View File

@ -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<INISettingsInterface>(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<bool> 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<bool> 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;

View File

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

View File

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