mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-09 21:31:04 +00:00
initial
This commit is contained in:
@@ -833,6 +833,10 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||||||
src/core/thread.h
|
src/core/thread.h
|
||||||
src/core/tls.cpp
|
src/core/tls.cpp
|
||||||
src/core/tls.h
|
src/core/tls.h
|
||||||
|
src/core/emulator_settings.cpp
|
||||||
|
src/core/emulator_settings.h
|
||||||
|
src/core/user_manager.cpp
|
||||||
|
src/core/user_manager.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (ARCHITECTURE STREQUAL "x86_64")
|
if (ARCHITECTURE STREQUAL "x86_64")
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ public:
|
|||||||
|
|
||||||
// General
|
// General
|
||||||
static ConfigEntry<int> volumeSlider(100);
|
static ConfigEntry<int> volumeSlider(100);
|
||||||
static ConfigEntry<bool> isNeo(false);
|
|
||||||
static ConfigEntry<bool> isDevKit(false);
|
static ConfigEntry<bool> isDevKit(false);
|
||||||
static ConfigEntry<int> extraDmemInMbytes(0);
|
static ConfigEntry<int> extraDmemInMbytes(0);
|
||||||
static ConfigEntry<bool> isPSNSignedIn(false);
|
static ConfigEntry<bool> isPSNSignedIn(false);
|
||||||
@@ -195,7 +194,6 @@ static ConfigEntry<bool> rdocEnable(false);
|
|||||||
// Debug
|
// Debug
|
||||||
static ConfigEntry<bool> isDebugDump(false);
|
static ConfigEntry<bool> isDebugDump(false);
|
||||||
static ConfigEntry<bool> isShaderDebug(false);
|
static ConfigEntry<bool> isShaderDebug(false);
|
||||||
static ConfigEntry<bool> isSeparateLogFilesEnabled(false);
|
|
||||||
static ConfigEntry<bool> isFpsColor(true);
|
static ConfigEntry<bool> isFpsColor(true);
|
||||||
static ConfigEntry<bool> logEnabled(true);
|
static ConfigEntry<bool> logEnabled(true);
|
||||||
|
|
||||||
@@ -298,10 +296,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) {
|
|||||||
volumeSlider.set(volumeValue, is_game_specific);
|
volumeSlider.set(volumeValue, is_game_specific);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isNeoModeConsole() {
|
|
||||||
return isNeo.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isDevKitConsole() {
|
bool isDevKitConsole() {
|
||||||
return isDevKit.get();
|
return isDevKit.get();
|
||||||
}
|
}
|
||||||
@@ -655,10 +649,6 @@ void setLanguage(u32 language, bool is_game_specific) {
|
|||||||
m_language.set(language, is_game_specific);
|
m_language.set(language, is_game_specific);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setNeoMode(bool enable, bool is_game_specific) {
|
|
||||||
isNeo.set(enable, is_game_specific);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDevKitConsole(bool enable, bool is_game_specific) {
|
void setDevKitConsole(bool enable, bool is_game_specific) {
|
||||||
isDevKit.set(enable, is_game_specific);
|
isDevKit.set(enable, is_game_specific);
|
||||||
}
|
}
|
||||||
@@ -671,10 +661,6 @@ void setLogFilter(const string& type, bool is_game_specific) {
|
|||||||
logFilter.set(type, is_game_specific);
|
logFilter.set(type, is_game_specific);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) {
|
|
||||||
isSeparateLogFilesEnabled.set(enabled, is_game_specific);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setUserName(const string& name, bool is_game_specific) {
|
void setUserName(const string& name, bool is_game_specific) {
|
||||||
userName.set(name, is_game_specific);
|
userName.set(name, is_game_specific);
|
||||||
}
|
}
|
||||||
@@ -768,10 +754,6 @@ u32 GetLanguage() {
|
|||||||
return m_language.get();
|
return m_language.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getSeparateLogFilesEnabled() {
|
|
||||||
return isSeparateLogFilesEnabled.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getPSNSignedIn() {
|
bool getPSNSignedIn() {
|
||||||
return isPSNSignedIn.get();
|
return isPSNSignedIn.get();
|
||||||
}
|
}
|
||||||
@@ -861,7 +843,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
|||||||
const toml::value& general = data.at("General");
|
const toml::value& general = data.at("General");
|
||||||
|
|
||||||
volumeSlider.setFromToml(general, "volumeSlider", is_game_specific);
|
volumeSlider.setFromToml(general, "volumeSlider", is_game_specific);
|
||||||
isNeo.setFromToml(general, "isPS4Pro", is_game_specific);
|
|
||||||
isDevKit.setFromToml(general, "isDevKit", is_game_specific);
|
isDevKit.setFromToml(general, "isDevKit", is_game_specific);
|
||||||
if (is_game_specific) { // do not get this value from the base config
|
if (is_game_specific) { // do not get this value from the base config
|
||||||
extraDmemInMbytes.setFromToml(general, "extraDmemInMbytes", is_game_specific);
|
extraDmemInMbytes.setFromToml(general, "extraDmemInMbytes", is_game_specific);
|
||||||
@@ -946,7 +927,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
|||||||
const toml::value& debug = data.at("Debug");
|
const toml::value& debug = data.at("Debug");
|
||||||
|
|
||||||
isDebugDump.setFromToml(debug, "DebugDump", is_game_specific);
|
isDebugDump.setFromToml(debug, "DebugDump", is_game_specific);
|
||||||
isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific);
|
|
||||||
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
|
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
|
||||||
isFpsColor.setFromToml(debug, "FPSColor", is_game_specific);
|
isFpsColor.setFromToml(debug, "FPSColor", is_game_specific);
|
||||||
logEnabled.setFromToml(debug, "logEnabled", is_game_specific);
|
logEnabled.setFromToml(debug, "logEnabled", is_game_specific);
|
||||||
@@ -1061,7 +1041,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
|||||||
userName.setTomlValue(data, "General", "userName", is_game_specific);
|
userName.setTomlValue(data, "General", "userName", is_game_specific);
|
||||||
isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific);
|
isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific);
|
||||||
isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific);
|
isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific);
|
||||||
isNeo.setTomlValue(data, "General", "isPS4Pro", is_game_specific);
|
|
||||||
isDevKit.setTomlValue(data, "General", "isDevKit", is_game_specific);
|
isDevKit.setTomlValue(data, "General", "isDevKit", is_game_specific);
|
||||||
if (is_game_specific) {
|
if (is_game_specific) {
|
||||||
extraDmemInMbytes.setTomlValue(data, "General", "extraDmemInMbytes", is_game_specific);
|
extraDmemInMbytes.setTomlValue(data, "General", "extraDmemInMbytes", is_game_specific);
|
||||||
@@ -1110,8 +1089,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
|||||||
|
|
||||||
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
|
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
|
||||||
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
|
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
|
||||||
isSeparateLogFilesEnabled.setTomlValue(data, "Debug", "isSeparateLogFilesEnabled",
|
|
||||||
is_game_specific);
|
|
||||||
logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific);
|
logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific);
|
||||||
|
|
||||||
m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific);
|
m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific);
|
||||||
@@ -1183,7 +1160,6 @@ void setDefaultValues(bool is_game_specific) {
|
|||||||
if (is_game_specific) {
|
if (is_game_specific) {
|
||||||
readbacksEnabled.set(false, is_game_specific);
|
readbacksEnabled.set(false, is_game_specific);
|
||||||
readbackLinearImagesEnabled.set(false, is_game_specific);
|
readbackLinearImagesEnabled.set(false, is_game_specific);
|
||||||
isNeo.set(false, is_game_specific);
|
|
||||||
isDevKit.set(false, is_game_specific);
|
isDevKit.set(false, is_game_specific);
|
||||||
isPSNSignedIn.set(false, is_game_specific);
|
isPSNSignedIn.set(false, is_game_specific);
|
||||||
isConnectedToNetwork.set(false, is_game_specific);
|
isConnectedToNetwork.set(false, is_game_specific);
|
||||||
@@ -1241,7 +1217,6 @@ void setDefaultValues(bool is_game_specific) {
|
|||||||
// GS - Debug
|
// GS - Debug
|
||||||
isDebugDump.set(false, is_game_specific);
|
isDebugDump.set(false, is_game_specific);
|
||||||
isShaderDebug.set(false, is_game_specific);
|
isShaderDebug.set(false, is_game_specific);
|
||||||
isSeparateLogFilesEnabled.set(false, is_game_specific);
|
|
||||||
logEnabled.set(true, is_game_specific);
|
logEnabled.set(true, is_game_specific);
|
||||||
|
|
||||||
// GS - Settings
|
// GS - Settings
|
||||||
|
|||||||
@@ -110,8 +110,6 @@ void setPadSpkOutputDevice(std::string device, bool is_game_specific = false);
|
|||||||
std::string getMicDevice();
|
std::string getMicDevice();
|
||||||
void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false);
|
void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false);
|
||||||
void setMicDevice(std::string device, bool is_game_specific = false);
|
void setMicDevice(std::string device, bool is_game_specific = false);
|
||||||
void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific = false);
|
|
||||||
bool getSeparateLogFilesEnabled();
|
|
||||||
u32 GetLanguage();
|
u32 GetLanguage();
|
||||||
void setLanguage(u32 language, bool is_game_specific = false);
|
void setLanguage(u32 language, bool is_game_specific = false);
|
||||||
void setUseSpecialPad(bool use);
|
void setUseSpecialPad(bool use);
|
||||||
@@ -122,8 +120,6 @@ bool getPSNSignedIn();
|
|||||||
void setPSNSignedIn(bool sign, bool is_game_specific = false);
|
void setPSNSignedIn(bool sign, bool is_game_specific = false);
|
||||||
bool patchShaders(); // no set
|
bool patchShaders(); // no set
|
||||||
bool fpsColor(); // no set
|
bool fpsColor(); // no set
|
||||||
bool isNeoModeConsole();
|
|
||||||
void setNeoMode(bool enable, bool is_game_specific = false);
|
|
||||||
bool isDevKitConsole();
|
bool isDevKitConsole();
|
||||||
void setDevKitConsole(bool enable, bool is_game_specific = false);
|
void setDevKitConsole(bool enable, bool is_game_specific = false);
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ static auto UserPaths = [] {
|
|||||||
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
||||||
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
|
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
|
||||||
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
|
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
|
||||||
|
create_path(PathType::HomeDir, user_dir / HOME_DIR);
|
||||||
|
|
||||||
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
|
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
|
||||||
if (notice_file.is_open()) {
|
if (notice_file.is_open()) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ enum class PathType {
|
|||||||
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
||||||
CustomTrophy, // Where custom files for trophies are stored.
|
CustomTrophy, // Where custom files for trophies are stored.
|
||||||
CustomConfigs, // Where custom files for different games are stored.
|
CustomConfigs, // Where custom files for different games are stored.
|
||||||
|
HomeDir, // PS4 home directory
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr auto PORTABLE_DIR = "user";
|
constexpr auto PORTABLE_DIR = "user";
|
||||||
@@ -42,6 +43,7 @@ constexpr auto PATCHES_DIR = "patches";
|
|||||||
constexpr auto METADATA_DIR = "game_data";
|
constexpr auto METADATA_DIR = "game_data";
|
||||||
constexpr auto CUSTOM_TROPHY = "custom_trophy";
|
constexpr auto CUSTOM_TROPHY = "custom_trophy";
|
||||||
constexpr auto CUSTOM_CONFIGS = "custom_configs";
|
constexpr auto CUSTOM_CONFIGS = "custom_configs";
|
||||||
|
constexpr auto HOME_DIR = "home";
|
||||||
|
|
||||||
// Filenames
|
// Filenames
|
||||||
constexpr auto LOG_FILE = "shad_log.txt";
|
constexpr auto LOG_FILE = "shad_log.txt";
|
||||||
|
|||||||
321
src/core/emulator_settings.cpp
Normal file
321
src/core/emulator_settings.cpp
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <common/path_util.h>
|
||||||
|
#include "emulator_settings.h"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
std::shared_ptr<EmulatorSettings> EmulatorSettings::s_instance = nullptr;
|
||||||
|
std::mutex EmulatorSettings::s_mutex;
|
||||||
|
|
||||||
|
namespace nlohmann {
|
||||||
|
template <>
|
||||||
|
struct adl_serializer<std::filesystem::path> {
|
||||||
|
static void to_json(json& j, const std::filesystem::path& p) {
|
||||||
|
j = p.u8string();
|
||||||
|
}
|
||||||
|
static void from_json(const json& j, std::filesystem::path& p) {
|
||||||
|
p = j.get<std::string>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace nlohmann
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Print summary
|
||||||
|
// --------------------
|
||||||
|
void EmulatorSettings::PrintChangedSummary(const std::vector<std::string>& changed) {
|
||||||
|
if (changed.empty()) {
|
||||||
|
std::cout << "[Settings] No game-specific overrides applied\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cout << "[Settings] Game-specific overrides applied:\n";
|
||||||
|
for (const auto& k : changed)
|
||||||
|
std::cout << " * " << k << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// ctor/dtor + singleton
|
||||||
|
// --------------------
|
||||||
|
EmulatorSettings::EmulatorSettings() {
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
EmulatorSettings::~EmulatorSettings() {
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<EmulatorSettings> EmulatorSettings::GetInstance() {
|
||||||
|
std::lock_guard<std::mutex> lock(s_mutex);
|
||||||
|
if (!s_instance)
|
||||||
|
s_instance = std::make_shared<EmulatorSettings>();
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorSettings::SetInstance(std::shared_ptr<EmulatorSettings> instance) {
|
||||||
|
std::lock_guard<std::mutex> lock(s_mutex);
|
||||||
|
s_instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// General helpers
|
||||||
|
// --------------------
|
||||||
|
bool EmulatorSettings::AddGameInstallDir(const std::filesystem::path& dir, bool enabled) {
|
||||||
|
for (const auto& d : m_general.install_dirs.value)
|
||||||
|
if (d.path == dir)
|
||||||
|
return false;
|
||||||
|
m_general.install_dirs.value.push_back({dir, enabled});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> EmulatorSettings::GetGameInstallDirs() const {
|
||||||
|
std::vector<std::filesystem::path> out;
|
||||||
|
for (const auto& d : m_general.install_dirs.value)
|
||||||
|
if (d.enabled)
|
||||||
|
out.push_back(d.path);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorSettings::SetAllGameInstallDirs(const std::vector<GameInstallDir>& dirs) {
|
||||||
|
m_general.install_dirs.value = dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path EmulatorSettings::GetHomeDir() {
|
||||||
|
if (m_general.home_dir.value.empty()) {
|
||||||
|
return Common::FS::GetUserPath(Common::FS::PathType::HomeDir);
|
||||||
|
}
|
||||||
|
return m_general.home_dir.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorSettings::SetHomeDir(const std::filesystem::path& dir) {
|
||||||
|
m_general.home_dir.value = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path EmulatorSettings::GetSysModulesDir() {
|
||||||
|
if (m_general.sys_modules_dir.value.empty()) {
|
||||||
|
return Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir);
|
||||||
|
}
|
||||||
|
return m_general.sys_modules_dir.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorSettings::SetSysModulesDir(const std::filesystem::path& dir) {
|
||||||
|
m_general.sys_modules_dir.value = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Save
|
||||||
|
// --------------------
|
||||||
|
bool EmulatorSettings::Save(const std::string& serial) const {
|
||||||
|
try {
|
||||||
|
if (!serial.empty()) {
|
||||||
|
const std::filesystem::path cfgDir =
|
||||||
|
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs);
|
||||||
|
std::filesystem::create_directories(cfgDir);
|
||||||
|
const std::filesystem::path path = cfgDir / (serial + ".json");
|
||||||
|
|
||||||
|
json j = json::object();
|
||||||
|
|
||||||
|
// Only write overrideable fields for each group
|
||||||
|
json generalObj = json::object();
|
||||||
|
for (auto& item : m_general.GetOverrideableFields()) {
|
||||||
|
json whole = m_general;
|
||||||
|
if (whole.contains(item.key))
|
||||||
|
generalObj[item.key] = whole[item.key];
|
||||||
|
}
|
||||||
|
j["General"] = generalObj;
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
json debugObj = json::object();
|
||||||
|
for (auto& item : m_debug.GetOverrideableFields()) {
|
||||||
|
json whole = m_debug;
|
||||||
|
if (whole.contains(item.key))
|
||||||
|
debugObj[item.key] = whole[item.key];
|
||||||
|
}
|
||||||
|
j["Debug"] = debugObj;
|
||||||
|
|
||||||
|
// Input
|
||||||
|
json inputObj = json::object();
|
||||||
|
for (auto& item : m_input.GetOverrideableFields()) {
|
||||||
|
json whole = m_input;
|
||||||
|
if (whole.contains(item.key))
|
||||||
|
inputObj[item.key] = whole[item.key];
|
||||||
|
}
|
||||||
|
j["Input"] = inputObj;
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
json audioObj = json::object();
|
||||||
|
for (auto& item : m_audio.GetOverrideableFields()) {
|
||||||
|
json whole = m_audio;
|
||||||
|
if (whole.contains(item.key))
|
||||||
|
audioObj[item.key] = whole[item.key];
|
||||||
|
}
|
||||||
|
j["Audio"] = audioObj;
|
||||||
|
|
||||||
|
// GPU
|
||||||
|
json gpuObj = json::object();
|
||||||
|
for (auto& item : m_gpu.GetOverrideableFields()) {
|
||||||
|
json whole = m_gpu;
|
||||||
|
if (whole.contains(item.key))
|
||||||
|
gpuObj[item.key] = whole[item.key];
|
||||||
|
}
|
||||||
|
j["GPU"] = gpuObj;
|
||||||
|
|
||||||
|
// Vulkan
|
||||||
|
json vulkanObj = json::object();
|
||||||
|
for (auto& item : m_vulkan.GetOverrideableFields()) {
|
||||||
|
json whole = m_vulkan;
|
||||||
|
if (whole.contains(item.key))
|
||||||
|
vulkanObj[item.key] = whole[item.key];
|
||||||
|
}
|
||||||
|
j["Vulkan"] = vulkanObj;
|
||||||
|
|
||||||
|
std::ofstream out(path);
|
||||||
|
if (!out.is_open()) {
|
||||||
|
std::cerr << "Failed to open file for writing: " << path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out << std::setw(4) << j;
|
||||||
|
out.flush();
|
||||||
|
if (out.fail()) {
|
||||||
|
std::cerr << "Failed to write settings to: " << path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
const std::filesystem::path path =
|
||||||
|
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.json";
|
||||||
|
json j;
|
||||||
|
j["General"] = m_general;
|
||||||
|
j["Debug"] = m_debug;
|
||||||
|
j["Input"] = m_input;
|
||||||
|
j["Audio"] = m_audio;
|
||||||
|
j["GPU"] = m_gpu;
|
||||||
|
j["Vulkan"] = m_vulkan;
|
||||||
|
j["Users"] = m_userManager.GetUsers();
|
||||||
|
|
||||||
|
std::ofstream out(path);
|
||||||
|
if (!out.is_open()) {
|
||||||
|
std::cerr << "Failed to open file for writing: " << path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out << std::setw(4) << j;
|
||||||
|
out.flush();
|
||||||
|
if (out.fail()) {
|
||||||
|
std::cerr << "Failed to write settings to: " << path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error saving settings: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Load
|
||||||
|
// --------------------
|
||||||
|
bool EmulatorSettings::Load(const std::string& serial) {
|
||||||
|
try {
|
||||||
|
const std::filesystem::path userDir =
|
||||||
|
Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||||
|
const std::filesystem::path configPath = userDir / "config.json";
|
||||||
|
|
||||||
|
// Load global config if exists
|
||||||
|
if (std::ifstream globalIn{configPath}; globalIn.good()) {
|
||||||
|
json gj;
|
||||||
|
globalIn >> gj;
|
||||||
|
if (gj.contains("General")) {
|
||||||
|
json current = m_general; // JSON from existing struct with all defaults
|
||||||
|
current.update(gj.at("General")); // merge only fields present in file
|
||||||
|
m_general = current.get<GeneralSettings>(); // convert back
|
||||||
|
}
|
||||||
|
if (gj.contains("Debug")) {
|
||||||
|
json current = m_debug;
|
||||||
|
current.update(gj.at("Debug"));
|
||||||
|
m_debug = current.get<DebugSettings>();
|
||||||
|
}
|
||||||
|
if (gj.contains("Input")) {
|
||||||
|
json current = m_input;
|
||||||
|
current.update(gj.at("Input"));
|
||||||
|
m_input = current.get<InputSettings>();
|
||||||
|
}
|
||||||
|
if (gj.contains("Audio")) {
|
||||||
|
json current = m_audio;
|
||||||
|
current.update(gj.at("Audio"));
|
||||||
|
m_audio = current.get<AudioSettings>();
|
||||||
|
}
|
||||||
|
if (gj.contains("GPU")) {
|
||||||
|
json current = m_gpu;
|
||||||
|
current.update(gj.at("GPU"));
|
||||||
|
m_gpu = current.get<GPUSettings>();
|
||||||
|
}
|
||||||
|
if (gj.contains("Vulkan")) {
|
||||||
|
json current = m_vulkan;
|
||||||
|
current.update(gj.at("Vulkan"));
|
||||||
|
m_vulkan = current.get<VulkanSettings>();
|
||||||
|
}
|
||||||
|
if (gj.contains("Users"))
|
||||||
|
m_userManager.GetUsers() = gj.at("Users").get<Users>();
|
||||||
|
} else {
|
||||||
|
// ensure a default user exists
|
||||||
|
if (m_userManager.GetUsers().user.empty())
|
||||||
|
m_userManager.GetUsers().user = m_userManager.CreateDefaultUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load per-game overrides and apply
|
||||||
|
if (!serial.empty()) {
|
||||||
|
const std::filesystem::path gamePath =
|
||||||
|
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (serial + ".json");
|
||||||
|
if (!std::filesystem::exists(gamePath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::ifstream in(gamePath);
|
||||||
|
if (!in.is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
json gj;
|
||||||
|
in >> gj;
|
||||||
|
|
||||||
|
std::vector<std::string> changed;
|
||||||
|
|
||||||
|
if (gj.contains("General")) {
|
||||||
|
ApplyGroupOverrides<GeneralSettings>(m_general, gj.at("General"), changed);
|
||||||
|
}
|
||||||
|
if (gj.contains("Debug")) {
|
||||||
|
ApplyGroupOverrides<DebugSettings>(m_debug, gj.at("Debug"), changed);
|
||||||
|
}
|
||||||
|
if (gj.contains("Input")) {
|
||||||
|
ApplyGroupOverrides<InputSettings>(m_input, gj.at("Input"), changed);
|
||||||
|
}
|
||||||
|
if (gj.contains("Audio")) {
|
||||||
|
ApplyGroupOverrides<AudioSettings>(m_audio, gj.at("Audio"), changed);
|
||||||
|
}
|
||||||
|
if (gj.contains("GPU")) {
|
||||||
|
ApplyGroupOverrides<GPUSettings>(m_gpu, gj.at("GPU"), changed);
|
||||||
|
}
|
||||||
|
if (gj.contains("Vulkan")) {
|
||||||
|
ApplyGroupOverrides<VulkanSettings>(m_vulkan, gj.at("Vulkan"), changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintChangedSummary(changed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error loading settings: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorSettings::setDefaultValues() {
|
||||||
|
m_general = GeneralSettings{};
|
||||||
|
m_debug = DebugSettings{};
|
||||||
|
m_input = InputSettings{};
|
||||||
|
m_audio = AudioSettings{};
|
||||||
|
m_gpu = GPUSettings{};
|
||||||
|
m_vulkan = VulkanSettings{};
|
||||||
|
}
|
||||||
328
src/core/emulator_settings.h
Normal file
328
src/core/emulator_settings.h
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/user_manager.h"
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Generic Setting wrapper
|
||||||
|
// -------------------------------
|
||||||
|
template <typename T>
|
||||||
|
struct Setting {
|
||||||
|
T value{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void to_json(nlohmann::json& j, const Setting<T>& s) {
|
||||||
|
j = s.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void from_json(const nlohmann::json& j, Setting<T>& s) {
|
||||||
|
s.value = j.get<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Helper to describe a per-field override action
|
||||||
|
// -------------------------------
|
||||||
|
struct OverrideItem {
|
||||||
|
const char* key;
|
||||||
|
// apply(basePtrToStruct, jsonEntry, changedFields)
|
||||||
|
std::function<void(void*, const nlohmann::json&, std::vector<std::string>&)> apply;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper factory: create an OverrideItem binding a pointer-to-member
|
||||||
|
template <typename Struct, typename T>
|
||||||
|
inline OverrideItem make_override(const char* key, Setting<T> Struct::* member) {
|
||||||
|
return OverrideItem{key, [member, key](void* base, const nlohmann::json& entry,
|
||||||
|
std::vector<std::string>& changed) {
|
||||||
|
if (!entry.is_object())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Struct* obj = reinterpret_cast<Struct*>(base);
|
||||||
|
Setting<T>& dst = obj->*member;
|
||||||
|
|
||||||
|
Setting<T> tmp = entry.get<Setting<T>>();
|
||||||
|
|
||||||
|
if (dst.value != tmp.value) {
|
||||||
|
changed.push_back(std::string(key) + " ( " +
|
||||||
|
nlohmann::json(dst.value).dump() + " → " +
|
||||||
|
nlohmann::json(tmp.value).dump() + " )");
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.value = tmp.value;
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Support types
|
||||||
|
// -------------------------------
|
||||||
|
struct GameInstallDir {
|
||||||
|
std::filesystem::path path;
|
||||||
|
bool enabled;
|
||||||
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GameInstallDir, path, enabled)
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// General settings
|
||||||
|
// -------------------------------
|
||||||
|
struct GeneralSettings {
|
||||||
|
Setting<std::vector<GameInstallDir>> install_dirs;
|
||||||
|
Setting<std::filesystem::path> addon_install_dir;
|
||||||
|
Setting<std::filesystem::path> home_dir;
|
||||||
|
Setting<std::filesystem::path> sys_modules_dir;
|
||||||
|
|
||||||
|
Setting<int> volume_slider{100};
|
||||||
|
Setting<bool> neo_mode{false};
|
||||||
|
Setting<bool> dev_kit_mode{false};
|
||||||
|
Setting<int> extra_dmem_in_mbytes{0};
|
||||||
|
Setting<bool> psn_signed_in{false};
|
||||||
|
Setting<bool> trophy_popup_disabled{false};
|
||||||
|
Setting<double> trophy_notification_duration{6.0};
|
||||||
|
Setting<std::string> log_filter{""};
|
||||||
|
Setting<std::string> log_type{"sync"};
|
||||||
|
Setting<bool> show_splash{false};
|
||||||
|
Setting<std::string> side_trophy{"right"};
|
||||||
|
Setting<bool> connected_to_network{false};
|
||||||
|
Setting<bool> discord_rpc_enabled{false};
|
||||||
|
|
||||||
|
// return a vector of override descriptors (runtime, but tiny)
|
||||||
|
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||||
|
return std::vector<OverrideItem>{
|
||||||
|
make_override<GeneralSettings>("volume_slider", &GeneralSettings::volume_slider),
|
||||||
|
make_override<GeneralSettings>("neo_mode", &GeneralSettings::neo_mode),
|
||||||
|
make_override<GeneralSettings>("dev_kit_mode", &GeneralSettings::dev_kit_mode),
|
||||||
|
make_override<GeneralSettings>("extra_dmem_in_mbytes",
|
||||||
|
&GeneralSettings::extra_dmem_in_mbytes),
|
||||||
|
make_override<GeneralSettings>("psn_signed_in", &GeneralSettings::psn_signed_in),
|
||||||
|
make_override<GeneralSettings>("trophy_popup_disabled",
|
||||||
|
&GeneralSettings::trophy_popup_disabled),
|
||||||
|
make_override<GeneralSettings>("trophy_notification_duration",
|
||||||
|
&GeneralSettings::trophy_notification_duration),
|
||||||
|
make_override<GeneralSettings>("log_filter", &GeneralSettings::log_filter),
|
||||||
|
make_override<GeneralSettings>("log_type", &GeneralSettings::log_type),
|
||||||
|
make_override<GeneralSettings>("show_splash", &GeneralSettings::show_splash),
|
||||||
|
make_override<GeneralSettings>("side_trophy", &GeneralSettings::side_trophy),
|
||||||
|
make_override<GeneralSettings>("connected_to_network",
|
||||||
|
&GeneralSettings::connected_to_network)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir,
|
||||||
|
sys_modules_dir, volume_slider, neo_mode, dev_kit_mode,
|
||||||
|
extra_dmem_in_mbytes, psn_signed_in, trophy_popup_disabled,
|
||||||
|
trophy_notification_duration, log_filter, log_type, show_splash,
|
||||||
|
side_trophy, connected_to_network, discord_rpc_enabled)
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Debug settings
|
||||||
|
// -------------------------------
|
||||||
|
struct DebugSettings {
|
||||||
|
Setting<bool> separate_logging_enabled{false}; // specific
|
||||||
|
Setting<bool> debug_dump{false}; // specific
|
||||||
|
Setting<bool> shader_debug{false}; // specific
|
||||||
|
Setting<bool> fps_color{true};
|
||||||
|
Setting<bool> log_enabled{true}; // specific
|
||||||
|
|
||||||
|
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||||
|
return std::vector<OverrideItem>{
|
||||||
|
make_override<DebugSettings>("debug_dump", &DebugSettings::debug_dump),
|
||||||
|
make_override<DebugSettings>("shader_debug", &DebugSettings::shader_debug),
|
||||||
|
make_override<DebugSettings>("separate_logging_enabled",
|
||||||
|
&DebugSettings::separate_logging_enabled),
|
||||||
|
make_override<DebugSettings>("log_enabled", &DebugSettings::log_enabled)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump,
|
||||||
|
shader_debug, fps_color, log_enabled)
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Input settings
|
||||||
|
// -------------------------------
|
||||||
|
enum HideCursorState : int { Never, Idle, Always };
|
||||||
|
|
||||||
|
struct InputSettings {
|
||||||
|
Setting<int> cursor_state{HideCursorState::Idle}; // specific
|
||||||
|
Setting<int> cursor_hide_timeout{5}; // specific
|
||||||
|
Setting<bool> use_special_pad{false};
|
||||||
|
Setting<int> special_pad_class{1};
|
||||||
|
Setting<bool> motion_controls_enabled{true}; // specific
|
||||||
|
Setting<bool> use_unified_Input_Config{true};
|
||||||
|
Setting<std::string> default_controller_id{""};
|
||||||
|
Setting<bool> background_controller_input{false}; // specific
|
||||||
|
|
||||||
|
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||||
|
return std::vector<OverrideItem>{
|
||||||
|
make_override<InputSettings>("cursor_state", &InputSettings::cursor_state),
|
||||||
|
make_override<InputSettings>("cursor_hide_timeout",
|
||||||
|
&InputSettings::cursor_hide_timeout),
|
||||||
|
make_override<InputSettings>("motion_controls_enabled",
|
||||||
|
&InputSettings::motion_controls_enabled),
|
||||||
|
make_override<InputSettings>("background_controller_input",
|
||||||
|
&InputSettings::background_controller_input)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings, cursor_state, cursor_hide_timeout,
|
||||||
|
use_special_pad, special_pad_class, motion_controls_enabled,
|
||||||
|
use_unified_Input_Config, default_controller_id,
|
||||||
|
background_controller_input)
|
||||||
|
// -------------------------------
|
||||||
|
// Audio settings
|
||||||
|
// -------------------------------
|
||||||
|
struct AudioSettings {
|
||||||
|
Setting<std::string> mic_device{"Default Device"};
|
||||||
|
Setting<std::string> main_output_device{"Default Device"};
|
||||||
|
Setting<std::string> padSpk_output_device{"Default Device"};
|
||||||
|
|
||||||
|
// TODO add overrides
|
||||||
|
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||||
|
return std::vector<OverrideItem>{};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AudioSettings, mic_device, main_output_device,
|
||||||
|
padSpk_output_device)
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// GPU settings
|
||||||
|
// -------------------------------
|
||||||
|
struct GPUSettings {
|
||||||
|
Setting<u32> window_width{1280};
|
||||||
|
Setting<u32> window_height{720};
|
||||||
|
Setting<u32> internal_screen_width{1280};
|
||||||
|
Setting<u32> internal_screen_height{720};
|
||||||
|
Setting<bool> null_gpu{false};
|
||||||
|
Setting<bool> should_copy_gpu_buffers{false};
|
||||||
|
Setting<bool> readbacks_enabled{false};
|
||||||
|
Setting<bool> readback_linear_images_enabled{false};
|
||||||
|
Setting<bool> direct_memory_access_enabled{false};
|
||||||
|
Setting<bool> should_dump_shaders{false};
|
||||||
|
Setting<bool> should_patch_shaders{false};
|
||||||
|
Setting<u32> vblank_frequency{60};
|
||||||
|
Setting<bool> full_screen{false};
|
||||||
|
Setting<std::string> full_screen_mode{"Windowed"};
|
||||||
|
Setting<std::string> present_mode{"Mailbox"};
|
||||||
|
Setting<bool> hdr_allowed{false};
|
||||||
|
Setting<bool> fsr_enabled{false};
|
||||||
|
Setting<bool> rcas_enabled{true};
|
||||||
|
Setting<int> rcas_attenuation{250};
|
||||||
|
// TODO add overrides
|
||||||
|
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||||
|
return std::vector<OverrideItem>{};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings, window_width, window_height, internal_screen_width,
|
||||||
|
internal_screen_height, null_gpu, should_copy_gpu_buffers,
|
||||||
|
readbacks_enabled, readback_linear_images_enabled,
|
||||||
|
direct_memory_access_enabled, should_dump_shaders,
|
||||||
|
should_patch_shaders, vblank_frequency, full_screen,
|
||||||
|
full_screen_mode, present_mode, hdr_allowed, fsr_enabled,
|
||||||
|
rcas_enabled, rcas_attenuation)
|
||||||
|
// -------------------------------
|
||||||
|
// Vulkan settings
|
||||||
|
// -------------------------------
|
||||||
|
struct VulkanSettings {
|
||||||
|
Setting<s32> gpu_id{-1};
|
||||||
|
// TODO
|
||||||
|
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||||
|
return std::vector<OverrideItem>{};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id)
|
||||||
|
// -------------------------------
|
||||||
|
// User settings
|
||||||
|
// -------------------------------
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, user_id, user_color, user_name, controller_port)
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Users, default_user_id, user)
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// Main manager
|
||||||
|
// -------------------------------
|
||||||
|
class EmulatorSettings {
|
||||||
|
public:
|
||||||
|
EmulatorSettings();
|
||||||
|
~EmulatorSettings();
|
||||||
|
|
||||||
|
static std::shared_ptr<EmulatorSettings> GetInstance();
|
||||||
|
static void SetInstance(std::shared_ptr<EmulatorSettings> instance);
|
||||||
|
|
||||||
|
bool Save(const std::string& serial = "") const;
|
||||||
|
bool Load(const std::string& serial = "");
|
||||||
|
void setDefaultValues();
|
||||||
|
|
||||||
|
// general accessors
|
||||||
|
bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
||||||
|
std::vector<std::filesystem::path> GetGameInstallDirs() const;
|
||||||
|
void SetAllGameInstallDirs(const std::vector<GameInstallDir>& dirs);
|
||||||
|
std::filesystem::path GetHomeDir();
|
||||||
|
void SetHomeDir(const std::filesystem::path& dir);
|
||||||
|
std::filesystem::path GetSysModulesDir();
|
||||||
|
void SetSysModulesDir(const std::filesystem::path& dir);
|
||||||
|
|
||||||
|
// user helpers
|
||||||
|
UserManager& GetUserManager() {
|
||||||
|
return m_userManager;
|
||||||
|
}
|
||||||
|
const UserManager& GetUserManager() const {
|
||||||
|
return m_userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GeneralSettings m_general{};
|
||||||
|
DebugSettings m_debug{};
|
||||||
|
InputSettings m_input{};
|
||||||
|
AudioSettings m_audio{};
|
||||||
|
GPUSettings m_gpu{};
|
||||||
|
VulkanSettings m_vulkan{};
|
||||||
|
UserManager m_userManager;
|
||||||
|
|
||||||
|
static std::shared_ptr<EmulatorSettings> s_instance;
|
||||||
|
static std::mutex s_mutex;
|
||||||
|
|
||||||
|
// Generic helper that applies override descriptors for a specific group
|
||||||
|
template <typename Group>
|
||||||
|
void ApplyGroupOverrides(Group& group, const nlohmann::json& groupJson,
|
||||||
|
std::vector<std::string>& changed) {
|
||||||
|
for (auto& item : group.GetOverrideableFields()) {
|
||||||
|
if (!groupJson.contains(item.key))
|
||||||
|
continue;
|
||||||
|
item.apply(&group, groupJson.at(item.key), changed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintChangedSummary(const std::vector<std::string>& changed);
|
||||||
|
|
||||||
|
public:
|
||||||
|
#define SETTING_FORWARD(group, Name, field) \
|
||||||
|
auto Get##Name() const { \
|
||||||
|
return group.field.value; \
|
||||||
|
} \
|
||||||
|
void Set##Name(const decltype(group.field.value)& v) { \
|
||||||
|
group.field.value = v; \
|
||||||
|
}
|
||||||
|
#define SETTING_FORWARD_BOOL(group, Name, field) \
|
||||||
|
auto Is##Name() const { \
|
||||||
|
return group.field.value; \
|
||||||
|
} \
|
||||||
|
void Set##Name(const decltype(group.field.value)& v) { \
|
||||||
|
group.field.value = v; \
|
||||||
|
}
|
||||||
|
// General settings
|
||||||
|
SETTING_FORWARD(m_general, VolumeSlider, volume_slider)
|
||||||
|
SETTING_FORWARD_BOOL(m_general, Neo, neo_mode)
|
||||||
|
SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir)
|
||||||
|
|
||||||
|
// Debug settings
|
||||||
|
SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled)
|
||||||
|
|
||||||
|
#undef SETTING_FORWARD
|
||||||
|
#undef SETTING_FORWARD_BOOL
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
#include "common/elf_info.h"
|
#include "common/elf_info.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/emulator_settings.h"
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
#include "core/libraries/kernel/orbis_error.h"
|
#include "core/libraries/kernel/orbis_error.h"
|
||||||
#include "core/libraries/kernel/process.h"
|
#include "core/libraries/kernel/process.h"
|
||||||
@@ -17,19 +18,19 @@ s32 PS4_SYSV_ABI sceKernelIsInSandbox() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
|
s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
|
||||||
return Config::isNeoModeConsole() &&
|
return EmulatorSettings::GetInstance()->IsNeo() &&
|
||||||
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
|
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
|
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
|
||||||
return Config::isNeoModeConsole();
|
return EmulatorSettings::GetInstance()->IsNeo();
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
|
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
|
||||||
// These hardcoded values are based on hardware observations.
|
// These hardcoded values are based on hardware observations.
|
||||||
// Different models of PS4/PS4 Pro likely return slightly different values.
|
// Different models of PS4/PS4 Pro likely return slightly different values.
|
||||||
LOG_DEBUG(Lib_Kernel, "called");
|
LOG_DEBUG(Lib_Kernel, "called");
|
||||||
if (Config::isNeoModeConsole()) {
|
if (EmulatorSettings::GetInstance()->IsNeo()) {
|
||||||
return 0x740f30;
|
return 0x740f30;
|
||||||
}
|
}
|
||||||
return 0x710f10;
|
return 0x710f10;
|
||||||
|
|||||||
112
src/core/user_manager.cpp
Normal file
112
src/core/user_manager.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <common/path_util.h>
|
||||||
|
#include "emulator_settings.h"
|
||||||
|
#include "user_manager.h"
|
||||||
|
|
||||||
|
bool UserManager::AddUser(const User& user) {
|
||||||
|
for (const auto& u : m_users.user) {
|
||||||
|
if (u.user_id == user.user_id)
|
||||||
|
return false; // already exists
|
||||||
|
}
|
||||||
|
|
||||||
|
m_users.user.push_back(user);
|
||||||
|
|
||||||
|
// Create user home directory and subfolders
|
||||||
|
const auto user_dir =
|
||||||
|
EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user.user_id);
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::exists(user_dir)) {
|
||||||
|
std::filesystem::create_directory(user_dir, ec);
|
||||||
|
std::filesystem::create_directory(user_dir / "savedata", ec);
|
||||||
|
std::filesystem::create_directory(user_dir / "trophy", ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserManager::RemoveUser(s32 user_id) {
|
||||||
|
auto it = std::remove_if(m_users.user.begin(), m_users.user.end(),
|
||||||
|
[user_id](const User& u) { return u.user_id == user_id; });
|
||||||
|
if (it == m_users.user.end())
|
||||||
|
return false; // not found
|
||||||
|
|
||||||
|
const auto user_dir = EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id);
|
||||||
|
|
||||||
|
if (std::filesystem::exists(user_dir)) {
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::remove_all(user_dir, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_users.user.erase(it, m_users.user.end());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserManager::RenameUser(s32 user_id, const std::string& new_name) {
|
||||||
|
// Find user in the internal list
|
||||||
|
for (auto& user : m_users.user) {
|
||||||
|
if (user.user_id == user_id) {
|
||||||
|
if (user.user_name == new_name)
|
||||||
|
return true; // no change
|
||||||
|
|
||||||
|
user.user_name = new_name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
User* UserManager::GetUserByID(s32 user_id) {
|
||||||
|
for (auto& u : m_users.user) {
|
||||||
|
if (u.user_id == user_id)
|
||||||
|
return &u;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<User>& UserManager::GetAllUsers() const {
|
||||||
|
return m_users.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<User> UserManager::CreateDefaultUser() {
|
||||||
|
User default_user;
|
||||||
|
default_user.user_id = 1;
|
||||||
|
default_user.user_color = 0; // BLUE
|
||||||
|
default_user.user_name = "shadPS4";
|
||||||
|
default_user.controller_port = 1;
|
||||||
|
|
||||||
|
const auto user_dir =
|
||||||
|
EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(default_user.user_id);
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(user_dir)) {
|
||||||
|
std::filesystem::create_directory(user_dir);
|
||||||
|
std::filesystem::create_directory(user_dir / "savedata");
|
||||||
|
std::filesystem::create_directory(user_dir / "trophy");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {default_user};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UserManager::SetDefaultUser(u32 user_id) {
|
||||||
|
auto it = std::find_if(m_users.user.begin(), m_users.user.end(),
|
||||||
|
[user_id](const User& u) { return u.user_id == user_id; });
|
||||||
|
if (it == m_users.user.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_users.default_user_id = user_id;
|
||||||
|
SetControllerPort(user_id, 1); // Set default user to port 1
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserManager::SetControllerPort(u32 user_id, int port) {
|
||||||
|
for (auto& u : m_users.user) {
|
||||||
|
if (u.user_id != user_id && u.controller_port == port)
|
||||||
|
u.controller_port = -1;
|
||||||
|
if (u.user_id == user_id)
|
||||||
|
u.controller_port = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/core/user_manager.h
Normal file
44
src/core/user_manager.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
struct User {
|
||||||
|
s32 user_id;
|
||||||
|
u32 user_color;
|
||||||
|
std::string user_name;
|
||||||
|
int controller_port; // 1<>4
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Users {
|
||||||
|
int default_user_id = 1;
|
||||||
|
std::vector<User> user;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserManager {
|
||||||
|
public:
|
||||||
|
UserManager() = default;
|
||||||
|
|
||||||
|
bool AddUser(const User& user);
|
||||||
|
bool RemoveUser(s32 user_id);
|
||||||
|
bool RenameUser(s32 user_id, const std::string& new_name);
|
||||||
|
User* GetUserByID(s32 user_id);
|
||||||
|
const std::vector<User>& GetAllUsers() const;
|
||||||
|
std::vector<User> CreateDefaultUser();
|
||||||
|
bool SetDefaultUser(u32 user_id);
|
||||||
|
void SetControllerPort(u32 user_id, int port);
|
||||||
|
|
||||||
|
Users& GetUsers() {
|
||||||
|
return m_users;
|
||||||
|
}
|
||||||
|
const Users& GetUsers() const {
|
||||||
|
return m_users;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Users m_users;
|
||||||
|
};
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
#include "core/debugger.h"
|
#include "core/debugger.h"
|
||||||
#include "core/devtools/widget/module_list.h"
|
#include "core/devtools/widget/module_list.h"
|
||||||
|
#include "core/emulator_settings.h"
|
||||||
#include "core/file_format/psf.h"
|
#include "core/file_format/psf.h"
|
||||||
#include "core/file_format/trp.h"
|
#include "core/file_format/trp.h"
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
@@ -180,7 +181,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
|||||||
true);
|
true);
|
||||||
|
|
||||||
// Initialize logging as soon as possible
|
// Initialize logging as soon as possible
|
||||||
if (!id.empty() && Config::getSeparateLogFilesEnabled()) {
|
if (!id.empty() && EmulatorSettings::GetInstance()->IsSeparateLoggingEnabled()) {
|
||||||
Common::Log::Initialize(id + ".log");
|
Common::Log::Initialize(id + ".log");
|
||||||
} else {
|
} else {
|
||||||
Common::Log::Initialize();
|
Common::Log::Initialize();
|
||||||
@@ -203,7 +204,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
|||||||
LOG_INFO(Config, "Game-specific config exists: {}", has_game_config);
|
LOG_INFO(Config, "Game-specific config exists: {}", has_game_config);
|
||||||
|
|
||||||
LOG_INFO(Config, "General LogType: {}", Config::getLogType());
|
LOG_INFO(Config, "General LogType: {}", Config::getLogType());
|
||||||
LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole());
|
LOG_INFO(Config, "General isNeo: {}", EmulatorSettings::GetInstance()->IsNeo());
|
||||||
LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole());
|
LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole());
|
||||||
LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork());
|
LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork());
|
||||||
LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn());
|
LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn());
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include <core/emulator_settings.h>
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -28,6 +29,8 @@ int main(int argc, char* argv[]) {
|
|||||||
IPC::Instance().Init();
|
IPC::Instance().Init();
|
||||||
|
|
||||||
// Load configurations
|
// Load configurations
|
||||||
|
std::shared_ptr<EmulatorSettings> emu_settings = std::make_shared<EmulatorSettings>();
|
||||||
|
EmulatorSettings::SetInstance(emu_settings);
|
||||||
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||||
Config::load(user_dir / "config.toml");
|
Config::load(user_dir / "config.toml");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user