diff options
| author | Ac_K <Acoustik666@gmail.com> | 2023-01-08 13:13:39 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-01-08 12:13:39 +0000 |
| commit | 550747eac6c0f6da14070c8b6d208bde6f1d1eb9 (patch) | |
| tree | 1be203777156dae17a4221589c852c638b273d94 /Ryujinx.Horizon/Prepo | |
| parent | 3ffceab1fb220c13f5982de599d788f2e3e7cc47 (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.cs | 218 | ||||
| -rw-r--r-- | Ryujinx.Horizon/Prepo/PrepoIpcServer.cs | 49 | ||||
| -rw-r--r-- | Ryujinx.Horizon/Prepo/PrepoMain.cs | 17 | ||||
| -rw-r--r-- | Ryujinx.Horizon/Prepo/PrepoResult.cs | 15 | ||||
| -rw-r--r-- | Ryujinx.Horizon/Prepo/PrepoServerManager.cs | 30 | ||||
| -rw-r--r-- | Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs | 12 | ||||
| -rw-r--r-- | Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs | 11 |
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 |
