diff options
Diffstat (limited to 'src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs')
| -rw-r--r-- | src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs | 225 |
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 |
