This commit is contained in:
georgemoralis
2025-11-26 20:44:20 +02:00
parent 1394852791
commit b6d2748ed7
12 changed files with 822 additions and 34 deletions

View File

@@ -833,6 +833,10 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/thread.h
src/core/tls.cpp
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")

View File

@@ -130,7 +130,6 @@ public:
// General
static ConfigEntry<int> volumeSlider(100);
static ConfigEntry<bool> isNeo(false);
static ConfigEntry<bool> isDevKit(false);
static ConfigEntry<int> extraDmemInMbytes(0);
static ConfigEntry<bool> isPSNSignedIn(false);
@@ -195,7 +194,6 @@ static ConfigEntry<bool> rdocEnable(false);
// Debug
static ConfigEntry<bool> isDebugDump(false);
static ConfigEntry<bool> isShaderDebug(false);
static ConfigEntry<bool> isSeparateLogFilesEnabled(false);
static ConfigEntry<bool> isFpsColor(true);
static ConfigEntry<bool> logEnabled(true);
@@ -298,10 +296,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) {
volumeSlider.set(volumeValue, is_game_specific);
}
bool isNeoModeConsole() {
return isNeo.get();
}
bool isDevKitConsole() {
return isDevKit.get();
}
@@ -655,10 +649,6 @@ void setLanguage(u32 language, bool 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) {
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);
}
void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) {
isSeparateLogFilesEnabled.set(enabled, is_game_specific);
}
void setUserName(const string& name, bool is_game_specific) {
userName.set(name, is_game_specific);
}
@@ -768,10 +754,6 @@ u32 GetLanguage() {
return m_language.get();
}
bool getSeparateLogFilesEnabled() {
return isSeparateLogFilesEnabled.get();
}
bool getPSNSignedIn() {
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");
volumeSlider.setFromToml(general, "volumeSlider", is_game_specific);
isNeo.setFromToml(general, "isPS4Pro", is_game_specific);
isDevKit.setFromToml(general, "isDevKit", is_game_specific);
if (is_game_specific) { // do not get this value from the base config
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");
isDebugDump.setFromToml(debug, "DebugDump", is_game_specific);
isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific);
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
isFpsColor.setFromToml(debug, "FPSColor", 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);
isShowSplash.setTomlValue(data, "General", "showSplash", 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);
if (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);
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
isSeparateLogFilesEnabled.setTomlValue(data, "Debug", "isSeparateLogFilesEnabled",
is_game_specific);
logEnabled.setTomlValue(data, "Debug", "logEnabled", 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) {
readbacksEnabled.set(false, is_game_specific);
readbackLinearImagesEnabled.set(false, is_game_specific);
isNeo.set(false, is_game_specific);
isDevKit.set(false, is_game_specific);
isPSNSignedIn.set(false, is_game_specific);
isConnectedToNetwork.set(false, is_game_specific);
@@ -1241,7 +1217,6 @@ void setDefaultValues(bool is_game_specific) {
// GS - Debug
isDebugDump.set(false, is_game_specific);
isShaderDebug.set(false, is_game_specific);
isSeparateLogFilesEnabled.set(false, is_game_specific);
logEnabled.set(true, is_game_specific);
// GS - Settings

View File

@@ -110,8 +110,6 @@ void setPadSpkOutputDevice(std::string device, bool is_game_specific = false);
std::string getMicDevice();
void setCursorHideTimeout(int newcursorHideTimeout, 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();
void setLanguage(u32 language, bool is_game_specific = false);
void setUseSpecialPad(bool use);
@@ -122,8 +120,6 @@ bool getPSNSignedIn();
void setPSNSignedIn(bool sign, bool is_game_specific = false);
bool patchShaders(); // no set
bool fpsColor(); // no set
bool isNeoModeConsole();
void setNeoMode(bool enable, bool is_game_specific = false);
bool isDevKitConsole();
void setDevKitConsole(bool enable, bool is_game_specific = false);

View File

@@ -127,6 +127,7 @@ static auto UserPaths = [] {
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
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");
if (notice_file.is_open()) {

View File

@@ -24,6 +24,7 @@ enum class PathType {
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
CustomTrophy, // Where custom files for trophies are stored.
CustomConfigs, // Where custom files for different games are stored.
HomeDir, // PS4 home directory
};
constexpr auto PORTABLE_DIR = "user";
@@ -42,6 +43,7 @@ constexpr auto PATCHES_DIR = "patches";
constexpr auto METADATA_DIR = "game_data";
constexpr auto CUSTOM_TROPHY = "custom_trophy";
constexpr auto CUSTOM_CONFIGS = "custom_configs";
constexpr auto HOME_DIR = "home";
// Filenames
constexpr auto LOG_FILE = "shad_log.txt";

View 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{};
}

View 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
};

View File

@@ -4,6 +4,7 @@
#include "common/config.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/process.h"
@@ -17,19 +18,19 @@ s32 PS4_SYSV_ABI sceKernelIsInSandbox() {
}
s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
return Config::isNeoModeConsole() &&
return EmulatorSettings::GetInstance()->IsNeo() &&
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
}
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
return Config::isNeoModeConsole();
return EmulatorSettings::GetInstance()->IsNeo();
}
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
// These hardcoded values are based on hardware observations.
// Different models of PS4/PS4 Pro likely return slightly different values.
LOG_DEBUG(Lib_Kernel, "called");
if (Config::isNeoModeConsole()) {
if (EmulatorSettings::GetInstance()->IsNeo()) {
return 0x740f30;
}
return 0x710f10;

112
src/core/user_manager.cpp Normal file
View 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
View 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;
};

View File

@@ -27,6 +27,7 @@
#include "common/singleton.h"
#include "core/debugger.h"
#include "core/devtools/widget/module_list.h"
#include "core/emulator_settings.h"
#include "core/file_format/psf.h"
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
@@ -180,7 +181,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
true);
// Initialize logging as soon as possible
if (!id.empty() && Config::getSeparateLogFilesEnabled()) {
if (!id.empty() && EmulatorSettings::GetInstance()->IsSeparateLoggingEnabled()) {
Common::Log::Initialize(id + ".log");
} else {
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, "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 isConnectedToNetwork: {}", Config::getIsConnectedToNetwork());
LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn());

View File

@@ -20,6 +20,7 @@
#ifdef _WIN32
#include <windows.h>
#endif
#include <core/emulator_settings.h>
int main(int argc, char* argv[]) {
#ifdef _WIN32
@@ -28,6 +29,8 @@ int main(int argc, char* argv[]) {
IPC::Instance().Init();
// 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);
Config::load(user_dir / "config.toml");