From 3f3ae99a0fa048e5ad532b5ebe646eb572787626 Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Mon, 25 Nov 2019 00:45:54 +0100
Subject: [PATCH] prepo: Implement calls of IPrepoService (#830)

* prepo: Implement calls of IPrepoService

- Implement `SaveReportOld`, `SaveReportWithUserOld`, `SaveReport`, `SaveReportWithUser` not accurate by RE (except for result codes). It's  here to do something with the data since we will never use the `Play Report` sent by the game. So it's better than just a stub.
- Fix a typo in `ldn` result code.

Close #807

* Add a processing method

* Address some comments

* remove unneeded using

* Resolve requested changes

* Typo

* Update IPrepoService.cs
---
 Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs    |   2 +-
 .../HOS/Services/Prepo/IPrepoService.cs       | 177 +++++++++++++++++-
 Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs  |  15 ++
 3 files changed, 188 insertions(+), 6 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs

diff --git a/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs
index 0c9f6209c..87674f7c3 100644
--- a/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs
@@ -11,6 +11,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn
         InvalidState    = (32 << ErrorCodeShift) | ModuleId,
         Unknown1        = (48 << ErrorCodeShift) | ModuleId,
         InvalidArgument = (96 << ErrorCodeShift) | ModuleId,
-        InvalidOjbect   = (97 << ErrorCodeShift) | ModuleId,
+        InvalidObject   = (97 << ErrorCodeShift) | ModuleId,
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs
index 94c027ab3..fdd156d30 100644
--- a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs
+++ b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs
@@ -1,4 +1,9 @@
 using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Buffers.Binary;
+using System.IO;
+using System.Text;
 
 namespace Ryujinx.HLE.HOS.Services.Prepo
 {
@@ -9,13 +14,175 @@ namespace Ryujinx.HLE.HOS.Services.Prepo
     {
         public IPrepoService(ServiceCtx context) { }
 
-        [Command(10101)]
-        // SaveReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
-        public static ResultCode SaveReportWithUser(ServiceCtx context)
+        [Command(10100)] // 1.0.0-5.1.0
+        // SaveReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
+        public ResultCode SaveReportOld(ServiceCtx context)
         {
-            Logger.PrintStub(LogClass.ServicePrepo);
+            // We don't care about the differences since we don't use the play report.
+            return ProcessReport(context, withUserID: false);
+        }
+
+        [Command(10101)] // 1.0.0-5.1.0
+        // SaveReportWithUserOld(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
+        public ResultCode SaveReportWithUserOld(ServiceCtx context)
+        {
+            // We don't care about the differences since we don't use the play report.
+            return ProcessReport(context, withUserID: true);
+        }
+
+        [Command(10102)] // 6.0.0+
+        // SaveReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
+        public ResultCode SaveReport(ServiceCtx context)
+        {
+            // We don't care about the differences since we don't use the play report.
+            return ProcessReport(context, withUserID: false);
+        }
+
+        [Command(10103)] // 6.0.0+
+        // SaveReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
+        public ResultCode SaveReportWithUser(ServiceCtx context)
+        {
+            // We don't care about the differences since we don't use the play report.
+            return ProcessReport(context, withUserID: true);
+        }
+
+        private ResultCode ProcessReport(ServiceCtx context, bool withUserID)
+        {
+            UInt128 userId   = withUserID ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128();
+            string  gameRoom = StringUtils.ReadUtf8String(context);
+
+            if (withUserID)
+            {
+                if (userId.IsNull)
+                {
+                    return ResultCode.InvalidArgument;
+                }
+            }
+
+            if (gameRoom == string.Empty)
+            {
+                return ResultCode.InvalidState;
+            }
+
+            long inputPosition = context.Request.SendBuff[0].Position;
+            long inputSize     = context.Request.SendBuff[0].Size;
+
+            if (inputSize == 0)
+            {
+                return ResultCode.InvalidBufferSize;
+            }
+
+            byte[] inputBuffer = context.Memory.ReadBytes(inputPosition, inputSize);
+
+            Logger.PrintInfo(LogClass.ServicePrepo, ReadReportBuffer(inputBuffer, gameRoom, userId));
 
             return ResultCode.Success;
         }
+
+        public string ReadReportBuffer(byte[] buffer, string room, UInt128 userId)
+        {
+            StringBuilder sb = new StringBuilder();
+
+            sb.AppendLine();
+            sb.AppendLine("PlayReport log:");
+
+            if (!userId.IsNull)
+            {
+                sb.AppendLine($" UserId: {userId.ToString()}");
+            }
+
+            sb.AppendLine($" Room: {room}");
+
+            using (MemoryStream stream = new MemoryStream(buffer))
+            using (BinaryReader reader = new BinaryReader(stream))
+            {
+                byte  unknown1 = reader.ReadByte();  // Version ?
+                short unknown2 = reader.ReadInt16(); // Size ?
+
+                bool isValue = false;
+
+                string fieldStr = string.Empty;
+
+                while (stream.Position != stream.Length)
+                {
+                    byte descriptor = reader.ReadByte();
+
+                    if (!isValue)
+                    {
+                        byte[] key = reader.ReadBytes(descriptor - 0xA0);
+
+                        fieldStr = $"  Key: {Encoding.ASCII.GetString(key)}";
+
+                        isValue = true;
+                    }
+                    else
+                    {
+                        if (descriptor > 0xD0) // Int value.
+                        {
+                            if (descriptor - 0xD0 == 1)
+                            {
+                                fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadUInt16())}";
+                            }
+                            else if (descriptor - 0xD0 == 2)
+                            {
+                                fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt32())}";
+                            }
+                            else if (descriptor - 0xD0 == 4)
+                            {
+                                fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt64())}";
+                            }
+                            else
+                            {
+                                // Unknown.
+                                break;
+                            }
+                        }
+                        else if (descriptor > 0xA0 && descriptor < 0xD0) // String value, max size = 0x20 bytes ?
+                        {
+                            int    size      = descriptor - 0xA0;
+                            string value     = string.Empty;
+                            byte[] rawValues = new byte[0];
+
+                            for (int i = 0; i < size; i++)
+                            {
+                                byte chr = reader.ReadByte();
+
+                                if (chr >= 0x20 && chr < 0x7f)
+                                {
+                                    value += (char)chr;
+                                }
+                                else
+                                {
+                                    Array.Resize(ref rawValues, rawValues.Length + 1);
+
+                                    rawValues[rawValues.Length - 1] = chr;
+                                }
+                            }
+
+                            if (value != string.Empty)
+                            { 
+                                fieldStr += $", Value: {value}";
+                            }
+
+                            // TODO(Ac_K): Determine why there are non-alphanumeric values sometimes.
+                            if (rawValues.Length > 0)
+                            {
+                                fieldStr += $", RawValue: 0x{BitConverter.ToString(rawValues).Replace("-", "")}";
+                            }
+                        }
+                        else // Byte value.
+                        {
+                            fieldStr += $", Value: {descriptor}";
+                        }
+
+                        sb.AppendLine(fieldStr);
+
+                        isValue = false;
+                    }
+                }
+            }
+
+            return sb.ToString();
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs
new file mode 100644
index 000000000..1e110ea68
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.HOS.Services.Prepo
+{
+    enum ResultCode
+    {
+        ModuleId       = 129,
+        ErrorCodeShift = 9,
+
+        Success = 0,
+
+        InvalidArgument   = (1 << ErrorCodeShift) | ModuleId,
+        InvalidState      = (5 << ErrorCodeShift) | ModuleId,
+        InvalidBufferSize = (9 << ErrorCodeShift) | ModuleId,
+        Unknown1          = (90 << ErrorCodeShift) | ModuleId
+    }
+}
\ No newline at end of file