From 33e673ceb84edf31dcb29abdf5df004cfd3beca5 Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Sun, 2 Oct 2022 10:30:46 +0200
Subject: [PATCH] fatal: Implement Service (#3573)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fatal: Implement Service

This PR adds a basic implementation of fatal service, guest processes call it when there is something wrong. But since we can already have all informations by debugging it's not really useful.
In any case, that's avoid an unimplemented service exception. Structs/Enum are based on Atmosphère source code.

After logs the error report, I call SvcBreak. Feedbacks are welcome on this, since some guests calls it right after fatal service so I can remove it if needed.

* Addresses gdkchan feedback
---
 Ryujinx.Common/Logging/LogClass.cs            |   1 +
 Ryujinx.HLE/HOS/Services/Fatal/IService.cs    | 141 +++++++++++++++++-
 .../HOS/Services/Fatal/Types/CpuContext32.cs  |  25 ++++
 .../HOS/Services/Fatal/Types/CpuContext64.cs  |  24 +++
 .../HOS/Services/Fatal/Types/FatalPolicy.cs   |   9 ++
 5 files changed, 199 insertions(+), 1 deletion(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs

diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs
index 20c8da3f81..2e936fc724 100644
--- a/Ryujinx.Common/Logging/LogClass.cs
+++ b/Ryujinx.Common/Logging/LogClass.cs
@@ -30,6 +30,7 @@ namespace Ryujinx.Common.Logging
         ServiceBsd,
         ServiceBtm,
         ServiceCaps,
+        ServiceFatal,
         ServiceFriend,
         ServiceFs,
         ServiceHid,
diff --git a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs
index 692d2b0b7d..6d663a4dec 100644
--- a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs
+++ b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs
@@ -1,8 +1,147 @@
-namespace Ryujinx.HLE.HOS.Services.Fatal
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Fatal.Types;
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Fatal
 {
     [Service("fatal:u")]
     class IService : IpcService
     {
         public IService(ServiceCtx context) { }
+
+        [CommandHipc(0)]
+        // ThrowFatal(u64 result_code, u64 pid)
+        public ResultCode ThrowFatal(ServiceCtx context)
+        {
+            ResultCode resultCode = (ResultCode)context.RequestData.ReadUInt64();
+            ulong      pid        = context.Request.HandleDesc.PId;
+
+            return ThrowFatalWithCpuContextImpl(context, resultCode, pid, FatalPolicy.ErrorReportAndErrorScreen, null);
+        }
+
+        [CommandHipc(1)]
+        // ThrowFatalWithPolicy(u64 result_code, u32 fatal_policy, u64 pid)
+        public ResultCode ThrowFatalWithPolicy(ServiceCtx context)
+        {
+            ResultCode  resultCode  = (ResultCode)context.RequestData.ReadUInt64();
+            FatalPolicy fatalPolicy = (FatalPolicy)context.RequestData.ReadUInt32();
+            ulong       pid         = context.Request.HandleDesc.PId;
+
+            return ThrowFatalWithCpuContextImpl(context, resultCode, pid, fatalPolicy, null);
+        }
+
+        [CommandHipc(2)]
+        // ThrowFatalWithCpuContext(u64 result_code, u32 fatal_policy, u64 pid, buffer<bytes, 0x15> cpu_context)
+        public ResultCode ThrowFatalWithCpuContext(ServiceCtx context)
+        {
+            ResultCode  resultCode  = (ResultCode)context.RequestData.ReadUInt64();
+            FatalPolicy fatalPolicy = (FatalPolicy)context.RequestData.ReadUInt32();
+            ulong       pid         = context.Request.HandleDesc.PId;
+
+            ulong cpuContextPosition = context.Request.SendBuff[0].Position;
+            ulong cpuContextSize     = context.Request.SendBuff[0].Size;
+
+            ReadOnlySpan<byte> cpuContextData = context.Memory.GetSpan(cpuContextPosition, (int)cpuContextSize);
+
+            return ThrowFatalWithCpuContextImpl(context, resultCode, pid, fatalPolicy, cpuContextData);
+        }
+
+        private ResultCode ThrowFatalWithCpuContextImpl(ServiceCtx context, ResultCode resultCode, ulong pid, FatalPolicy fatalPolicy, ReadOnlySpan<byte> cpuContext)
+        {
+            StringBuilder errorReport = new StringBuilder();
+
+            errorReport.AppendLine();
+            errorReport.AppendLine("ErrorReport log:");
+
+            errorReport.AppendLine($"\tTitleId: {context.Device.Application.TitleId:x16}");
+            errorReport.AppendLine($"\tPid: {pid}");
+            errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
+            errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
+
+            if (cpuContext != null)
+            {
+                errorReport.AppendLine("CPU Context:");
+
+                if (context.Device.Application.TitleIs64Bit)
+                {
+                    CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0];
+
+                    errorReport.AppendLine($"\tStartAddress: 0x{cpuContext64.StartAddress:x16}");
+                    errorReport.AppendLine($"\tRegisterSetFlags: {cpuContext64.RegisterSetFlags}");
+
+                    if (cpuContext64.StackTraceSize > 0)
+                    {
+                        errorReport.AppendLine("\tStackTrace:");
+
+                        for (int i = 0; i < cpuContext64.StackTraceSize; i++)
+                        {
+                            errorReport.AppendLine($"\t\t0x{cpuContext64.StackTrace[i]:x16}");
+                        }
+                    }
+
+                    errorReport.AppendLine("\tRegisters:");
+
+                    for (int i = 0; i < cpuContext64.X.Length; i++)
+                    {
+                        errorReport.AppendLine($"\t\tX[{i:d2}]:\t0x{cpuContext64.X[i]:x16}");
+                    }
+
+                    errorReport.AppendLine();
+                    errorReport.AppendLine($"\t\tFP:\t0x{cpuContext64.FP:x16}");
+                    errorReport.AppendLine($"\t\tLR:\t0x{cpuContext64.LR:x16}");
+                    errorReport.AppendLine($"\t\tSP:\t0x{cpuContext64.SP:x16}");
+                    errorReport.AppendLine($"\t\tPC:\t0x{cpuContext64.PC:x16}");
+                    errorReport.AppendLine($"\t\tPState:\t0x{cpuContext64.PState:x16}");
+                    errorReport.AppendLine($"\t\tAfsr0:\t0x{cpuContext64.Afsr0:x16}");
+                    errorReport.AppendLine($"\t\tAfsr1:\t0x{cpuContext64.Afsr1:x16}");
+                    errorReport.AppendLine($"\t\tEsr:\t0x{cpuContext64.Esr:x16}");
+                    errorReport.AppendLine($"\t\tFar:\t0x{cpuContext64.Far:x16}");
+                }
+                else
+                {
+                    CpuContext32 cpuContext32 = MemoryMarshal.Cast<byte, CpuContext32>(cpuContext)[0];
+
+                    errorReport.AppendLine($"\tStartAddress: 0x{cpuContext32.StartAddress:16}");
+                    errorReport.AppendLine($"\tRegisterSetFlags: {cpuContext32.RegisterSetFlags}");
+
+                    if (cpuContext32.StackTraceSize > 0)
+                    {
+                        errorReport.AppendLine("\tStackTrace:");
+
+                        for (int i = 0; i < cpuContext32.StackTraceSize; i++)
+                        {
+                            errorReport.AppendLine($"\t\t0x{cpuContext32.StackTrace[i]:x16}");
+                        }
+                    }
+
+                    errorReport.AppendLine("\tRegisters:");
+
+                    for (int i = 0; i < cpuContext32.X.Length; i++)
+                    {
+                        errorReport.AppendLine($"\t\tX[{i:d2}]:\t0x{cpuContext32.X[i]:x16}");
+                    }
+
+                    errorReport.AppendLine();
+                    errorReport.AppendLine($"\t\tFP:\t0x{cpuContext32.FP:x16}");
+                    errorReport.AppendLine($"\t\tFP:\t0x{cpuContext32.IP:x16}");
+                    errorReport.AppendLine($"\t\tSP:\t0x{cpuContext32.SP:x16}");
+                    errorReport.AppendLine($"\t\tLR:\t0x{cpuContext32.LR:x16}");
+                    errorReport.AppendLine($"\t\tPC:\t0x{cpuContext32.PC:x16}");
+                    errorReport.AppendLine($"\t\tPState:\t0x{cpuContext32.PState:x16}");
+                    errorReport.AppendLine($"\t\tAfsr0:\t0x{cpuContext32.Afsr0:x16}");
+                    errorReport.AppendLine($"\t\tAfsr1:\t0x{cpuContext32.Afsr1:x16}");
+                    errorReport.AppendLine($"\t\tEsr:\t0x{cpuContext32.Esr:x16}");
+                    errorReport.AppendLine($"\t\tFar:\t0x{cpuContext32.Far:x16}");
+                }
+            }
+
+            Logger.Info?.Print(LogClass.ServiceFatal, errorReport.ToString());
+
+            context.Device.System.KernelContext.Syscall.Break((ulong)resultCode);
+
+            return ResultCode.Success;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs b/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs
new file mode 100644
index 0000000000..5c0b116bc3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Common.Memory;
+
+namespace Ryujinx.HLE.HOS.Services.Fatal.Types
+{
+    public struct CpuContext32
+    {
+        public Array11<uint> X;
+        public uint FP;
+        public uint IP;
+        public uint SP;
+        public uint LR;
+        public uint PC;
+
+        public uint PState;
+        public uint Afsr0;
+        public uint Afsr1;
+        public uint Esr;
+        public uint Far;
+
+        public Array32<uint> StackTrace;
+        public uint StackTraceSize;
+        public uint StartAddress;
+        public uint RegisterSetFlags;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs b/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs
new file mode 100644
index 0000000000..24829a78af
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs
@@ -0,0 +1,24 @@
+using Ryujinx.Common.Memory;
+
+namespace Ryujinx.HLE.HOS.Services.Fatal.Types
+{
+    public struct CpuContext64
+    {
+        public Array29<ulong> X;
+        public ulong FP;
+        public ulong LR;
+        public ulong SP;
+        public ulong PC;
+
+        public ulong PState;
+        public ulong Afsr0;
+        public ulong Afsr1;
+        public ulong Esr;
+        public ulong Far;
+
+        public Array32<ulong> StackTrace;
+        public ulong StartAddress;
+        public ulong RegisterSetFlags;
+        public uint  StackTraceSize;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs b/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs
new file mode 100644
index 0000000000..fe55cf12b7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Fatal.Types
+{
+    enum FatalPolicy
+    {
+        ErrorReportAndErrorScreen,
+        ErrorReport,
+        ErrorScreen
+    }
+}
\ No newline at end of file