using Ryujinx.Common.Logging; using Ryujinx.HLE.Utilities; using System; using System.Buffers.Binary; using System.IO; using System.Text; namespace Ryujinx.HLE.HOS.Services.Prepo { [Service("prepo:a")] [Service("prepo:a2")] [Service("prepo:u")] class IPrepoService : IpcService { public IPrepoService(ServiceCtx context) { } [Command(10100)] // 1.0.0-5.1.0 // SaveReport(u64, pid, buffer, buffer) public ResultCode SaveReportOld(ServiceCtx context) { // We don't care about the differences since we don't use the play report. return ProcessReport(context, withUserID: false); } [Command(10101)] // 1.0.0-5.1.0 // SaveReportWithUserOld(nn::account::Uid, u64, pid, buffer, buffer) public ResultCode SaveReportWithUserOld(ServiceCtx context) { // We don't care about the differences since we don't use the play report. return ProcessReport(context, withUserID: true); } [Command(10102)] // 6.0.0+ // SaveReport(u64, pid, buffer, buffer) public ResultCode SaveReport(ServiceCtx context) { // We don't care about the differences since we don't use the play report. return ProcessReport(context, withUserID: false); } [Command(10103)] // 6.0.0+ // SaveReportWithUser(nn::account::Uid, u64, pid, buffer, buffer) public ResultCode SaveReportWithUser(ServiceCtx context) { // 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) { UInt128 userId = withUserID ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128(); string gameRoom = StringUtils.ReadUtf8String(context); if (withUserID) { if (userId.IsNull) { return ResultCode.InvalidArgument; } } if (gameRoom == string.Empty) { return ResultCode.InvalidState; } long inputPosition = context.Request.SendBuff[0].Position; long inputSize = context.Request.SendBuff[0].Size; if (inputSize == 0) { return ResultCode.InvalidBufferSize; } byte[] inputBuffer = context.Memory.ReadBytes(inputPosition, inputSize); Logger.PrintInfo(LogClass.ServicePrepo, ReadReportBuffer(inputBuffer, gameRoom, userId)); return ResultCode.Success; } public string ReadReportBuffer(byte[] buffer, string room, UInt128 userId) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.AppendLine("PlayReport log:"); if (!userId.IsNull) { sb.AppendLine($" UserId: {userId.ToString()}"); } sb.AppendLine($" Room: {room}"); try { using (MemoryStream stream = new MemoryStream(buffer)) using (BinaryReader reader = new BinaryReader(stream)) { byte unknown1 = reader.ReadByte(); // Version ? short unknown2 = reader.ReadInt16(); // Size ? bool isValue = false; string fieldStr = string.Empty; while (stream.Position != stream.Length) { byte descriptor = reader.ReadByte(); if (!isValue) { byte[] key = reader.ReadBytes(descriptor - 0xA0); fieldStr = $" Key: {Encoding.ASCII.GetString(key)}"; isValue = true; } else { if (descriptor > 0xD0) // Int value. { if (descriptor - 0xD0 == 1) { fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadUInt16())}"; } else if (descriptor - 0xD0 == 2) { fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt32())}"; } else if (descriptor - 0xD0 == 4) { fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt64())}"; } else { // Unknown. break; } } else if (descriptor > 0xA0 && descriptor < 0xD0) // String value, max size = 0x20 bytes ? { int size = descriptor - 0xA0; string value = string.Empty; byte[] rawValues = new byte[0]; for (int i = 0; i < size; i++) { byte chr = reader.ReadByte(); if (chr >= 0x20 && chr < 0x7f) { value += (char)chr; } else { Array.Resize(ref rawValues, rawValues.Length + 1); rawValues[rawValues.Length - 1] = chr; } } if (value != string.Empty) { fieldStr += $", Value: {value}"; } // TODO(Ac_K): Determine why there are non-alphanumeric values sometimes. if (rawValues.Length > 0) { fieldStr += $", RawValue: 0x{BitConverter.ToString(rawValues).Replace("-", "")}"; } } else // Byte value. { fieldStr += $", Value: {descriptor}"; } sb.AppendLine(fieldStr); isValue = false; } } } } catch (Exception) { sb.AppendLine(" Error while parsing the report buffer."); } return sb.ToString(); } } }