2021-12-13 12:12:54 +00:00
|
|
|
/* PCSX2 - PS2 Emulator for PCs
|
|
|
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
|
|
|
*
|
|
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
|
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "PrecompiledHeader.h"
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
#include <csignal>
|
|
|
|
|
|
|
|
#include <QtCore/QTimer>
|
|
|
|
#include <QtWidgets/QMessageBox>
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "common/RedtapeWindows.h"
|
2022-04-23 12:59:55 +00:00
|
|
|
#include <KnownFolders.h>
|
|
|
|
#include <ShlObj.h>
|
2022-03-04 10:40:03 +00:00
|
|
|
#endif
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
#include "common/Assertions.h"
|
|
|
|
#include "common/Console.h"
|
2022-04-18 14:58:34 +00:00
|
|
|
#include "common/CrashHandler.h"
|
2021-12-13 12:12:54 +00:00
|
|
|
#include "common/FileSystem.h"
|
2022-05-19 14:46:33 +00:00
|
|
|
#include "common/Path.h"
|
2021-12-13 12:12:54 +00:00
|
|
|
#include "common/SettingsWrapper.h"
|
|
|
|
#include "common/StringUtil.h"
|
|
|
|
|
|
|
|
#include "pcsx2/Frontend/GameList.h"
|
|
|
|
#include "pcsx2/Frontend/INISettingsInterface.h"
|
|
|
|
#include "pcsx2/HostSettings.h"
|
|
|
|
#include "pcsx2/PAD/Host/PAD.h"
|
|
|
|
|
|
|
|
#include "EmuThread.h"
|
|
|
|
#include "GameList/GameListWidget.h"
|
|
|
|
#include "MainWindow.h"
|
|
|
|
#include "QtHost.h"
|
2022-05-11 08:24:56 +00:00
|
|
|
#include "svnrev.h"
|
2021-12-13 12:12:54 +00:00
|
|
|
|
|
|
|
#include "pcsx2/DebugTools/Debug.h"
|
|
|
|
|
|
|
|
static constexpr u32 SETTINGS_VERSION = 1;
|
|
|
|
static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Local function declarations
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
2022-03-04 10:40:03 +00:00
|
|
|
namespace QtHost {
|
2021-12-13 12:12:54 +00:00
|
|
|
static bool InitializeConfig();
|
2022-04-23 12:59:55 +00:00
|
|
|
static bool ShouldUsePortableMode();
|
|
|
|
static void SetResourcesDirectory();
|
|
|
|
static void SetDataDirectory();
|
2022-03-04 10:40:03 +00:00
|
|
|
static void HookSignals();
|
|
|
|
static bool SetCriticalFolders();
|
2021-12-13 12:12:54 +00:00
|
|
|
static void SetDefaultConfig();
|
|
|
|
static void SaveSettings();
|
2022-03-04 10:40:03 +00:00
|
|
|
}
|
2021-12-13 12:12:54 +00:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Local variable declarations
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
static std::unique_ptr<QTimer> s_settings_save_timer;
|
|
|
|
static std::unique_ptr<INISettingsInterface> s_base_settings_interface;
|
2022-05-03 04:26:49 +00:00
|
|
|
static bool s_batch_mode = false;
|
2021-12-13 12:12:54 +00:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Initialization/Shutdown
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool QtHost::Initialize()
|
|
|
|
{
|
2022-04-06 10:42:23 +00:00
|
|
|
qRegisterMetaType<std::optional<bool>>();
|
|
|
|
qRegisterMetaType<std::function<void()>>();
|
2021-12-13 12:12:54 +00:00
|
|
|
qRegisterMetaType<std::shared_ptr<VMBootParameters>>();
|
|
|
|
qRegisterMetaType<GSRendererType>();
|
|
|
|
qRegisterMetaType<InputBindingKey>();
|
2022-04-06 10:42:23 +00:00
|
|
|
qRegisterMetaType<const GameList::Entry*>();
|
2021-12-13 12:12:54 +00:00
|
|
|
|
|
|
|
if (!InitializeConfig())
|
|
|
|
{
|
2022-05-23 14:20:29 +00:00
|
|
|
// 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."));
|
2021-12-13 12:12:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
HookSignals();
|
2021-12-13 12:12:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
void QtHost::Shutdown()
|
|
|
|
{
|
|
|
|
EmuThread::stop();
|
|
|
|
if (g_main_window)
|
|
|
|
{
|
|
|
|
g_main_window->close();
|
|
|
|
delete g_main_window;
|
|
|
|
}
|
|
|
|
}
|
2021-12-13 12:12:54 +00:00
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
bool QtHost::SetCriticalFolders()
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(FileSystem::GetProgramPath()));
|
2022-04-23 12:59:55 +00:00
|
|
|
SetResourcesDirectory();
|
|
|
|
SetDataDirectory();
|
2021-12-13 12:12:54 +00:00
|
|
|
|
2022-04-23 12:59:55 +00:00
|
|
|
// allow SetDataDirectory() to change settings directory (if we want to split config later on)
|
2022-05-19 14:46:33 +00:00
|
|
|
if (EmuFolders::Settings.empty())
|
|
|
|
EmuFolders::Settings = Path::Combine(EmuFolders::DataRoot, "inis");
|
2021-12-13 12:12:54 +00:00
|
|
|
|
2022-04-18 14:58:34 +00:00
|
|
|
// Write crash dumps to the data directory, since that'll be accessible for certain.
|
2022-05-19 14:46:33 +00:00
|
|
|
CrashHandler::SetWriteDirectory(EmuFolders::DataRoot);
|
2022-04-18 14:58:34 +00:00
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
// the resources directory should exist, bail out if not
|
2022-05-19 14:46:33 +00:00
|
|
|
if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str()))
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
QMessageBox::critical(nullptr, QStringLiteral("Error"),
|
|
|
|
QStringLiteral("Resources directory is missing, your installation is incomplete."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-23 12:59:55 +00:00
|
|
|
bool QtHost::ShouldUsePortableMode()
|
|
|
|
{
|
|
|
|
// Check whether portable.ini exists in the program directory.
|
2022-05-19 14:46:33 +00:00
|
|
|
return FileSystem::FileExists(Path::Combine(EmuFolders::AppRoot, "portable.ini").c_str());
|
2022-04-23 12:59:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetResourcesDirectory()
|
|
|
|
{
|
2022-05-04 12:24:52 +00:00
|
|
|
#ifndef __APPLE__
|
2022-04-23 12:59:55 +00:00
|
|
|
// On Windows/Linux, these are in the binary directory.
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources");
|
2022-05-04 12:24:52 +00:00
|
|
|
#else
|
|
|
|
// On macOS, this is in the bundle resources directory.
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::Resources = Path::Canonicalize(Path::Combine(EmuFolders::AppRoot, "../Resources"));
|
2022-05-04 12:24:52 +00:00
|
|
|
#endif
|
2022-04-23 12:59:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetDataDirectory()
|
|
|
|
{
|
|
|
|
if (ShouldUsePortableMode())
|
|
|
|
{
|
|
|
|
EmuFolders::DataRoot = EmuFolders::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)
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::DataRoot = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "PCSX2");
|
2022-04-23 12:59:55 +00:00
|
|
|
CoTaskMemFree(documents_directory);
|
|
|
|
}
|
|
|
|
#elif defined(__linux__)
|
|
|
|
// Check for $HOME/PCSX2 first, for legacy installs.
|
|
|
|
const char* home_dir = getenv("HOME");
|
2022-05-19 14:46:33 +00:00
|
|
|
const std::string legacy_dir(home_dir ? Path::Combine(home_dir, "PCSX2") : std::string());
|
2022-04-23 12:59:55 +00:00
|
|
|
if (!legacy_dir.empty() && FileSystem::DirectoryExists(legacy_dir.c_str()))
|
|
|
|
{
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::DataRoot = std::move(legacy_dir);
|
2022-04-23 12:59:55 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// otherwise, use $XDG_CONFIG_HOME/PCSX2.
|
|
|
|
const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
|
|
|
|
if (xdg_config_home && xdg_config_home[0] == '/' && FileSystem::DirectoryExists(xdg_config_home))
|
|
|
|
{
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::DataRoot = Path::Combine(xdg_config_home, "PCSX2");
|
2022-04-23 12:59:55 +00:00
|
|
|
}
|
|
|
|
else if (!legacy_dir.empty())
|
|
|
|
{
|
|
|
|
// fall back to the legacy PCSX2-in-home.
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::DataRoot = std::move(legacy_dir);
|
2022-04-23 12:59:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#elif defined(__APPLE__)
|
|
|
|
static constexpr char MAC_DATA_DIR[] = "Library/Application Support/PCSX2";
|
|
|
|
const char* home_dir = getenv("HOME");
|
|
|
|
if (home_dir)
|
2022-05-19 14:46:33 +00:00
|
|
|
EmuFolders::DataRoot = Path::Combine(home_dir, MAC_DATA_DIR);
|
2022-04-23 12:59:55 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// make sure it exists
|
2022-05-19 14:46:33 +00:00
|
|
|
if (!EmuFolders::DataRoot.empty() && !FileSystem::DirectoryExists(EmuFolders::DataRoot.c_str()))
|
2022-04-23 12:59:55 +00:00
|
|
|
{
|
|
|
|
// we're in trouble if we fail to create this directory... but try to hobble on with portable
|
2022-05-19 14:46:33 +00:00
|
|
|
if (!FileSystem::CreateDirectoryPath(EmuFolders::DataRoot.c_str(), false))
|
|
|
|
EmuFolders::DataRoot.clear();
|
2022-04-23 12:59:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// couldn't determine the data directory? fallback to portable.
|
2022-05-19 14:46:33 +00:00
|
|
|
if (EmuFolders::DataRoot.empty())
|
2022-04-23 12:59:55 +00:00
|
|
|
EmuFolders::DataRoot = EmuFolders::AppRoot;
|
|
|
|
}
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
void QtHost::UpdateFolders()
|
|
|
|
{
|
|
|
|
// TODO: This should happen with the VM thread paused.
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
EmuFolders::LoadConfig(*s_base_settings_interface.get());
|
|
|
|
EmuFolders::EnsureFoldersExist();
|
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
bool QtHost::InitializeConfig()
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
if (!SetCriticalFolders())
|
|
|
|
return false;
|
|
|
|
|
2022-05-19 14:46:33 +00:00
|
|
|
const std::string path(Path::Combine(EmuFolders::Settings, "PCSX2.ini"));
|
2021-12-13 12:12:54 +00:00
|
|
|
s_base_settings_interface = std::make_unique<INISettingsInterface>(std::move(path));
|
|
|
|
Host::Internal::SetBaseSettingsLayer(s_base_settings_interface.get());
|
|
|
|
|
|
|
|
uint settings_version;
|
|
|
|
if (!s_base_settings_interface->Load() ||
|
|
|
|
!s_base_settings_interface->GetUIntValue("UI", "SettingsVersion", &settings_version) ||
|
|
|
|
settings_version != SETTINGS_VERSION)
|
|
|
|
{
|
|
|
|
QMessageBox::critical(
|
|
|
|
g_main_window, qApp->translate("QtHost", "Settings Reset"),
|
|
|
|
qApp->translate("QtHost", "Settings do not exist or are the incorrect version, resetting to defaults."));
|
|
|
|
SetDefaultConfig();
|
|
|
|
s_base_settings_interface->Save();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Handle reset to defaults if load fails.
|
|
|
|
EmuFolders::LoadConfig(*s_base_settings_interface.get());
|
|
|
|
EmuFolders::EnsureFoldersExist();
|
|
|
|
QtHost::UpdateLogging();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
void QtHost::SetDefaultConfig()
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
EmuConfig = Pcsx2Config();
|
|
|
|
EmuFolders::SetDefaults();
|
|
|
|
|
|
|
|
SettingsInterface& si = *s_base_settings_interface.get();
|
|
|
|
si.SetUIntValue("UI", "SettingsVersion", SETTINGS_VERSION);
|
|
|
|
|
|
|
|
{
|
|
|
|
SettingsSaveWrapper wrapper(si);
|
|
|
|
EmuConfig.LoadSave(wrapper);
|
|
|
|
}
|
|
|
|
|
|
|
|
EmuFolders::Save(si);
|
|
|
|
PAD::SetDefaultConfig(si);
|
|
|
|
}
|
|
|
|
|
2022-03-25 12:16:21 +00:00
|
|
|
SettingsInterface* QtHost::GetBaseSettingsInterface()
|
|
|
|
{
|
|
|
|
return s_base_settings_interface.get();
|
|
|
|
}
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
std::string QtHost::GetBaseStringSettingValue(const char* section, const char* key, const char* default_value /*= ""*/)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
return s_base_settings_interface->GetStringValue(section, key, default_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QtHost::GetBaseBoolSettingValue(const char* section, const char* key, bool default_value /*= false*/)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
return s_base_settings_interface->GetBoolValue(section, key, default_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
int QtHost::GetBaseIntSettingValue(const char* section, const char* key, int default_value /*= 0*/)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
return s_base_settings_interface->GetIntValue(section, key, default_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
float QtHost::GetBaseFloatSettingValue(const char* section, const char* key, float default_value /*= 0.0f*/)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
return s_base_settings_interface->GetFloatValue(section, key, default_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> QtHost::GetBaseStringListSetting(const char* section, const char* key)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
return s_base_settings_interface->GetStringList(section, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetBaseBoolSettingValue(const char* section, const char* key, bool value)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
s_base_settings_interface->SetBoolValue(section, key, value);
|
|
|
|
QueueSettingsSave();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetBaseIntSettingValue(const char* section, const char* key, int value)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
s_base_settings_interface->SetIntValue(section, key, value);
|
|
|
|
QueueSettingsSave();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetBaseFloatSettingValue(const char* section, const char* key, float value)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
s_base_settings_interface->SetFloatValue(section, key, value);
|
|
|
|
QueueSettingsSave();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetBaseStringSettingValue(const char* section, const char* key, const char* value)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
s_base_settings_interface->SetStringValue(section, key, value);
|
|
|
|
QueueSettingsSave();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetBaseStringListSettingValue(const char* section, const char* key, const std::vector<std::string>& values)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
s_base_settings_interface->SetStringList(section, key, values);
|
|
|
|
QueueSettingsSave();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QtHost::AddBaseValueToStringList(const char* section, const char* key, const char* value)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
if (!s_base_settings_interface->AddToStringList(section, key, value))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
QueueSettingsSave();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QtHost::RemoveBaseValueFromStringList(const char* section, const char* key, const char* value)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
if (!s_base_settings_interface->RemoveFromStringList(section, key, value))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
QueueSettingsSave();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::RemoveBaseSettingValue(const char* section, const char* key)
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
s_base_settings_interface->DeleteValue(section, key);
|
|
|
|
QueueSettingsSave();
|
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
void QtHost::SaveSettings()
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
pxAssertRel(!g_emu_thread->isOnEmuThread(), "Saving should happen on the UI thread.");
|
|
|
|
|
|
|
|
{
|
|
|
|
auto lock = Host::GetSettingsLock();
|
|
|
|
if (!s_base_settings_interface->Save())
|
|
|
|
Console.Error("Failed to save settings.");
|
|
|
|
}
|
|
|
|
|
|
|
|
s_settings_save_timer->deleteLater();
|
|
|
|
s_settings_save_timer.release();
|
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
void QtHost::QueueSettingsSave()
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
if (s_settings_save_timer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
s_settings_save_timer = std::make_unique<QTimer>();
|
|
|
|
s_settings_save_timer->connect(s_settings_save_timer.get(), &QTimer::timeout, SaveSettings);
|
|
|
|
s_settings_save_timer->setSingleShot(true);
|
|
|
|
s_settings_save_timer->start(SETTINGS_SAVE_DELAY);
|
|
|
|
}
|
|
|
|
|
2022-05-03 04:26:49 +00:00
|
|
|
bool QtHost::InBatchMode()
|
|
|
|
{
|
|
|
|
return s_batch_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::SetBatchMode(bool enabled)
|
|
|
|
{
|
|
|
|
s_batch_mode = enabled;
|
|
|
|
}
|
|
|
|
|
2022-04-06 10:49:00 +00:00
|
|
|
void QtHost::RunOnUIThread(const std::function<void()>& func, bool block /*= false*/)
|
|
|
|
{
|
|
|
|
// main window always exists, so it's fine to attach it to that.
|
|
|
|
QMetaObject::invokeMethod(g_main_window, "runOnUIThread",
|
|
|
|
block ? Qt::BlockingQueuedConnection : Qt::QueuedConnection,
|
|
|
|
Q_ARG(const std::function<void()>&, func));
|
|
|
|
}
|
|
|
|
|
2022-05-11 08:24:56 +00:00
|
|
|
QString QtHost::GetAppNameAndVersion()
|
|
|
|
{
|
|
|
|
QString ret;
|
|
|
|
if constexpr (!PCSX2_isReleaseVersion && GIT_TAGGED_COMMIT)
|
|
|
|
{
|
|
|
|
ret = QStringLiteral("PCSX2 Nightly - " GIT_TAG);
|
|
|
|
}
|
|
|
|
else if constexpr (PCSX2_isReleaseVersion)
|
|
|
|
{
|
|
|
|
#define APPNAME_STRINGIZE(x) #x
|
|
|
|
ret = QStringLiteral("PCSX2 "
|
|
|
|
APPNAME_STRINGIZE(PCSX2_VersionHi) "."
|
|
|
|
APPNAME_STRINGIZE(PCSX2_VersionMid) "."
|
|
|
|
APPNAME_STRINGIZE(PCSX2_VersionLo));
|
|
|
|
#undef APPNAME_STRINGIZE
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return QStringLiteral("PCSX2 " GIT_REV);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString QtHost::GetAppConfigSuffix()
|
|
|
|
{
|
|
|
|
#if defined(PCSX2_DEBUG)
|
|
|
|
return QStringLiteral(" [Debug]");
|
|
|
|
#elif defined(PCSX2_DEVBUILD)
|
|
|
|
return QStringLiteral(" [Devel]");
|
|
|
|
#else
|
|
|
|
return QString();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
std::optional<std::vector<u8>> Host::ReadResourceFile(const char* filename)
|
|
|
|
{
|
2022-05-19 14:46:33 +00:00
|
|
|
const std::string path(Path::Combine(EmuFolders::Resources, filename));
|
2021-12-13 12:12:54 +00:00
|
|
|
std::optional<std::vector<u8>> ret(FileSystem::ReadBinaryFile(path.c_str()));
|
|
|
|
if (!ret.has_value())
|
|
|
|
Console.Error("Failed to read resource file '%s'", filename);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::string> Host::ReadResourceFileToString(const char* filename)
|
|
|
|
{
|
2022-05-19 14:46:33 +00:00
|
|
|
const std::string path(Path::Combine(EmuFolders::Resources, filename));
|
2021-12-13 12:12:54 +00:00
|
|
|
std::optional<std::string> ret(FileSystem::ReadFileToString(path.c_str()));
|
|
|
|
if (!ret.has_value())
|
|
|
|
Console.Error("Failed to read resource file to string '%s'", filename);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Host::ReportErrorAsync(const std::string_view& title, const std::string_view& message)
|
|
|
|
{
|
|
|
|
if (!title.empty() && !message.empty())
|
|
|
|
{
|
2022-03-20 03:40:47 +00:00
|
|
|
Console.Error("ReportErrorAsync: %.*s: %.*s",
|
2021-12-13 12:12:54 +00:00
|
|
|
static_cast<int>(title.size()), title.data(),
|
|
|
|
static_cast<int>(message.size()), message.data());
|
|
|
|
}
|
|
|
|
else if (!message.empty())
|
|
|
|
{
|
2022-03-20 03:40:47 +00:00
|
|
|
Console.Error("ReportErrorAsync: %.*s",
|
2021-12-13 12:12:54 +00:00
|
|
|
static_cast<int>(message.size()), message.data());
|
|
|
|
}
|
|
|
|
|
|
|
|
QMetaObject::invokeMethod(g_main_window, "reportError", Qt::QueuedConnection,
|
|
|
|
Q_ARG(const QString&, title.empty() ? QString() : QString::fromUtf8(title.data(), title.size())),
|
|
|
|
Q_ARG(const QString&, message.empty() ? QString() : QString::fromUtf8(message.data(), message.size())));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name)
|
|
|
|
{
|
|
|
|
emit g_emu_thread->onInputDeviceConnected(
|
|
|
|
identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()),
|
|
|
|
device_name.empty() ? QString() : QString::fromUtf8(device_name.data(), device_name.size()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
|
|
|
|
{
|
|
|
|
emit g_emu_thread->onInputDeviceDisconnected(
|
|
|
|
identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()));
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Interface Stuff
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const IConsoleWriter* PatchesCon = &Console;
|
|
|
|
|
|
|
|
void LoadAllPatchesAndStuff(const Pcsx2Config& cfg)
|
|
|
|
{
|
|
|
|
// FIXME
|
|
|
|
}
|
|
|
|
|
|
|
|
void PatchesVerboseReset()
|
|
|
|
{
|
|
|
|
// FIXME
|
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
static void SignalHandler(int signal)
|
|
|
|
{
|
|
|
|
// First try the normal (graceful) shutdown/exit.
|
|
|
|
static bool graceful_shutdown_attempted = false;
|
|
|
|
if (!graceful_shutdown_attempted && g_main_window)
|
|
|
|
{
|
|
|
|
std::fprintf(stderr, "Received CTRL+C, attempting graceful shutdown. Press CTRL+C again to force.\n");
|
|
|
|
graceful_shutdown_attempted = true;
|
|
|
|
|
|
|
|
// This could be a bit risky invoking from a signal handler... hopefully it's okay.
|
|
|
|
QMetaObject::invokeMethod(g_main_window, &MainWindow::requestExit, Qt::QueuedConnection);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::signal(signal, SIG_DFL);
|
|
|
|
|
|
|
|
// MacOS is missing std::quick_exit() despite it being C++11...
|
|
|
|
#ifndef __APPLE__
|
|
|
|
std::quick_exit(1);
|
|
|
|
#else
|
|
|
|
_Exit(1);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::HookSignals()
|
|
|
|
{
|
|
|
|
std::signal(SIGINT, SignalHandler);
|
|
|
|
std::signal(SIGTERM, SignalHandler);
|
|
|
|
}
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
// Replacement for Console so we actually get output to our console window on Windows.
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
|
|
static bool s_debugger_attached = false;
|
|
|
|
static bool s_console_handle_set = false;
|
2022-05-03 04:06:04 +00:00
|
|
|
static bool s_console_allocated = false;
|
|
|
|
static HANDLE s_console_handle = INVALID_HANDLE_VALUE;
|
|
|
|
static HANDLE s_old_console_stdin = NULL;
|
|
|
|
static HANDLE s_old_console_stdout = NULL;
|
|
|
|
static HANDLE s_old_console_stderr = NULL;
|
2021-12-13 12:12:54 +00:00
|
|
|
|
2022-05-18 13:27:23 +00:00
|
|
|
static void ConsoleWinQt_SetTitle(const char* title)
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
2022-05-18 13:27:23 +00:00
|
|
|
SetConsoleTitleW(StringUtil::UTF8StringToWideString(title).c_str());
|
2021-12-13 12:12:54 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 12:34:42 +00:00
|
|
|
static void ConsoleWinQt_DoSetColor(ConsoleColors color)
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
if (!s_console_handle)
|
|
|
|
return;
|
|
|
|
|
2022-05-03 04:06:04 +00:00
|
|
|
static constexpr wchar_t colors[][ConsoleColors_Count] = {
|
|
|
|
L"\033[0m", // default
|
|
|
|
L"\033[30m\033[1m", // black
|
|
|
|
L"\033[32m", // green
|
|
|
|
L"\033[31m", // red
|
|
|
|
L"\033[34m", // blue
|
|
|
|
L"\033[35m", // magenta
|
|
|
|
L"\033[35m", // orange (FIXME)
|
|
|
|
L"\033[37m", // gray
|
|
|
|
L"\033[36m", // cyan
|
|
|
|
L"\033[33m", // yellow
|
|
|
|
L"\033[37m", // white
|
|
|
|
L"\033[30m\033[1m", // strong black
|
|
|
|
L"\033[31m\033[1m", // strong red
|
|
|
|
L"\033[32m\033[1m", // strong green
|
|
|
|
L"\033[34m\033[1m", // strong blue
|
|
|
|
L"\033[35m\033[1m", // strong magenta
|
|
|
|
L"\033[35m\033[1m", // strong orange (FIXME)
|
|
|
|
L"\033[37m\033[1m", // strong gray
|
|
|
|
L"\033[36m\033[1m", // strong cyan
|
|
|
|
L"\033[33m\033[1m", // strong yellow
|
|
|
|
L"\033[37m\033[1m", // strong white
|
2021-12-13 12:12:54 +00:00
|
|
|
};
|
|
|
|
|
2022-05-03 04:06:04 +00:00
|
|
|
const wchar_t* colortext = colors[static_cast<u32>(color)];
|
|
|
|
DWORD written;
|
|
|
|
WriteConsoleW(s_console_handle, colortext, std::wcslen(colortext), &written, nullptr);
|
2021-12-13 12:12:54 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 12:34:42 +00:00
|
|
|
static void ConsoleWinQt_Newline()
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
if (!s_console_handle)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (s_debugger_attached)
|
|
|
|
OutputDebugStringW(L"\n");
|
|
|
|
|
|
|
|
DWORD written;
|
|
|
|
WriteConsoleW(s_console_handle, L"\n", 1, &written, nullptr);
|
|
|
|
}
|
|
|
|
|
2022-05-18 13:27:23 +00:00
|
|
|
static void ConsoleWinQt_DoWrite(const char* fmt)
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
if (!s_console_handle)
|
|
|
|
return;
|
|
|
|
|
2022-05-18 13:27:23 +00:00
|
|
|
// TODO: Put this on the stack.
|
|
|
|
std::wstring wfmt(StringUtil::UTF8StringToWideString(fmt));
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
if (s_debugger_attached)
|
2022-05-18 13:27:23 +00:00
|
|
|
OutputDebugStringW(wfmt.c_str());
|
2021-12-13 12:12:54 +00:00
|
|
|
|
|
|
|
DWORD written;
|
2022-05-18 13:27:23 +00:00
|
|
|
WriteConsoleW(s_console_handle, wfmt.c_str(), static_cast<DWORD>(wfmt.length()), &written, nullptr);
|
2021-12-13 12:12:54 +00:00
|
|
|
}
|
|
|
|
|
2022-05-18 13:27:23 +00:00
|
|
|
static void ConsoleWinQt_DoWriteLn(const char* fmt)
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
if (!s_console_handle)
|
|
|
|
return;
|
|
|
|
|
2022-05-18 13:27:23 +00:00
|
|
|
// TODO: Put this on the stack.
|
|
|
|
std::wstring wfmt(StringUtil::UTF8StringToWideString(fmt));
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
if (s_debugger_attached)
|
|
|
|
{
|
2022-05-18 13:27:23 +00:00
|
|
|
OutputDebugStringW(wfmt.c_str());
|
2021-12-13 12:12:54 +00:00
|
|
|
OutputDebugStringW(L"\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
DWORD written;
|
2022-05-18 13:27:23 +00:00
|
|
|
WriteConsoleW(s_console_handle, wfmt.c_str(), static_cast<DWORD>(wfmt.length()), &written, nullptr);
|
2021-12-13 12:12:54 +00:00
|
|
|
WriteConsoleW(s_console_handle, L"\n", 1, &written, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const IConsoleWriter ConsoleWriter_WinQt =
|
|
|
|
{
|
|
|
|
ConsoleWinQt_DoWrite,
|
|
|
|
ConsoleWinQt_DoWriteLn,
|
|
|
|
ConsoleWinQt_DoSetColor,
|
|
|
|
|
|
|
|
ConsoleWinQt_DoWrite,
|
|
|
|
ConsoleWinQt_Newline,
|
|
|
|
ConsoleWinQt_SetTitle,
|
|
|
|
};
|
2022-03-04 10:40:03 +00:00
|
|
|
|
|
|
|
static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType)
|
|
|
|
{
|
|
|
|
Console.WriteLn("Handler %u", dwCtrlType);
|
|
|
|
if (dwCtrlType != CTRL_C_EVENT)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
SignalHandler(SIGTERM);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2022-05-03 04:06:04 +00:00
|
|
|
static bool EnableVirtualTerminalProcessing(HANDLE hConsole)
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
2022-05-03 04:06:04 +00:00
|
|
|
if (hConsole == INVALID_HANDLE_VALUE)
|
|
|
|
return false;
|
2021-12-13 12:12:54 +00:00
|
|
|
|
2022-05-03 04:06:04 +00:00
|
|
|
DWORD old_mode;
|
|
|
|
if (!GetConsoleMode(hConsole, &old_mode))
|
|
|
|
return false;
|
2021-12-13 12:12:54 +00:00
|
|
|
|
2022-05-03 04:06:04 +00:00
|
|
|
// already enabled?
|
|
|
|
if (old_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return SetConsoleMode(hConsole, old_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SetSystemConsoleEnabled(bool enabled)
|
|
|
|
{
|
|
|
|
if (enabled)
|
2021-12-13 12:12:54 +00:00
|
|
|
{
|
|
|
|
s_debugger_attached = IsDebuggerPresent();
|
|
|
|
if (!s_console_handle_set)
|
|
|
|
{
|
2022-05-03 04:06:04 +00:00
|
|
|
s_old_console_stdin = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
s_old_console_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
s_old_console_stderr = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
bool handle_valid = (GetConsoleWindow() != NULL);
|
|
|
|
if (!handle_valid)
|
|
|
|
{
|
2022-05-03 04:06:04 +00:00
|
|
|
s_console_allocated = AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole();
|
|
|
|
handle_valid = (GetConsoleWindow() != NULL);
|
2021-12-13 12:12:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (handle_valid)
|
|
|
|
{
|
|
|
|
s_console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
2022-05-03 04:06:04 +00:00
|
|
|
if (s_console_handle != INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
s_console_handle_set = true;
|
|
|
|
|
|
|
|
// This gets us unix-style coloured output.
|
|
|
|
EnableVirtualTerminalProcessing(GetStdHandle(STD_OUTPUT_HANDLE));
|
|
|
|
EnableVirtualTerminalProcessing(GetStdHandle(STD_ERROR_HANDLE));
|
|
|
|
|
|
|
|
// Redirect stdout/stderr.
|
|
|
|
std::FILE* fp;
|
|
|
|
freopen_s(&fp, "CONIN$", "r", stdin);
|
|
|
|
freopen_s(&fp, "CONOUT$", "w", stdout);
|
|
|
|
freopen_s(&fp, "CONOUT$", "w", stderr);
|
|
|
|
}
|
2021-12-13 12:12:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-03 04:06:04 +00:00
|
|
|
if (!s_console_handle_set && !s_debugger_attached)
|
2022-03-04 10:40:03 +00:00
|
|
|
{
|
2021-12-13 12:12:54 +00:00
|
|
|
Console_SetActiveHandler(ConsoleWriter_Null);
|
2022-03-04 10:40:03 +00:00
|
|
|
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
|
|
|
|
}
|
2021-12-13 12:12:54 +00:00
|
|
|
else
|
2022-03-04 10:40:03 +00:00
|
|
|
{
|
2021-12-13 12:12:54 +00:00
|
|
|
Console_SetActiveHandler(ConsoleWriter_WinQt);
|
2022-03-04 10:40:03 +00:00
|
|
|
SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Console_SetActiveHandler(ConsoleWriter_Null);
|
|
|
|
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
|
2022-05-03 04:06:04 +00:00
|
|
|
|
|
|
|
if (s_console_handle_set)
|
|
|
|
{
|
|
|
|
s_console_handle_set = false;
|
|
|
|
|
|
|
|
// redirect stdout/stderr back to null.
|
|
|
|
std::FILE* fp;
|
|
|
|
freopen_s(&fp, "NUL:", "w", stderr);
|
|
|
|
freopen_s(&fp, "NUL:", "w", stdout);
|
|
|
|
freopen_s(&fp, "NUL:", "w", stdin);
|
|
|
|
|
|
|
|
// release console and restore state
|
|
|
|
SetStdHandle(STD_INPUT_HANDLE, s_old_console_stdin);
|
|
|
|
SetStdHandle(STD_OUTPUT_HANDLE, s_old_console_stdout);
|
|
|
|
SetStdHandle(STD_ERROR_HANDLE, s_old_console_stderr);
|
|
|
|
s_old_console_stdin = NULL;
|
|
|
|
s_old_console_stdout = NULL;
|
|
|
|
s_old_console_stderr = NULL;
|
|
|
|
if (s_console_allocated)
|
|
|
|
{
|
|
|
|
s_console_allocated = false;
|
|
|
|
FreeConsole();
|
|
|
|
}
|
|
|
|
}
|
2022-03-04 10:40:03 +00:00
|
|
|
}
|
2022-05-03 04:06:04 +00:00
|
|
|
}
|
|
|
|
|
2021-12-13 12:12:54 +00:00
|
|
|
#else
|
2022-05-03 04:06:04 +00:00
|
|
|
|
|
|
|
// Unix doesn't need any special handling for console.
|
|
|
|
static void SetSystemConsoleEnabled(bool enabled)
|
|
|
|
{
|
|
|
|
if (enabled)
|
2021-12-13 12:12:54 +00:00
|
|
|
Console_SetActiveHandler(ConsoleWriter_Stdout);
|
|
|
|
else
|
|
|
|
Console_SetActiveHandler(ConsoleWriter_Null);
|
2022-05-03 04:06:04 +00:00
|
|
|
}
|
|
|
|
|
2022-03-04 10:40:03 +00:00
|
|
|
#endif
|
2022-05-03 04:06:04 +00:00
|
|
|
|
|
|
|
void QtHost::InitializeEarlyConsole()
|
|
|
|
{
|
|
|
|
SetSystemConsoleEnabled(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHost::UpdateLogging()
|
|
|
|
{
|
|
|
|
const bool system_console_enabled = QtHost::GetBaseBoolSettingValue("Logging", "EnableSystemConsole", false);
|
|
|
|
|
|
|
|
const bool any_logging_sinks = system_console_enabled;
|
|
|
|
DevConWriterEnabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableVerbose", false);
|
|
|
|
SysConsole.eeConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableEEConsole", true);
|
|
|
|
SysConsole.iopConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableIOPConsole", true);
|
2022-04-04 00:06:59 +00:00
|
|
|
|
|
|
|
// Input Recording Logs
|
2022-05-23 00:04:27 +00:00
|
|
|
SysConsole.recordingConsole.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableInputRecordingLogs", true);
|
|
|
|
SysConsole.controlInfo.Enabled = any_logging_sinks && QtHost::GetBaseBoolSettingValue("Logging", "EnableControllerLogs", false);
|
2022-05-03 04:06:04 +00:00
|
|
|
|
|
|
|
SetSystemConsoleEnabled(system_console_enabled);
|
2021-12-13 12:12:54 +00:00
|
|
|
}
|