aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAc_K <Acoustik666@gmail.com>2019-11-15 01:25:22 +0100
committerThog <me@thog.eu>2019-11-15 01:25:22 +0100
commit79abc6ed9375116a08eda6f8e722df56d029c373 (patch)
treebba489695596fdfaf50ac42e54379458d2a74c35
parentb9ae0b09d91fea3adc3ad10f9621ef8a4597c637 (diff)
Implement IApplicationFunctions & IQueryService commands (#823)
* Implement IApplicationFunctions & IQueryService commands - Fix some nits in `IApplicationFunctions` - Implement `QueryApplicationPlayStatistics` and `QueryApplicationPlayStatisticsByUid` checked by RE. - Implement `QueryApplicationPlayStatisticsForSystem` and `QueryApplicationPlayStatisticsByUserAccountIdForSystem` checked by RE. - Implement `QueryPlayStatisticsManager` to get/set played games statistics. We currently don't store any statistics because it's handled by qlaunch (or maybe am service?) on Switch. We can add support later if games use returned statistics for something. * Fix reviews
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs36
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/ResultCode.cs1
-rw-r--r--Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs18
-rw-r--r--Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs83
-rw-r--r--Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs13
7 files changed, 161 insertions, 11 deletions
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
index f65509b5..464d0b47 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
@@ -4,6 +4,8 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage;
+using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
+using Ryujinx.HLE.Utilities;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy
@@ -31,13 +33,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// EnsureSaveData(nn::account::Uid) -> u64
public ResultCode EnsureSaveData(ServiceCtx context)
{
- long uIdLow = context.RequestData.ReadInt64();
- long uIdHigh = context.RequestData.ReadInt64();
-
- Logger.PrintStub(LogClass.ServiceAm);
+ UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
context.ResponseData.Write(0L);
+ Logger.PrintStub(LogClass.ServiceAm, new { userId });
+
return ResultCode.Success;
}
@@ -54,9 +55,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// SetTerminateResult(u32)
public ResultCode SetTerminateResult(ServiceCtx context)
{
- int errorCode = context.RequestData.ReadInt32();
-
- string result = GetFormattedErrorCode(errorCode);
+ int errorCode = context.RequestData.ReadInt32();
+ string result = GetFormattedErrorCode(errorCode);
Logger.PrintInfo(LogClass.ServiceAm, $"Result = 0x{errorCode:x8} ({result}).");
@@ -95,11 +95,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// GetPseudoDeviceId() -> nn::util::Uuid
public ResultCode GetPseudoDeviceId(ServiceCtx context)
{
- Logger.PrintStub(LogClass.ServiceAm);
-
context.ResponseData.Write(0L);
context.ResponseData.Write(0L);
+ Logger.PrintStub(LogClass.ServiceAm);
+
return ResultCode.Success;
}
@@ -118,11 +118,27 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
{
int state = context.RequestData.ReadInt32();
- Logger.PrintStub(LogClass.ServiceAm);
+ Logger.PrintStub(LogClass.ServiceAm, new { state });
return ResultCode.Success;
}
+ [Command(110)] // 5.0.0+
+ // QueryApplicationPlayStatistics(buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
+ public ResultCode QueryApplicationPlayStatistics(ServiceCtx context)
+ {
+ // TODO: Call pdm:qry cmd 13 when IPC call between services will be implemented.
+ return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context);
+ }
+
+ [Command(111)] // 6.0.0+
+ // QueryApplicationPlayStatisticsByUid(nn::account::Uid, buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
+ public ResultCode QueryApplicationPlayStatisticsByUid(ServiceCtx context)
+ {
+ // TODO: Call pdm:qry cmd 16 when IPC call between services will be implemented.
+ return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true);
+ }
+
[Command(130)] // 8.0.0+
// GetGpuErrorDetectedSystemEvent() -> handle<copy>
public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context)
diff --git a/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs
index b46bd2b3..a5eb42f3 100644
--- a/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.HLE.HOS.Services.Am
Success = 0,
NoMessages = (3 << ErrorCodeShift) | ModuleId,
+ ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs
index 61b26b8c..210dd98b 100644
--- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs
+++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs
@@ -1,8 +1,24 @@
-namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm
+using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
+
+namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm
{
[Service("pdm:qry")]
class IQueryService : IpcService
{
public IQueryService(ServiceCtx context) { }
+
+ [Command(13)] // 5.0.0+
+ // QueryApplicationPlayStatisticsForSystem(buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
+ public ResultCode QueryApplicationPlayStatisticsForSystem(ServiceCtx context)
+ {
+ return QueryPlayStatisticsManager.GetPlayStatistics(context);
+ }
+
+ [Command(16)] // 6.0.0+
+ // QueryApplicationPlayStatisticsByUserAccountIdForSystem(nn::account::Uid, buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
+ public ResultCode QueryApplicationPlayStatisticsByUserAccountIdForSystem(ServiceCtx context)
+ {
+ return QueryPlayStatisticsManager.GetPlayStatistics(context, true);
+ }
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
new file mode 100644
index 00000000..b3646925
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
@@ -0,0 +1,83 @@
+using ARMeilleure.Memory;
+using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
+{
+ static class QueryPlayStatisticsManager
+ {
+ private static Dictionary<UInt128, ApplicationPlayStatistics> applicationPlayStatistics = new Dictionary<UInt128, ApplicationPlayStatistics>();
+
+ internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
+ {
+ long inputPosition = context.Request.SendBuff[0].Position;
+ long inputSize = context.Request.SendBuff[0].Size;
+
+ long outputPosition = context.Request.ReceiveBuff[0].Position;
+ long outputSize = context.Request.ReceiveBuff[0].Size;
+
+ UInt128 userId = byUserId ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128();
+
+ if (byUserId)
+ {
+ if (!context.Device.System.State.Account.TryGetUser(userId, out _))
+ {
+ return ResultCode.UserNotFound;
+ }
+ }
+
+ PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.System.ControlData.PlayLogQueryCapability;
+
+ List<ulong> titleIds = new List<ulong>();
+
+ for (int i = 0; i < inputSize / sizeof(ulong); i++)
+ {
+ titleIds.Add(BitConverter.ToUInt64(context.Memory.ReadBytes(inputPosition, inputSize), 0));
+ }
+
+ if (queryCapability == PlayLogQueryCapability.WhiteList)
+ {
+ // Check if input title ids are in the whitelist.
+ foreach (ulong titleId in titleIds)
+ {
+ if (!context.Device.System.ControlData.PlayLogQueryableApplicationId.Contains(titleId))
+ {
+ return (ResultCode)Am.ResultCode.ObjectInvalid;
+ }
+ }
+ }
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
+
+ // Return ResultCode.ServiceUnavailable if data is locked by another process.
+ var filteredApplicationPlayStatistics = applicationPlayStatistics.AsEnumerable();
+
+ if (queryCapability == PlayLogQueryCapability.None)
+ {
+ filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Value.TitleId == context.Process.TitleId);
+ }
+ else // PlayLogQueryCapability.All
+ {
+ filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => titleIds.Contains(kv.Value.TitleId));
+ }
+
+ if (byUserId)
+ {
+ filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Key == userId);
+ }
+
+ for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++)
+ {
+ MemoryHelper.Write(context.Memory, outputPosition + (i * Marshal.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value);
+ }
+
+ context.ResponseData.Write(filteredApplicationPlayStatistics.Count());
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs
new file mode 100644
index 00000000..c28d757e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x18)]
+ struct ApplicationPlayStatistics
+ {
+ public ulong TitleId;
+ public long TotalPlayTime; // In nanoseconds.
+ public long TotalLaunchCount;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs
new file mode 100644
index 00000000..9e4b85de
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types
+{
+ enum PlayLogQueryCapability
+ {
+ None,
+ WhiteList,
+ All
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs
new file mode 100644
index 00000000..3ceb8d1a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm
+{
+ enum ResultCode
+ {
+ ModuleId = 178,
+ ErrorCodeShift = 9,
+
+ Success = 0,
+
+ UserNotFound = (101 << ErrorCodeShift) | ModuleId,
+ ServiceUnavailable = (150 << ErrorCodeShift) | ModuleId
+ }
+} \ No newline at end of file