2023-01-08 13:13:39 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-13 01:50:14 +01:00
|
|
|
|
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null);
|
2023-01-08 13:13:39 +01:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-13 01:50:14 +01:00
|
|
|
|
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true);
|
2023-01-08 13:13:39 +01:00
|
|
|
|
|
|
|
|
|
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)]
|
2023-01-13 01:50:14 +01:00
|
|
|
|
public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
|
2023-01-08 13:13:39 +01:00
|
|
|
|
{
|
|
|
|
|
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
|
|
|
|
|
{
|
|
|
|
|
return PrepoResult.PermissionDenied;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-13 01:50:14 +01:00
|
|
|
|
return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, Uid.Null, false, applicationId);
|
2023-01-08 13:13:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[CmifCommand(20101)]
|
2023-01-13 01:50:14 +01:00
|
|
|
|
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)
|
2023-01-08 13:13:39 +01:00
|
|
|
|
{
|
|
|
|
|
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
|
|
|
|
|
{
|
|
|
|
|
return PrepoResult.PermissionDenied;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-13 01:50:14 +01:00
|
|
|
|
return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, userId, true, applicationId);
|
2023-01-08 13:13:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-13 01:50:14 +01:00
|
|
|
|
private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, Uid userId, bool withUserId = false, Sdk.Ncm.ApplicationId applicationId = default)
|
2023-01-08 13:13:39 +01:00
|
|
|
|
{
|
|
|
|
|
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}");
|
2023-01-13 01:50:14 +01:00
|
|
|
|
|
|
|
|
|
// 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}");
|
|
|
|
|
}
|
2023-01-08 13:13:39 +01:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|