PrepoService.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. using MsgPack;
  2. using MsgPack.Serialization;
  3. using Ryujinx.Common.Logging;
  4. using Ryujinx.Common.Utilities;
  5. using Ryujinx.Horizon.Common;
  6. using Ryujinx.Horizon.Prepo.Types;
  7. using Ryujinx.Horizon.Sdk.Account;
  8. using Ryujinx.Horizon.Sdk.Prepo;
  9. using Ryujinx.Horizon.Sdk.Sf;
  10. using Ryujinx.Horizon.Sdk.Sf.Hipc;
  11. using System;
  12. using System.Text;
  13. namespace Ryujinx.Horizon.Prepo.Ipc
  14. {
  15. partial class PrepoService : IPrepoService
  16. {
  17. enum PlayReportKind
  18. {
  19. Normal,
  20. System
  21. }
  22. private readonly PrepoServicePermissionLevel _permissionLevel;
  23. private ulong _systemSessionId;
  24. private bool _immediateTransmissionEnabled = false;
  25. private bool _userAgreementCheckEnabled = true;
  26. public PrepoService(PrepoServicePermissionLevel permissionLevel)
  27. {
  28. _permissionLevel = permissionLevel;
  29. }
  30. [CmifCommand(10100)] // 1.0.0-5.1.0
  31. [CmifCommand(10102)] // 6.0.0-9.2.0
  32. [CmifCommand(10104)] // 10.0.0+
  33. public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
  34. {
  35. if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
  36. {
  37. return PrepoResult.PermissionDenied;
  38. }
  39. ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null);
  40. return Result.Success;
  41. }
  42. [CmifCommand(10101)] // 1.0.0-5.1.0
  43. [CmifCommand(10103)] // 6.0.0-9.2.0
  44. [CmifCommand(10105)] // 10.0.0+
  45. public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
  46. {
  47. if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
  48. {
  49. return PrepoResult.PermissionDenied;
  50. }
  51. ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true);
  52. return Result.Success;
  53. }
  54. [CmifCommand(10200)]
  55. public Result RequestImmediateTransmission()
  56. {
  57. _immediateTransmissionEnabled = true;
  58. // It signals an event of nn::prepo::detail::service::core::TransmissionStatusManager that requests the transmission of the report.
  59. // Since we don't use reports, it's fine to do nothing.
  60. return Result.Success;
  61. }
  62. [CmifCommand(10300)]
  63. public Result GetTransmissionStatus(out int status)
  64. {
  65. status = 0;
  66. if (_immediateTransmissionEnabled && _userAgreementCheckEnabled)
  67. {
  68. status = 1;
  69. }
  70. return Result.Success;
  71. }
  72. [CmifCommand(10400)] // 9.0.0+
  73. public Result GetSystemSessionId(out ulong systemSessionId)
  74. {
  75. systemSessionId = default;
  76. if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
  77. {
  78. return PrepoResult.PermissionDenied;
  79. }
  80. if (_systemSessionId == 0)
  81. {
  82. _systemSessionId = (ulong)Random.Shared.NextInt64();
  83. }
  84. systemSessionId = _systemSessionId;
  85. return Result.Success;
  86. }
  87. [CmifCommand(20100)]
  88. public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
  89. {
  90. if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
  91. {
  92. return PrepoResult.PermissionDenied;
  93. }
  94. return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, Uid.Null, false, applicationId);
  95. }
  96. [CmifCommand(20101)]
  97. 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)
  98. {
  99. if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
  100. {
  101. return PrepoResult.PermissionDenied;
  102. }
  103. return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, userId, true, applicationId);
  104. }
  105. [CmifCommand(40100)] // 2.0.0+
  106. public Result IsUserAgreementCheckEnabled(out bool enabled)
  107. {
  108. enabled = false;
  109. if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
  110. {
  111. enabled = _userAgreementCheckEnabled;
  112. // If "enabled" is false, it sets some internal fields to 0.
  113. // Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and returns the contained bool.
  114. // We can return the private bool instead, we don't care about the agreement since we don't send reports.
  115. return Result.Success;
  116. }
  117. return PrepoResult.PermissionDenied;
  118. }
  119. [CmifCommand(40101)] // 2.0.0+
  120. public Result SetUserAgreementCheckEnabled(bool enabled)
  121. {
  122. if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
  123. {
  124. _userAgreementCheckEnabled = enabled;
  125. // If "enabled" is false, it sets some internal fields to 0.
  126. // Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and stores the "enabled" value.
  127. // We can store in the private bool instead, we don't care about the agreement since we don't send reports.
  128. return Result.Success;
  129. }
  130. return PrepoResult.PermissionDenied;
  131. }
  132. private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, Uid userId, bool withUserId = false, Sdk.Ncm.ApplicationId applicationId = default)
  133. {
  134. if (withUserId)
  135. {
  136. if (userId.IsNull)
  137. {
  138. return PrepoResult.InvalidArgument;
  139. }
  140. }
  141. if (gameRoomBuffer.Length > 31)
  142. {
  143. return PrepoResult.InvalidArgument;
  144. }
  145. string gameRoom = Encoding.UTF8.GetString(gameRoomBuffer).TrimEnd();
  146. if (gameRoom == string.Empty)
  147. {
  148. return PrepoResult.InvalidState;
  149. }
  150. if (reportBuffer.Length == 0)
  151. {
  152. return PrepoResult.InvalidBufferSize;
  153. }
  154. StringBuilder builder = new();
  155. MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
  156. builder.AppendLine();
  157. builder.AppendLine("PlayReport log:");
  158. builder.AppendLine($" Kind: {playReportKind}");
  159. // NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned.
  160. // Reports are stored internally and an event is signaled to transmit them.
  161. if (pid != 0)
  162. {
  163. builder.AppendLine($" Pid: {pid}");
  164. }
  165. else
  166. {
  167. builder.AppendLine($" ApplicationId: {applicationId}");
  168. }
  169. if (!userId.IsNull)
  170. {
  171. builder.AppendLine($" UserId: {userId}");
  172. }
  173. builder.AppendLine($" Room: {gameRoom}");
  174. builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
  175. Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
  176. return Result.Success;
  177. }
  178. }
  179. }