From a0014a885346db4fef9e2694954ee9f612ed7d06 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 12 Jan 2026 18:24:19 +0200 Subject: [PATCH 1/7] create sys_fonts in users folder and instructions.txt of what to put inside --- src/common/path_util.cpp | 11 +++++++++++ src/common/path_util.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index b0cbb10cf..1cf80430a 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -128,6 +128,10 @@ static auto UserPaths = [] { create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY); create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS); create_path(PathType::CacheDir, user_dir / CACHE_DIR); + create_path(PathType::FontDir, user_dir / FONT_DIR); + // subdirectory for fonts + std::filesystem::create_directory(user_dir / FONT_DIR / "font"); + std::filesystem::create_directory(user_dir / FONT_DIR / "font2"); std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt"); if (notice_file.is_open()) { @@ -144,6 +148,13 @@ static auto UserPaths = [] { notice_file.close(); } + std::ofstream font_instructions(user_dir / FONT_DIR / "Instructions.txt"); + if (font_instructions.is_open()) { + font_instructions << "Place /preinst/common/font contents into font folder\n" + "Place /system/common/font2 contents into font2 folder\n"; + font_instructions.close(); + } + return paths; }(); diff --git a/src/common/path_util.h b/src/common/path_util.h index fd2c18baa..f5979e3b2 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -25,6 +25,7 @@ enum class PathType { CustomTrophy, // Where custom files for trophies are stored. CustomConfigs, // Where custom files for different games are stored. CacheDir, // Where pipeline and shader cache is stored. + FontDir // Where font files are stored. }; constexpr auto PORTABLE_DIR = "user"; @@ -44,6 +45,7 @@ constexpr auto METADATA_DIR = "game_data"; constexpr auto CUSTOM_TROPHY = "custom_trophy"; constexpr auto CUSTOM_CONFIGS = "custom_configs"; constexpr auto CACHE_DIR = "cache"; +constexpr auto FONT_DIR = "sys_fonts"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; From aba1d8242fcb1afc7e9d676d77493eab7751bcf6 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 12 Jan 2026 18:48:12 +0200 Subject: [PATCH 2/7] cut version of emulator settings to support custom sysfonts folder --- src/common/path_util.cpp | 8 +- src/common/path_util.h | 2 +- src/core/emulator_settings.cpp | 293 +++++++++++++++++++++++++++++++++ src/core/emulator_settings.h | 194 ++++++++++++++++++++++ 4 files changed, 492 insertions(+), 5 deletions(-) create mode 100644 src/core/emulator_settings.cpp create mode 100644 src/core/emulator_settings.h diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 1cf80430a..7137a2205 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -128,10 +128,10 @@ static auto UserPaths = [] { create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY); create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS); create_path(PathType::CacheDir, user_dir / CACHE_DIR); - create_path(PathType::FontDir, user_dir / FONT_DIR); + create_path(PathType::FontDir, user_dir / SYSFONTS_DIR); // subdirectory for fonts - std::filesystem::create_directory(user_dir / FONT_DIR / "font"); - std::filesystem::create_directory(user_dir / FONT_DIR / "font2"); + std::filesystem::create_directory(user_dir / SYSFONTS_DIR / "font"); + std::filesystem::create_directory(user_dir / SYSFONTS_DIR / "font2"); std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt"); if (notice_file.is_open()) { @@ -148,7 +148,7 @@ static auto UserPaths = [] { notice_file.close(); } - std::ofstream font_instructions(user_dir / FONT_DIR / "Instructions.txt"); + std::ofstream font_instructions(user_dir / SYSFONTS_DIR / "Instructions.txt"); if (font_instructions.is_open()) { font_instructions << "Place /preinst/common/font contents into font folder\n" "Place /system/common/font2 contents into font2 folder\n"; diff --git a/src/common/path_util.h b/src/common/path_util.h index f5979e3b2..00cf870e7 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -45,7 +45,7 @@ constexpr auto METADATA_DIR = "game_data"; constexpr auto CUSTOM_TROPHY = "custom_trophy"; constexpr auto CUSTOM_CONFIGS = "custom_configs"; constexpr auto CACHE_DIR = "cache"; -constexpr auto FONT_DIR = "sys_fonts"; +constexpr auto SYSFONTS_DIR = "sys_fonts"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp new file mode 100644 index 000000000..49d997c49 --- /dev/null +++ b/src/core/emulator_settings.cpp @@ -0,0 +1,293 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "emulator_settings.h" + +using json = nlohmann::json; + +std::shared_ptr EmulatorSettings::s_instance = nullptr; +std::mutex EmulatorSettings::s_mutex; + +namespace nlohmann { +template <> +struct adl_serializer { + static void to_json(json& j, const std::filesystem::path& p) { + j = p.string(); + } + static void from_json(const json& j, std::filesystem::path& p) { + p = j.get(); + } +}; +} // namespace nlohmann + +// -------------------- +// Print summary +// -------------------- +void EmulatorSettings::PrintChangedSummary(const std::vector& 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::GetInstance() { + std::lock_guard lock(s_mutex); + if (!s_instance) + s_instance = std::make_shared(); + return s_instance; +} + +void EmulatorSettings::SetInstance(std::shared_ptr instance) { + std::lock_guard lock(s_mutex); + s_instance = instance; +} + +// -------------------- +// General helpers +// -------------------- + +std::filesystem::path EmulatorSettings::GetSysFontsDir() { + if (m_general.sys_fonts_dir.value.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::FontDir); + } + return m_general.sys_fonts_dir.value; +} + +void EmulatorSettings::SetSysFontsDir(const std::filesystem::path& dir) { + m_general.sys_fonts_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(); // convert back + } + /* if (gj.contains("Debug")) { + json current = m_debug; + current.update(gj.at("Debug")); + m_debug = current.get(); + } + if (gj.contains("Input")) { + json current = m_input; + current.update(gj.at("Input")); + m_input = current.get(); + } + if (gj.contains("Audio")) { + json current = m_audio; + current.update(gj.at("Audio")); + m_audio = current.get(); + } + if (gj.contains("GPU")) { + json current = m_gpu; + current.update(gj.at("GPU")); + m_gpu = current.get(); + } + if (gj.contains("Vulkan")) { + json current = m_vulkan; + current.update(gj.at("Vulkan")); + m_vulkan = current.get(); + } + if (gj.contains("Users")) + m_userManager.GetUsers() = gj.at("Users").get();*/ + } else { + SetDefaultValues(); + // ensure a default user exists + /* if (m_userManager.GetUsers().user.empty()) + m_userManager.GetUsers().user = m_userManager.CreateDefaultUser();*/ + Save(); + } + + // 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 changed; + + if (gj.contains("General")) { + ApplyGroupOverrides(m_general, gj.at("General"), changed); + } + if (gj.contains("Debug")) { + ApplyGroupOverrides(m_debug, gj.at("Debug"), changed); + } + if (gj.contains("Input")) { + ApplyGroupOverrides(m_input, gj.at("Input"), changed); + } + if (gj.contains("Audio")) { + ApplyGroupOverrides(m_audio, gj.at("Audio"), changed); + } + if (gj.contains("GPU")) { + ApplyGroupOverrides(m_gpu, gj.at("GPU"), changed); + } + if (gj.contains("Vulkan")) { + ApplyGroupOverrides(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{}; +} diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h new file mode 100644 index 000000000..73a659a32 --- /dev/null +++ b/src/core/emulator_settings.h @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "common/types.h" + +// ------------------------------- +// Generic Setting wrapper +// ------------------------------- +template +struct Setting { + T value{}; +}; + +template +void to_json(nlohmann::json& j, const Setting& s) { + j = s.value; +} + +template +void from_json(const nlohmann::json& j, Setting& s) { + s.value = j.get(); +} + +// ------------------------------- +// Helper to describe a per-field override action +// ------------------------------- +struct OverrideItem { + const char* key; + // apply(basePtrToStruct, jsonEntry, changedFields) + std::function&)> apply; +}; + +// Helper factory: create an OverrideItem binding a pointer-to-member +template +inline OverrideItem make_override(const char* key, Setting Struct::* member) { + return OverrideItem{key, [member, key](void* base, const nlohmann::json& entry, + std::vector& changed) { + if (!entry.is_object()) + return; + + Struct* obj = reinterpret_cast(base); + Setting& dst = obj->*member; + + Setting tmp = entry.get>(); + + 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; + }}; +} + +// ------------------------------- +// General settings +// ------------------------------- +struct GeneralSettings { + Setting sys_fonts_dir; + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, sys_fonts_dir) + +// ------------------------------- +// Debug settings +// ------------------------------- +struct DebugSettings { + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; +//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings) + +// ------------------------------- +// Input settings +// ------------------------------- + +struct InputSettings { + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; +//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings) + +// ------------------------------- +// Audio settings +// ------------------------------- +struct AudioSettings { + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; + +//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AudioSettings) + +// ------------------------------- +// GPU settings +// ------------------------------- +struct GPUSettings { + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; +//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings) +// ------------------------------- +// Vulkan settings +// ------------------------------- +struct VulkanSettings { + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; +//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings) + +// ------------------------------- +// Main manager +// ------------------------------- +class EmulatorSettings { +public: + EmulatorSettings(); + ~EmulatorSettings(); + + static std::shared_ptr GetInstance(); + static void SetInstance(std::shared_ptr instance); + + bool Save(const std::string& serial = "") const; + bool Load(const std::string& serial = ""); + void SetDefaultValues(); + + // general accessors + std::filesystem::path GetSysFontsDir(); + void SetSysFontsDir(const std::filesystem::path& dir); + +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 s_instance; + static std::mutex s_mutex; + + // Generic helper that applies override descriptors for a specific group + template + void ApplyGroupOverrides(Group& group, const nlohmann::json& groupJson, + std::vector& 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& 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; \ + } +#define SETTING_FORWARD_BOOL_READONLY(group, Name, field) \ + auto Is##Name() const { \ + return group.field.value; \ + } + +#undef SETTING_FORWARD +#undef SETTING_FORWARD_BOOL +}; From b97cd2753c567aa92dfec227b4181398eed25772 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 12 Jan 2026 18:53:22 +0200 Subject: [PATCH 3/7] load settings --- src/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index aa3f4de45..080fc94a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ #ifdef _WIN32 #include #endif +#include int main(int argc, char* argv[]) { #ifdef _WIN32 @@ -32,6 +33,10 @@ int main(int argc, char* argv[]) { std::shared_ptr m_emu_state = std::make_shared(); EmulatorState::SetInstance(m_emu_state); // Load configurations + std::shared_ptr emu_settings = std::make_shared(); + EmulatorSettings::SetInstance(emu_settings); + emu_settings->Load(); + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); From 877a60fe3b79ac2984a3a02c5bfce9144db40cc2 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 12 Jan 2026 18:54:53 +0200 Subject: [PATCH 4/7] cmakelist --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38341e81e..943609923 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later # Version 3.24 needed for FetchContent OVERRIDE_FIND_PACKAGE @@ -848,6 +848,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/tls.h src/core/emulator_state.cpp src/core/emulator_state.h + src/core/emulator_settings.cpp + src/core/emulator_settings.h ) if (ARCHITECTURE STREQUAL "x86_64") From 11bdfd3a98f285da2d806d249e7df7499747e315 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 12 Jan 2026 19:18:54 +0200 Subject: [PATCH 5/7] using new saving system but i can't understand the fallback mechanism --- src/common/config.cpp | 101 +--------------------- src/common/config.h | 11 --- src/core/libraries/font/font_internal.cpp | 7 +- 3 files changed, 5 insertions(+), 114 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 34e5524cf..eac463d0a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -4,9 +4,8 @@ #include #include #include -#include -#include #include +#include // for wstring support #include #include "common/assert.h" @@ -145,9 +144,6 @@ static ConfigEntry isSideTrophy("right"); static ConfigEntry isConnectedToNetwork(false); static bool enableDiscordRPC = false; static std::filesystem::path sys_modules_path = {}; -static std::filesystem::path sys_font_path = {}; -static std::string sys_font_fallback_name = {}; -static std::unordered_map system_font_overrides; // Input static ConfigEntry cursorState(HideCursorState::Idle); @@ -238,44 +234,6 @@ void setSysModulesPath(const std::filesystem::path& path) { sys_modules_path = path; } -std::filesystem::path getSysFontPath() { - return sys_font_path; -} - -void setSysFontPath(const std::filesystem::path& path) { - sys_font_path = path; -} - -std::optional getSystemFontOverride(std::string_view key) { - if (key.empty()) { - return std::nullopt; - } - auto it = system_font_overrides.find(std::string(key)); - if (it == system_font_overrides.end()) { - return std::nullopt; - } - return it->second; -} - -std::string getSystemFontFallbackName() { - return sys_font_fallback_name; -} - -void setSystemFontFallbackName(const std::string& name) { - sys_font_fallback_name = name; -} - -void setSystemFontOverride(std::string_view key, const std::filesystem::path& path) { - if (key.empty()) { - return; - } - system_font_overrides[std::string(key)] = path; -} - -void clearSystemFontOverrides() { - system_font_overrides.clear(); -} - int getVolumeSlider() { return volumeSlider.get(); } @@ -904,10 +862,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { return; } - if (!is_game_specific) { - system_font_overrides.clear(); - } - if (data.contains("General")) { const toml::value& general = data.at("General"); @@ -931,43 +885,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path); - // Accept alias without trailing 's' - sys_modules_path = toml::find_fs_path_or(general, "sysModulePath", sys_modules_path); - // Prefer 'sysFontPath'; accept 'SysFontPath' for compatibility - sys_font_path = toml::find_fs_path_or(general, "sysFontPath", sys_font_path); - sys_font_path = toml::find_fs_path_or(general, "SysFontPath", sys_font_path); - } - - if (data.contains("SystemFonts")) { - const toml::value& fonts = data.at("SystemFonts"); - if (fonts.is_table()) { - // Read fallback (lowercase preferred), accept 'Fallback'/'FallbackFontName' for compat - if (fonts.contains("fallback")) { - const auto& v = fonts.at("fallback"); - if (v.is_string()) { - sys_font_fallback_name = toml::get(v); - } - } else if (fonts.contains("Fallback")) { - const auto& v = fonts.at("Fallback"); - if (v.is_string()) { - sys_font_fallback_name = toml::get(v); - } - } else if (fonts.contains("FallbackFontName")) { - const auto& v = fonts.at("FallbackFontName"); - if (v.is_string()) { - sys_font_fallback_name = toml::get(v); - } - } - for (const auto& [name, value] : fonts.as_table()) { - if (name == "fallback" || name == "Fallback" || name == "FallbackFontName") { - continue; - } - if (value.is_string()) { - system_font_overrides[name] = - std::filesystem::path(toml::get(value)); - } - } - } } if (data.contains("Input")) { @@ -1241,22 +1158,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { // Non game-specific entries data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data}; - // Save using 'sysFontPath' to match style - data["General"]["sysFontPath"] = string{fmt::UTF(sys_font_path.u8string()).data}; - { - toml::table fonts_table; - if (!sys_font_fallback_name.empty()) { - fonts_table["fallback"] = sys_font_fallback_name; - } - for (const auto& [name, path_override] : system_font_overrides) { - fonts_table[name] = string{fmt::UTF(path_override.u8string()).data}; - } - if (!fonts_table.empty()) { - data["SystemFonts"] = fonts_table; - } else if (data.is_table()) { - data.as_table().erase("SystemFonts"); - } - } data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; diff --git a/src/common/config.h b/src/common/config.h index 9903e6e35..2a95e6cf0 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -4,8 +4,6 @@ #pragma once #include -#include -#include #include #include "types.h" @@ -155,15 +153,6 @@ void setConnectedToNetwork(bool enable, bool is_game_specific = false); void setUserName(const std::string& name, bool is_game_specific = false); std::filesystem::path getSysModulesPath(); void setSysModulesPath(const std::filesystem::path& path); -std::filesystem::path getSysFontPath(); -void setSysFontPath(const std::filesystem::path& path); -std::optional getSystemFontOverride(std::string_view key); -std::string getSystemFontFallbackName(); -void setSystemFontFallbackName(const std::string& name); -void setSystemFontOverride(std::string_view key, const std::filesystem::path& path); -void clearSystemFontOverrides(); -bool getLoadAutoPatches(); -void setLoadAutoPatches(bool enable); enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad }; int getUsbDeviceBackend(); diff --git a/src/core/libraries/font/font_internal.cpp b/src/core/libraries/font/font_internal.cpp index ace9cdca0..84afc8b78 100644 --- a/src/core/libraries/font/font_internal.cpp +++ b/src/core/libraries/font/font_internal.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "font_internal.h" @@ -10,6 +10,7 @@ #include FT_OUTLINE_H #include FT_TRUETYPE_TABLES_H +#include "core/emulator_settings.h" #include "core/libraries/font/fontft_internal.h" namespace Libraries::Font::Internal { @@ -1539,7 +1540,7 @@ static std::optional FindChildDirContainingFile( } std::filesystem::path GetSysFontBaseDir() { - std::filesystem::path base = Config::getSysFontPath(); + std::filesystem::path base = EmulatorSettings::GetInstance()->GetSysFontsDir(); std::error_code ec; if (base.empty()) { LOG_ERROR(Lib_Font, "SystemFonts: SysFontPath not set"); @@ -1972,7 +1973,7 @@ std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHan } if (!st.system_requested) { st.system_requested = true; - const auto configured = Config::getSysFontPath(); + const auto configured = EmulatorSettings::GetInstance()->GetSysFontsDir(); return fmt::format("SystemFace: handle={} requested internal font but sysFontPath ('{}') " "could not be loaded", static_cast(handle), configured.string()); From a30014fbfcb766fed7830314a2496c7d0bd39842 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 12 Jan 2026 19:38:51 +0200 Subject: [PATCH 6/7] removed lle versions --- src/core/libraries/libs.cpp | 4 ++++ src/emulator.cpp | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 7f679e7c2..63e8d7467 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -12,6 +12,8 @@ #include "core/libraries/companion/companion_httpd.h" #include "core/libraries/companion/companion_util.h" #include "core/libraries/disc_map/disc_map.h" +#include "core/libraries/font/font.h" +#include "core/libraries/font/fontft.h" #include "core/libraries/game_live_streaming/gamelivestreaming.h" #include "core/libraries/gnmdriver/gnmdriver.h" #include "core/libraries/hmd/hmd.h" @@ -142,6 +144,8 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Voice::RegisterLib(sym); Libraries::Rudp::RegisterLib(sym); Libraries::VrTracker::RegisterLib(sym); + Libraries::Font::RegisterlibSceFont(sym); + Libraries::FontFt::RegisterlibSceFontFt(sym); // Loading libSceSsl is locked behind a title workaround that currently applies to nothing. // Libraries::Ssl::RegisterLib(sym); diff --git a/src/emulator.cpp b/src/emulator.cpp index 44f8b0e72..1403b1254 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -32,8 +32,6 @@ #include "core/file_format/trp.h" #include "core/file_sys/fs.h" #include "core/libraries/disc_map/disc_map.h" -#include "core/libraries/font/font.h" -#include "core/libraries/font/fontft.h" #include "core/libraries/jpeg/jpegenc.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" @@ -531,10 +529,7 @@ void Emulator::LoadSystemModules(const std::string& game_serial) { {"libSceJson.sprx", nullptr}, {"libSceJson2.sprx", nullptr}, {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib}, - {"libSceCesCs.sprx", nullptr}, - {"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont}, - {"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt}, - {"libSceFreeTypeOt.sprx", nullptr}}); + {"libSceCesCs.sprx", nullptr}}); std::vector found_modules; const auto& sys_module_path = Config::getSysModulesPath(); From 296e4385c4a250a36f91cfdbc45fceea425e43db Mon Sep 17 00:00:00 2001 From: w1naenator Date: Fri, 16 Jan 2026 16:25:49 +0200 Subject: [PATCH 7/7] Add system font path and override config support Introduce new config options for system font directory, fallback font name, and per-font overrides. Update config load/save logic to handle a [SystemFonts] TOML section, supporting both fallback and individual font overrides. Improve user instructions for custom font setup and clarify related code comments. These changes enhance flexibility and user experience for system font configuration. --- src/common/config.cpp | 110 +++++++++++++++++++++++++++++++++++++-- src/common/path_util.cpp | 15 +++--- src/common/path_util.h | 2 +- 3 files changed, 116 insertions(+), 11 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index eac463d0a..5159c9cf3 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -4,8 +4,9 @@ #include #include #include +#include +#include #include -#include // for wstring support #include #include "common/assert.h" @@ -144,6 +145,9 @@ static ConfigEntry isSideTrophy("right"); static ConfigEntry isConnectedToNetwork(false); static bool enableDiscordRPC = false; static std::filesystem::path sys_modules_path = {}; +static std::filesystem::path sys_font_path = {}; +static std::string sys_font_fallback_name = {}; +static std::unordered_map system_font_overrides; // Input static ConfigEntry cursorState(HideCursorState::Idle); @@ -234,6 +238,47 @@ void setSysModulesPath(const std::filesystem::path& path) { sys_modules_path = path; } +std::filesystem::path getSysFontPath() { + if (sys_font_path.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::FontDir); + } + return sys_font_path; +} + +void setSysFontPath(const std::filesystem::path& path) { + sys_font_path = path; +} + +std::optional getSystemFontOverride(std::string_view key) { + if (key.empty()) { + return std::nullopt; + } + auto it = system_font_overrides.find(std::string(key)); + if (it == system_font_overrides.end()) { + return std::nullopt; + } + return it->second; +} + +std::string getSystemFontFallbackName() { + return sys_font_fallback_name; +} + +void setSystemFontFallbackName(const std::string& name) { + sys_font_fallback_name = name; +} + +void setSystemFontOverride(std::string_view key, const std::filesystem::path& path) { + if (key.empty()) { + return; + } + system_font_overrides[std::string(key)] = path; +} + +void clearSystemFontOverrides() { + system_font_overrides.clear(); +} + int getVolumeSlider() { return volumeSlider.get(); } @@ -460,7 +505,7 @@ void setShowFpsCounter(bool enable, bool is_game_specific) { showFpsCounter.set(enable, is_game_specific); } -bool isLoggingEnabled() { +static bool isLoggingEnabled() { return logEnabled.get(); } @@ -862,6 +907,10 @@ void load(const std::filesystem::path& path, bool is_game_specific) { return; } + if (!is_game_specific) { + system_font_overrides.clear(); + } + if (data.contains("General")) { const toml::value& general = data.at("General"); @@ -885,6 +934,43 @@ void load(const std::filesystem::path& path, bool is_game_specific) { isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path); + // Accept alias without trailing 's' + sys_modules_path = toml::find_fs_path_or(general, "sysModulePath", sys_modules_path); + // Prefer 'sysFontPath'; accept 'SysFontPath' for compatibility + sys_font_path = toml::find_fs_path_or(general, "sysFontPath", sys_font_path); + sys_font_path = toml::find_fs_path_or(general, "SysFontPath", sys_font_path); + } + + if (data.contains("SystemFonts")) { + const toml::value& fonts = data.at("SystemFonts"); + if (fonts.is_table()) { + // Read fallback (lowercase preferred), accept 'Fallback'/'FallbackFontName' for compat + if (fonts.contains("fallback")) { + const auto& v = fonts.at("fallback"); + if (v.is_string()) { + sys_font_fallback_name = toml::get(v); + } + } else if (fonts.contains("Fallback")) { + const auto& v = fonts.at("Fallback"); + if (v.is_string()) { + sys_font_fallback_name = toml::get(v); + } + } else if (fonts.contains("FallbackFontName")) { + const auto& v = fonts.at("FallbackFontName"); + if (v.is_string()) { + sys_font_fallback_name = toml::get(v); + } + } + for (const auto& [name, value] : fonts.as_table()) { + if (name == "fallback" || name == "Fallback" || name == "FallbackFontName") { + continue; + } + if (value.is_string()) { + system_font_overrides[name] = + std::filesystem::path(toml::get(value)); + } + } + } } if (data.contains("Input")) { @@ -1005,7 +1091,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { } } -void sortTomlSections(toml::ordered_value& data) { +static void sortTomlSections(toml::ordered_value& data) { toml::ordered_value ordered_data; std::vector section_order = {"General", "Input", "Audio", "GPU", "Vulkan", "Debug", "Keys", "GUI", "Settings"}; @@ -1158,6 +1244,22 @@ void save(const std::filesystem::path& path, bool is_game_specific) { // Non game-specific entries data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data}; + // Save using 'sysFontPath' to match style + data["General"]["sysFontPath"] = string{fmt::UTF(sys_font_path.u8string()).data}; + { + toml::table fonts_table; + if (!sys_font_fallback_name.empty()) { + fonts_table["fallback"] = sys_font_fallback_name; + } + for (const auto& [name, path_override] : system_font_overrides) { + fonts_table[name] = string{fmt::UTF(path_override.u8string()).data}; + } + if (!fonts_table.empty()) { + data["SystemFonts"] = fonts_table; + } else if (data.is_table()) { + data.as_table().erase("SystemFonts"); + } + } data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; @@ -1302,7 +1404,7 @@ hotkey_quit = lctrl, lshift, end )"; } -constexpr std::string_view GetDefaultInputConfig() { +static constexpr std::string_view GetDefaultInputConfig() { return R"(#Feeling lost? Check out the Help section! # Keyboard bindings diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 7137a2205..0415c1a26 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -129,7 +129,6 @@ static auto UserPaths = [] { create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS); create_path(PathType::CacheDir, user_dir / CACHE_DIR); create_path(PathType::FontDir, user_dir / SYSFONTS_DIR); - // subdirectory for fonts std::filesystem::create_directory(user_dir / SYSFONTS_DIR / "font"); std::filesystem::create_directory(user_dir / SYSFONTS_DIR / "font2"); @@ -148,11 +147,15 @@ static auto UserPaths = [] { notice_file.close(); } - std::ofstream font_instructions(user_dir / SYSFONTS_DIR / "Instructions.txt"); - if (font_instructions.is_open()) { - font_instructions << "Place /preinst/common/font contents into font folder\n" - "Place /system/common/font2 contents into font2 folder\n"; - font_instructions.close(); + const auto instructions_path = user_dir / SYSFONTS_DIR / "Instructions.txt"; + std::error_code ec; + if (!std::filesystem::exists(instructions_path, ec)) { + std::ofstream font_instructions(instructions_path); + if (font_instructions.is_open()) { + font_instructions << "Place system font files (.otf/.ttf) into the 'font' and 'font2' " + "folders.\n"; + font_instructions.close(); + } } return paths; diff --git a/src/common/path_util.h b/src/common/path_util.h index 00cf870e7..cf4611903 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -25,7 +25,7 @@ enum class PathType { CustomTrophy, // Where custom files for trophies are stored. CustomConfigs, // Where custom files for different games are stored. CacheDir, // Where pipeline and shader cache is stored. - FontDir // Where font files are stored. + FontDir, // Where system font files are stored. }; constexpr auto PORTABLE_DIR = "user";