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>
This commit is contained in:
218
Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
Normal file
218
Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
49
Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
Normal file
49
Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
17
Ryujinx.Horizon/Prepo/PrepoMain.cs
Normal file
17
Ryujinx.Horizon/Prepo/PrepoMain.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
15
Ryujinx.Horizon/Prepo/PrepoResult.cs
Normal file
15
Ryujinx.Horizon/Prepo/PrepoResult.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
30
Ryujinx.Horizon/Prepo/PrepoServerManager.cs
Normal file
30
Ryujinx.Horizon/Prepo/PrepoServerManager.cs
Normal file
@@ -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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
12
Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs
Normal file
12
Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Horizon.Prepo.Types
|
||||
{
|
||||
enum PrepoPortIndex
|
||||
{
|
||||
Admin,
|
||||
Admin2,
|
||||
Manager,
|
||||
User,
|
||||
System,
|
||||
Debug
|
||||
}
|
||||
}
|
11
Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs
Normal file
11
Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.Horizon.Prepo.Types
|
||||
{
|
||||
enum PrepoServicePermissionLevel
|
||||
{
|
||||
Admin = -1,
|
||||
User = 1,
|
||||
System = 2,
|
||||
Manager = 6,
|
||||
Debug = unchecked((int)0x80000006)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user