diff --git a/.gitmodules b/.gitmodules index 55ae48ea3cc..e717b4cb51e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -133,3 +133,8 @@ [submodule "externals/openal-soft"] path = externals/openal-soft url = https://github.com/shadexternals/openal-soft.git +[submodule "externals/spdlog"] + path = externals/spdlog + url = https://github.com/gabime/spdlog + shallow = true + branch = v1.x diff --git a/CMakeLists.txt b/CMakeLists.txt index 06f8cb6ff8e..37f50b63b29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -698,16 +698,8 @@ set(DEV_TOOLS src/core/devtools/layer.cpp src/core/devtools/widget/text_editor.h ) -set(COMMON src/common/logging/backend.cpp - src/common/logging/backend.h - src/common/logging/filter.cpp - src/common/logging/filter.h - src/common/logging/formatter.h - src/common/logging/log_entry.h +set(COMMON src/common/logging/log.cpp src/common/logging/log.h - src/common/logging/text_formatter.cpp - src/common/logging/text_formatter.h - src/common/logging/types.h src/common/aes.h src/common/alignment.h src/common/arch.h @@ -1134,7 +1126,7 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib spdlog::spdlog) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/documents/Debugging/Debugging.md b/documents/Debugging/Debugging.md index 013ca15fb82..096b71e03d7 100644 --- a/documents/Debugging/Debugging.md +++ b/documents/Debugging/Debugging.md @@ -58,30 +58,39 @@ You can configure the emulator by editing the `config.toml` file found in the `u
Some configuration entries worth changing -- `[General]` - - - `logType`: Configures logging synchronization (`sync`/`async`) - - By default, the emulator logs messages asynchronously for better performance. Some log messages may end up being received out-of-order. - - It can be beneficial to set this to `sync` in order for the log to accurately maintain message order, at the cost of performance. - - When communicating about issues with games and the log messages aren't clear due to potentially confusing order, set this to `sync` and send that log as well. - - `logFilter`: Sets the logging category for various logging classes. - - Format: `: ...` - - Multiple classes can be set by separating them with a space. (example: `Render:Warning Debug:Critical Lib.Pad:Error`) +- `[Log]` + - `sync`: Log synchronously (`true`/`false`) + - By default `true`, the emulator logs messages synchronously to respect the order. + - It can be beneficial to set this to `false` for better performance. + - When communicating about issues with games and the log messages aren't clear due to potentially confusing order, set this to `true` and send that log instead. + - `filter`: Sets the logging category for various logging classes. + - Format: `=,...` + - Multiple classes can be set by separating them with a comma. (example: `Render=warning,Debug=critical,Lib.Pad=error`) - Sub-classes can be specified in the same format as seen in the console/log (such as `Core.Linker`). - - All classes and sub-classes can be set by specifying a `*` symbol. (example: `Kernel.*:Critical`) - - Valid log levels: `Trace, Debug, Info, Warning, Error, Critical` - in this order, setting a level silences all levels preceding it and logs every level after it. + - Valid log levels: `trace, debug, info, warning, error, critical` - in this order, setting a level silences all levels preceding it and logs every level after it. - Examples: - - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad:Critical` to only log critical-level messages. - - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Critical Render.Vulkan:Info` - - `isIdenticalLogGrouped`: Group same logs in one line with a counter (`true`/`false`) - - By default, the emulator will not rewrite the same line, and instead add a counter. + - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad=critical` to only log critical-level messages. + - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `off,Render.Vulkan=info` (if you want critical at least `critical,Render.Vulkan=info`) + - this option is evaluated after the environment variable `SPDLOG_LEVEL` but before the command line argument (their syntax are `SPDLOG_LEVEL="..."`, `"SPDLOG_LEVEL=..."` respectively) + - `skipDuplicate`: Skip same lines with a `Skipped N duplicate messages..` message (`true`/`false`) + - By default, the emulator will skip same lines for `maxSkipDuration` milliseconds. + - `append`: Append log to the existing file (`true`/`false`) + - By default, the emulator will overwrite the log file. (it can also be by-passed with CLI `--log-append`) + - `separate`: Write log to `log/{GAME ID}.log` instead of `log/shad_log.txt` (`true`/`false`) + - By default, the emulator use `log/shad_log.txt`. + - `maxSkipDuration`: Amount of time in which identical lines will not be logged (milliseconds). + - By default, 5'000 milliseconds. + - `sizeLimit`: Size limit for log files (bytes). + - By default, 100 MB. + - `type`: Choose between `wincolor` (WriteConsole*) and `msvc` (OutputDebugString*) - only for Windows. + - By default, `wincolor`. - - `Fullscreen`: Display the game in a full screen borderless window. - `[GPU]` - `dumpShaders`: Dump shaders that are loaded by the emulator. Dump path: `../user/shader/dumps` - `nullGpu`: Disables rendering. - `screenWidth` and `screenHeight`: Configures the game window width and height. + - `Fullscreen`: Display the game in a full screen borderless window. - `[Vulkan]` - `validation`-related settings: Use when debugging Vulkan. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 41a0f71c71d..93fbb7017e3 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -317,3 +317,8 @@ endif() add_library(Cpp_Httplib INTERFACE) target_include_directories(Cpp_Httplib INTERFACE cpp-httplib/) +# spdlog +set(SPDLOG_NO_EXCEPTIONS ON) +set(SPDLOG_FMT_EXTERNAL ON) +set(SPDLOG_DISABLE_DEFAULT_LOGGER ON) +add_subdirectory(spdlog EXCLUDE_FROM_ALL SYSTEM) diff --git a/externals/spdlog b/externals/spdlog new file mode 160000 index 00000000000..c49c7cf9609 --- /dev/null +++ b/externals/spdlog @@ -0,0 +1 @@ +Subproject commit c49c7cf96093d662826a76608d57be4611751c0e diff --git a/src/common/assert.cpp b/src/common/assert.cpp index be0feb71dac..c891b1ed011 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -3,7 +3,7 @@ #include "common/arch.h" #include "common/assert.h" -#include "common/logging/backend.h" +#include "common/logging/log.h" #if defined(ARCH_X86_64) #define Crash() __asm__ __volatile__("int $3") @@ -14,14 +14,12 @@ #endif void assert_fail_impl() { - Common::Log::Stop(); - std::fflush(stdout); + Common::Log::Shutdown(); Crash(); } [[noreturn]] void unreachable_impl() { - Common::Log::Stop(); - std::fflush(stdout); + Common::Log::Shutdown(); Crash(); throw std::runtime_error("Unreachable code"); } diff --git a/src/common/config.cpp b/src/common/config.cpp index 79d3f799f37..c5dedc55b17 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -11,7 +11,6 @@ #include "common/assert.h" #include "common/config.h" -#include "common/logging/formatter.h" #include "common/path_util.h" #include "common/scm_rev.h" @@ -55,6 +54,10 @@ std::optional get_optional(const toml::value& v, const std::string& key) { if (it->second.is_integer()) { return static_cast(toml::get(it->second)); } + } else if constexpr (std::is_same_v) { + if (it->second.is_integer()) { + return static_cast(toml::get(it->second)); + } } else if constexpr (std::is_same_v) { if (it->second.is_floating()) { return toml::get(it->second); @@ -139,9 +142,6 @@ static ConfigEntry extraDmemInMbytes(0); static ConfigEntry isPSNSignedIn(false); static ConfigEntry isTrophyPopupDisabled(false); static ConfigEntry trophyNotificationDuration(6.0); -static ConfigEntry logFilter(""); -static ConfigEntry logType("sync"); -static ConfigEntry isIdenticalLogGrouped(true); static ConfigEntry userName("shadPS4"); static ConfigEntry isShowSplash(false); static ConfigEntry isSideTrophy("right"); @@ -150,6 +150,19 @@ static bool enableDiscordRPC = false; static std::filesystem::path sys_modules_path = {}; static std::filesystem::path fonts_path = {}; +// Log +static ConfigEntry logAppend(false); +static ConfigEntry logEnable(true); +static ConfigEntry logFilter(""); +static ConfigEntry logMaxSkipDuration(5'000); +static ConfigEntry logSeparate(false); +static ConfigEntry logSizeLimit(100_MB); +static ConfigEntry logSkipDuplicate(true); +static ConfigEntry logSync(true); +#ifdef _WIN32 +static ConfigEntry logType("wincolor"); +#endif + // Input static ConfigEntry cursorState(HideCursorState::Idle); static ConfigEntry cursorHideTimeout(5); // 5 seconds (default) @@ -202,9 +215,7 @@ static ConfigEntry pipelineCacheArchive(false); // Debug static ConfigEntry isDebugDump(false); static ConfigEntry isShaderDebug(false); -static ConfigEntry isSeparateLogFilesEnabled(false); static ConfigEntry showFpsCounter(false); -static ConfigEntry logEnabled(true); // GUI static std::vector settings_install_dirs = {}; @@ -219,7 +230,7 @@ ConfigEntry m_language(1); // english static ConfigEntry usbDeviceBackend(UsbBackendType::Real); // Keys -static string trophyKey = ""; +static ConfigEntry trophyKey{}; // Config version, used to determine if a user's config file is outdated. static string config_version = Common::g_scm_rev; @@ -278,7 +289,7 @@ int* GetControllerCustomColor() { } bool getLoggingEnabled() { - return logEnabled.get(); + return logEnable.get(); } void SetControllerCustomColor(int r, int b, int g) { @@ -288,7 +299,7 @@ void SetControllerCustomColor(int r, int b, int g) { } string getTrophyKey() { - return trophyKey; + return trophyKey.get(); } void setTrophyKey(string key) { @@ -392,12 +403,30 @@ string getLogFilter() { return logFilter.get(); } +bool isLogSync() { + return logSync.get(); +} + +#ifdef _WIN32 string getLogType() { return logType.get(); } +#endif -bool groupIdenticalLogs() { - return isIdenticalLogGrouped.get(); +bool getLogSkipDuplicate() { + return logSkipDuplicate.get(); +} + +bool isLogAppend() { + return logAppend.get(); +} + +u32 getMaxSkipDuration() { + return logMaxSkipDuration.get(); +} + +unsigned long long getLogSizeLimit() { + return logSizeLimit.get(); } string getUserName() { @@ -481,7 +510,7 @@ void setShowFpsCounter(bool enable, bool is_game_specific) { } bool isLoggingEnabled() { - return logEnabled.get(); + return logEnable.get(); } u32 vblankFreq() { @@ -564,7 +593,7 @@ void setDebugDump(bool enable, bool is_game_specific) { } void setLoggingEnabled(bool enable, bool is_game_specific) { - logEnabled.set(enable, is_game_specific); + logEnable.set(enable, is_game_specific); } void setCollectShaderForDebug(bool enable, bool is_game_specific) { @@ -695,12 +724,30 @@ void setDevKitConsole(bool enable, bool is_game_specific) { isDevKit.set(enable, is_game_specific); } +void setLogSync(bool sync, bool is_game_specific) { + logSync.set(sync, is_game_specific); +} + +#ifdef _WIN32 void setLogType(const string& type, bool is_game_specific) { logType.set(type, is_game_specific); } +#endif -void setIdenticalLogGrouped(bool enable, bool is_game_specific) { - isIdenticalLogGrouped.set(enable, is_game_specific); +void setLogSkipDuplicate(bool enable, bool is_game_specific) { + logSkipDuplicate.set(enable, is_game_specific); +} + +void setMaxSkipDuration(u32 duration, bool is_game_specific) { + logMaxSkipDuration.set(duration, is_game_specific); +} + +void setLogSizeLimit(unsigned long long size, bool is_game_specific) { + logSizeLimit.set(size, is_game_specific); +} + +void setLogAppend(bool enable, bool is_game_specific) { + logAppend.set(enable, is_game_specific); } void setLogFilter(const string& type, bool is_game_specific) { @@ -708,7 +755,7 @@ void setLogFilter(const string& type, bool is_game_specific) { } void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) { - isSeparateLogFilesEnabled.set(enabled, is_game_specific); + logSeparate.set(enabled, is_game_specific); } void setUserName(const string& name, bool is_game_specific) { @@ -805,7 +852,7 @@ u32 GetLanguage() { } bool getSeparateLogFilesEnabled() { - return isSeparateLogFilesEnabled.get(); + return logSeparate.get(); } bool getPSNSignedIn() { @@ -880,7 +927,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs.open(path, std::ios_base::binary); - data = toml::parse(ifs, string{fmt::UTF(path.filename().u8string()).data}); + data = toml::parse(ifs, path.filename().string()); } catch (std::exception& ex) { fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); return; @@ -900,9 +947,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { trophyNotificationDuration.setFromToml(general, "trophyNotificationDuration", is_game_specific); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); - logFilter.setFromToml(general, "logFilter", is_game_specific); - logType.setFromToml(general, "logType", is_game_specific); - isIdenticalLogGrouped.setFromToml(general, "isIdenticalLogGrouped", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); isShowSplash.setFromToml(general, "showSplash", is_game_specific); isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific); @@ -913,6 +957,22 @@ void load(const std::filesystem::path& path, bool is_game_specific) { fonts_path = toml::find_fs_path_or(general, "fontsPath", fonts_path); } + if (data.contains("Log")) { + const toml::value& log = data.at("Log"); + + logFilter.setFromToml(log, "filter", is_game_specific); + logSkipDuplicate.setFromToml(log, "skipDuplicate", is_game_specific); + logSync.setFromToml(log, "sync", is_game_specific); +#ifdef _WIN32 + logType.setFromToml(log, "type", is_game_specific); +#endif + logAppend.setFromToml(log, "append", is_game_specific); + logSeparate.setFromToml(log, "separate", is_game_specific); + logEnable.setFromToml(log, "enable", is_game_specific); + logMaxSkipDuration.setFromToml(log, "maxSkipDuration", is_game_specific); + logSizeLimit.setFromToml(log, "sizeLimit", is_game_specific); + } + if (data.contains("Input")) { const toml::value& input = data.at("Input"); @@ -979,10 +1039,8 @@ 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); showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific); - logEnabled.setFromToml(debug, "logEnabled", is_game_specific); current_version = toml::find_or(debug, "ConfigVersion", current_version); } @@ -1022,7 +1080,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { if (data.contains("Keys")) { const toml::value& keys = data.at("Keys"); - trophyKey = toml::find_or(keys, "TrophyKey", trophyKey); + trophyKey = toml::find_or(keys, "TrophyKey", trophyKey.get()); } // Run save after loading to generate any missing fields with default values. @@ -1033,8 +1091,9 @@ void load(const std::filesystem::path& path, bool is_game_specific) { void sortTomlSections(toml::ordered_value& data) { toml::ordered_value ordered_data; - std::vector section_order = {"General", "Input", "Audio", "GPU", "Vulkan", - "Debug", "Keys", "GUI", "Settings"}; + std::vector section_order = {"General", "Log", "Input", "Audio", "GPU", + "Vulkan", "Debug", "Keys", "GUI", "Settings", + "keys"}; for (const auto& section : section_order) { if (data.contains(section)) { @@ -1071,8 +1130,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs.open(path, std::ios_base::binary); - data = toml::parse( - ifs, string{fmt::UTF(path.filename().u8string()).data}); + data = toml::parse(ifs, path.filename().string()); } catch (const std::exception& ex) { fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); return; @@ -1081,7 +1139,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { if (error) { fmt::print("Filesystem error: {}\n", error.message()); } - fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); + fmt::print("Saving new configuration file {}\n", path.string()); } // Entries saved by the game-specific settings GUI @@ -1089,9 +1147,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { isTrophyPopupDisabled.setTomlValue(data, "General", "isTrophyPopupDisabled", is_game_specific); trophyNotificationDuration.setTomlValue(data, "General", "trophyNotificationDuration", is_game_specific); - logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); - logType.setTomlValue(data, "General", "logType", is_game_specific); - isIdenticalLogGrouped.setTomlValue(data, "General", "isIdenticalLogGrouped", 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); @@ -1103,6 +1158,18 @@ void save(const std::filesystem::path& path, bool is_game_specific) { isPSNSignedIn.setTomlValue(data, "General", "isPSNSignedIn", is_game_specific); isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific); + logAppend.setTomlValue(data, "Log", "append", is_game_specific); + logEnable.setTomlValue(data, "Log", "enable", is_game_specific); + logFilter.setTomlValue(data, "Log", "filter", is_game_specific); + logMaxSkipDuration.setTomlValue(data, "Log", "maxSkipDuration", is_game_specific); + logSeparate.setTomlValue(data, "Log", "separate", is_game_specific); + logSizeLimit.setTomlValue(data, "Log", "sizeLimit", is_game_specific); + logSkipDuplicate.setTomlValue(data, "Log", "skipDuplicate", is_game_specific); + logSync.setTomlValue(data, "Log", "sync", is_game_specific); +#ifdef _WIN32 + logType.setTomlValue(data, "Log", "type", is_game_specific); +#endif + cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); isMotionControlsEnabled.setTomlValue(data, "Input", "isMotionControlsEnabled", @@ -1146,12 +1213,11 @@ 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); + trophyKey.setTomlValue(data, "Keys", "TrophyKey", is_game_specific); + if (!is_game_specific) { std::vector install_dirs; std::vector install_dirs_enabled; @@ -1164,8 +1230,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { std::vector sorted_dirs; for (const auto& dirInfo : settings_install_dirs) { - sorted_dirs.push_back( - {string{fmt::UTF(dirInfo.path.u8string()).data}, dirInfo.enabled}); + sorted_dirs.push_back({dirInfo.path.string(), dirInfo.enabled}); } // Sort directories alphabetically @@ -1184,15 +1249,14 @@ 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}; - data["General"]["fontsPath"] = string{fmt::UTF(fonts_path.u8string()).data}; + data["General"]["sysModulesPath"] = sys_modules_path.string(); + data["General"]["fontsPath"] = fonts_path.u8string(); data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; - data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; - data["GUI"]["addonInstallDir"] = - string{fmt::UTF(settings_addon_install_dir.u8string()).data}; + data["GUI"]["saveDataPath"] = save_data_path.u8string(); + data["GUI"]["addonInstallDir"] = settings_addon_install_dir.u8string(); data["Debug"]["ConfigVersion"] = config_version; - data["Keys"]["TrophyKey"] = trophyKey; + data["Keys"]["TrophyKey"] = trophyKey.get(); // Do not save these entries in the game-specific dialog since they are not in the GUI data["General"]["defaultControllerID"] = defaultControllerID.base_value; @@ -1233,13 +1297,23 @@ void setDefaultValues(bool is_game_specific) { volumeSlider.set(100, is_game_specific); isTrophyPopupDisabled.set(false, is_game_specific); trophyNotificationDuration.set(6.0, is_game_specific); - logFilter.set("", is_game_specific); - logType.set("sync", is_game_specific); - isIdenticalLogGrouped.set("isIdenticalLogGrouped", is_game_specific); userName.set("shadPS4", is_game_specific); isShowSplash.set(false, is_game_specific); isSideTrophy.set("right", is_game_specific); + // GS - Log + logFilter.set("", is_game_specific); + logSync.set(true, is_game_specific); +#ifdef _WIN32 + logType.set("wincolor", is_game_specific); +#endif + logSkipDuplicate.set(true, is_game_specific); + logAppend.set(false, is_game_specific); + logSeparate.set(false, is_game_specific); + logEnable.set(true, is_game_specific); + logMaxSkipDuration.set(5'000, is_game_specific); + logSizeLimit.set(100_MB, is_game_specific); + // GS - Input cursorState.set(HideCursorState::Idle, is_game_specific); cursorHideTimeout.set(5, is_game_specific); @@ -1281,8 +1355,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 m_language.set(1, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index b341030e09a..41bd3732674 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -105,10 +105,20 @@ bool isPipelineCacheArchived(); void setRdocEnabled(bool enable, bool is_game_specific = false); void setPipelineCacheEnabled(bool enable, bool is_game_specific = false); void setPipelineCacheArchived(bool enable, bool is_game_specific = false); +bool isLogSync(); +void setLogSync(bool sync, bool is_game_specific = false); +#ifdef _WIN32 std::string getLogType(); void setLogType(const std::string& type, bool is_game_specific = false); -bool groupIdenticalLogs(); -void setGroupIdenticalLogs(bool enable, bool is_game_specific = false); +#endif +bool getLogSkipDuplicate(); +void setLogSkipDuplicate(bool enable, bool is_game_specific = false); +u32 getMaxSkipDuration(); +void setMaxSkipDuration(u32 duration, bool is_game_specific = false); +unsigned long long getLogSizeLimit(); +void setLogSizeLimit(unsigned long long size, bool is_game_specific = false); +bool isLogAppend(); +void setLogAppend(bool enable, bool is_game_specific = false); std::string getLogFilter(); void setLogFilter(const std::string& type, bool is_game_specific = false); double getTrophyNotificationDuration(); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp deleted file mode 100644 index 5de4f64a08a..00000000000 --- a/src/common/logging/backend.cpp +++ /dev/null @@ -1,396 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include - -#include - -#ifdef _WIN32 -#include // For OutputDebugStringW -#endif - -#include "common/bounded_threadsafe_queue.h" -#include "common/debug.h" -#include "common/io_file.h" -#include "common/logging/backend.h" -#include "common/logging/log.h" -#include "common/logging/log_entry.h" -#include "common/logging/text_formatter.h" -#include "common/path_util.h" -#include "common/string_util.h" -#include "common/thread.h" -#include "core/emulator_settings.h" - -namespace Common::Log { - -using namespace Common::FS; - -namespace { - -/** - * Backend that writes to stderr and with color - */ -class ColorConsoleBackend { -public: - explicit ColorConsoleBackend() = default; - - ~ColorConsoleBackend() = default; - - void Write(const Entry& entry) { - if (enabled.load(std::memory_order_relaxed)) { - PrintColoredMessage(entry); - } - } - - void Flush() { - // stderr shouldn't be buffered - } - - void SetEnabled(bool enabled_) { - enabled = enabled_; - } - -private: - std::atomic_bool enabled{true}; -}; - -/** - * Backend that writes to a file passed into the constructor - */ -class FileBackend { -public: - explicit FileBackend(const std::filesystem::path& filename, bool should_append = false) - : file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Create, - FS::FileType::TextFile} {} - - ~FileBackend() = default; - - void Write(const Entry& entry) { - if (!enabled && entry.log_level != Level::Critical) { - return; - } - - bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); - - // Prevent logs from exceeding a set maximum size in the event that log entries are spammed. - const auto write_limit = 100_MB; - const bool write_limit_exceeded = bytes_written > write_limit; - if (entry.log_level >= Level::Error || write_limit_exceeded) { - if (write_limit_exceeded) { - // Stop writing after the write limit is exceeded. - // Don't close the file so we can print a stacktrace if necessary - enabled = false; - } - file.Flush(); - } - } - - void Flush() { - file.Flush(); - } - -private: - Common::FS::IOFile file; - bool enabled = true; - std::size_t bytes_written = 0; -}; - -#ifdef _WIN32 -/** - * Backend that writes to Visual Studio's output window - */ -class DebuggerBackend { -public: - explicit DebuggerBackend() = default; - - ~DebuggerBackend() = default; - - void Write(const Entry& entry) { - ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); - } - - void Flush() {} - - void EnableForStacktrace() {} -}; -#endif - -bool initialization_in_progress_suppress_logging = true; - -/** - * Static state as a singleton. - */ -class Impl { -public: - static Impl& Instance() { - if (!instance) { - throw std::runtime_error("Using Logging instance before its initialization"); - } - return *instance; - } - - static void Initialize(std::string_view log_file) { - if (instance) { - LOG_WARNING(Log, "Reinitializing logging backend"); - return; - } - const auto& log_dir = GetUserPath(PathType::LogDir); - std::filesystem::create_directory(log_dir); - Filter filter; - filter.ParseFilterString(EmulatorSettings.GetLogFilter()); - const auto& log_file_path = log_file.empty() ? LOG_FILE : log_file; - instance = std::unique_ptr( - new Impl(log_dir / log_file_path, filter), Deleter); - initialization_in_progress_suppress_logging = false; - } - - static void ResetInstance() { - initialization_in_progress_suppress_logging = true; - instance.reset(); - } - - static bool IsActive() { - return instance != nullptr; - } - - static void Start() { - instance->StartBackendThread(); - } - - static void Stop() { - instance->StopBackendThread(); - } - - static void SetAppend() { - should_append = true; - } - - Impl(const Impl&) = delete; - Impl& operator=(const Impl&) = delete; - - Impl(Impl&&) = delete; - Impl& operator=(Impl&&) = delete; - - void SetGlobalFilter(const Filter& f) { - filter = f; - } - - void SetColorConsoleBackendEnabled(bool enabled) { - color_console_backend.SetEnabled(enabled); - } - - void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, - const char* function, const char* format, const fmt::format_args& args) { - if (!filter.CheckMessage(log_class, log_level) || !EmulatorSettings.IsLogEnabled()) { - return; - } - - const auto message = fmt::vformat(format, args); - - // Propagate important log messages to the profiler - if (IsProfilerConnected()) { - const auto& msg_str = fmt::format("[{}] {}", GetLogClassName(log_class), message); - switch (log_level) { - case Level::Warning: - TRACE_WARN(msg_str); - break; - case Level::Error: - TRACE_ERROR(msg_str); - break; - case Level::Critical: - TRACE_CRIT(msg_str); - break; - default: - break; - } - } - - using std::chrono::duration_cast; - using std::chrono::microseconds; - using std::chrono::steady_clock; - - if (EmulatorSettings.IsIdenticalLogGrouped()) { - std::unique_lock entry_loc(_mutex); - - if (_last_entry.message == message) { - ++_last_entry.counter; - return; - } - - if (_last_entry.counter >= 2) { - _last_entry.message += " x" + std::to_string(_last_entry.counter); - } - - if (_last_entry.counter >= 1) { - if (EmulatorSettings.GetLogType() == "async") { - message_queue.EmplaceWait(_last_entry); - } else { - ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); - std::fflush(stdout); - } - } - - this->_last_entry = { - .timestamp = duration_cast(steady_clock::now() - time_origin), - .log_class = log_class, - .log_level = log_level, - .filename = filename, - .line_num = line_num, - .function = function, - .message = message, - .thread = Common::GetCurrentThreadName(), - .counter = 1, - }; - } else { - const Entry entry = { - .timestamp = duration_cast(steady_clock::now() - time_origin), - .log_class = log_class, - .log_level = log_level, - .filename = filename, - .line_num = line_num, - .function = function, - .message = message, - .thread = Common::GetCurrentThreadName(), - .counter = 1, - }; - - if (EmulatorSettings.GetLogType() == "async") { - message_queue.EmplaceWait(entry); - } else { - ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); - std::fflush(stdout); - } - } - } - -private: - Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) - : filter{filter_}, file_backend{file_backend_filename, should_append} {} - - ~Impl() = default; - - void StartBackendThread() { - backend_thread = std::jthread([this](std::stop_token stop_token) { - Common::SetCurrentThreadName("shadPS4:Log"); - Entry entry; - const auto write_logs = [this, &entry]() { - ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); - }; - while (!stop_token.stop_requested()) { - message_queue.PopWait(entry, stop_token); - if (entry.filename != nullptr) { - write_logs(); - } - } - // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a - // case where a system is repeatedly spamming logs even on close. - int max_logs_to_write = filter.IsDebug() ? std::numeric_limits::max() : 100; - while (max_logs_to_write-- && message_queue.TryPop(entry)) { - write_logs(); - } - }); - } - - void StopBackendThread() { - if (EmulatorSettings.IsIdenticalLogGrouped()) { - // log last message - if (_last_entry.counter >= 2) { - _last_entry.message += " x" + std::to_string(_last_entry.counter); - } - - if (_last_entry.counter >= 1) { - if (EmulatorSettings.GetLogType() == "async") { - message_queue.EmplaceWait(_last_entry); - } else { - ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); - std::fflush(stdout); - } - } - - this->_last_entry = {}; - } - - backend_thread.request_stop(); - if (backend_thread.joinable()) { - backend_thread.join(); - } - - ForEachBackend([](auto& backend) { backend.Flush(); }); - } - - void ForEachBackend(auto lambda) { -#ifdef _WIN32 - lambda(debugger_backend); -#endif - lambda(color_console_backend); - lambda(file_backend); - } - - static void Deleter(Impl* ptr) { - delete ptr; - } - - static inline std::unique_ptr instance{nullptr, Deleter}; - static inline bool should_append{false}; - - Filter filter; -#ifdef _WIN32 - DebuggerBackend debugger_backend{}; -#endif - ColorConsoleBackend color_console_backend{}; - FileBackend file_backend; - - MPSCQueue message_queue{}; - std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; - std::jthread backend_thread; - Entry _last_entry; - std::mutex _mutex; -}; -} // namespace - -void Initialize(std::string_view log_file) { - Impl::Initialize(log_file.empty() ? LOG_FILE : log_file); -} - -bool IsActive() { - return Impl::IsActive(); -} - -void Start() { - Impl::Start(); -} - -void Stop() { - Impl::Stop(); -} - -void Denitializer() { - Impl::Stop(); - Impl::ResetInstance(); -} - -void SetGlobalFilter(const Filter& filter) { - Impl::Instance().SetGlobalFilter(filter); -} - -void SetColorConsoleBackendEnabled(bool enabled) { - Impl::Instance().SetColorConsoleBackendEnabled(enabled); -} - -void SetAppend() { - Impl::SetAppend(); -} - -void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, - unsigned int line_num, const char* function, const char* format, - const fmt::format_args& args) { - if (!initialization_in_progress_suppress_logging) [[likely]] { - Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function, format, - args); - } -} -} // namespace Common::Log diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h deleted file mode 100644 index 3003d1eaeb9..00000000000 --- a/src/common/logging/backend.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "common/logging/filter.h" - -namespace Common::Log { - -class Filter; - -/// Initializes the logging system. This should be the first thing called in main. -void Initialize(std::string_view log_file = ""); - -bool IsActive(); - -/// Starts the logging threads. -void Start(); - -/// Explictily stops the logger thread and flushes the buffers -void Stop(); - -/// Closes log files and stops the logger -void Denitializer(); - -/// The global filter will prevent any messages from even being processed if they are filtered. -void SetGlobalFilter(const Filter& filter); - -void SetColorConsoleBackendEnabled(bool enabled); - -void SetAppend(); - -} // namespace Common::Log diff --git a/src/common/logging/classes.h b/src/common/logging/classes.h new file mode 100644 index 00000000000..667d207fe13 --- /dev/null +++ b/src/common/logging/classes.h @@ -0,0 +1,221 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Common::Log { +// clang-format off +/// Llisting all log classes, if you add here, dont forget ALL_LOG_CLASSES +constexpr auto Log = "Log"; +constexpr auto Common = "Common"; +constexpr auto Common_Filesystem = "Common.Filesystem"; +constexpr auto Common_Memory = "Common.Memory"; +constexpr auto Core = "Core"; +constexpr auto Core_Linker = "Core.Linker"; +constexpr auto Core_Devices = "Core.Devices"; +constexpr auto Config = "Config"; +constexpr auto Debug = "Debug"; +constexpr auto Kernel = "Kernel"; +constexpr auto Kernel_Pthread = "Kernel.Pthread"; +constexpr auto Kernel_Vmm = "Kernel.Vmm"; +constexpr auto Kernel_Fs = "Kernel.Fs"; +constexpr auto Kernel_Event = "Kernel.Event"; +constexpr auto Kernel_Sce = "Kernel.Sce"; +constexpr auto Lib = "Lib"; +constexpr auto Lib_LibC = "Lib.LibC"; +constexpr auto Lib_LibcInternal = "Lib.LibcInternal"; +constexpr auto Lib_Kernel = "Lib.Kernel"; +constexpr auto Lib_Pad = "Lib.Pad"; +constexpr auto Lib_SystemGesture = "Lib.SystemGesture"; +constexpr auto Lib_GnmDriver = "Lib.GnmDriver"; +constexpr auto Lib_SystemService = "Lib.SystemService"; +constexpr auto Lib_UserService = "Lib.UserService"; +constexpr auto Lib_VideoOut = "Lib.VideoOut"; +constexpr auto Lib_CommonDlg = "Lib.CommonDlg"; +constexpr auto Lib_MsgDlg = "Lib.MsgDlg"; +constexpr auto Lib_AudioOut = "Lib.AudioOut"; +constexpr auto Lib_AudioIn = "Lib.AudioIn"; +constexpr auto Lib_Net = "Lib.Net"; +constexpr auto Lib_NetCtl = "Lib.NetCtl"; +constexpr auto Lib_SaveData = "Lib.SaveData"; +constexpr auto Lib_SaveDataDialog = "Lib.SaveDataDialog"; +constexpr auto Lib_Http = "Lib.Http"; +constexpr auto Lib_Http2 = "Lib.Http2"; +constexpr auto Lib_Ssl = "Lib.Ssl"; +constexpr auto Lib_Ssl2 = "Lib.Ssl2"; +constexpr auto Lib_SysModule = "Lib.SysModule"; +constexpr auto Lib_Move = "Lib.Move"; +constexpr auto Lib_NpAuth = "Lib.NpAuth"; +constexpr auto Lib_NpCommon = "Lib.NpCommon"; +constexpr auto Lib_NpCommerce = "Lib.NpCommerce"; +constexpr auto Lib_NpManager = "Lib.NpManager"; +constexpr auto Lib_NpMatching2 = "Lib.NpMatching2"; +constexpr auto Lib_NpScore = "Lib.NpScore"; +constexpr auto Lib_NpTrophy = "Lib.NpTrophy"; +constexpr auto Lib_NpTus = "Lib.NpTus"; +constexpr auto Lib_NpWebApi = "Lib.NpWebApi"; +constexpr auto Lib_NpWebApi2 = "Lib.NpWebApi2"; +constexpr auto Lib_NpProfileDialog = "Lib.NpProfileDialog"; +constexpr auto Lib_NpSnsFacebookDialog = "Lib.NpSnsFacebookDialog"; +constexpr auto Lib_NpPartner = "Lib.NpPartner"; +constexpr auto Lib_Screenshot = "Lib.Screenshot"; +constexpr auto Lib_LibCInternal = "Lib.LibCInternal"; +constexpr auto Lib_AppContent = "Lib.AppContent"; +constexpr auto Lib_Rtc = "Lib.Rtc"; +constexpr auto Lib_Rudp = "Lib.Rudp"; +constexpr auto Lib_DiscMap = "Lib.DiscMap"; +constexpr auto Lib_Png = "Lib.Png"; +constexpr auto Lib_Jpeg = "Lib.Jpeg"; +constexpr auto Lib_PlayGo = "Lib.PlayGo"; +constexpr auto Lib_PlayGoDialog = "Lib.PlayGoDialog"; +constexpr auto Lib_Random = "Lib.Random"; +constexpr auto Lib_Usbd = "Lib.Usbd"; +constexpr auto Lib_Ajm = "Lib.Ajm"; +constexpr auto Lib_ErrorDialog = "Lib.ErrorDialog"; +constexpr auto Lib_ImeDialog = "Lib.ImeDialog"; +constexpr auto Lib_AvPlayer = "Lib.AvPlayer"; +constexpr auto Lib_Ngs2 = "Lib.Ngs2"; +constexpr auto Lib_Audio3d = "Lib.Audio3d"; +constexpr auto Lib_Ime = "Lib.Ime"; +constexpr auto Lib_GameLiveStreaming = "Lib.GameLiveStreaming"; +constexpr auto Lib_Remoteplay = "Lib.Remoteplay"; +constexpr auto Lib_SharePlay = "Lib.SharePlay"; +constexpr auto Lib_Fiber = "Lib.Fiber"; +constexpr auto Lib_Vdec2 = "Lib.Vdec2"; +constexpr auto Lib_Videodec = "Lib.Videodec"; +constexpr auto Lib_RazorCpu = "Lib.RazorCpu"; +constexpr auto Lib_Mouse = "Lib.Mouse"; +constexpr auto Lib_WebBrowserDialog = "Lib.WebBrowserDialog"; +constexpr auto Lib_NpParty = "Lib.NpParty"; +constexpr auto Lib_Zlib = "Lib.Zlib"; +constexpr auto Lib_Hmd = "Lib.Hmd"; +constexpr auto Lib_Font = "Lib.Font"; +constexpr auto Lib_FontFt = "Lib.FontFt"; +constexpr auto Lib_HmdSetupDialog = "Lib.HmdSetupDialog"; +constexpr auto Lib_SigninDialog = "Lib.SigninDialog"; +constexpr auto Lib_Camera = "Lib.Camera"; +constexpr auto Lib_CompanionHttpd = "Lib.CompanionHttpd"; +constexpr auto Lib_CompanionUtil = "Lib.CompanionUtil"; +constexpr auto Lib_Voice = "Lib.Voice"; +constexpr auto Lib_VrTracker = "Lib.VrTracker"; +constexpr auto Frontend = "Frontend"; +constexpr auto Render = "Render"; +constexpr auto Render_Vulkan = "Render.Vulkan"; +constexpr auto Render_Recompiler = "Render.Recompiler"; +constexpr auto ImGui = "ImGui"; +constexpr auto Input = "Input"; +constexpr auto Tty = "Tty"; +constexpr auto IPC = "IPC"; +constexpr auto KeyManager = "KeyManager"; +constexpr auto EmuSettings = "EmuSettings"; +constexpr auto Loader = "Loader"; +// clang-format on + +constexpr std::array ALL_LOG_CLASSES{ + Log, + Common, + Common_Filesystem, + Common_Memory, + Core, + Core_Linker, + Core_Devices, + Config, + Debug, + Kernel, + Kernel_Pthread, + Kernel_Vmm, + Kernel_Fs, + Kernel_Event, + Kernel_Sce, + Lib, + Lib_LibC, + Lib_LibcInternal, + Lib_Kernel, + Lib_Pad, + Lib_SystemGesture, + Lib_GnmDriver, + Lib_SystemService, + Lib_UserService, + Lib_VideoOut, + Lib_CommonDlg, + Lib_MsgDlg, + Lib_AudioOut, + Lib_AudioIn, + Lib_Net, + Lib_NetCtl, + Lib_SaveData, + Lib_SaveDataDialog, + Lib_Http, + Lib_Http2, + Lib_Ssl, + Lib_Ssl2, + Lib_SysModule, + Lib_Move, + Lib_NpAuth, + Lib_NpCommon, + Lib_NpCommerce, + Lib_NpManager, + Lib_NpMatching2, + Lib_NpScore, + Lib_NpTrophy, + Lib_NpTus, + Lib_NpWebApi, + Lib_NpWebApi2, + Lib_NpProfileDialog, + Lib_NpSnsFacebookDialog, + Lib_NpPartner, + Lib_Screenshot, + Lib_LibCInternal, + Lib_AppContent, + Lib_Rtc, + Lib_Rudp, + Lib_DiscMap, + Lib_Png, + Lib_Jpeg, + Lib_PlayGo, + Lib_PlayGoDialog, + Lib_Random, + Lib_Usbd, + Lib_Ajm, + Lib_ErrorDialog, + Lib_ImeDialog, + Lib_AvPlayer, + Lib_Ngs2, + Lib_Audio3d, + Lib_Ime, + Lib_GameLiveStreaming, + Lib_Remoteplay, + Lib_SharePlay, + Lib_Fiber, + Lib_Vdec2, + Lib_Videodec, + Lib_RazorCpu, + Lib_Mouse, + Lib_WebBrowserDialog, + Lib_NpParty, + Lib_Zlib, + Lib_Hmd, + Lib_Font, + Lib_FontFt, + Lib_HmdSetupDialog, + Lib_SigninDialog, + Lib_Camera, + Lib_CompanionHttpd, + Lib_CompanionUtil, + Lib_Voice, + Lib_VrTracker, + Frontend, + Render, + Render_Vulkan, + Render_Recompiler, + ImGui, + Input, + Tty, + IPC, + KeyManager, + EmuSettings, + Loader, +}; +} // namespace Common::Log diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp deleted file mode 100644 index f2597603e09..00000000000 --- a/src/common/logging/filter.cpp +++ /dev/null @@ -1,249 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/assert.h" -#include "common/logging/filter.h" - -namespace Common::Log { -namespace { -template -Level GetLevelByName(const It begin, const It end) { - for (u8 i = 0; i < static_cast(Level::Count); ++i) { - const char* level_name = GetLevelName(static_cast(i)); - if (std::string_view(begin, end).compare(level_name) == 0) { - return static_cast(i); - } - } - return Level::Count; -} - -template -Class GetClassByName(const It begin, const It end) { - for (u8 i = 0; i < static_cast(Class::Count); ++i) { - const char* level_name = GetLogClassName(static_cast(i)); - if (std::string_view(begin, end).compare(level_name) == 0) { - return static_cast(i); - } - } - return Class::Count; -} - -template -bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { - auto level_separator = std::find(begin, end, ':'); - if (level_separator == end) { - LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", - std::string_view(begin, end)); - return false; - } - - const Level level = GetLevelByName(level_separator + 1, end); - if (level == Level::Count) { - LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end)); - return false; - } - - if (std::string_view(begin, level_separator).compare("*") == 0) { - instance.ResetAll(level); - return true; - } - - const Class log_class = GetClassByName(begin, level_separator); - if (log_class == Class::Count) { - LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end)); - return false; - } - - instance.SetClassLevel(log_class, level); - return true; -} -} // Anonymous namespace - -/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. -#define ALL_LOG_CLASSES() \ - CLS(Log) \ - CLS(Common) \ - SUB(Common, Filesystem) \ - SUB(Common, Memory) \ - CLS(Core) \ - SUB(Core, Linker) \ - SUB(Core, Devices) \ - CLS(Config) \ - CLS(Debug) \ - CLS(Kernel) \ - SUB(Kernel, Pthread) \ - SUB(Kernel, Vmm) \ - SUB(Kernel, Fs) \ - SUB(Kernel, Event) \ - SUB(Kernel, Sce) \ - CLS(Lib) \ - SUB(Lib, LibC) \ - SUB(Lib, LibcInternal) \ - SUB(Lib, Kernel) \ - SUB(Lib, Pad) \ - SUB(Lib, SystemGesture) \ - SUB(Lib, GnmDriver) \ - SUB(Lib, SystemService) \ - SUB(Lib, UserService) \ - SUB(Lib, VideoOut) \ - SUB(Lib, CommonDlg) \ - SUB(Lib, MsgDlg) \ - SUB(Lib, AudioOut) \ - SUB(Lib, AudioIn) \ - SUB(Lib, Net) \ - SUB(Lib, NetCtl) \ - SUB(Lib, SaveData) \ - SUB(Lib, SaveDataDialog) \ - SUB(Lib, Http) \ - SUB(Lib, Http2) \ - SUB(Lib, Ssl) \ - SUB(Lib, Ssl2) \ - SUB(Lib, SysModule) \ - SUB(Lib, Move) \ - SUB(Lib, NpAuth) \ - SUB(Lib, NpCommon) \ - SUB(Lib, NpCommerce) \ - SUB(Lib, NpManager) \ - SUB(Lib, NpMatching2) \ - SUB(Lib, NpScore) \ - SUB(Lib, NpTrophy) \ - SUB(Lib, NpTus) \ - SUB(Lib, NpWebApi) \ - SUB(Lib, NpWebApi2) \ - SUB(Lib, NpProfileDialog) \ - SUB(Lib, NpSnsFacebookDialog) \ - SUB(Lib, NpPartner) \ - SUB(Lib, Screenshot) \ - SUB(Lib, LibCInternal) \ - SUB(Lib, AppContent) \ - SUB(Lib, Rtc) \ - SUB(Lib, Rudp) \ - SUB(Lib, DiscMap) \ - SUB(Lib, Png) \ - SUB(Lib, Jpeg) \ - SUB(Lib, PlayGo) \ - SUB(Lib, PlayGoDialog) \ - SUB(Lib, Random) \ - SUB(Lib, Usbd) \ - SUB(Lib, Ajm) \ - SUB(Lib, ErrorDialog) \ - SUB(Lib, ImeDialog) \ - SUB(Lib, AvPlayer) \ - SUB(Lib, Ngs2) \ - SUB(Lib, Audio3d) \ - SUB(Lib, Ime) \ - SUB(Lib, GameLiveStreaming) \ - SUB(Lib, Remoteplay) \ - SUB(Lib, SharePlay) \ - SUB(Lib, Fiber) \ - SUB(Lib, Vdec2) \ - SUB(Lib, Videodec) \ - SUB(Lib, RazorCpu) \ - SUB(Lib, Mouse) \ - SUB(Lib, WebBrowserDialog) \ - SUB(Lib, NpParty) \ - SUB(Lib, Zlib) \ - SUB(Lib, Hmd) \ - SUB(Lib, Font) \ - SUB(Lib, FontFt) \ - SUB(Lib, HmdSetupDialog) \ - SUB(Lib, SigninDialog) \ - SUB(Lib, Camera) \ - SUB(Lib, CompanionHttpd) \ - SUB(Lib, CompanionUtil) \ - SUB(Lib, Voice) \ - SUB(Lib, VrTracker) \ - CLS(Frontend) \ - CLS(Render) \ - SUB(Render, Vulkan) \ - SUB(Render, Recompiler) \ - CLS(ImGui) \ - CLS(Input) \ - CLS(Tty) \ - CLS(KeyManager) \ - CLS(EmuSettings) \ - CLS(Loader) - -// GetClassName is a macro defined by Windows.h, grrr... -const char* GetLogClassName(Class log_class) { - switch (log_class) { -#define CLS(x) \ - case Class::x: \ - return #x; -#define SUB(x, y) \ - case Class::x##_##y: \ - return #x "." #y; - ALL_LOG_CLASSES() -#undef CLS -#undef SUB - case Class::Count: - default: - break; - } - UNREACHABLE(); -} - -const char* GetLevelName(Level log_level) { -#define LVL(x) \ - case Level::x: \ - return #x - switch (log_level) { - LVL(Trace); - LVL(Debug); - LVL(Info); - LVL(Warning); - LVL(Error); - LVL(Critical); - case Level::Count: - default: - break; - } -#undef LVL - UNREACHABLE(); -} - -Filter::Filter(Level default_level) { - ResetAll(default_level); -} - -void Filter::ResetAll(Level level) { - class_levels.fill(level); -} - -void Filter::SetClassLevel(Class log_class, Level level) { - class_levels[static_cast(log_class)] = level; -} - -void Filter::ParseFilterString(std::string_view filter_view) { - auto clause_begin = filter_view.cbegin(); - while (clause_begin != filter_view.cend()) { - auto clause_end = std::find(clause_begin, filter_view.cend(), ' '); - - // If clause isn't empty - if (clause_end != clause_begin) { - ParseFilterRule(*this, clause_begin, clause_end); - } - - if (clause_end != filter_view.cend()) { - // Skip over the whitespace - ++clause_end; - } - clause_begin = clause_end; - } -} - -bool Filter::CheckMessage(Class log_class, Level level) const { - return static_cast(level) >= - static_cast(class_levels[static_cast(log_class)]); -} - -bool Filter::IsDebug() const { - return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) { - return static_cast(l) <= static_cast(Level::Debug); - }); -} - -} // namespace Common::Log diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h deleted file mode 100644 index 7c46fd8221f..00000000000 --- a/src/common/logging/filter.h +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include "common/logging/types.h" - -namespace Common::Log { - -/** - * Returns the name of the passed log class as a C-string. Subclasses are separated by periods - * instead of underscores as in the enumeration. - */ -const char* GetLogClassName(Class log_class); - -/** - * Returns the name of the passed log level as a C-string. - */ -const char* GetLevelName(Level log_level); - -/** - * Implements a log message filter which allows different log classes to have different minimum - * severity levels. The filter can be changed at runtime and can be parsed from a string to allow - * editing via the interface or loading from a configuration file. - */ -class Filter { -public: - /// Initializes the filter with all classes having `default_level` as the minimum level. - explicit Filter(Level default_level = Level::Info); - - /// Resets the filter so that all classes have `level` as the minimum displayed level. - void ResetAll(Level level); - - /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. - void SetClassLevel(Class log_class, Level level); - - /** - * Parses a filter string and applies it to this filter. - * - * A filter string consists of a space-separated list of filter rules, each of the format - * `:`. `` is a log class name, with subclasses separated using periods. - * `*` is allowed as a class name and will reset all filters to the specified level. `` - * a severity level name which will be set as the minimum logging level of the matched classes. - * Rules are applied left to right, with each rule overriding previous ones in the sequence. - * - * A few examples of filter rules: - * - `*:Info` -- Resets the level of all classes to Info. - * - `Service:Info` -- Sets the level of Service to Info. - * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. - */ - void ParseFilterString(std::string_view filter_view); - - /// Matches class/level combination against the filter, returning true if it passed. - bool CheckMessage(Class log_class, Level level) const; - - /// Returns true if any logging classes are set to debug - bool IsDebug() const; - -private: - std::array(Class::Count)> class_levels; -}; - -} // namespace Common::Log diff --git a/src/common/logging/formatter.h b/src/common/logging/formatter.h deleted file mode 100644 index fad8451e9bb..00000000000 --- a/src/common/logging/formatter.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -// Adapted from https://github.com/fmtlib/fmt/issues/2704 -// a generic formatter for enum classes -#if FMT_VERSION >= 80100 -template -struct fmt::formatter, char>> - : formatter> { - template - auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { - return fmt::formatter>::format( - static_cast>(value), ctx); - } -}; -#endif - -namespace fmt { -template -struct UTF { - T data; - - explicit UTF(const std::u8string_view view) { - data = view.empty() ? T{} : T{(const char*)&view.front(), (const char*)&view.back() + 1}; - } - - explicit UTF(const std::u8string& str) : UTF(std::u8string_view{str}) {} -}; -} // namespace fmt - -template <> -struct fmt::formatter, char> : formatter { - template - auto format(const UTF& wrapper, FormatContext& ctx) const { - return formatter::format(wrapper.data, ctx); - } -}; \ No newline at end of file diff --git a/src/common/logging/log.cpp b/src/common/logging/log.cpp new file mode 100644 index 00000000000..e5e8bf54649 --- /dev/null +++ b/src/common/logging/log.cpp @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "log.h" +#include "common/config.h" +#include "core/emulator_settings.h" +#include "common/types.h" +#include +#include + +namespace Common::Log { +std::shared_ptr g_console_sink; +std::shared_ptr g_shad_file_sink; +std::shared_ptr g_game_file_sink; +bool g_should_append = false; + +void Setup(int argc, char* argv[]) { + if (EmulatorSettings.IsLogEnable()) { + spdlog::cfg::load_env_levels(); + spdlog::cfg::helpers::load_levels(EmulatorSettings.GetLogFilter()); + spdlog::cfg::load_argv_levels(argc, argv); + } else { + spdlog::cfg::helpers::load_levels("off"); + } + + if (!EmulatorSettings.IsLogSync()) { + spdlog::init_thread_pool(8192, 1); + } + + std::atexit(Shutdown); + std::at_quick_exit(Shutdown); + + spdlog::set_formatter(std::make_unique(UNLIMITED_SIZE)); + +#ifdef _WIN32 + if (EmulatorSettings.GetLogType() == "wincolor") { + g_console_sink = std::make_shared(); + } else { + g_console_sink = std::make_shared(); + } +#else + g_console_sink = std::make_shared(); +#endif + + g_shad_file_sink = std::make_shared( + (GetUserPath(Common::FS::PathType::LogDir) / "shad_log.txt").string()); + g_shad_file_sink->set_formatter( + std::make_unique(EmulatorSettings.GetLogSizeLimit())); + + std::shared_ptr dup_filter; + + if (EmulatorSettings.IsLogSkipDuplicate()) { + dup_filter = std::make_shared( + std::chrono::milliseconds(EmulatorSettings.GetLogMaxSkipDuration())); + dup_filter->set_sinks( + {g_console_sink, g_shad_file_sink, + std::make_shared< + spdlog::sinks::null_sink_mt>() /*for POSITON_GAME_LOG id + ".log" */}); + } + + for (const auto& name : Common::Log::ALL_LOG_CLASSES) { + if (EmulatorSettings.IsLogSkipDuplicate()) { + spdlog::initialize_logger(EmulatorSettings.IsLogSync() + ? std::make_shared(name, dup_filter) + : std::make_shared( + name, dup_filter, spdlog::thread_pool())); + } else { + spdlog::initialize_logger( + EmulatorSettings.IsLogSync() + ? std::make_shared( + name, + std::initializer_list{ + g_console_sink, g_shad_file_sink, + std::make_shared< + spdlog::sinks:: + null_sink_mt>() /*for POSITON_GAME_LOG id + ".log" */}) + : std::make_shared( + name, + std::initializer_list{ + g_console_sink, g_shad_file_sink, + std::make_shared< + spdlog::sinks:: + null_sink_mt>() /*for POSITON_GAME_LOG id + ".log" */}, + spdlog::thread_pool())); + } + } +} + +void Redirect(const std::string& name) { + g_game_file_sink = std::make_shared( + (GetUserPath(Common::FS::PathType::LogDir) / name).string(), !g_should_append); + g_game_file_sink->set_formatter( + std::make_unique(EmulatorSettings.GetLogSizeLimit())); + + for (const auto& name : Common::Log::ALL_LOG_CLASSES) { + auto l = spdlog::get(name); + auto& sinks = EmulatorSettings.IsLogSkipDuplicate() + ? std::dynamic_pointer_cast( + l->sinks()[POSITON_DUPLICATE_SINK]) + ->sinks() + : l->sinks(); + + if (sinks.size() == 3) { + sinks[POSITON_GAME_LOG] = g_game_file_sink; + } + } + + g_shad_file_sink->set_level(spdlog::level::off); +} +} // namespace Common::Log diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 5fa430348a8..5f123f1e6f4 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -1,70 +1,144 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project +// 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 +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +using spdlog_stdout = spdlog::sinks::sink; +#else +using spdlog_stdout = spdlog::sinks::stdout_color_sink_mt; +#endif +#include -#include "common/logging/formatter.h" -#include "common/logging/types.h" +#include "common/logging/classes.h" +#include "common/path_util.h" +#include "common/thread.h" namespace Common::Log { +static constexpr unsigned long long UNLIMITED_SIZE = 0; + +struct thread_name_formatter : spdlog::formatter { + ~thread_name_formatter() override = default; + + thread_name_formatter(unsigned long long size_limit) : _size_limit(size_limit) {} + + void format(const spdlog::details::log_msg& msg, spdlog::memory_buf_t& dest) override { + if (_size_limit != UNLIMITED_SIZE && _current_size >= _size_limit) { + return; + } + + msg.color_range_start = dest.size(); + + dest.push_back('['); + spdlog::details::fmt_helper::append_string_view(msg.logger_name, dest); + dest.push_back(']'); + dest.push_back(' '); + dest.push_back('<'); + spdlog::details::fmt_helper::append_string_view(spdlog::level::to_string_view(msg.level), + dest); + dest.push_back('>'); + dest.push_back(' '); + dest.push_back('('); + spdlog::details::fmt_helper::append_string_view(GetCurrentThreadName(), dest); + dest.push_back(')'); + dest.push_back(' '); + spdlog::details::fmt_helper::append_string_view( + std::filesystem::path(msg.source.filename).filename().string(), dest); + dest.push_back(':'); + spdlog::details::fmt_helper::append_int(msg.source.line, dest); + dest.push_back(' '); + spdlog::details::fmt_helper::append_string_view( + std::string_view(msg.source.funcname).contains("(anonymous class)::operator()") + ? "lambda" + : msg.source.funcname, + dest); + dest.push_back(':'); + dest.push_back(' '); + spdlog::details::fmt_helper::append_string_view(msg.payload, dest); + spdlog::details::fmt_helper::append_string_view(spdlog::details::os::default_eol, dest); + + msg.color_range_end = dest.size(); -constexpr const char* TrimSourcePath(std::string_view source) { - const auto rfind = [source](const std::string_view match) { - return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size()); - }; - auto idx = std::max({rfind("/"), rfind("\\")}); - return source.data() + idx; + _current_size += dest.size(); + } + + std::unique_ptr clone() const override { + return std::make_unique(_size_limit); + } + + const unsigned long long _size_limit; + unsigned long long _current_size = 0; +}; + +static constexpr auto POSITON_CONSOLE_LOG = 0; +static constexpr auto POSITON_SHAD_LOG = 1; +static constexpr auto POSITON_GAME_LOG = 2; + +static constexpr auto POSITON_DUPLICATE_SINK = 0; + +extern std::shared_ptr g_console_sink; +extern std::shared_ptr g_shad_file_sink; +extern std::shared_ptr g_game_file_sink; +extern bool g_should_append; + +static void Shutdown() { + g_game_file_sink.reset(); + g_shad_file_sink.reset(); + g_console_sink.reset(); + spdlog::shutdown(); } -/// Logs a message to the global logger, using fmt -void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, - unsigned int line_num, const char* function, const char* format, - const fmt::format_args& args); +void Setup(int argc, char* argv[]); -template -void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, - const char* function, const char* format, const Args&... args) { - FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, - fmt::make_format_args(args...)); +void Redirect(const std::string& name); + +static void StopRedirection() { + g_game_file_sink->flush(); + g_game_file_sink->set_level(spdlog::level::off); + g_shad_file_sink->set_level(spdlog::level::trace); } +static void Truncate() { + if (g_game_file_sink != nullptr) { + g_game_file_sink->truncate(); + } + if (g_shad_file_sink != nullptr) { + g_shad_file_sink->truncate(); + } +} } // namespace Common::Log // Define the fmt lib macros #define LOG_GENERIC(log_class, log_level, ...) \ - Common::Log::FmtLogMessage(log_class, log_level, Common::Log::TrimSourcePath(__FILE__), \ - __LINE__, __func__, __VA_ARGS__) + SPDLOG_LOGGER_CALL(spdlog::get(log_class), log_level, __VA_ARGS__) #ifdef _DEBUG #define LOG_TRACE(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + SPDLOG_LOGGER_TRACE(spdlog::get(Common::Log::log_class), __VA_ARGS__) #else -#define LOG_TRACE(log_class, fmt, ...) (void(0)) +#define LOG_TRACE(log_class, ...) (void(0)) #endif #define LOG_DEBUG(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Debug, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::log_class, spdlog::level::debug, __VA_ARGS__) #define LOG_INFO(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Info, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::log_class, spdlog::level::info, __VA_ARGS__) #define LOG_WARNING(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Warning, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::log_class, spdlog::level::warn, __VA_ARGS__) #define LOG_ERROR(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Error, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::log_class, spdlog::level::err, __VA_ARGS__) #define LOG_CRITICAL(log_class, ...) \ - Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Critical, \ - Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \ - __VA_ARGS__) + LOG_GENERIC(Common::Log::log_class, spdlog::level::critical, __VA_ARGS__) diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h deleted file mode 100644 index 7b52ad7e12e..00000000000 --- a/src/common/logging/log_entry.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "common/logging/types.h" - -namespace Common::Log { - -/** - * A log entry. Log entries are store in a structured format to permit more varied output - * formatting on different frontends, as well as facilitating filtering and aggregation. - */ -struct Entry { - std::chrono::microseconds timestamp; - Class log_class{}; - Level log_level{}; - const char* filename = nullptr; - u32 line_num = 0; - std::string function; - std::string message; - std::string thread; - u32 counter = 0; -}; - -} // namespace Common::Log diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp deleted file mode 100644 index e8c5f49799f..00000000000 --- a/src/common/logging/text_formatter.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include - -#ifdef _WIN32 -#include -#endif - -#include "common/assert.h" -#include "common/logging/filter.h" -#include "common/logging/log.h" -#include "common/logging/log_entry.h" -#include "common/logging/text_formatter.h" - -namespace Common::Log { - -std::string FormatLogMessage(const Entry& entry) { - const u32 time_seconds = static_cast(entry.timestamp.count() / 1000000); - const u32 time_fractional = static_cast(entry.timestamp.count() % 1000000); - - const char* class_name = GetLogClassName(entry.log_class); - const char* level_name = GetLevelName(entry.log_level); - - return fmt::format("[{}] <{}> ({}) {}:{} {}: {}", class_name, level_name, entry.thread, - entry.filename, entry.line_num, entry.function, entry.message); -} - -void PrintMessage(const Entry& entry) { - const auto str = FormatLogMessage(entry).append(1, '\n'); - fputs(str.c_str(), stdout); -} - -void PrintColoredMessage(const Entry& entry) { -#ifdef _WIN32 - HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); - if (console_handle == INVALID_HANDLE_VALUE) { - return; - } - - CONSOLE_SCREEN_BUFFER_INFO original_info{}; - GetConsoleScreenBufferInfo(console_handle, &original_info); - - WORD color = 0; - switch (entry.log_level) { - case Level::Trace: // Grey - color = FOREGROUND_INTENSITY; - break; - case Level::Debug: // Cyan - color = FOREGROUND_GREEN | FOREGROUND_BLUE; - break; - case Level::Info: // Bright gray - color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - break; - case Level::Warning: // Bright yellow - color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; - break; - case Level::Error: // Bright red - color = FOREGROUND_RED | FOREGROUND_INTENSITY; - break; - case Level::Critical: // Bright magenta - color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; - break; - case Level::Count: - UNREACHABLE(); - } - - SetConsoleTextAttribute(console_handle, color); -#else -#define ESC "\x1b" - const char* color = ""; - switch (entry.log_level) { - case Level::Trace: // Grey - color = ESC "[1;30m"; - break; - case Level::Debug: // Cyan - color = ESC "[0;36m"; - break; - case Level::Info: // Bright gray - color = ESC "[0;37m"; - break; - case Level::Warning: // Bright yellow - color = ESC "[1;33m"; - break; - case Level::Error: // Bright red - color = ESC "[1;31m"; - break; - case Level::Critical: // Bright magenta - color = ESC "[1;35m"; - break; - case Level::Count: - UNREACHABLE(); - } - - fputs(color, stdout); -#endif - - PrintMessage(entry); - -#ifdef _WIN32 - SetConsoleTextAttribute(console_handle, original_info.wAttributes); -#else - fputs(ESC "[0m", stdout); -#undef ESC -#endif -} - -} // namespace Common::Log diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h deleted file mode 100644 index 504d8639b67..00000000000 --- a/src/common/logging/text_formatter.h +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -namespace Common::Log { - -struct Entry; - -/// Formats a log entry into the provided text buffer. -std::string FormatLogMessage(const Entry& entry); - -/// Formats and prints a log entry to stderr. -void PrintMessage(const Entry& entry); - -/// Prints the same message as `PrintMessage`, but colored according to the severity level. -void PrintColoredMessage(const Entry& entry); - -} // namespace Common::Log diff --git a/src/common/logging/types.h b/src/common/logging/types.h deleted file mode 100644 index 4c6e5345350..00000000000 --- a/src/common/logging/types.h +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project -// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Common::Log { - -/// Specifies the severity or level of detail of the log message. -enum class Level : u8 { - Trace, ///< Extremely detailed and repetitive debugging information that is likely to - ///< pollute logs. - Debug, ///< Less detailed debugging information. - Info, ///< Status information from important points during execution. - Warning, ///< Minor or potential problems found during execution of a task. - Error, ///< Major problems found during execution of a task that prevent it from being - ///< completed. - Critical, ///< Major problems during execution that threaten the stability of the entire - ///< application. - - Count, ///< Total number of logging levels -}; - -/** - * Specifies the sub-system that generated the log message. - * - * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in - * filter.cpp. - */ -enum class Class : u8 { - Log, ///< Messages about the log system itself - Common, ///< Library routines - Common_Filesystem, ///< Filesystem interface library - Common_Memory, ///< Memory mapping and management functions - Core, ///< LLE emulation core - Core_Linker, ///< The module linker - Core_Devices, ///< Devices emulation - Config, ///< Emulator configuration (including commandline) - Debug, ///< Debugging tools - Kernel, ///< The HLE implementation of the PS4 kernel. - Kernel_Pthread, ///< The pthread implementation of the kernel. - Kernel_Fs, ///< The filesystem implementation of the kernel. - Kernel_Vmm, ///< The virtual memory implementation of the kernel. - Kernel_Event, ///< The event management implementation of the kernel. - Kernel_Sce, ///< The sony specific interfaces provided by the kernel. - Lib, ///< HLE implementation of system library. Each major library - ///< should have its own subclass. - Lib_LibC, ///< The LibC implementation. - Lib_LibcInternal, ///< The LibcInternal implementation. - Lib_Kernel, ///< The LibKernel implementation. - Lib_Pad, ///< The LibScePad implementation. - Lib_SystemGesture, ///< The LibSceSystemGesture implementation. - Lib_GnmDriver, ///< The LibSceGnmDriver implementation. - Lib_SystemService, ///< The LibSceSystemService implementation. - Lib_UserService, ///< The LibSceUserService implementation. - Lib_VideoOut, ///< The LibSceVideoOut implementation. - Lib_CommonDlg, ///< The LibSceCommonDialog implementation. - Lib_MsgDlg, ///< The LibSceMsgDialog implementation. - Lib_AudioOut, ///< The LibSceAudioOut implementation. - Lib_AudioIn, ///< The LibSceAudioIn implementation. - Lib_Move, ///< The LibSceMove implementation. - Lib_Net, ///< The LibSceNet implementation. - Lib_NetCtl, ///< The LibSceNetCtl implementation. - Lib_SaveData, ///< The LibSceSaveData implementation. - Lib_SaveDataDialog, ///< The LibSceSaveDataDialog implementation. - Lib_Ssl, ///< The LibSceSsl implementation. - Lib_Ssl2, ///< The LibSceSsl2 implementation. - Lib_Http, ///< The LibSceHttp implementation. - Lib_Http2, ///< The LibSceHttp2 implementation. - Lib_SysModule, ///< The LibSceSysModule implementation - Lib_NpCommon, ///< The LibSceNpCommon implementation - Lib_NpCommerce, ///< The LibSceNpCommerce implementation - Lib_NpAuth, ///< The LibSceNpAuth implementation - Lib_NpManager, ///< The LibSceNpManager implementation - Lib_NpMatching2, ///< The LibSceNpMatching2 implementation - Lib_NpScore, ///< The LibSceNpScore implementation - Lib_NpTrophy, ///< The LibSceNpTrophy implementation - Lib_NpTus, ///< The LibSceNpTus implementation - Lib_NpWebApi, ///< The LibSceWebApi implementation - Lib_NpWebApi2, ///< The LibSceWebApi2 implementation - Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation - Lib_NpSnsFacebookDialog, ///< The LibSceNpSnsFacebookDialog implementation - Lib_Screenshot, ///< The LibSceScreenshot implementation - Lib_LibCInternal, ///< The LibCInternal implementation. - Lib_AppContent, ///< The LibSceAppContent implementation. - Lib_Rtc, ///< The LibSceRtc implementation. - Lib_Rudp, ///< The LibSceRudp implementation. - Lib_DiscMap, ///< The LibSceDiscMap implementation. - Lib_Png, ///< The LibScePng implementation. - Lib_Jpeg, ///< The LibSceJpeg implementation. - Lib_PlayGo, ///< The LibScePlayGo implementation. - Lib_PlayGoDialog, ///< The LibScePlayGoDialog implementation. - Lib_Random, ///< The LibSceRandom implementation. - Lib_Usbd, ///< The LibSceUsbd implementation. - Lib_Ajm, ///< The LibSceAjm implementation. - Lib_ErrorDialog, ///< The LibSceErrorDialog implementation. - Lib_ImeDialog, ///< The LibSceImeDialog implementation. - Lib_AvPlayer, ///< The LibSceAvPlayer implementation. - Lib_Ngs2, ///< The LibSceNgs2 implementation. - Lib_Audio3d, ///< The LibSceAudio3d implementation. - Lib_Ime, ///< The LibSceIme implementation - Lib_GameLiveStreaming, ///< The LibSceGameLiveStreaming implementation - Lib_Remoteplay, ///< The LibSceRemotePlay implementation - Lib_SharePlay, ///< The LibSceSharePlay implemenation - Lib_Fiber, ///< The LibSceFiber implementation. - Lib_Vdec2, ///< The LibSceVideodec2 implementation. - Lib_Videodec, ///< The LibSceVideodec implementation. - Lib_Voice, ///< The LibSceVoice implementation. - Lib_RazorCpu, ///< The LibRazorCpu implementation. - Lib_Mouse, ///< The LibSceMouse implementation - Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation - Lib_NpParty, ///< The LibSceNpParty implementation - Lib_NpPartner, ///< The LibSceNpPartner implementation - Lib_Zlib, ///< The LibSceZlib implementation. - Lib_Hmd, ///< The LibSceHmd implementation. - Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation. - Lib_SigninDialog, ///< The LibSigninDialog implementation. - Lib_Camera, ///< The LibCamera implementation. - Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation. - Lib_CompanionUtil, ///< The LibCompanionUtil implementation. - Lib_VrTracker, ///< The LibSceVrTracker implementation. - Lib_Font, ///< The libSceFont implementation. - Lib_FontFt, ///< The libSceFontFt implementation. - Frontend, ///< Emulator UI - Render, ///< Video Core - Render_Vulkan, ///< Vulkan backend - Render_Recompiler, ///< Shader recompiler - ImGui, ///< ImGui - Loader, ///< ROM loader - Input, ///< Input emulation - Tty, ///< Debug output from emu - KeyManager, ///< Key management system - EmuSettings, /// Emulator settings system - Count ///< Total number of logging classes -}; - -} // namespace Common::Log diff --git a/src/core/devtools/widget/text_editor.cpp b/src/core/devtools/widget/text_editor.cpp index 12031d1efa0..460b4bfd2ad 100644 --- a/src/core/devtools/widget/text_editor.cpp +++ b/src/core/devtools/widget/text_editor.cpp @@ -12,6 +12,7 @@ #include "text_editor.h" #define IMGUI_DEFINE_MATH_OPERATORS +#include "common/types.h" #include "imgui.h" // for imGui::GetCurrentWindow() // TODO diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index c1c0342ead9..295bb4ab933 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -52,6 +52,10 @@ std::optional get_optional(const toml::value& v, const std::string& key) { if (it->second.is_integer()) { return static_cast(toml::get(it->second)); } + } else if constexpr (std::is_same_v) { + if (it->second.is_integer()) { + return static_cast(toml::get(it->second)); + } } else if constexpr (std::is_same_v) { if (it->second.is_floating()) { return toml::get(it->second); @@ -203,14 +207,27 @@ void EmulatorSettingsImpl::SetFontsDir(const std::filesystem::path& dir) { m_general.font_dir.value = dir; } +std::filesystem::path EmulatorSettingsImpl::GetSaveDataPath() { + if (m_general.save_data_path.value.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "savedata"; + } + return m_general.save_data_path.value; +} + +void EmulatorSettingsImpl::SetSaveDataPath(const std::filesystem::path& dir) { + m_general.save_data_path.value = dir; +} + // ── Game-specific override management ──────────────────────────────── void EmulatorSettingsImpl::ClearGameSpecificOverrides() { ClearGroupOverrides(m_general); + ClearGroupOverrides(m_log); ClearGroupOverrides(m_debug); ClearGroupOverrides(m_input); ClearGroupOverrides(m_audio); ClearGroupOverrides(m_gpu); ClearGroupOverrides(m_vulkan); + ClearGroupOverrides(m_keys); LOG_DEBUG(EmuSettings, "All game-specific overrides cleared"); } @@ -227,6 +244,8 @@ void EmulatorSettingsImpl::ResetGameSpecificValue(const std::string& key) { }; if (tryGroup(m_general)) return; + if (tryGroup(m_log)) + return; if (tryGroup(m_debug)) return; if (tryGroup(m_input)) @@ -237,6 +256,8 @@ void EmulatorSettingsImpl::ResetGameSpecificValue(const std::string& key) { return; if (tryGroup(m_vulkan)) return; + if (tryGroup(m_keys)) + return; LOG_WARNING(EmuSettings, "ResetGameSpecificValue: key '{}' not found", key); } @@ -253,6 +274,10 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { SaveGroupGameSpecific(m_general, generalObj); j["General"] = generalObj; + json logObj = json::object(); + SaveGroupGameSpecific(m_log, logObj); + j["Log"] = logObj; + json debugObj = json::object(); SaveGroupGameSpecific(m_debug, debugObj); j["Debug"] = debugObj; @@ -273,6 +298,10 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { SaveGroupGameSpecific(m_vulkan, vulkanObj); j["Vulkan"] = vulkanObj; + json keysObj = json::object(); + SaveGroupGameSpecific(m_keys, keysObj); + j["Keys"] = keysObj; + std::ofstream out(path); if (!out) { LOG_ERROR(EmuSettings, "Failed to open game config for writing: {}", path.string()); @@ -290,11 +319,13 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) { json j; j["General"] = m_general; + j["Log"] = m_log; j["Debug"] = m_debug; j["Input"] = m_input; j["Audio"] = m_audio; j["GPU"] = m_gpu; j["Vulkan"] = m_vulkan; + j["Keys"] = m_keys; // Read the existing file so we can preserve keys unknown to this build json existing = json::object(); @@ -351,11 +382,13 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { }; mergeGroup(m_general, "General"); + mergeGroup(m_log, "Log"); mergeGroup(m_debug, "Debug"); mergeGroup(m_input, "Input"); mergeGroup(m_audio, "Audio"); mergeGroup(m_gpu, "GPU"); mergeGroup(m_vulkan, "Vulkan"); + mergeGroup(m_keys, "Keys"); LOG_DEBUG(EmuSettings, "Global config loaded successfully"); } else { @@ -429,6 +462,8 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { // time without ever touching the base values. if (gj.contains("General")) ApplyGroupOverrides(m_general, gj.at("General"), changed); + if (gj.contains("Log")) + ApplyGroupOverrides(m_log, gj.at("Log"), changed); if (gj.contains("Debug")) ApplyGroupOverrides(m_debug, gj.at("Debug"), changed); if (gj.contains("Input")) @@ -439,6 +474,8 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { ApplyGroupOverrides(m_gpu, gj.at("GPU"), changed); if (gj.contains("Vulkan")) ApplyGroupOverrides(m_vulkan, gj.at("Vulkan"), changed); + if (gj.contains("Keys")) + ApplyGroupOverrides(m_keys, gj.at("Keys"), changed); PrintChangedSummary(changed); EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true); @@ -452,11 +489,13 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) { void EmulatorSettingsImpl::SetDefaultValues() { m_general = GeneralSettings{}; + m_log = LogSettings{}; m_debug = DebugSettings{}; m_input = InputSettings{}; m_audio = AudioSettings{}; m_gpu = GPUSettings{}; m_vulkan = VulkanSettings{}; + m_keys = KeysSettings{}; } bool EmulatorSettingsImpl::TransferSettings() { @@ -467,7 +506,7 @@ bool EmulatorSettingsImpl::TransferSettings() { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs.open(path, std::ios_base::binary); - og_data = toml::parse(ifs, std::string{fmt::UTF(path.filename().u8string()).data}); + og_data = toml::parse(ifs, path.filename().string()); } catch (std::exception& ex) { fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); return false; @@ -486,9 +525,6 @@ bool EmulatorSettingsImpl::TransferSettings() { setFromToml(s.trophy_popup_disabled, general, "isTrophyPopupDisabled"); setFromToml(s.trophy_notification_duration, general, "trophyNotificationDuration"); setFromToml(s.discord_rpc_enabled, general, "enableDiscordRPC"); - setFromToml(s.log_filter, general, "logFilter"); - setFromToml(s.log_type, general, "logType"); - setFromToml(s.identical_log_grouped, general, "isIdenticalLogGrouped"); setFromToml(s.show_splash, general, "showSplash"); setFromToml(s.trophy_notification_side, general, "sideTrophy"); setFromToml(s.connected_to_network, general, "isConnectedToNetwork"); @@ -498,6 +534,23 @@ bool EmulatorSettingsImpl::TransferSettings() { // setFromToml(s.defaultControllerID, general, "defaultControllerID"); } + if (og_data.contains("Log")) { + const toml::value& log = og_data.at("Log"); + auto& s = m_log; + + setFromToml(s.append, log, "append"); + setFromToml(s.enable, log, "enable"); + setFromToml(s.filter, log, "filter"); + setFromToml(s.max_skip_duration, log, "maxSkipDuration"); + setFromToml(s.separate, log, "separate"); + setFromToml(s.size_limit, log, "sizeLimit"); + setFromToml(s.skip_duplicate, log, "skipDuplicate"); + setFromToml(s.sync, log, "sync"); +#ifdef _WIN32 + setFromToml(s.type, log, "type"); +#endif + } + if (og_data.contains("Input")) { const toml::value& input = og_data.at("Input"); auto& s = m_input; @@ -568,9 +621,7 @@ bool EmulatorSettingsImpl::TransferSettings() { auto& s = m_debug; setFromToml(s.debug_dump, debug, "DebugDump"); - setFromToml(s.separate_logging_enabled, debug, "isSeparateLogFilesEnabled"); setFromToml(s.shader_collect, debug, "CollectShader"); - setFromToml(s.log_enabled, debug, "logEnabled"); setFromToml(m_general.show_fps_counter, debug, "showFpsCounter"); } @@ -628,6 +679,12 @@ bool EmulatorSettingsImpl::TransferSettings() { } } + if (og_data.contains("Keys")) { + const toml::value& keys = og_data.at("Keys"); + auto& s = m_keys; + setFromToml(s.trophy_key, keys, "TrophyKey"); + } + return true; } @@ -638,6 +695,7 @@ std::vector EmulatorSettingsImpl::GetAllOverrideableKeys() const { keys.push_back(item.key); }; addGroup(m_general.GetOverrideableFields()); + addGroup(m_log.GetOverrideableFields()); addGroup(m_debug.GetOverrideableFields()); addGroup(m_input.GetOverrideableFields()); addGroup(m_audio.GetOverrideableFields()); diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index 0490aba7724..314e36f9bae 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -13,6 +13,8 @@ #include #include "common/logging/log.h" #include "common/types.h" +#include +#include #define EmulatorSettings (*EmulatorSettingsImpl::GetInstance()) @@ -107,6 +109,28 @@ struct OverrideItem { std::function reset_game_specific; }; +#ifdef foobar +template +struct fmt::formatter> { + constexpr auto parse(format_parse_context& ctx) { + + } + + template + auto format(const std::array& p, FormatContext& ctx) const { + + } +}; + +namespace fmt { +template +auto vformat_to(const std::array& value, string_view fmt, format_args args) -> remove_cvref_t { + for (const auto& v : value) { + return fmt::formatter::format(v, ctx); + } +} +} +#endif template inline OverrideItem make_override(const char* key, Setting Struct::* member) { return OverrideItem{ @@ -122,7 +146,27 @@ inline OverrideItem make_override(const char* key, Setting Struct::* member) LOG_DEBUG(EmuSettings, "[make_override] Current value: {}", dst.value); if (dst.value != newValue) { std::ostringstream oss; - oss << key << " ( " << dst.value << " → " << newValue << " )"; + oss << key << " ( " << key; + + constexpr bool iterable = requires(const T& t) { + t.operator[](0); + }; + + if constexpr (iterable) { + for (const auto v : dst.value) { + oss << v; + } + + oss << " → "; + + for (const auto v : newValue) { + oss << v; + } + } else { + oss << dst.value << " → " << newValue; + } + + oss << " )"; changed.push_back(oss.str()); LOG_DEBUG(EmuSettings, "[make_override] Recorded change: {}", oss.str()); } @@ -170,6 +214,7 @@ struct GeneralSettings { Setting home_dir; Setting sys_modules_dir; Setting font_dir; + Setting save_data_path; Setting volume_slider{100}; Setting neo_mode{false}; @@ -179,10 +224,7 @@ struct GeneralSettings { Setting trophy_popup_disabled{false}; Setting trophy_notification_duration{6.0}; Setting trophy_notification_side{"right"}; - Setting log_filter{""}; - Setting log_type{"sync"}; Setting show_splash{false}; - Setting identical_log_grouped{true}; Setting connected_to_network{false}; Setting discord_rpc_enabled{false}; Setting show_fps_counter{false}; @@ -201,10 +243,6 @@ struct GeneralSettings { &GeneralSettings::trophy_popup_disabled), make_override("trophy_notification_duration", &GeneralSettings::trophy_notification_duration), - make_override("log_filter", &GeneralSettings::log_filter), - make_override("log_type", &GeneralSettings::log_type), - make_override("identical_log_grouped", - &GeneralSettings::identical_log_grouped), make_override("show_splash", &GeneralSettings::show_splash), make_override("trophy_notification_side", &GeneralSettings::trophy_notification_side), @@ -215,32 +253,66 @@ struct GeneralSettings { NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir, sys_modules_dir, font_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, - identical_log_grouped, trophy_notification_side, - connected_to_network, discord_rpc_enabled, show_fps_counter, - console_language) + trophy_notification_duration, show_splash, + trophy_notification_side, connected_to_network, + discord_rpc_enabled, show_fps_counter, console_language) + +// ------------------------------- +// Log settings +// ------------------------------- +struct LogSettings { + Setting append{false}; // specific + Setting enable{true}; // specific + Setting filter{""}; + Setting max_skip_duration{5'000}; + Setting separate{false}; // specific + Setting size_limit{100_MB}; + Setting skip_duplicate{true}; + Setting sync{true}; +#ifdef _WIN32 + Setting type{"wincolor"}; +#endif + + // return a vector of override descriptors (runtime, but tiny) + std::vector GetOverrideableFields() const { + return std::vector{ + make_override("append", &LogSettings::append), + make_override("enable", &LogSettings::enable), + make_override("filter", &LogSettings::filter), + make_override("max_skip_duration", &LogSettings::max_skip_duration), + make_override("separate", &LogSettings::separate), + make_override("size_limit", &LogSettings::size_limit), + make_override("skip_duplicate", &LogSettings::skip_duplicate), + make_override("sync", &LogSettings::sync), +#ifdef _WIN32 + make_override("type", &LogSettings::type) +#endif + }; + } +}; +#ifdef _WIN32 +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LogSettings, append, enable, filter, max_skip_duration, separate, + size_limit, skip_duplicate, sync, type) +#else +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LogSettings, append, enable, filter, max_skip_duration, separate, + size_limit, skip_duplicate, sync) +#endif // ------------------------------- // Debug settings // ------------------------------- struct DebugSettings { - Setting separate_logging_enabled{false}; // specific - Setting debug_dump{false}; // specific - Setting shader_collect{false}; // specific - Setting log_enabled{true}; // specific - Setting config_version{""}; // specific + Setting debug_dump{false}; // specific + Setting shader_collect{false}; // specific + Setting config_version{""}; // specific std::vector GetOverrideableFields() const { return std::vector{ make_override("debug_dump", &DebugSettings::debug_dump), - make_override("shader_collect", &DebugSettings::shader_collect), - make_override("separate_logging_enabled", - &DebugSettings::separate_logging_enabled), - make_override("log_enabled", &DebugSettings::log_enabled)}; + make_override("shader_collect", &DebugSettings::shader_collect)}; } }; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump, - shader_collect, log_enabled, config_version) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, debug_dump, shader_collect, config_version) // ------------------------------- // Input settings @@ -257,6 +329,8 @@ struct InputSettings { Setting default_controller_id{""}; Setting background_controller_input{false}; // specific Setting camera_id{-1}; + Setting override_controller_color{false}; + Setting> controller_custom_color_rgb{{0, 0, 255}}; std::vector GetOverrideableFields() const { return std::vector{ @@ -268,7 +342,8 @@ struct InputSettings { &InputSettings::motion_controls_enabled), make_override("background_controller_input", &InputSettings::background_controller_input), - make_override("camera_id", &InputSettings::camera_id)}; + make_override("camera_id", &InputSettings::camera_id), + make_override("controller_custom_color_rgb", &InputSettings::controller_custom_color_rgb)}; } }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings, cursor_state, cursor_hide_timeout, @@ -406,6 +481,21 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id, renderdoc_enabled, vk vkhost_markers, vkguest_markers, pipeline_cache_enabled, pipeline_cache_archived) +// ------------------------------- +// Keys settings +// ------------------------------- +struct KeysSettings { + Setting trophy_key{}; + + std::vector GetOverrideableFields() const { + return std::vector{ + make_override("trophy_key", + &KeysSettings::trophy_key), + }; + } +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(KeysSettings, trophy_key) + // ------------------------------- // Main manager // ------------------------------- @@ -455,14 +545,18 @@ class EmulatorSettingsImpl { void SetSysModulesDir(const std::filesystem::path& dir); std::filesystem::path GetFontsDir(); void SetFontsDir(const std::filesystem::path& dir); + std::filesystem::path GetSaveDataPath(); + void SetSaveDataPath(const std::filesystem::path& dir); private: GeneralSettings m_general{}; + LogSettings m_log{}; DebugSettings m_debug{}; InputSettings m_input{}; AudioSettings m_audio{}; GPUSettings m_gpu{}; VulkanSettings m_vulkan{}; + KeysSettings m_keys{}; ConfigMode m_configMode{ConfigMode::Default}; static std::shared_ptr s_instance; @@ -546,15 +640,25 @@ class EmulatorSettingsImpl { SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration) SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side) SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash) - SETTING_FORWARD_BOOL(m_general, IdenticalLogGrouped, identical_log_grouped) SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir) - SETTING_FORWARD(m_general, LogFilter, log_filter) - SETTING_FORWARD(m_general, LogType, log_type) SETTING_FORWARD_BOOL(m_general, ConnectedToNetwork, connected_to_network) SETTING_FORWARD_BOOL(m_general, DiscordRPCEnabled, discord_rpc_enabled) SETTING_FORWARD_BOOL(m_general, ShowFpsCounter, show_fps_counter) SETTING_FORWARD(m_general, ConsoleLanguage, console_language) + // Log settings + SETTING_FORWARD_BOOL(m_log, LogAppend, append) + SETTING_FORWARD_BOOL(m_log, LogEnable, enable) + SETTING_FORWARD(m_log, LogFilter, filter) + SETTING_FORWARD(m_log, LogMaxSkipDuration, max_skip_duration) + SETTING_FORWARD_BOOL(m_log, LogSeparate, separate) + SETTING_FORWARD(m_log, LogSizeLimit, size_limit) + SETTING_FORWARD_BOOL(m_log, LogSkipDuplicate, skip_duplicate) + SETTING_FORWARD_BOOL(m_log, LogSync, sync) +#ifdef _WIN32 + SETTING_FORWARD(m_log, LogType, type) +#endif + // Audio settings SETTING_FORWARD(m_audio, AudioBackend, audio_backend) SETTING_FORWARD(m_audio, SDLMicDevice, sdl_mic_device) @@ -565,10 +669,8 @@ class EmulatorSettingsImpl { SETTING_FORWARD(m_audio, OpenALPadSpkOutputDevice, openal_padSpk_output_device) // Debug settings - SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled) SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump) SETTING_FORWARD_BOOL(m_debug, ShaderCollect, shader_collect) - SETTING_FORWARD_BOOL(m_debug, LogEnabled, log_enabled) SETTING_FORWARD(m_debug, ConfigVersion, config_version) // GPU Settings @@ -616,6 +718,8 @@ class EmulatorSettingsImpl { SETTING_FORWARD(m_input, SpecialPadClass, special_pad_class) SETTING_FORWARD_BOOL(m_input, UseUnifiedInputConfig, use_unified_input_config) SETTING_FORWARD(m_input, CameraId, camera_id) + SETTING_FORWARD(m_input, OverrideControllerColor, override_controller_color) + SETTING_FORWARD(m_input, ControllerCustomColor, controller_custom_color_rgb) // Vulkan settings SETTING_FORWARD(m_vulkan, GpuId, gpu_id) @@ -630,6 +734,9 @@ class EmulatorSettingsImpl { SETTING_FORWARD_BOOL(m_vulkan, PipelineCacheEnabled, pipeline_cache_enabled) SETTING_FORWARD_BOOL(m_vulkan, PipelineCacheArchived, pipeline_cache_archived) + // Keys settings + SETTING_FORWARD(m_keys, TrophyKey, trophy_key) + #undef SETTING_FORWARD #undef SETTING_FORWARD_BOOL #undef SETTING_FORWARD_BOOL_READONLY diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 0522c3d8a5d..0c7856b6bbb 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -10,7 +10,6 @@ #include #include #include "common/io_file.h" -#include "common/logging/formatter.h" #include "core/file_sys/devices/base_device.h" #include "core/file_sys/directories/base_directory.h" @@ -54,7 +53,7 @@ class MntPoints { const MntPair* GetMountFromHostPath(const std::string& host_path) { std::scoped_lock lock{m_mutex}; const auto it = std::ranges::find_if(m_mnt_pairs, [&](const MntPair& mount) { - return host_path.starts_with(std::string{fmt::UTF(mount.host_path.u8string()).data}); + return host_path.starts_with(mount.host_path.string()); }); return it == m_mnt_pairs.end() ? nullptr : &*it; } diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 3ab2ed4ab0e..5a4d4ec17f2 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -158,7 +158,7 @@ AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buf break; } default: - UNREACHABLE_MSG("Unknown chunk: {}", header.ident); + UNREACHABLE_MSG("Unknown chunk: {}", +header.ident); } } @@ -257,7 +257,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { break; } default: - UNREACHABLE_MSG("Unknown chunk: {}", header.ident); + UNREACHABLE_MSG("Unknown chunk: {}", +header.ident); } } diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index 96ae446fa00..82f48e52ead 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -12,8 +12,8 @@ namespace Libraries::Ime { static std::queue g_ime_events; -static ImeState g_ime_state{}; -static ImeUi g_ime_ui; +static std::unique_ptr g_ime_state{}; +static std::unique_ptr g_ime_ui; class ImeHandler { public: @@ -67,14 +67,14 @@ class ImeHandler { }*/ if (ime_mode) { - g_ime_state = ImeState(&m_param.ime, &m_param.ime_ext); - g_ime_ui = ImeUi(&g_ime_state, &m_param.ime, &m_param.ime_ext); + g_ime_state = std::make_unique(&m_param.ime, &m_param.ime_ext); + g_ime_ui = std::make_unique(g_ime_state.get(), &m_param.ime, &m_param.ime_ext); // Queue the Open event so it is delivered on next sceImeUpdate LOG_DEBUG(Lib_Ime, "IME Event queued: Open rect x={}, y={}, w={}, h={}", openEvent.param.rect.x, openEvent.param.rect.y, openEvent.param.rect.width, openEvent.param.rect.height); - g_ime_state.SendEvent(&openEvent); + g_ime_state->SendEvent(&openEvent); } } @@ -84,11 +84,11 @@ class ImeHandler { return Error::OK; } - std::unique_lock lock{g_ime_state.queue_mutex}; + std::unique_lock lock{g_ime_state->queue_mutex}; - while (!g_ime_state.event_queue.empty()) { - OrbisImeEvent event = g_ime_state.event_queue.front(); - g_ime_state.event_queue.pop(); + while (!g_ime_state->event_queue.empty()) { + OrbisImeEvent event = g_ime_state->event_queue.front(); + g_ime_state->event_queue.pop(); Execute(handler, &event, false); } @@ -118,7 +118,7 @@ class ImeHandler { LOG_WARNING(Lib_Ime, "ImeHandler::SetText received null text pointer"); return Error::INVALID_ADDRESS; } - g_ime_state.SetText(text, length); + g_ime_state->SetText(text, length); return Error::OK; } @@ -127,7 +127,7 @@ class ImeHandler { LOG_WARNING(Lib_Ime, "ImeHandler::SetCaret received null caret pointer"); return Error::INVALID_ADDRESS; } - g_ime_state.SetCaret(caret->index); + g_ime_state->SetCaret(caret->index); return Error::OK; } @@ -185,8 +185,8 @@ Error PS4_SYSV_ABI sceImeClose() { LOG_ERROR(Lib_Ime, "Failed to close IME handler, it is still open"); return Error::INTERNAL; } - g_ime_ui = ImeUi(); - g_ime_state = ImeState(); + g_ime_ui = std::make_unique(); + g_ime_state = std::make_unique(); LOG_DEBUG(Lib_Ime, "IME closed successfully"); return Error::OK; diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index 229ae33af87..5ac4a3961ac 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -7,11 +7,13 @@ #include "common/config.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_manager.h" #include "core/tls.h" +#include "core/user_settings.h" namespace Libraries::Np::NpManager { @@ -631,7 +633,8 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use return ORBIS_NP_ERROR_SIGNED_OUT; } memset(np_id, 0, sizeof(OrbisNpId)); - strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data)); + const auto name = UserManagement.GetUserByID(user_id)->user_name; + strncpy(np_id->handle.data, name.c_str(), sizeof(np_id->handle.data)); return ORBIS_OK; } @@ -645,7 +648,8 @@ s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId return ORBIS_NP_ERROR_SIGNED_OUT; } memset(online_id, 0, sizeof(OrbisNpOnlineId)); - strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data)); + const auto name = UserManagement.GetUserByID(user_id)->user_name; + strncpy(online_id->data, name.c_str(), sizeof(online_id->data)); return ORBIS_OK; } @@ -784,7 +788,7 @@ void DeregisterNpCallback(std::string key) { } void RegisterLib(Core::Loader::SymbolsResolver* sym) { - g_signed_in = Config::getPSNSignedIn(); + g_signed_in = EmulatorSettings.IsPSNSignedIn(); LIB_FUNCTION("GpLQDNKICac", "libSceNpManager", 1, "libSceNpManager", sceNpCreateRequest); LIB_FUNCTION("eiqMCt9UshI", "libSceNpManager", 1, "libSceNpManager", sceNpCreateAsyncRequest); diff --git a/src/core/libraries/np/trophy_ui.cpp b/src/core/libraries/np/trophy_ui.cpp index a4fd21a33cc..0d6943ff587 100644 --- a/src/core/libraries/np/trophy_ui.cpp +++ b/src/core/libraries/np/trophy_ui.cpp @@ -38,8 +38,7 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin if (std::filesystem::exists(trophyIconPath)) { trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath); } else { - LOG_ERROR(Lib_NpTrophy, "Couldnt load trophy icon at {}", - fmt::UTF(trophyIconPath.u8string())); + LOG_ERROR(Lib_NpTrophy, "Couldnt load trophy icon at {}", trophyIconPath.string()); } std::string pathString = "src/images/"; diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index b31ed1f0b4b..8b4345292b0 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -4,6 +4,7 @@ #include "common/config.h" #include "common/logging/log.h" #include "common/singleton.h" +#include "core/emulator_settings.h" #include "core/libraries/libs.h" #include "core/libraries/pad/pad_errors.h" #include "input/controller.h" @@ -30,8 +31,8 @@ int PS4_SYSV_ABI scePadDeviceClassGetExtendedInformation( s32 handle, OrbisPadDeviceClassExtendedInformation* pExtInfo) { LOG_ERROR(Lib_Pad, "(STUBBED) called"); std::memset(pExtInfo, 0, sizeof(OrbisPadDeviceClassExtendedInformation)); - if (Config::getUseSpecialPad()) { - pExtInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); + if (EmulatorSettings.IsUsingSpecialPad()) { + pExtInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass(); } return ORBIS_OK; } @@ -107,9 +108,9 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn return ORBIS_OK; } pInfo->connected = true; - if (Config::getUseSpecialPad()) { + if (EmulatorSettings.IsUsingSpecialPad()) { pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL; - pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); + pInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass(); } return ORBIS_OK; } @@ -257,7 +258,7 @@ int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userI if (userId == -1) { return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE; } - if (Config::getUseSpecialPad()) { + if (EmulatorSettings.IsUsingSpecialPad()) { if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } else { @@ -274,7 +275,7 @@ int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userI int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type, s32 index, const OrbisPadOpenExtParam* pParam) { LOG_ERROR(Lib_Pad, "(STUBBED) called"); - if (Config::getUseSpecialPad()) { + if (EmulatorSettings.IsUsingSpecialPad()) { if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } else { @@ -455,7 +456,7 @@ int PS4_SYSV_ABI scePadResetLightBar(s32 handle) { return ORBIS_PAD_ERROR_INVALID_HANDLE; } auto* controller = Common::Singleton::Instance(); - int* rgb = Config::GetControllerCustomColor(); + const auto rgb = EmulatorSettings.GetControllerCustomColor(); controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]); return ORBIS_OK; } diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp index c5f66d883e4..a51d812171e 100644 --- a/src/core/libraries/save_data/save_backup.cpp +++ b/src/core/libraries/save_data/save_backup.cpp @@ -11,7 +11,6 @@ #include "save_instance.h" #include "common/logging/log.h" -#include "common/logging/log_entry.h" #include "common/polyfill_thread.h" #include "common/thread.h" @@ -107,16 +106,14 @@ static void BackupThreadBody() { } g_backup_status = WorkerStatus::Running; - LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", - fmt::UTF(req.save_path.u8string())); + LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string()); try { backup(req.save_path); } catch (const std::filesystem::filesystem_error& err) { - LOG_ERROR(Lib_SaveData, "Failed to backup {}: {}", fmt::UTF(req.save_path.u8string()), - err.what()); + LOG_ERROR(Lib_SaveData, "Failed to backup {}: {}", req.save_path.string(), err.what()); } LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished", - fmt::UTF(req.save_path.u8string())); + req.save_path.string()); { std::scoped_lock lk{g_backup_queue_mutex}; g_backup_queue.front().done = true; @@ -173,7 +170,7 @@ bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::str if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) { LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored", - magic_enum::enum_name(g_backup_status.load()), fmt::UTF(save_path.u8string())); + magic_enum::enum_name(g_backup_status.load()), save_path.string()); return false; } { @@ -197,7 +194,7 @@ bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::str } bool Restore(const std::filesystem::path& save_path) { - LOG_INFO(Lib_SaveData, "Restoring backup for {}", fmt::UTF(save_path.u8string())); + LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string()); std::unique_lock lk{g_backup_running_mutex}; if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) { return false; diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index baeec5d2c40..4cd0721262d 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -9,6 +9,7 @@ #include "common/config.h" #include "common/path_util.h" #include "common/singleton.h" +#include "core/emulator_settings.h" #include "core/file_sys/fs.h" #include "save_backup.h" #include "save_instance.h" @@ -48,12 +49,12 @@ namespace Libraries::SaveData { fs::path SaveInstance::MakeTitleSavePath(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial) { - return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial; + return EmulatorSettings.GetSaveDataPath() / std::to_string(user_id) / game_serial; } fs::path SaveInstance::MakeDirSavePath(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial, std::string_view dir_name) { - return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name; + return EmulatorSettings.GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name; } uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) { @@ -71,7 +72,7 @@ fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) { void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial) { - int locale = Config::GetLanguage(); + int locale = EmulatorSettings.GetConsoleLanguage(); if (!default_title.contains(locale)) { locale = 1; // default to en_US if not found } diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 48b0864578f..7dd559432d9 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -15,6 +15,7 @@ #include "common/logging/log.h" #include "common/path_util.h" #include "common/string_util.h" +#include "core/emulator_settings.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" @@ -441,7 +442,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info, LOG_INFO(Lib_SaveData, "called with invalid block size"); } - const auto root_save = Config::GetSaveDataPath(); + const auto root_save = EmulatorSettings.GetSaveDataPath(); fs::create_directories(root_save); const auto available = fs::space(root_save).available; @@ -615,7 +616,7 @@ Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData if (check->param != nullptr) { PSF sfo; if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) { - LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", fmt::UTF(backup_path.u8string())); + LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", backup_path.string()); return Error::INTERNAL; } check->param->FromSFO(sfo); @@ -826,7 +827,7 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path); PSF sfo; if (!sfo.Open(sfo_path)) { - LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", fmt::UTF(sfo_path.u8string())); + LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", sfo_path.string()); ASSERT_MSG(false, "Failed to read SFO"); } diff --git a/src/core/libraries/system/userservice.cpp b/src/core/libraries/system/userservice.cpp index 508b1d7e58f..63d49f963f2 100644 --- a/src/core/libraries/system/userservice.cpp +++ b/src/core/libraries/system/userservice.cpp @@ -7,6 +7,7 @@ #include "core/libraries/libs.h" #include "core/libraries/system/userservice.h" #include "core/libraries/system/userservice_error.h" +#include "core/user_settings.h" namespace Libraries::UserService { @@ -1073,7 +1074,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::si LOG_ERROR(Lib_UserService, "user_name is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; } - std::string name = Config::getUserName(); + const auto name = UserManagement.GetUserByID(user_id)->user_name; if (size < name.length()) { LOG_ERROR(Lib_UserService, "buffer is too short"); return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT; diff --git a/src/core/thread.cpp b/src/core/thread.cpp index 0015f40b995..8edb9e8c959 100644 --- a/src/core/thread.cpp +++ b/src/core/thread.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/alignment.h" +#include "common/assert.h" #include "core/libraries/kernel/threads/pthread.h" #include "thread.h" #ifdef _WIN64 diff --git a/src/emulator.cpp b/src/emulator.cpp index 5206a309d11..2f1e56ef854 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -11,7 +11,6 @@ #include #include "common/debug.h" -#include "common/logging/backend.h" #include "common/logging/log.h" #include "common/thread.h" #include "core/emulator_settings.h" @@ -206,12 +205,10 @@ void Emulator::Run(std::filesystem::path file, std::vector args, } // Initialize logging as soon as possible - if (!id.empty() && EmulatorSettings.IsSeparateLoggingEnabled()) { - Common::Log::Initialize(id + ".log"); - } else { - Common::Log::Initialize(); + if (!id.empty() && EmulatorSettings.IsLogSeparate()) { + Common::Log::Redirect(id + ".log"); } - Common::Log::Start(); + if (!std::filesystem::exists(file)) { LOG_CRITICAL(Loader, "eboot.bin does not exist: {}", std::filesystem::absolute(file).string()); @@ -228,12 +225,16 @@ void Emulator::Run(std::filesystem::path file, std::vector args, Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".json")); LOG_INFO(Config, "Game-specific config exists: {}", has_game_config); - LOG_INFO(Config, "General LogType: {}", EmulatorSettings.GetLogType()); - LOG_INFO(Config, "General isIdenticalLogGrouped: {}", EmulatorSettings.IsIdenticalLogGrouped()); LOG_INFO(Config, "General isNeo: {}", EmulatorSettings.IsNeo()); LOG_INFO(Config, "General isDevKit: {}", EmulatorSettings.IsDevKit()); LOG_INFO(Config, "General isConnectedToNetwork: {}", EmulatorSettings.IsConnectedToNetwork()); LOG_INFO(Config, "General isPsnSignedIn: {}", EmulatorSettings.IsPSNSignedIn()); + LOG_INFO(Config, "Log sync: {}", EmulatorSettings.IsLogSync()); +#ifdef _WIN32 + LOG_INFO(Config, "Log type: {}", EmulatorSettings.GetLogType()); +#endif + LOG_INFO(Config, "Log skipDuplicate: {}", EmulatorSettings.IsLogSkipDuplicate()); + LOG_INFO(Config, "Log filter: {}", EmulatorSettings.GetLogFilter()); LOG_INFO(Config, "GPU isNullGpu: {}", EmulatorSettings.IsNullGPU()); LOG_INFO(Config, "GPU readbacksMode: {}", EmulatorSettings.GetReadbacksMode()); LOG_INFO(Config, "GPU readbackLinearImages: {}", @@ -476,7 +477,8 @@ void Emulator::Restart(std::filesystem::path eboot_path, LOG_INFO(Common, "Restarting the emulator with args: {}", fmt::join(args, " ")); Libraries::SaveData::Backup::StopThread(); - Common::Log::Denitializer(); + + Common::Log::Shutdown(); auto& ipc = IPC::Instance(); @@ -506,7 +508,7 @@ void Emulator::Restart(std::filesystem::path eboot_path, nullptr, &si, &pi); if (!success) { - std::cerr << "Failed to restart game: {}" << GetLastError() << std::endl; + std::cerr << "Failed to restart game: " << GetLastError() << std::endl; std::quick_exit(1); } diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 3606ad5d2c1..de6571e5f83 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -4,6 +4,7 @@ #include #include "common/config.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" #include "input/controller.h" @@ -245,11 +246,11 @@ namespace GamepadSelect { int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) { char GUIDbuf[33]; - if (Config::getDefaultControllerID() != "") { + if (EmulatorSettings.GetDefaultControllerId() != "") { for (int i = 0; i < gamepadCount; i++) { SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33); std::string currentGUID = std::string(GUIDbuf); - if (currentGUID == Config::getDefaultControllerID()) { + if (currentGUID == EmulatorSettings.GetDefaultControllerId()) { return i; } } diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index e44693fbf0d..a0183b977d5 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -23,6 +23,7 @@ #include "common/io_file.h" #include "common/path_util.h" #include "core/devtools/layer.h" +#include "core/emulator_settings.h" #include "core/emulator_state.h" #include "input/controller.h" #include "input/input_mouse.h" @@ -223,7 +224,7 @@ InputBinding GetBindingFromString(std::string& line) { } void ParseInputConfig(const std::string game_id = "") { - std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id; + std::string game_id_or_default = EmulatorSettings.IsUseUnifiedInputConfig() ? "default" : game_id; const auto config_file = Config::GetInputConfigFile(game_id_or_default); const auto global_config_file = Config::GetInputConfigFile("global"); @@ -239,8 +240,8 @@ void ParseInputConfig(const std::string game_id = "") { lefttrigger_deadzone = {1, 127}; righttrigger_deadzone = {1, 127}; - Config::SetOverrideControllerColor(false); - Config::SetControllerCustomColor(0, 0, 255); + EmulatorSettings.SetOverrideControllerColor(false); + EmulatorSettings.SetControllerCustomColor({0, 0, 255}); int lineCount = 0; @@ -389,8 +390,8 @@ void ParseInputConfig(const std::string game_id = "") { lineCount, line); return; } - Config::SetOverrideControllerColor(enable == "true"); - Config::SetControllerCustomColor(*r, *g, *b); + EmulatorSettings.SetOverrideControllerColor(enable == "true"); + EmulatorSettings.SetControllerCustomColor({*r, *g, *b}); LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}", enable == "true" ? "override" : "no override", *r, *b, *g); return; @@ -598,12 +599,12 @@ void ControllerOutput::FinalizeUpdate() { PushSDLEvent(SDL_EVENT_RDOC_CAPTURE); break; case HOTKEY_VOLUME_UP: - Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() + 10, 0, 500), + Config::setVolumeSlider(std::clamp(EmulatorSettings.GetVolumeSlider() + 10, 0, 500), is_game_specific); Overlay::ShowVolume(); break; case HOTKEY_VOLUME_DOWN: - Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() - 10, 0, 500), + Config::setVolumeSlider(std::clamp(EmulatorSettings.GetVolumeSlider() - 10, 0, 500), is_game_specific); Overlay::ShowVolume(); break; diff --git a/src/main.cpp b/src/main.cpp index fb9ff307844..757e29759d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,51 +13,28 @@ #include #include "common/config.h" #include "common/key_manager.h" -#include "common/logging/backend.h" +#include "common/logging/log.h" #include "common/memory_patcher.h" #include "common/path_util.h" #include "core/debugger.h" #include "core/file_sys/fs.h" #include "core/ipc/ipc.h" +#include #include "emulator.h" #ifdef _WIN32 #include #endif -#include int main(int argc, char* argv[]) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); #endif - IPC::Instance().Init(); - - auto emu_state = std::make_shared(); - EmulatorState::SetInstance(emu_state); - UserSettings.Load(); - + // 1. Load config const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::load(user_dir / "config.toml"); - - // ---- Trophy key migration ---- - auto key_manager = KeyManager::GetInstance(); - key_manager->LoadFromFile(); - if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty() && - !Config::getTrophyKey().empty()) { - auto keys = key_manager->GetAllKeys(); - if (keys.TrophyKeySet.ReleaseTrophyKey.empty() && !Config::getTrophyKey().empty()) { - keys.TrophyKeySet.ReleaseTrophyKey = - KeyManager::HexStringToBytes(Config::getTrophyKey()); - key_manager->SetAllKeys(keys); - key_manager->SaveToFile(); - } - } - - // Load configurations - std::shared_ptr emu_settings = std::make_shared(); - EmulatorSettingsImpl::SetInstance(emu_settings); - emu_settings->Load(); + EmulatorSettings.Load(user_dir / "config.toml"); + // 2. Load CLI CLI::App app{"shadPS4 Emulator CLI"}; // ---- CLI state ---- @@ -72,7 +49,7 @@ int main(int argc, char* argv[]) { bool showFps = false; bool configClean = false; bool configGlobal = false; - bool logAppend = false; + Common::Log::g_should_append = EmulatorSettings.IsLogAppend(); std::optional addGameFolder; std::optional setAddonFolder; @@ -95,7 +72,7 @@ int main(int argc, char* argv[]) { app.add_flag("--show-fps", showFps); app.add_flag("--config-clean", configClean); app.add_flag("--config-global", configGlobal); - app.add_flag("--log-append", logAppend); + app.add_flag("--log-append", Common::Log::g_should_append); app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory); app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory); @@ -125,6 +102,35 @@ int main(int argc, char* argv[]) { return app.exit(e); } + // 3. Wait for potential previous instance + if (waitPid) + Core::Debugger::WaitForPid(*waitPid); + + // 4. Load Log + Common::Log::Setup(argc, argv); + + // 5. Load IPC + IPC::Instance().Init(); + + // 6. Load emulator + auto emu_state = std::make_shared(); + EmulatorState::SetInstance(emu_state); + UserSettings.Load(); + + // ---- Trophy key migration ---- + auto key_manager = KeyManager::GetInstance(); + key_manager->LoadFromFile(); + if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty() && + !EmulatorSettings.GetTrophyKey().empty()) { + auto keys = key_manager->GetAllKeys(); + if (keys.TrophyKeySet.ReleaseTrophyKey.empty() && !EmulatorSettings.GetTrophyKey().empty()) { + keys.TrophyKeySet.ReleaseTrophyKey = + KeyManager::HexStringToBytes(EmulatorSettings.GetTrophyKey()); + key_manager->SetAllKeys(keys); + key_manager->SaveToFile(); + } + } + // ---- Utility commands ---- if (addGameFolder) { EmulatorSettings.AddGameInstallDir(*addGameFolder); @@ -185,8 +191,8 @@ int main(int argc, char* argv[]) { if (configGlobal) EmulatorSettings.SetConfigMode(ConfigMode::Global); - if (logAppend) - Common::Log::SetAppend(); + if (!Common::Log::g_should_append) + Common::Log::Truncate(); // ---- Resolve game path or ID ---- std::filesystem::path ebootPath(*gamePath); @@ -206,9 +212,6 @@ int main(int argc, char* argv[]) { } } - if (waitPid) - Core::Debugger::WaitForPid(*waitPid); - auto* emulator = Common::Singleton::Instance(); emulator->executableName = argv[0]; emulator->waitForDebuggerBeforeRun = waitForDebugger; diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 0c7374bfb57..f1b923078f2 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -12,6 +12,7 @@ #include "common/elf_info.h" #include "core/debug_state.h" #include "core/devtools/layer.h" +#include "core/emulator_settings.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" #include "imgui/renderer/imgui_core.h" @@ -114,7 +115,7 @@ void SDLInputEngine::Init() { int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, GamepadSelect::GetSelectedGamepad()); int defaultIndex = - GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID()); + GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, EmulatorSettings.GetDefaultControllerId()); // If user selects a gamepad in the GUI, use that, otherwise try the default if (!m_gamepad) { @@ -149,7 +150,7 @@ void SDLInputEngine::Init() { LOG_INFO(Input, "Detected DualSense Controller"); } - if (Config::getIsMotionControlsEnabled()) { + if (EmulatorSettings.IsMotionControlsEnabled()) { if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) { m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO); LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate); @@ -170,7 +171,7 @@ void SDLInputEngine::Init() { SDL_free(gamepads); - int* rgb = Config::GetControllerCustomColor(); + const auto rgb = EmulatorSettings.GetControllerCustomColor(); if (isDualSense) { if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) { @@ -323,9 +324,9 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ } if (!error) { SDL_SetWindowFullscreenMode( - window, Config::getFullscreenMode() == "Fullscreen" ? displayMode : NULL); + window, EmulatorSettings.GetFullScreenMode() == "Fullscreen" ? displayMode : NULL); } - SDL_SetWindowFullscreen(window, Config::getIsFullscreen()); + SDL_SetWindowFullscreen(window, EmulatorSettings.IsFullScreen()); SDL_SyncWindow(window); SDL_InitSubSystem(SDL_INIT_GAMEPAD); @@ -358,7 +359,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ Input::ControllerOutput::LinkJoystickAxes(); Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); - if (Config::getBackgroundControllerInput()) { + if (EmulatorSettings.IsBackgroundControllerInput()) { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); } } diff --git a/src/video_core/renderdoc.cpp b/src/video_core/renderdoc.cpp index b0275221299..9fb10d15ed4 100644 --- a/src/video_core/renderdoc.cpp +++ b/src/video_core/renderdoc.cpp @@ -122,7 +122,7 @@ void SetOutputDir(const std::filesystem::path& path, const std::string& prefix) return; } LOG_WARNING(Common, "RenderDoc capture path: {}", (path / prefix).string()); - rdoc_api->SetCaptureFilePathTemplate(fmt::UTF((path / prefix).u8string()).data.data()); + rdoc_api->SetCaptureFilePathTemplate((path / prefix).string().data()); } } // namespace VideoCore diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 754397214a5..8b10725baf8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -133,7 +133,7 @@ class PipelineCache { ComputePipelineKey compute_key{}; u32 num_new_pipelines{}; // new pipelines added to the cache since the game start - // Only if Config::collectShadersForDebug() + // Only if EmulatorSettings.collectShadersForDebug() tsl::robin_map>> module_related_pipelines; diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index c77c8022363..35a7b7d7968 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -36,23 +36,23 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { - Common::Log::Level level{}; + spdlog::level::level_enum level{}; switch (severity) { case vk::DebugUtilsMessageSeverityFlagBitsEXT::eError: - level = Common::Log::Level::Error; + level = spdlog::level::err; break; case vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning: - level = Common::Log::Level::Info; + level = spdlog::level::info; break; case vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo: case vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose: - level = Common::Log::Level::Debug; + level = spdlog::level::debug; break; default: - level = Common::Log::Level::Info; + level = spdlog::level::info; } - LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, "{}: {}", + LOG_GENERIC(Common::Log::Render_Vulkan, level, "{}: {}", callback_data->pMessageIdName ? callback_data->pMessageIdName : "", callback_data->pMessage ? callback_data->pMessage : ""); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 16371275692..507238d7c00 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -415,7 +415,7 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag cache_image.info.size.height, cache_image.info.size.depth, cache_image.info.pitch, cache_image.info.resources.levels, cache_image.info.resources.layers, cache_image.info.num_samples, static_cast(cache_image.info.tile_mode), - cache_image.info.num_bits, cache_image.info.props.is_block, + cache_image.info.num_bits, +cache_image.info.props.is_block, cache_image.info.guest_size, cache_image.tick_accessed_last, safe_to_delete, // New image details