Added support for pdmqry.

This commit is contained in:
yellows8 2019-07-09 21:18:42 -04:00
parent 4f441a4f28
commit f418536e37
No known key found for this signature in database
GPG Key ID: 0AF90DA3F1E60E43
2 changed files with 658 additions and 1 deletions

View File

@ -7,11 +7,263 @@
#pragma once
#include "../types.h"
#include "../services/sm.h"
#include "../kernel/event.h"
/// PlayEventType
typedef enum {
PdmPlayEventType_Applet = 0, ///< Applet
PdmPlayEventType_Account = 1, ///< Account
PdmPlayEventType_PowerStateChange = 2, ///< PowerStateChange
PdmPlayEventType_OperationModeChange = 3, ///< OperationModeChange
PdmPlayEventType_Initialize = 4, ///< Initialize. Used for the very first PlayEvent entry in the log.
} PdmPlayEventType;
/// AppletEventType
typedef enum {
PdmAppletEventType_Launch = 0, ///< "launch"
PdmAppletEventType_Exit = 1, ///< "exit"
PdmAppletEventType_InFocus = 2, ///< "in_focus"
PdmAppletEventType_OutOfFocus = 3, ///< "out_of_focus"
PdmAppletEventType_OutOfFocus4 = 4, ///< "out_of_focus"
PdmAppletEventType_Exit5 = 5, ///< "exit"
PdmAppletEventType_Exit6 = 6, ///< "exit"
} PdmAppletEventType;
/// PlayLogPolicy
typedef enum {
PdmPlayLogPolicy_All = 0, ///< All pdm:qry commands which require ::PdmPlayEventType_Applet and AppletId = Application will only return the entry when PlayLogPolicy matches this value.
PdmPlayLogPolicy_LogOnly = 1, ///< The above commands will filter out the entry with this.
PdmPlayLogPolicy_None = 2, ///< The pdm:ntfy commands which handle ::PdmPlayEventType_Applet logging will immediately return 0 when the input param matches this value.
} PdmPlayLogPolicy;
/// ApplicationEvent
/// Timestamp format, converted from PosixTime: total minutes since epoch UTC 1999/12/31 00:00:00.
/// See \ref pdmPlayTimestampToPosix.
typedef struct {
u64 titleID; ///< Application titleID.
u32 entryindex; ///< Entry index.
u32 timestampUser; ///< See PdmPlayEvent::timestampUser, with the above timestamp format.
u32 timestampNetwork; ///< See PdmPlayEvent::timestampNetwork, with the above timestamp format.
u8 eventType; ///< \ref PdmAppletEventType
u8 pad[3]; ///< Padding.
} PdmApplicationEvent;
/// PlayStatistics
typedef struct {
u64 titleID; ///< Application titleID.
u32 first_entryindex; ///< Entry index for the first time the title was played.
u32 first_timestampUser; ///< See PdmApplicationEvent::timestampUser. This is for the first time the title was played.
u32 first_timestampNetwork; ///< See PdmApplicationEvent::timestampNetwork. This is for the first time the title was played.
u32 last_entryindex; ///< Entry index for the last time the title was played.
u32 last_timestampUser; ///< See PdmApplicationEvent::timestampUser. This is for the last time the title was played.
u32 last_timestampNetwork; ///< See PdmApplicationEvent::timestampNetwork. This is for the last time the title was played.
u32 playtimeMinutes; ///< Total play-time in minutes.
u32 totalLaunches; ///< Total times the application title was launched.
} PdmPlayStatistics;
/// LastPlayTime
/// This contains data from the last time the title was played.
typedef struct {
u64 titleID; ///< Application titleID.
u32 timestampUser; ///< See PdmApplicationEvent::timestampUser.
u32 timestampNetwork; ///< See PdmApplicationEvent::timestampNetwork.
u32 lastPlayedMinutes; ///< Total minutes since the title was last played.
u8 flag; ///< Flag indicating whether the above field is set.
u8 pad[3]; ///< Padding.
} PdmLastPlayTime;
/// PlayEvent
/// This is the raw entry struct directly read from FS, without any entry filtering.
typedef struct {
union {
struct {
u32 titleID[2]; ///< titleID.
union {
struct {
u32 version; ///< Title version.
} application; ///< For AppletId = 0x01.
struct {
u8 flag; ///< Set to 0x1 by pdm:ntfy cmd8, indicating that the below field is set to an input param.
u8 unk_x9; ///< Input value from pdm:ntfy cmd8.
u8 pad[2]; ///< Padding.
} applet; ///< For AppletId != 0x01.
u32 data;
} unk_x8;
u8 appletId; ///< \ref AppletId
u8 storageId; ///< See FsStorageId.
u8 logPolicy; ///< \ref PdmPlayLogPolicy
u8 eventType; ///< \ref PdmAppletEventType
u8 unused[0xc]; ///< Unused.
} applet;
struct {
u32 userID[4]; ///< userID.
u32 titleID[2]; ///< Application titleID, see below.
u8 type; ///< 0-1 to be listed by \ref pdmqryQueryAccountEvent, or 2 to include the above titleID.
} account;
struct {
u8 value; ///< Input value from the pdm:ntfy command.
u8 unused[0x1b]; ///< Unused.
} powerStateChange;
struct {
u8 value; ///< Input value from the pdm:ntfy command.
u8 unused[0x1b]; ///< Unused.
} operationModeChange;
u8 data[0x1c];
} eventData; ///< titleID/userID stored within here have the u32 low/high swapped in each u64.
u8 playEventType; ///< \ref PdmPlayEventType. Controls which struct in the above eventData is used. ::PdmPlayEventType_Initialize doesn't use eventData.
u8 pad[3]; ///< Padding.
u64 timestampUser; ///< PosixTime timestamp from StandardUserSystemClock.
u64 timestampNetwork; ///< PosixTime timestamp from StandardNetworkSystemClock.
u64 timestampSteady; ///< Timestamp in seconds derived from StandardSteadyClock.
} PdmPlayEvent;
/// AccountEvent
typedef struct {
union { u128 userID; } PACKED; ///< userID.
u32 entryindex; ///< Entry index.
u8 pad[4]; ///< Padding.
u64 timestampUser; ///< See PdmPlayEvent::timestampUser.
u64 timestampNetwork; ///< See PdmPlayEvent::timestampNetwork.
u64 timestampSteady; ///< See PdmPlayEvent::timestampSteady.
u8 eventType; ///< Account eventType.
u8 pad_x31[7]; ///< Padding.
} PdmAccountEvent;
/// AccountPlayEvent
/// This is the raw entry struct directly read from FS, without any entry filtering. This is separate from \ref PdmPlayEvent.
typedef struct {
u8 unk_x0[4]; ///< Unknown.
u32 titleID[2]; ///< titleID, with the u32 low/high words swapped.
u8 unk_xc[0xc]; ///< Unknown.
u64 timestamp0; ///< POSIX timestamp.
u64 timestamp1; ///< POSIX timestamp.
} PdmAccountPlayEvent;
/// ApplicationPlayStatistics
typedef struct {
u64 titleID; ///< titleID
u64 titleID; ///< Application titleID.
u64 totalPlayTime; ///< Total play-time in nanoseconds.
u64 totalLaunches; ///< Total times the application title was launched.
} PdmApplicationPlayStatistics;
Result pdmqryInitialize(void);
void pdmqryExit(void);
/**
* @brief Gets a list of \ref PdmApplicationEvent.
* @param[in] entryindex Start entry index.
* @param[out] events Output \ref PdmApplicationEvent array.
* @param[in] count Max entries in the output array.
* @param[out] total_out Total output entries.
*/
Result pdmqryQueryApplicationEvent(u32 entryindex, PdmApplicationEvent *events, s32 count, s32 *total_out);
/**
* @brief Gets \ref PdmPlayStatistics for the specified titleID.
* @param[in] titleID Application titleID.
* @param[out] stats \ref PdmPlayStatistics
*/
Result pdmqryQueryPlayStatisticsByApplicationId(u64 titleID, PdmPlayStatistics *stats);
/**
* @brief Gets \ref PdmPlayStatistics for the specified titleID and account userID.
* @param[in] titleID Application titleID.
* @param[in] userID Account userID.
* @param[out] stats \ref PdmPlayStatistics
*/
Result pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(u64 titleID, u128 userID, PdmPlayStatistics *stats);
/**
* @brief Gets \ref PdmLastPlayTime for the specified titles.
* @param[out] playtimes Output \ref PdmLastPlayTime array.
* @param[in] titleIDs Input titleIDs array.
* @param[in] count Total entries in the input/output arrays.
* @param[out] total_out Total output entries.
*/
Result pdmqryQueryLastPlayTime(PdmLastPlayTime *playtimes, const u64 *titleIDs, s32 count, s32 *total_out);
/**
* @brief Gets a list of \ref PdmPlayEvent.
* @param[in] entryindex Start entry index.
* @param[out] events Output \ref PdmPlayEvent array.
* @param[in] count Max entries in the output array.
* @param[out] total_out Total output entries.
*/
Result pdmqryQueryPlayEvent(u32 entryindex, PdmPlayEvent *events, s32 count, s32 *total_out);
/**
* @brief Gets range fields which can then be used with the other pdmqry funcs, except for \ref pdmqryQueryAccountPlayEvent.
* @param[out] total_entries Total entries.
* @param[out] start_entryindex Start entry index.
* @param[out] end_entryindex End entry index.
*/
Result pdmqryGetAvailablePlayEventRange(u32 *total_entries, u32 *start_entryindex, u32 *end_entryindex);
/**
* @brief Gets a list of \ref PdmAccountEvent.
* @param[in] entryindex Start entry index.
* @param[out] events Output \ref PdmAccountEvent array.
* @param[in] count Max entries in the output array.
* @param[out] total_out Total output entries.
*/
Result pdmqryQueryAccountEvent(u32 entryindex, PdmAccountEvent *events, s32 count, s32 *total_out);
/**
* @brief Gets a list of \ref PdmAccountPlayEvent.
* @note Only available with [4.0.0+].
* @param[in] entryindex Start entry index.
* @param[in] userID Account userID.
* @param[out] events Output \ref PdmAccountPlayEvent array.
* @param[in] count Max entries in the output array.
* @param[out] total_out Total output entries.
*/
Result pdmqryQueryAccountPlayEvent(u32 entryindex, u128 userID, PdmAccountPlayEvent *events, s32 count, s32 *total_out);
/**
* @brief Gets range fields which can then be used with \ref pdmqryQueryAccountPlayEvent.
* @param[in] userID Account userID.
* @param[out] total_entries Total entries.
* @param[out] start_entryindex Start entry index.
* @param[out] end_entryindex End entry index.
*/
Result pdmqryGetAvailableAccountPlayEventRange(u128 userID, u32 *total_entries, u32 *start_entryindex, u32 *end_entryindex);
/**
* @brief Gets a list of titles played by the specified user.
* @note Only available with [6.0.0+].
* @param[in] userID Account userID.
* @param[out] titleIDs Output titleID array.
* @param[in] count Max entries in the output array.
* @param[out] total_out Total output entries.
*/
Result pdmqryGetUserPlayedApplications(u128 userID, u64 *titleIDs, size_t count, u32 *total_out);
/**
* @brief Gets an Event which is signaled when logging a new \ref PdmPlayEvent which would be available via \ref pdmqryQueryAccountEvent, where PdmPlayEvent::eventData::account::type is 0.
* @note Only available with [6.0.0+].
* @note The Event must be closed by the user once finished with it.
* @param[out] event_out Output Event with autoclear=false.
*/
Result pdmqryGetUserAccountEvent(Event* event_out);
/**
* @brief Helper function which converts a Play timestamp from the Pdm*Event structs to POSIX.
* @param[in] timestamp Input timestamp.
*/
static inline u64 pdmPlayTimestampToPosix(u32 timestamp) {
return ((u64)timestamp) * 60 + 946598400;
}

