Merge branch 'fontfixes' into fontlib

This commit is contained in:
Valdis Bogdāns 2026-01-16 16:38:06 +02:00 committed by GitHub
commit d448ddc8ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 526 additions and 25 deletions

View File

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

View File

@ -239,6 +239,9 @@ void setSysModulesPath(const std::filesystem::path& path) {
}
std::filesystem::path getSysFontPath() {
if (sys_font_path.empty()) {
return Common::FS::GetUserPath(Common::FS::PathType::FontDir);
}
return sys_font_path;
}
@ -502,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();
}
@ -1088,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<string> section_order = {"General", "Input", "Audio", "GPU", "Vulkan",
"Debug", "Keys", "GUI", "Settings"};
@ -1401,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

View File

@ -4,8 +4,6 @@
#pragma once
#include <filesystem>
#include <optional>
#include <string_view>
#include <vector>
#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<std::filesystem::path> 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();

View File

@ -128,6 +128,9 @@ 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 / SYSFONTS_DIR);
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()) {
@ -144,6 +147,17 @@ static auto UserPaths = [] {
notice_file.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;
}();

View File

@ -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 system 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 SYSFONTS_DIR = "sys_fonts";
// Filenames
constexpr auto LOG_FILE = "shad_log.txt";

View File

@ -0,0 +1,293 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 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.string();
}
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
// --------------------
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<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 {
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<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,194 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 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"
// -------------------------------
// 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;
}};
}
// -------------------------------
// General settings
// -------------------------------
struct GeneralSettings {
Setting<std::filesystem::path> sys_fonts_dir;
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, sys_fonts_dir)
// -------------------------------
// Debug settings
// -------------------------------
struct DebugSettings {
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{};
}
};
//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings)
// -------------------------------
// Input settings
// -------------------------------
struct InputSettings {
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{};
}
};
//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings)
// -------------------------------
// Audio settings
// -------------------------------
struct AudioSettings {
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{};
}
};
//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AudioSettings)
// -------------------------------
// GPU settings
// -------------------------------
struct GPUSettings {
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{};
}
};
//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings)
// -------------------------------
// Vulkan settings
// -------------------------------
struct VulkanSettings {
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{};
}
};
//NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings)
// -------------------------------
// 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
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<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; \
}
#define SETTING_FORWARD_BOOL_READONLY(group, Name, field) \
auto Is##Name() const { \
return group.field.value; \
}
#undef SETTING_FORWARD
#undef SETTING_FORWARD_BOOL
};

View File

@ -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 {
@ -1559,7 +1560,7 @@ static std::optional<std::filesystem::path> 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");
@ -1992,7 +1993,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<const void*>(handle), configured.string());

View File

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

View File

@ -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,11 +529,7 @@ void Emulator::LoadSystemModules(const std::string& game_serial) {
{"libSceJson.sprx", nullptr},
{"libSceJson2.sprx", nullptr},
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
{"libSceCesCs.sprx", nullptr},
{"libSceAudiodec.sprx", nullptr},
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
{"libSceFreeTypeOt.sprx", nullptr}});
{"libSceCesCs.sprx", nullptr}});
std::vector<std::filesystem::path> found_modules;
const auto& sys_module_path = Config::getSysModulesPath();

View File

@ -22,6 +22,7 @@
#ifdef _WIN32
#include <windows.h>
#endif
#include <core/emulator_settings.h>
int main(int argc, char* argv[]) {
#ifdef _WIN32
@ -32,6 +33,10 @@ int main(int argc, char* argv[]) {
std::shared_ptr<EmulatorState> m_emu_state = std::make_shared<EmulatorState>();
EmulatorState::SetInstance(m_emu_state);
// Load configurations
std::shared_ptr<EmulatorSettings> emu_settings = std::make_shared<EmulatorSettings>();
EmulatorSettings::SetInstance(emu_settings);
emu_settings->Load();
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(user_dir / "config.toml");