using MsgPack; using MsgPack.Serialization; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.Utilities; using System; using System.Text; namespace Ryujinx.HLE.HOS.Services.Prepo { [Service("prepo:a", PrepoServicePermissionLevel.Admin)] // 1.0.0-5.1.0 [Service("prepo:a2", PrepoServicePermissionLevel.Admin)] // 6.0.0+ [Service("prepo:m", PrepoServicePermissionLevel.Manager)] [Service("prepo:u", PrepoServicePermissionLevel.User)] [Service("prepo:s", PrepoServicePermissionLevel.System)] class IPrepoService : IpcService { private PrepoServicePermissionLevel _permission; private ulong _systemSessionId; public IPrepoService(ServiceCtx context, PrepoServicePermissionLevel permission) { _permission = permission; } [CommandHipc(10100)] // 1.0.0-5.1.0 [CommandHipc(10102)] // 6.0.0-9.2.0 [CommandHipc(10104)] // 10.0.0+ // SaveReport(u64, pid, buffer, buffer) public ResultCode SaveReport(ServiceCtx context) { if ((_permission & PrepoServicePermissionLevel.User) == 0) { return ResultCode.PermissionDenied; } // We don't care about the differences since we don't use the play report. return ProcessReport(context, withUserID: false); } [CommandHipc(10101)] // 1.0.0-5.1.0 [CommandHipc(10103)] // 6.0.0-9.2.0 [CommandHipc(10105)] // 10.0.0+ // SaveReportWithUser(nn::account::Uid, u64, pid, buffer, buffer) public ResultCode SaveReportWithUser(ServiceCtx context) { if ((_permission & PrepoServicePermissionLevel.User) == 0) { return ResultCode.PermissionDenied; } // We don't care about the differences since we don't use the play report. return ProcessReport(context, withUserID: true); } [CommandHipc(10200)] // RequestImmediateTransmission() public ResultCode RequestImmediateTransmission(ServiceCtx context) { // 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 ResultCode.Success; } [CommandHipc(10300)] // GetTransmissionStatus() -> u32 public ResultCode GetTransmissionStatus(ServiceCtx context) { // It returns the transmission result of nn::prepo::detail::service::core::TransmissionStatusManager. // Since we don't use reports it's fine to return ResultCode.Success. context.ResponseData.Write((int)ResultCode.Success); return ResultCode.Success; } [CommandHipc(10400)] // 9.0.0+ // GetSystemSessionId() -> u64 public ResultCode GetSystemSessionId(ServiceCtx context) { if ((_permission & PrepoServicePermissionLevel.User) == 0) { return ResultCode.PermissionDenied; } if (_systemSessionId == 0) { byte[] randomBuffer = new byte[8]; new Random().NextBytes(randomBuffer); _systemSessionId = BitConverter.ToUInt64(randomBuffer, 0); } context.ResponseData.Write(_systemSessionId); return ResultCode.Success; } [CommandHipc(20100)] // SaveSystemReport(u64, pid, buffer, buffer) public ResultCode SaveSystemReport(ServiceCtx context) { if ((_permission & PrepoServicePermissionLevel.System) != 0) { return ResultCode.PermissionDenied; } // We don't care about the differences since we don't use the play report. return ProcessReport(context, withUserID: false); } [CommandHipc(20101)] // SaveSystemReportWithUser(nn::account::Uid, u64, pid, buffer, buffer) public ResultCode SaveSystemReportWithUser(ServiceCtx context) { if ((_permission & PrepoServicePermissionLevel.System) != 0) { return ResultCode.PermissionDenied; } // We don't care about the differences since we don't use the play report. return ProcessReport(context, withUserID: true); } private ResultCode ProcessReport(ServiceCtx context, bool withUserID) { UserId userId = withUserID ? context.RequestData.ReadStruct() : new UserId(); string gameRoom = StringUtils.ReadUtf8String(context); if (withUserID) { if (userId.IsNull) { return ResultCode.InvalidArgument; } } if (gameRoom == string.Empty) { return ResultCode.InvalidState; } ulong inputPosition = context.Request.SendBuff[0].Position; ulong inputSize = context.Request.SendBuff[0].Size; if (inputSize == 0) { return ResultCode.InvalidBufferSize; } byte[] inputBuffer = new byte[inputSize]; context.Memory.Read(inputPosition, inputBuffer); Logger.Info?.Print(LogClass.ServicePrepo, ReadReportBuffer(inputBuffer, gameRoom, userId)); return ResultCode.Success; } private string ReadReportBuffer(byte[] buffer, string room, UserId userId) { StringBuilder builder = new StringBuilder(); MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(buffer); builder.AppendLine(); builder.AppendLine("PlayReport log:"); if (!userId.IsNull) { builder.AppendLine($" UserId: {userId}"); } builder.AppendLine($" Room: {room}"); builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}"); return builder.ToString(); } } }