aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Horizon/Prepo
diff options
context:
space:
mode:
authorAc_K <Acoustik666@gmail.com>2023-01-08 13:13:39 +0100
committerGitHub <noreply@github.com>2023-01-08 12:13:39 +0000
commit550747eac6c0f6da14070c8b6d208bde6f1d1eb9 (patch)
tree1be203777156dae17a4221589c852c638b273d94 /Ryujinx.Horizon/Prepo
parent3ffceab1fb220c13f5982de599d788f2e3e7cc47 (diff)
Horizon: Impl Prepo, Fixes bugs, Clean things (#4220)
* Horizon: Impl Prepo, Fixes bugs, Clean things * remove ToArray() * resultCode > status * Remove old services * Addresses gdkchan's comments and more cleanup * Addresses Gdkchan's feedback 2 * Reorganize services, make sure service are loaded before guest Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com> * Create interfaces for lm and sm Co-authored-by: gdkchan <5624669+gdkchan@users.noreply.github.com>
Diffstat (limited to 'Ryujinx.Horizon/Prepo')
-rw-r--r--Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs218
-rw-r--r--Ryujinx.Horizon/Prepo/PrepoIpcServer.cs49
-rw-r--r--Ryujinx.Horizon/Prepo/PrepoMain.cs17
-rw-r--r--Ryujinx.Horizon/Prepo/PrepoResult.cs15
-rw-r--r--Ryujinx.Horizon/Prepo/PrepoServerManager.cs30
-rw-r--r--Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs12
-rw-r--r--Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs11
7 files changed, 352 insertions, 0 deletions
diff --git a/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs b/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
new file mode 100644
index 00000000..672bba4e
--- /dev/null
+++ b/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
@@ -0,0 +1,218 @@
+using MsgPack;
+using MsgPack.Serialization;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Prepo.Types;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.Prepo;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Text;
+
+namespace Ryujinx.Horizon.Prepo.Ipc
+{
+ partial class PrepoService : IPrepoService
+ {
+ enum PlayReportKind
+ {
+ Normal,
+ System
+ }
+
+ private readonly PrepoServicePermissionLevel _permissionLevel;
+ private ulong _systemSessionId;
+
+ private bool _immediateTransmissionEnabled = false;
+ private bool _userAgreementCheckEnabled = true;
+
+ public PrepoService(PrepoServicePermissionLevel permissionLevel)
+ {
+ _permissionLevel = permissionLevel;
+ }
+
+ [CmifCommand(10100)] // 1.0.0-5.1.0
+ [CmifCommand(10102)] // 6.0.0-9.2.0
+ [CmifCommand(10104)] // 10.0.0+
+ public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
+ {
+ if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
+ {
+ return PrepoResult.PermissionDenied;
+ }
+
+ ProcessPlayReport(PlayReportKind.Normal, pid, gameRoomBuffer, reportBuffer, Uid.Null);
+
+ return Result.Success;
+ }
+
+ [CmifCommand(10101)] // 1.0.0-5.1.0
+ [CmifCommand(10103)] // 6.0.0-9.2.0
+ [CmifCommand(10105)] // 10.0.0+
+ public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
+ {
+ if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
+ {
+ return PrepoResult.PermissionDenied;
+ }
+
+ ProcessPlayReport(PlayReportKind.Normal, pid, gameRoomBuffer, reportBuffer, userId, true);
+
+ return Result.Success;
+ }
+
+ [CmifCommand(10200)]
+ public Result RequestImmediateTransmission()
+ {
+ _immediateTransmissionEnabled = true;
+
+ // It signals an event of nn::prepo::detail::service::core::TransmissionStatusManager that requests the transmission of the report.
+ // Since we don't use reports, it's fine to do nothing.
+
+ return Result.Success;
+ }
+
+ [CmifCommand(10300)]
+ public Result GetTransmissionStatus(out int status)
+ {
+ status = 0;
+
+ if (_immediateTransmissionEnabled && _userAgreementCheckEnabled)
+ {
+ status = 1;
+ }
+
+ return Result.Success;
+ }
+
+ [CmifCommand(10400)] // 9.0.0+
+ public Result GetSystemSessionId(out ulong systemSessionId)
+ {
+ systemSessionId = default;
+
+ if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
+ {
+ return PrepoResult.PermissionDenied;
+ }
+
+ if (_systemSessionId == 0)
+ {
+ _systemSessionId = (ulong)Random.Shared.NextInt64();
+ }
+
+ systemSessionId = _systemSessionId;
+
+ return Result.Success;
+ }
+
+ [CmifCommand(20100)]
+ public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
+ {
+ if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
+ {
+ return PrepoResult.PermissionDenied;
+ }
+
+ return ProcessPlayReport(PlayReportKind.System, pid, gameRoomBuffer, reportBuffer, Uid.Null);
+ }
+
+ [CmifCommand(20101)]
+ public Result SaveSystemReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
+ {
+ if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
+ {
+ return PrepoResult.PermissionDenied;
+ }
+
+ return ProcessPlayReport(PlayReportKind.System, pid, gameRoomBuffer, reportBuffer, userId, true);
+ }
+
+ [CmifCommand(40100)] // 2.0.0+
+ public Result IsUserAgreementCheckEnabled(out bool enabled)
+ {
+ enabled = false;
+
+ if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
+ {
+ enabled = _userAgreementCheckEnabled;
+
+ // If "enabled" is false, it sets some internal fields to 0.
+ // Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and returns the contained bool.
+ // We can return the private bool instead, we don't care about the agreement since we don't send reports.
+
+ return Result.Success;
+ }
+
+ return PrepoResult.PermissionDenied;
+ }
+
+ [CmifCommand(40101)] // 2.0.0+
+ public Result SetUserAgreementCheckEnabled(bool enabled)
+ {
+ if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
+ {
+ _userAgreementCheckEnabled = enabled;
+
+ // If "enabled" is false, it sets some internal fields to 0.
+ // Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and stores the "enabled" value.
+ // We can store in the private bool instead, we don't care about the agreement since we don't send reports.
+
+ return Result.Success;
+ }
+
+ return PrepoResult.PermissionDenied;
+ }
+
+ private static Result ProcessPlayReport(PlayReportKind playReportKind, ulong pid, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, Uid userId, bool withUserId = false)
+ {
+ if (withUserId)
+ {
+ if (userId.IsNull)
+ {
+ return PrepoResult.InvalidArgument;
+ }
+ }
+
+ if (gameRoomBuffer.Length > 31)
+ {
+ return PrepoResult.InvalidArgument;
+ }
+
+ string gameRoom = Encoding.UTF8.GetString(gameRoomBuffer).TrimEnd();
+
+ if (gameRoom == string.Empty)
+ {
+ return PrepoResult.InvalidState;
+ }
+
+ if (reportBuffer.Length == 0)
+ {
+ return PrepoResult.InvalidBufferSize;
+ }
+
+ // NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned.
+ // Reports are stored internally and an event is signaled to transmit them.
+
+ StringBuilder builder = new();
+ MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
+
+ builder.AppendLine();
+ builder.AppendLine("PlayReport log:");
+ builder.AppendLine($" Kind: {playReportKind}");
+ builder.AppendLine($" Pid: {pid}");
+
+ if (!userId.IsNull)
+ {
+ builder.AppendLine($" UserId: {userId}");
+ }
+
+ builder.AppendLine($" Room: {gameRoom}");
+ builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
+
+ Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
+
+ return Result.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs b/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
new file mode 100644
index 00000000..ba19ff6f
--- /dev/null
+++ b/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Horizon.Prepo.Types;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using Ryujinx.Horizon.Sdk.Sm;
+
+namespace Ryujinx.Horizon.Prepo
+{
+ class PrepoIpcServer
+ {
+ private const int PrepoMaxSessionsCount = 12;
+ private const int PrepoTotalMaxSessionsCount = PrepoMaxSessionsCount * 6;
+
+ private const int PointerBufferSize = 0x3800;
+ private const int MaxDomains = 64;
+ private const int MaxDomainObjects = 16;
+ private const int MaxPortsCount = 6;
+
+ private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
+
+ private SmApi _sm;
+ private PrepoServerManager _serverManager;
+
+ public void Initialize()
+ {
+ HeapAllocator allocator = new();
+
+ _sm = new SmApi();
+ _sm.Initialize().AbortOnFailure();
+
+ _serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, PrepoTotalMaxSessionsCount);
+
+ _serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), PrepoMaxSessionsCount); // 1.0.0-5.1.0
+ _serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), PrepoMaxSessionsCount); // 6.0.0+
+ _serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), PrepoMaxSessionsCount);
+ _serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), PrepoMaxSessionsCount);
+ _serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), PrepoMaxSessionsCount);
+ _serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), PrepoMaxSessionsCount); // 1.0.0
+ }
+
+ public void ServiceRequests()
+ {
+ _serverManager.ServiceRequests();
+ }
+
+ public void Shutdown()
+ {
+ _serverManager.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Prepo/PrepoMain.cs b/Ryujinx.Horizon/Prepo/PrepoMain.cs
new file mode 100644
index 00000000..5ff0f53d
--- /dev/null
+++ b/Ryujinx.Horizon/Prepo/PrepoMain.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Horizon.Prepo
+{
+ class PrepoMain : IService
+ {
+ public static void Main(ServiceTable serviceTable)
+ {
+ PrepoIpcServer ipcServer = new();
+
+ ipcServer.Initialize();
+
+ serviceTable.SignalServiceReady();
+
+ ipcServer.ServiceRequests();
+ ipcServer.Shutdown();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Prepo/PrepoResult.cs b/Ryujinx.Horizon/Prepo/PrepoResult.cs
new file mode 100644
index 00000000..12255e3d
--- /dev/null
+++ b/Ryujinx.Horizon/Prepo/PrepoResult.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Prepo
+{
+ static class PrepoResult
+ {
+ private const int ModuleId = 129;
+
+ public static Result InvalidArgument => new(ModuleId, 1);
+ public static Result InvalidState => new(ModuleId, 5);
+ public static Result InvalidBufferSize => new(ModuleId, 9);
+ public static Result PermissionDenied => new(ModuleId, 90);
+ public static Result InvalidPid => new(ModuleId, 101);
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Prepo/PrepoServerManager.cs b/Ryujinx.Horizon/Prepo/PrepoServerManager.cs
new file mode 100644
index 00000000..55e4ff7d
--- /dev/null
+++ b/Ryujinx.Horizon/Prepo/PrepoServerManager.cs
@@ -0,0 +1,30 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Prepo.Ipc;
+using Ryujinx.Horizon.Prepo.Types;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using Ryujinx.Horizon.Sdk.Sm;
+using System;
+
+namespace Ryujinx.Horizon.Prepo
+{
+ class PrepoServerManager : ServerManager
+ {
+ public PrepoServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions)
+ {
+ }
+
+ protected override Result OnNeedsToAccept(int portIndex, Server server)
+ {
+ return (PrepoPortIndex)portIndex switch
+ {
+ PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)),
+ PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)),
+ PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Manager)),
+ PrepoPortIndex.User => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.User)),
+ PrepoPortIndex.System => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.System)),
+ PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Debug)),
+ _ => throw new ArgumentOutOfRangeException(nameof(portIndex)),
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs b/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs
new file mode 100644
index 00000000..f4d6b877
--- /dev/null
+++ b/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Horizon.Prepo.Types
+{
+ enum PrepoPortIndex
+ {
+ Admin,
+ Admin2,
+ Manager,
+ User,
+ System,
+ Debug
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs b/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs
new file mode 100644
index 00000000..8214f4b9
--- /dev/null
+++ b/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Horizon.Prepo.Types
+{
+ enum PrepoServicePermissionLevel
+ {
+ Admin = -1,
+ User = 1,
+ System = 2,
+ Manager = 6,
+ Debug = unchecked((int)0x80000006)
+ }
+} \ No newline at end of file