aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs')
-rw-r--r--src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs225
1 files changed, 225 insertions, 0 deletions
diff --git a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
new file mode 100644
index 00000000..e157fa56
--- /dev/null
+++ b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
@@ -0,0 +1,225 @@
+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, gameRoomBuffer, reportBuffer, pid, 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, gameRoomBuffer, reportBuffer, pid, 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, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
+ {
+ if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
+ {
+ return PrepoResult.PermissionDenied;
+ }
+
+ return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, Uid.Null, false, applicationId);
+ }
+
+ [CmifCommand(20101)]
+ public Result SaveSystemReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
+ {
+ if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
+ {
+ return PrepoResult.PermissionDenied;
+ }
+
+ return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, userId, true, applicationId);
+ }
+
+ [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, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, Uid userId, bool withUserId = false, Sdk.Ncm.ApplicationId applicationId = default)
+ {
+ 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;
+ }
+
+ StringBuilder builder = new();
+ MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
+
+ builder.AppendLine();
+ builder.AppendLine("PlayReport log:");
+ builder.AppendLine($" Kind: {playReportKind}");
+
+ // 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.
+ if (pid != 0)
+ {
+ builder.AppendLine($" Pid: {pid}");
+ }
+ else
+ {
+ builder.AppendLine($" ApplicationId: {applicationId}");
+ }
+
+ 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