diff --git a/Ryujinx.Common/Logging/LogLevel.cs b/Ryujinx.Common/Logging/LogLevel.cs index ba3fa99f4d..5f80714f1a 100644 --- a/Ryujinx.Common/Logging/LogLevel.cs +++ b/Ryujinx.Common/Logging/LogLevel.cs @@ -6,6 +6,8 @@ namespace Ryujinx.Common.Logging Stub, Info, Warning, - Error + Error, + Guest, + AccessLog } } diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs index 88ebe4731a..95b67c8ede 100644 --- a/Ryujinx.Common/Logging/Logger.cs +++ b/Ryujinx.Common/Logging/Logger.cs @@ -22,10 +22,12 @@ namespace Ryujinx.Common.Logging m_EnabledLevels = new bool[Enum.GetNames(typeof(LogLevel)).Length]; m_EnabledClasses = new bool[Enum.GetNames(typeof(LogClass)).Length]; - m_EnabledLevels[(int)LogLevel.Stub] = true; - m_EnabledLevels[(int)LogLevel.Info] = true; - m_EnabledLevels[(int)LogLevel.Warning] = true; - m_EnabledLevels[(int)LogLevel.Error] = true; + m_EnabledLevels[(int)LogLevel.Stub] = true; + m_EnabledLevels[(int)LogLevel.Info] = true; + m_EnabledLevels[(int)LogLevel.Warning] = true; + m_EnabledLevels[(int)LogLevel.Error] = true; + m_EnabledLevels[(int)LogLevel.Guest] = true; + m_EnabledLevels[(int)LogLevel.AccessLog] = true; for (int index = 0; index < m_EnabledClasses.Length; index++) { @@ -101,6 +103,16 @@ namespace Ryujinx.Common.Logging Print(LogLevel.Stub, logClass, GetFormattedMessage(logClass, "Stubbed. " + message, caller), obj); } + public static void PrintGuest(LogClass logClass, string message, [CallerMemberName] string caller = "") + { + Print(LogLevel.Guest, logClass, GetFormattedMessage(logClass, message, caller)); + } + + public static void PrintAccessLog(LogClass logClass, string message) + { + Print(LogLevel.AccessLog, logClass, message); + } + private static void Print(LogLevel logLevel, LogClass logClass, string message) { if (m_EnabledLevels[(int)logLevel] && m_EnabledClasses[(int)logClass]) diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 05b2b28fa5..51db8884c0 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -105,6 +105,8 @@ namespace Ryujinx.HLE.HOS public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; } + public int GlobalAccessLogMode { get; set; } + internal long HidBaseAddress { get; private set; } public Horizon(Switch device) diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs index 05abb7f068..2398936549 100644 --- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs @@ -1,6 +1,7 @@ using LibHac; using LibHac.Fs; using LibHac.Fs.NcaUtils; +using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.Utilities; @@ -32,7 +33,8 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv { 200, OpenDataStorageByCurrentProcess }, { 202, OpenDataStorageByDataId }, { 203, OpenPatchDataStorageByCurrentProcess }, - { 1005, GetGlobalAccessLogMode } + { 1005, GetGlobalAccessLogMode }, + { 1006, OutputAccessLogToSdCard } }; } @@ -208,7 +210,20 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv // GetGlobalAccessLogMode() -> u32 logMode public long GetGlobalAccessLogMode(ServiceCtx context) { - context.ResponseData.Write(0); + int mode = context.Device.System.GlobalAccessLogMode; + + context.ResponseData.Write(mode); + + return 0; + } + + // OutputAccessLogToSdCard(buffer log_text) + public long OutputAccessLogToSdCard(ServiceCtx context) + { + string message = ReadUtf8StringSend(context); + + // FS ends each line with a newline. Remove it because Ryujinx logging adds its own newline + Logger.PrintAccessLog(LogClass.ServiceFs, message.TrimEnd('\n')); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs index 070cf9aed1..9122970c6a 100644 --- a/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs +++ b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs @@ -41,6 +41,8 @@ namespace Ryujinx.HLE.HOS.Services.Lm sb.AppendLine("Guest log:"); + sb.AppendLine($" Log level: {(LmLogLevel)level}"); + while (ms.Position < ms.Length) { byte type = reader.ReadByte(); @@ -86,14 +88,7 @@ namespace Ryujinx.HLE.HOS.Services.Lm string text = sb.ToString(); - switch((LmLogLevel)level) - { - case LmLogLevel.Trace: Logger.PrintDebug (LogClass.ServiceLm, text); break; - case LmLogLevel.Info: Logger.PrintInfo (LogClass.ServiceLm, text); break; - case LmLogLevel.Warning: Logger.PrintWarning(LogClass.ServiceLm, text); break; - case LmLogLevel.Error: Logger.PrintError (LogClass.ServiceLm, text); break; - case LmLogLevel.Critical: Logger.PrintError (LogClass.ServiceLm, text); break; - } + Logger.PrintGuest(LogClass.ServiceLm, text); } return 0; diff --git a/Ryujinx.HLE/Utilities/StringUtils.cs b/Ryujinx.HLE/Utilities/StringUtils.cs index e6602f48f3..055b8339ca 100644 --- a/Ryujinx.HLE/Utilities/StringUtils.cs +++ b/Ryujinx.HLE/Utilities/StringUtils.cs @@ -72,5 +72,28 @@ namespace Ryujinx.HLE.Utilities return Encoding.UTF8.GetString(ms.ToArray()); } } + + public static string ReadUtf8StringSend(ServiceCtx context, int index = 0) + { + long position = context.Request.SendBuff[index].Position; + long size = context.Request.SendBuff[index].Size; + + using (MemoryStream ms = new MemoryStream()) + { + while (size-- > 0) + { + byte value = context.Memory.ReadByte(position++); + + if (value == 0) + { + break; + } + + ms.WriteByte(value); + } + + return Encoding.UTF8.GetString(ms.ToArray()); + } + } } } diff --git a/Ryujinx/Config.jsonc b/Ryujinx/Config.jsonc index e362a0d459..01013ec401 100644 --- a/Ryujinx/Config.jsonc +++ b/Ryujinx/Config.jsonc @@ -19,6 +19,12 @@ // Enable print error logs "logging_enable_error": true, + // Enable printing guest logs + "logging_enable_guest": true, + + // Enable printing FS access logs. fs_global_access_log_mode must be 2 or 3 + "logging_enable_fs_access_log": false, + // Filtered log classes, in a JSON array, eg. `[ "Loader", "ServiceFs" ]` "logging_filtered_classes": [ ], @@ -44,6 +50,9 @@ // Enable integrity checks on Switch content files "enable_fs_integrity_checks": true, + // Sets the "GlobalAccessLogMode". Possible modes are 0-3 + "fs_global_access_log_mode": 0, + // Enable or disable aggressive CPU optimizations "enable_aggressive_cpu_opts": true, diff --git a/Ryujinx/Configuration.cs b/Ryujinx/Configuration.cs index 1f670ca570..13e7c357c2 100644 --- a/Ryujinx/Configuration.cs +++ b/Ryujinx/Configuration.cs @@ -52,6 +52,16 @@ namespace Ryujinx /// public bool LoggingEnableError { get; private set; } + /// + /// Enables printing guest log messages + /// + public bool LoggingEnableGuest { get; private set; } + + /// + /// Enables printing FS access log messages + /// + public bool LoggingEnableFsAccessLog { get; private set; } + /// /// Controls which log messages are written to the log targets /// @@ -92,6 +102,11 @@ namespace Ryujinx /// public bool EnableFsIntegrityChecks { get; private set; } + /// + /// Enables FS access log output to the console. Possible modes are 0-3 + /// + public int FsGlobalAccessLogMode { get; private set; } + /// /// Enable or Disable aggressive CPU optimizations /// @@ -184,11 +199,13 @@ namespace Ryujinx )); } - Logger.SetEnable(LogLevel.Debug, Instance.LoggingEnableDebug); - Logger.SetEnable(LogLevel.Stub, Instance.LoggingEnableStub); - Logger.SetEnable(LogLevel.Info, Instance.LoggingEnableInfo); - Logger.SetEnable(LogLevel.Warning, Instance.LoggingEnableWarn); - Logger.SetEnable(LogLevel.Error, Instance.LoggingEnableError); + Logger.SetEnable(LogLevel.Debug, Instance.LoggingEnableDebug); + Logger.SetEnable(LogLevel.Stub, Instance.LoggingEnableStub); + Logger.SetEnable(LogLevel.Info, Instance.LoggingEnableInfo); + Logger.SetEnable(LogLevel.Warning, Instance.LoggingEnableWarn); + Logger.SetEnable(LogLevel.Error, Instance.LoggingEnableError); + Logger.SetEnable(LogLevel.Guest, Instance.LoggingEnableGuest); + Logger.SetEnable(LogLevel.AccessLog, Instance.LoggingEnableFsAccessLog); if (Instance.LoggingFilteredClasses.Length > 0) { @@ -220,6 +237,8 @@ namespace Ryujinx ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; + device.System.GlobalAccessLogMode = Instance.FsGlobalAccessLogMode; + if (Instance.EnableAggressiveCpuOpts) { Optimizations.AssumeStrictAbiCompliance = true; diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json index 1eb046b3cb..f83426b201 100644 --- a/Ryujinx/_schema.json +++ b/Ryujinx/_schema.json @@ -10,6 +10,8 @@ "logging_enable_info", "logging_enable_warn", "logging_enable_error", + "logging_enable_guest", + "logging_enable_fs_access_log", "logging_filtered_classes", "enable_file_log", "system_language", @@ -17,6 +19,7 @@ "enable_vsync", "enable_multicore_scheduling", "enable_fs_integrity_checks", + "fs_global_access_log_mode", "enable_aggressive_cpu_opts", "controller_type", "enable_keyboard", @@ -265,6 +268,28 @@ false ] }, + "logging_enable_guest": { + "$id": "#/properties/logging_enable_guest", + "type": "boolean", + "title": "Logging Enable Guest", + "description": "Enables printing guest log messages", + "default": true, + "examples": [ + true, + false + ] + }, + "logging_enable_fs_access": { + "$id": "#/properties/logging_enable_fs_access_log", + "type": "boolean", + "title": "Logging Enable FS Access Log", + "description": "Enables printing FS access log messages", + "default": true, + "examples": [ + true, + false + ] + }, "logging_filtered_classes": { "$id": "#/properties/logging_filtered_classes", "type": "array", @@ -412,6 +437,20 @@ false ] }, + "fs_global_access_log_mode": { + "$id": "#/properties/fs_global_access_log_mode", + "type": "integer", + "title": "Enable FS access log", + "description": "Enables FS access log output. Possible modes are 0-3. Modes 2 and 3 output to the console.", + "default": 0, + "minimum": 0, + "examples": [ + 0, + 1, + 2, + 3 + ] + }, "enable_aggressive_cpu_opts": { "$id": "#/properties/enable_aggressive_cpu_opts", "type": "boolean",