405
nx/source/services/pdm.c Normal file
View File

@ -0,0 +1,405 @@
#include <string.h>
#include "types.h"
#include "result.h"
#include "arm/atomics.h"
#include "kernel/ipc.h"
#include "kernel/event.h"
#include "services/sm.h"
#include "services/pdm.h"
#include "runtime/hosversion.h"
static Service g_pdmqrySrv;
static u64 g_pdmqryRefCnt;
Result pdmqryInitialize(void) {
atomicIncrement64(&g_pdmqryRefCnt);
if (serviceIsActive(&g_pdmqrySrv))
return 0;
Result rc = smGetService(&g_pdmqrySrv, "pdm:qry");
if (R_FAILED(rc)) pdmqryExit();
return rc;
}
void pdmqryExit(void) {
if (atomicDecrement64(&g_pdmqryRefCnt) == 0)
serviceClose(&g_pdmqrySrv);
}
static Result _pdmqryQueryEvent(u64 cmd_id, u32 entryindex, void* events, size_t entrysize, s32 count, s32 *total_out) {
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvBuffer(&c, events, count*entrysize, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
u32 entryindex;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = cmd_id;
raw->entryindex = entryindex;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
s32 total_out;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && total_out) *total_out = resp->total_out;
}
return rc;
}
Result pdmqryQueryApplicationEvent(u32 entryindex, PdmApplicationEvent *events, s32 count, s32 *total_out) {
return _pdmqryQueryEvent(0, entryindex, events, sizeof(PdmApplicationEvent), count, total_out);
}
Result pdmqryQueryPlayStatisticsByApplicationId(u64 titleID, PdmPlayStatistics *stats) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 titleID;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 4;
raw->titleID = titleID;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
PdmPlayStatistics stats;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && stats) memcpy(stats, &resp->stats, sizeof(resp->stats));
}
return rc;
}
Result pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(u64 titleID, u128 userID, PdmPlayStatistics *stats) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 titleID;
union { u128 userID; } PACKED;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 5;
raw->titleID = titleID;
raw->userID = userID;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
PdmPlayStatistics stats;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && stats) memcpy(stats, &resp->stats, sizeof(resp->stats));
}
return rc;
}
Result pdmqryQueryLastPlayTime(PdmLastPlayTime *playtimes, const u64 *titleIDs, s32 count, s32 *total_out) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, titleIDs, count*sizeof(u64), BufferType_Normal);
ipcAddRecvBuffer(&c, playtimes, count*sizeof(PdmLastPlayTime), BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 7;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
s32 total_out;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && total_out) *total_out = resp->total_out;
}
return rc;
}
Result pdmqryQueryPlayEvent(u32 entryindex, PdmPlayEvent *events, s32 count, s32 *total_out) {
return _pdmqryQueryEvent(8, entryindex, events, sizeof(PdmPlayEvent), count, total_out);
}
Result pdmqryGetAvailablePlayEventRange(u32 *total_entries, u32 *start_entryindex, u32 *end_entryindex) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 9;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
u32 total_entries;
u32 start_entryindex;
u32 end_entryindex;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries;
if (R_SUCCEEDED(rc) && start_entryindex) *start_entryindex = resp->start_entryindex;
if (R_SUCCEEDED(rc) && end_entryindex) *end_entryindex = resp->end_entryindex;
}
return rc;
}
Result pdmqryQueryAccountEvent(u32 entryindex, PdmAccountEvent *events, s32 count, s32 *total_out) {
return _pdmqryQueryEvent(10, entryindex, events, sizeof(PdmAccountEvent), count, total_out);
}
Result pdmqryQueryAccountPlayEvent(u32 entryindex, u128 userID, PdmAccountPlayEvent *events, s32 count, s32 *total_out) {
IpcCommand c;
ipcInitialize(&c);
if (hosversionBefore(4,0,0))
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
ipcAddRecvBuffer(&c, events, count*sizeof(PdmAccountPlayEvent), BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
u32 entryindex;
u32 pad;
union { u128 userID; } PACKED;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 11;
raw->entryindex = entryindex;
raw->userID = userID;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
s32 total_out;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && total_out) *total_out = resp->total_out;
}
return rc;
}
Result pdmqryGetAvailableAccountPlayEventRange(u128 userID, u32 *total_entries, u32 *start_entryindex, u32 *end_entryindex) {
IpcCommand c;
ipcInitialize(&c);
if (hosversionBefore(4,0,0))
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
struct {
u64 magic;
u64 cmd_id;
u128 userID;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 12;
raw->userID = userID;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
u32 total_entries;
u32 start_entryindex;
u32 end_entryindex;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries;
if (R_SUCCEEDED(rc) && start_entryindex) *start_entryindex = resp->start_entryindex;
if (R_SUCCEEDED(rc) && end_entryindex) *end_entryindex = resp->end_entryindex;
}
return rc;
}
Result pdmqryGetUserPlayedApplications(u128 userID, u64 *titleIDs, size_t count, u32 *total_out) {
IpcCommand c;
ipcInitialize(&c);
if (hosversionBefore(6,0,0))
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
ipcAddRecvBuffer(&c, titleIDs, count*sizeof(u64), BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
u128 userID;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 14;
raw->userID = userID;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
u32 total_out;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && total_out) *total_out = resp->total_out;
}
return rc;
}
Result pdmqryGetUserAccountEvent(Event* event_out) {
IpcCommand c;
ipcInitialize(&c);
if (hosversionBefore(6,0,0))
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(&g_pdmqrySrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 15;
Result rc = serviceIpcDispatch(&g_pdmqrySrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(&g_pdmqrySrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
eventLoadRemote(event_out, r.Handles[0], false);
}
}
return rc;
}