diff --git a/nx/include/switch.h b/nx/include/switch.h index 15697d1d..72a09075 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -85,6 +85,7 @@ extern "C" { #include "switch/services/ncm.h" #include "switch/services/psc.h" #include "switch/services/caps.h" +#include "switch/services/capsu.h" #include "switch/services/capssc.h" #include "switch/services/capssu.h" #include "switch/services/nfc.h" diff --git a/nx/include/switch/services/caps.h b/nx/include/switch/services/caps.h index 88bbf989..965af738 100644 --- a/nx/include/switch/services/caps.h +++ b/nx/include/switch/services/caps.h @@ -25,6 +25,13 @@ typedef enum { AlbumReportOption_Unknown3 = 3, ///< Unknown. } AlbumReportOption; +/// ContentType +typedef enum { + CapsContentType_Screenshot = 0, ///< Album screenshots. + CapsContentType_Movie = 1, ///< Album videos. + CapsContentType_ExtraMovie = 3, ///< Videos recorded by the current Application host title via \ref grcCreateMovieMaker. +} CapsContentType; + /// ScreenShotAttribute typedef struct { u32 unk_x0; ///< Always set to 0 by official sw. @@ -34,11 +41,36 @@ typedef struct { u8 unk_x10[0x30]; ///< Always set to 0 by official sw. } CapsScreenShotAttribute; +/// ScreenShotAttributeForApplication. Only unk_x0 is used by official sw. +typedef struct { + u32 unk_x0; ///< Unknown. + u8 unk_x4; ///< Unknown. + u8 unk_x5; ///< Unknown. + u8 unk_x6; ///< Unknown. + u8 pad; ///< Unknown. + u32 unk_x8; ///< Unknown. + u32 unk_xc; ///< Unknown. + u32 unk_x10; ///< Unknown. + u32 unk_x14; ///< Unknown. + u32 unk_x18; ///< Unknown. + u32 unk_x1c; ///< Unknown. + u16 unk_x20; ///< Unknown. + u16 unk_x22; ///< Unknown. + u16 unk_x24; ///< Unknown. + u16 unk_x26; ///< Unknown. + u8 reserved[0x18]; ///< Always zero. +} CapsScreenShotAttributeForApplication; + +/// ScreenShotDecodeOption +typedef struct { + u8 unk_x0[0x20]; ///< Unknown. Set to all-zero by official sw. +} CapsScreenShotDecodeOption; + /// AlbumFileDateTime. This corresponds to each field in the Album entry filename, prior to the "-": "YYYYMMDDHHMMSSII". typedef struct { u16 year; ///< Year. u8 month; ///< Month. - u8 day; ///< Day. + u8 day; ///< Day of the month. u8 hour; ///< Hour. u8 minute; ///< Minute. u8 second; ///< Second. @@ -56,15 +88,35 @@ typedef struct { /// AlbumEntry typedef struct { - u8 unk_x0[0x8]; - CapsAlbumEntryId id; + u8 unk_x0[0x8]; ///< Unknown. + CapsAlbumEntryId id; ///< \ref CapsAlbumEntryId } CapsAlbumEntry; /// ApplicationAlbumEntry typedef struct { - u8 data[0x20]; + union { + u8 data[0x20]; ///< Data. + + struct { + u8 unk_x0[0x20]; ///< Unknown. + } v0; ///< Pre-7.0.0 + + struct { + u8 unk_x0[0x8]; ///< Unknown. + u8 unk_x8[0x8]; ///< Unknown. + CapsAlbumFileDateTime datetime; ///< \ref CapsAlbumFileDateTime + u8 unk_x18[0x8]; ///< Unknown. + } v1; ///< [7.0.0+] + }; } CapsApplicationAlbumEntry; +/// ApplicationAlbumFileEntry +typedef struct { + CapsApplicationAlbumEntry entry; ///< \ref CapsApplicationAlbumEntry + CapsAlbumFileDateTime datetime; ///< \ref CapsAlbumFileDateTime + u64 unk_x28; ///< Unknown. +} CapsApplicationAlbumFileEntry; + /// ApplicationData typedef struct { u8 userdata[0x400]; ///< UserData. @@ -78,6 +130,37 @@ typedef struct { u8 pad[7]; ///< Padding. } CapsUserIdList; -// Get the ShimLibraryVersion. +/// LoadAlbumScreenShotImageOutputForApplication +typedef struct { + s64 width; ///< Width. Official sw copies this to a s32 output field. + s64 height; ///< Height. Official sw copies this to a s32 output field. + CapsScreenShotAttributeForApplication attr; ///< \ref CapsScreenShotAttributeForApplication + CapsApplicationData appdata; ///< \ref CapsApplicationData + u8 reserved[0xac]; ///< Unused. +} CapsLoadAlbumScreenShotImageOutputForApplication; + +/// Gets the ShimLibraryVersion. u64 capsGetShimLibraryVersion(void); +/// Gets the default start_datetime. +static inline CapsAlbumFileDateTime capsGetDefaultStartDateTime(void) { + return (CapsAlbumFileDateTime){.year = 1970, .month = 1, .day = 1}; +} + +/// Gets the default end_datetime. +static inline CapsAlbumFileDateTime capsGetDefaultEndDateTime(void) { + return (CapsAlbumFileDateTime){.year = 3000, .month = 1, .day = 1}; +} + +/// Convert a \ref CapsApplicationAlbumFileEntry to \ref CapsApplicationAlbumEntry. +static inline void capsConvertApplicationAlbumFileEntryToApplicationAlbumEntry(CapsApplicationAlbumEntry *out, CapsApplicationAlbumFileEntry *in) { + *out = in->entry; +} + +/// Convert a \ref CapsApplicationAlbumEntry to \ref CapsApplicationAlbumFileEntry. Should only be used on [7.0.0+]. +static inline void capsConvertApplicationAlbumEntryToApplicationAlbumFileEntry(CapsApplicationAlbumFileEntry *out, CapsApplicationAlbumEntry *in) { + out->entry = *in; + out->datetime = in->v1.datetime; + out->unk_x28 = 0; +} + diff --git a/nx/include/switch/services/capssc.h b/nx/include/switch/services/capssc.h index 7a1e4546..b04e2e80 100644 --- a/nx/include/switch/services/capssc.h +++ b/nx/include/switch/services/capssc.h @@ -11,7 +11,11 @@ /// Initialize caps:sc. Only available on [2.0.0+]. Result capsscInitialize(void); + +/// Exit caps:sc. void capsscExit(void); + +/// Gets the Service for caps:sc. Service* capsscGetServiceSession(void); /** diff --git a/nx/include/switch/services/capssu.h b/nx/include/switch/services/capssu.h index adfb0fab..2b133924 100644 --- a/nx/include/switch/services/capssu.h +++ b/nx/include/switch/services/capssu.h @@ -74,7 +74,7 @@ Result capssuSaveScreenShotEx0(const void* buffer, size_t size, const CapsScreen * @param[in] size Size of the buffer, must be at least 0x384000. * @param[in] attr \ref CapsScreenShotAttribute * @param[in] reportoption \ref AlbumReportOption - * @parma[in] appdata \ref CapsApplicationData + * @param[in] appdata \ref CapsApplicationData * @param[out] out \ref CapsApplicationAlbumEntry. Optional, can be NULL. */ Result capssuSaveScreenShotEx1(const void* buffer, size_t size, const CapsScreenShotAttribute *attr, AlbumReportOption reportoption, CapsApplicationData *appdata, CapsApplicationAlbumEntry *out); @@ -86,7 +86,7 @@ Result capssuSaveScreenShotEx1(const void* buffer, size_t size, const CapsScreen * @param[in] size Size of the buffer, must be at least 0x384000. * @param[in] attr \ref CapsScreenShotAttribute * @param[in] reportoption \ref AlbumReportOption - * @parma[in] list \ref CapsUserIdList + * @param[in] list \ref CapsUserIdList * @param[out] out \ref CapsApplicationAlbumEntry. Optional, can be NULL. */ Result capssuSaveScreenShotEx2(const void* buffer, size_t size, const CapsScreenShotAttribute *attr, AlbumReportOption reportoption, CapsUserIdList *list, CapsApplicationAlbumEntry *out); diff --git a/nx/include/switch/services/capsu.h b/nx/include/switch/services/capsu.h new file mode 100644 index 00000000..958561c5 --- /dev/null +++ b/nx/include/switch/services/capsu.h @@ -0,0 +1,172 @@ +/** + * @file capsu.h + * @brief Application Album (caps:u) service IPC wrapper. + * This is only usable with AlbumFiles associated with the current Application host title. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../services/sm.h" +#include "../services/caps.h" + +/// Initialize caps:u. Only available on [5.0.0+]. +Result capsuInitialize(void); + +/// Exit caps:u. +void capsuExit(void); + +/// Gets the Service for caps:u. +Service* capsuGetServiceSession(void); + +/// Gets the Service for IAlbumAccessorApplicationSession, only initialized after \ref capsuOpenAlbumMovieStream (unaffected by using \ref capsuCloseAlbumMovieStream). +Service* capsuGetServiceSession_Accessor(void); + +/** + * @brief Gets a listing of \ref CapsApplicationAlbumFileEntry. + * @note On [6.0.0+] this uses GetAlbumFileList1AafeAruidDeprecated, otherwise this uses GetAlbumFileList0AafeAruidDeprecated. + * @note This is an old version of \ref capsuGetAlbumFileList3. + * @param[out] entries Output array of \ref CapsApplicationAlbumFileEntry. + * @param[in] count Max size of the output array in entries. + * @param[in] type \ref CapsContentType + * @param[in] start_datetime Start \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[in] end_datetime End \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[out] total_entries Total output entries. + */ +Result capsuGetAlbumFileListDeprecated1(CapsApplicationAlbumFileEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u64 *total_entries); + +/** + * @brief Gets a listing of \ref CapsApplicationAlbumFileEntry, where the AlbumFile has an UserId which matches the input one. See also \ref capssuSaveScreenShotWithUserIds. + * @note Only available on [6.0.0+]. + * @note This is an old version of \ref capsuGetAlbumFileList4. + * @param[out] entries Output array of \ref CapsApplicationAlbumFileEntry. + * @param[in] count Max size of the output array in entries. + * @param[in] type \ref CapsContentType + * @param[in] start_datetime Start \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[in] end_datetime End \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[in] userID userID. + * @param[out] total_entries Total output entries. + */ +Result capsuGetAlbumFileListDeprecated2(CapsApplicationAlbumFileEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u128 userID, u64 *total_entries); + +/** + * @brief Gets a listing of \ref CapsApplicationAlbumEntry. + * @note Only available on [7.0.0+], on prior sysvers use \ref capsuGetAlbumFileListDeprecated1 instead. + * @param[out] entries Output array of \ref CapsApplicationAlbumEntry. + * @param[in] count Max size of the output array in entries. + * @param[in] type \ref CapsContentType + * @param[in] start_datetime Start \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[in] end_datetime End \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[out] total_entries Total output entries. + */ +Result capsuGetAlbumFileList3(CapsApplicationAlbumEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u64 *total_entries); + +/** + * @brief Gets a listing of \ref CapsApplicationAlbumEntry, where the AlbumFile has an UserId which matches the input one. See also \ref capssuSaveScreenShotWithUserIds. + * @note Only available on [7.0.0+], on prior sysvers use \ref capsuGetAlbumFileListDeprecated2 instead. + * @param[out] entries Output array of \ref CapsApplicationAlbumEntry. + * @param[in] count Max size of the output array in entries. + * @param[in] type \ref CapsContentType + * @param[in] start_datetime Start \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[in] end_datetime End \ref CapsAlbumFileDateTime, when NULL the default is used. + * @param[in] userID userID. + * @param[out] total_entries Total output entries. + */ +Result capsuGetAlbumFileList4(CapsApplicationAlbumEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u128 userID, u64 *total_entries); + +/** + * @brief Deletes the specified AlbumFile. + * @param[in] type \ref CapsContentType, must match ::CapsContentType_ExtraMovie. + * @param[in] entry \ref CapsApplicationAlbumFileEntry + */ +Result capsuDeleteAlbumFile(CapsContentType type, const CapsApplicationAlbumFileEntry *entry); + +/** + * @brief Gets the filesize for the entire specified AlbumFile. + * @param[in] entry \ref CapsApplicationAlbumFileEntry + * @param[out] size Output filesize. + */ +Result capsuGetAlbumFileSize(const CapsApplicationAlbumFileEntry *entry, u64 *size); + +/** + * @brief Load the ScreenShotImage for the specified AlbumFile. + * @param[out] width Output image width. Optional, can be NULL. + * @param[out] height Output image height. Optional, can be NULL. + * @param[out] attr \ref CapsScreenShotAttributeForApplication + * @param[out] userdata Output buffer containing the UserData. Optional, can be NULL. This buffer is cleared to 0 using userdata_maxsize, prior to doing the memcpy. + * @param[in] userdata_maxsize Max size of the userdata buffer. Optional, can be 0. + * @param[out] userdata_size Userdata size field, clamped to max size sizeof(CapsApplicationData::userdata) when needed. + * @param[out] image RGBA8 image output buffer. + * @param[out] image_size Image buffer size, should be at least large enough for RGBA8 1280x720. + * @param[out] workbuf Work buffer, cleared to 0 by the cmd before it returns. + * @param[out] workbuf_size Work buffer size, must be at least the size of the JPEG within the AlbumFile. + * @param[in] entry \ref CapsApplicationAlbumFileEntry + * @param[in] option \ref CapsScreenShotDecodeOption + */ +Result capsuLoadAlbumScreenShotImage(s32 *width, s32 *height, CapsScreenShotAttributeForApplication *attr, void* userdata, size_t userdata_maxsize, u32 *userdata_size, void* image, size_t image_size, void* workbuf, size_t workbuf_size, const CapsApplicationAlbumFileEntry *entry, const CapsScreenShotDecodeOption *option); + +/** + * @brief Load the ScreenShotThumbnailImage for the specified AlbumFile. + * @param[out] width Output image width. Optional, can be NULL. + * @param[out] height Output image height. Optional, can be NULL. + * @param[out] attr \ref CapsScreenShotAttributeForApplication + * @param[out] userdata Output buffer containing the UserData. Optional, can be NULL. This buffer is cleared to 0 using userdata_maxsize, prior to doing the memcpy. + * @param[in] userdata_maxsize Max size of the userdata buffer. Optional, can be 0. + * @param[out] userdata_size Userdata size field, clamped to max size sizeof(CapsApplicationData::userdata) when needed. + * @param[out] image RGBA8 image output buffer. + * @param[out] image_size Image buffer size, should be at least large enough for RGBA8 320x180. + * @param[out] workbuf Work buffer, cleared to 0 by the cmd before it returns. + * @param[out] workbuf_size Work buffer size, must be at least the size of the JPEG within the AlbumFile. + * @param[in] entry \ref CapsApplicationAlbumFileEntry + * @param[in] option \ref CapsScreenShotDecodeOption + */ +Result capsuLoadAlbumScreenShotThumbnailImage(s32 *width, s32 *height, CapsScreenShotAttributeForApplication *attr, void* userdata, size_t userdata_maxsize, u32 *userdata_size, void* image, size_t image_size, void* workbuf, size_t workbuf_size, const CapsApplicationAlbumFileEntry *entry, const CapsScreenShotDecodeOption *option); + +/** + * @brief PrecheckToCreateContents. Official sw only uses this with ::CapsContentType_ExtraMovie. + * @param[in] type \ref CapsContentType + * @param[in] unk Unknown. + */ +Result capsuPrecheckToCreateContents(CapsContentType type, u64 unk); + +/** + * @brief Opens an AlbumMovieStream. + * @note This opens IAlbumAccessorApplicationSession if not previously opened, it's closed during \ref capsuExit. + * @note Up to 4 streams can be open at the same time. Multiple streams can be open at the same time for the same \ref CapsApplicationAlbumFileEntry. + * @param[out] stream Stream handle. + * @param[in] entry \ref CapsApplicationAlbumFileEntry + */ +Result capsuOpenAlbumMovieStream(u64 *stream, const CapsApplicationAlbumFileEntry *entry); + +/** + * @brief Closes an AlbumMovieStream. + * @param[in] stream Stream handle. + */ +Result capsuCloseAlbumMovieStream(u64 stream); + +/** + * @brief Gets the data size of an AlbumMovieStream. + * @param[in] stream Stream handle. + * @param[out] size Size of the actual MP4, without the JPEG at the end. + */ +Result capsuGetAlbumMovieStreamSize(u64 stream, u64 *size); + +/** + * @brief Reads data from an AlbumMovieStream. + * @note offset(+size) must not be negative. offset and size must be aligned to 0x40000-bytes. + * @note When offset(+size) goes beyond the size from \ref capsuGetAlbumMovieStreamSize, the regions of the buffer which goes beyond that are cleared to 0, and actual_size is still set to the input size. + * @param[in] stream Stream handle. + * @param[in] offset Offset. + * @param[out] Output data buffer. + * @param[in] size Data buffer size. + * @param[out] actual_size Actual read size. + */ +Result capsuReadAlbumMovieStream(u64 stream, s64 offset, void* buffer, size_t size, u64 *actual_size); + +/** + * @brief Gets the BrokenReason for an AlbumMovieStream. + * @note Official sw doesn't use this. + * @param[in] stream Stream handle. + */ +Result capsuGetAlbumMovieStreamBrokenReason(u64 stream); + diff --git a/nx/source/services/capsu.c b/nx/source/services/capsu.c new file mode 100644 index 00000000..370c0a8b --- /dev/null +++ b/nx/source/services/capsu.c @@ -0,0 +1,761 @@ +#include +#include +#include "types.h" +#include "result.h" +#include "arm/atomics.h" +#include "kernel/ipc.h" +#include "runtime/hosversion.h" +#include "services/applet.h" +#include "services/caps.h" +#include "services/capsu.h" +#include "services/sm.h" + +static Service g_capsuSrv; +static Service g_capsuAccessor; +static u64 g_capsuRefCnt; + +static Result _capsuSetShimLibraryVersion(u64 version); + +Result capsuInitialize(void) { + Result rc=0; + + atomicIncrement64(&g_capsuRefCnt); + + if (serviceIsActive(&g_capsuSrv)) + return 0; + + if (hosversionBefore(5,0,0)) + rc = MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + if (R_SUCCEEDED(rc)) rc = smGetService(&g_capsuSrv, "caps:u"); + + if (R_SUCCEEDED(rc) && hosversionAtLeast(7,0,0)) rc = _capsuSetShimLibraryVersion(capsGetShimLibraryVersion()); + + if (R_FAILED(rc)) capsuExit(); + + return rc; +} + +void capsuExit(void) { + if (atomicDecrement64(&g_capsuRefCnt) == 0) { + serviceClose(&g_capsuAccessor); + serviceClose(&g_capsuSrv); + } +} + +Service* capsuGetServiceSession(void) { + return &g_capsuSrv; +} + +Service* capsuGetServiceSession_Accessor(void) { + return &g_capsuAccessor; +} + +static Result _capsuCmdInU64(Service* srv, u64 inval, u64 cmd_id) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u64 inval; + } *raw; + + raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = cmd_id; + raw->inval = inval; + + Result rc = serviceIpcDispatch(srv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(srv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +static Result _capsuSetShimLibraryVersion(u64 version) { + if (hosversionBefore(7,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u64 version; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 32; + raw->version = version; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +static Result _capsuGetAlbumFileList0AafeAruidDeprecated(void* entries, size_t entrysize, size_t count, u8 type, u64 start_timestamp, u64 end_timestamp, u64 *total_entries) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u8 type; + u64 start_timestamp; + u64 end_timestamp; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + ipcAddRecvBuffer(&c, entries, count*entrysize, BufferType_Normal); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 102; + raw->type = type; + raw->start_timestamp = start_timestamp; + raw->end_timestamp = end_timestamp; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 total_entries; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries; + } + + return rc; +} + +static Result _capsuDeleteAlbumFileByAruid(u64 cmd_id, u8 type, const CapsApplicationAlbumFileEntry *entry) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u8 type; + CapsApplicationAlbumFileEntry entry; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = cmd_id; + raw->type = type; + raw->entry = *entry; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +static Result _capsuGetAlbumFileSizeByAruid(const CapsApplicationAlbumFileEntry *entry, u64 *size) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + CapsApplicationAlbumFileEntry entry; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 104; + raw->entry = *entry; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 size; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && size) *size = resp->size; + } + + return rc; +} + +static Result _capsuPrecheckToCreateContentsByAruid(u8 type, u64 unk) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u8 type; + u64 unk; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 130; + raw->type = type; + raw->unk = unk; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +static Result _capsuLoadAlbumScreenShotImageByAruid(u64 cmd_id, CapsLoadAlbumScreenShotImageOutputForApplication *out, void* image, size_t image_size, void* workbuf, size_t workbuf_size, const CapsApplicationAlbumFileEntry *entry, const CapsScreenShotDecodeOption *option) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + CapsApplicationAlbumFileEntry entry; + CapsScreenShotDecodeOption option; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + ipcAddRecvBuffer(&c, out, sizeof(*out), BufferType_Normal); + ipcAddRecvBuffer(&c, image, image_size, BufferType_Type1); + ipcAddRecvBuffer(&c, workbuf, workbuf_size, BufferType_Normal); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = cmd_id; + raw->entry = *entry; + raw->option = *option; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +static Result _capsuGetAlbumFileListAaeAruid(u64 cmd_id, void* entries, size_t entrysize, size_t count, u8 type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u64 *total_entries) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u8 type; + CapsAlbumFileDateTime start_datetime; + CapsAlbumFileDateTime end_datetime; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + ipcAddRecvBuffer(&c, entries, count*entrysize, BufferType_Normal); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = cmd_id; + raw->type = type; + raw->start_datetime = *start_datetime; + raw->end_datetime = *end_datetime; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 total_entries; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries; + } + + return rc; +} + +static Result _capsuGetAlbumFileListAaeUidAruid(u64 cmd_id, void* entries, size_t entrysize, size_t count, u8 type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u128 userID, u64 *total_entries) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u8 type; + CapsAlbumFileDateTime start_datetime; + CapsAlbumFileDateTime end_datetime; + u8 pad[6]; + union { u128 userID; } PACKED; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + ipcAddRecvBuffer(&c, entries, count*entrysize, BufferType_Normal); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = cmd_id; + raw->type = type; + raw->start_datetime = *start_datetime; + raw->end_datetime = *end_datetime; + raw->userID = userID; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 total_entries; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries; + } + + return rc; +} + +static Result _capsuOpenAccessorSessionForApplication(Service* srv_out, const CapsApplicationAlbumFileEntry *entry) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + CapsApplicationAlbumFileEntry entry; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + + raw = serviceIpcPrepareHeader(&g_capsuSrv, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 60002; + raw->entry = *entry; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_capsuSrv, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && srv_out) { + serviceCreateSubservice(srv_out, &g_capsuSrv, &r, 0); + } + } + + return rc; +} + +static Result _capsuOpenAlbumMovieReadStream(u64 *stream, const CapsApplicationAlbumFileEntry *entry) { + u64 AppletResourceUserId = 0; + appletGetAppletResourceUserId(&AppletResourceUserId); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + CapsApplicationAlbumFileEntry entry; + u64 AppletResourceUserId; + } *raw; + + ipcSendPid(&c); + + raw = serviceIpcPrepareHeader(&g_capsuAccessor, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 2001; + raw->entry = *entry; + raw->AppletResourceUserId = AppletResourceUserId; + + Result rc = serviceIpcDispatch(&g_capsuAccessor); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 stream; + } *resp; + + serviceIpcParse(&g_capsuAccessor, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && stream) *stream = resp->stream; + } + + return rc; +} + +static Result _capsuGetAlbumMovieReadStreamMovieDataSize(u64 stream, u64 *size) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u64 stream; + } *raw; + + raw = serviceIpcPrepareHeader(&g_capsuAccessor, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 2003; + raw->stream = stream; + + Result rc = serviceIpcDispatch(&g_capsuAccessor); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 size; + } *resp; + + serviceIpcParse(&g_capsuAccessor, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && size) *size = resp->size; + } + + return rc; +} + +static Result _capsuReadMovieDataFromAlbumMovieReadStream(u64 stream, s64 offset, void* buffer, size_t size, u64 *actual_size) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u64 stream; + s64 offset; + } *raw; + + ipcAddRecvBuffer(&c, buffer, size, BufferType_Normal); + + raw = serviceIpcPrepareHeader(&g_capsuAccessor, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 2004; + raw->stream = stream; + raw->offset = offset; + + Result rc = serviceIpcDispatch(&g_capsuAccessor); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 actual_size; + } *resp; + + serviceIpcParse(&g_capsuAccessor, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && actual_size) *actual_size = resp->actual_size; + } + + return rc; +} + +static inline u64 _capsuMakeTimestamp(const CapsAlbumFileDateTime *datetime) { + struct tm tmptm = {.tm_sec = datetime->second, .tm_min = datetime->minute, .tm_hour = datetime->hour, + .tm_mday = datetime->day, .tm_mon = datetime->month, .tm_year = datetime->year - 1900}; + + return mktime(&tmptm); +} + +Result capsuGetAlbumFileListDeprecated1(CapsApplicationAlbumFileEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u64 *total_entries) { + u64 start_timestamp = 0x386BF200; + u64 end_timestamp = 0xF4865700; + + CapsAlbumFileDateTime default_start = capsGetDefaultStartDateTime(); + CapsAlbumFileDateTime default_end = capsGetDefaultEndDateTime(); + + if (hosversionBefore(6,0,0)) { // GetAlbumFileListDeprecated0 + if (start_datetime) start_timestamp = _capsuMakeTimestamp(start_datetime); + if (end_datetime) end_timestamp = _capsuMakeTimestamp(end_datetime); + return _capsuGetAlbumFileList0AafeAruidDeprecated(entries, sizeof(CapsApplicationAlbumFileEntry), count, type, start_timestamp, end_timestamp, total_entries); + } + + return _capsuGetAlbumFileListAaeAruid(140, entries, sizeof(CapsApplicationAlbumFileEntry), count, type, start_datetime ? start_datetime : &default_start, end_datetime ? end_datetime : &default_end, total_entries); +} + +Result capsuGetAlbumFileListDeprecated2(CapsApplicationAlbumFileEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u128 userID, u64 *total_entries) { + if (hosversionBefore(6,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + CapsAlbumFileDateTime default_start = capsGetDefaultStartDateTime(); + CapsAlbumFileDateTime default_end = capsGetDefaultEndDateTime(); + + return _capsuGetAlbumFileListAaeUidAruid(141, entries, sizeof(CapsApplicationAlbumFileEntry), count, type, start_datetime ? start_datetime : &default_start, end_datetime ? end_datetime : &default_end, userID, total_entries); +} + +Result capsuGetAlbumFileList3(CapsApplicationAlbumEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u64 *total_entries) { + if (hosversionBefore(7,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + CapsAlbumFileDateTime default_start = capsGetDefaultStartDateTime(); + CapsAlbumFileDateTime default_end = capsGetDefaultEndDateTime(); + + return _capsuGetAlbumFileListAaeAruid(142, entries, sizeof(CapsApplicationAlbumEntry), count, type, start_datetime ? start_datetime : &default_start, end_datetime ? end_datetime : &default_end, total_entries); +} + +Result capsuGetAlbumFileList4(CapsApplicationAlbumEntry *entries, size_t count, CapsContentType type, const CapsAlbumFileDateTime *start_datetime, const CapsAlbumFileDateTime *end_datetime, u128 userID, u64 *total_entries) { + if (hosversionBefore(7,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + CapsAlbumFileDateTime default_start = capsGetDefaultStartDateTime(); + CapsAlbumFileDateTime default_end = capsGetDefaultEndDateTime(); + + return _capsuGetAlbumFileListAaeUidAruid(143, entries, sizeof(CapsApplicationAlbumEntry), count, type, start_datetime ? start_datetime : &default_start, end_datetime ? end_datetime : &default_end, userID, total_entries); +} + +Result capsuDeleteAlbumFile(CapsContentType type, const CapsApplicationAlbumFileEntry *entry) { + return _capsuDeleteAlbumFileByAruid(103, type, entry); +} + +Result capsuGetAlbumFileSize(const CapsApplicationAlbumFileEntry *entry, u64 *size) { + return _capsuGetAlbumFileSizeByAruid(entry, size); +} + +static void _capsuProcessImageOutput(CapsLoadAlbumScreenShotImageOutputForApplication *out, s32 *width, s32 *height, CapsScreenShotAttributeForApplication *attr, void* userdata, size_t userdata_maxsize, u32 *userdata_size) { + if (out==NULL) return; + + if (width) *width = out->width; + if (height) *height = out->height; + if (attr) memcpy(attr, &out->attr, sizeof(out->attr)); + + if (userdata && userdata_maxsize) { + memset(userdata, 0, userdata_maxsize); + if (userdata_maxsize > sizeof(out->appdata.userdata)) userdata_maxsize = sizeof(out->appdata.userdata); + if (userdata_maxsize > out->appdata.size) userdata_maxsize = out->appdata.size; + memcpy(userdata, out->appdata.userdata, userdata_maxsize); + } + if (userdata_size) *userdata_size = out->appdata.size > sizeof(out->appdata.userdata) ? sizeof(out->appdata.userdata) : out->appdata.size; +} + +Result capsuLoadAlbumScreenShotImage(s32 *width, s32 *height, CapsScreenShotAttributeForApplication *attr, void* userdata, size_t userdata_maxsize, u32 *userdata_size, void* image, size_t image_size, void* workbuf, size_t workbuf_size, const CapsApplicationAlbumFileEntry *entry, const CapsScreenShotDecodeOption *option) { + Result rc=0; + CapsLoadAlbumScreenShotImageOutputForApplication out={0}; + + rc = _capsuLoadAlbumScreenShotImageByAruid(110, &out, image, image_size, workbuf, workbuf_size, entry, option); + if (R_SUCCEEDED(rc)) _capsuProcessImageOutput(&out, width, height, attr, userdata, userdata_maxsize, userdata_size); + return rc; +} + +Result capsuLoadAlbumScreenShotThumbnailImage(s32 *width, s32 *height, CapsScreenShotAttributeForApplication *attr, void* userdata, size_t userdata_maxsize, u32 *userdata_size, void* image, size_t image_size, void* workbuf, size_t workbuf_size, const CapsApplicationAlbumFileEntry *entry, const CapsScreenShotDecodeOption *option) { + Result rc=0; + CapsLoadAlbumScreenShotImageOutputForApplication out={0}; + + rc = _capsuLoadAlbumScreenShotImageByAruid(120, &out, image, image_size, workbuf, workbuf_size, entry, option); + if (R_SUCCEEDED(rc)) _capsuProcessImageOutput(&out, width, height, attr, userdata, userdata_maxsize, userdata_size); + return rc; +} + +Result capsuPrecheckToCreateContents(CapsContentType type, u64 unk) { + return _capsuPrecheckToCreateContentsByAruid(type, unk); +} + +Result capsuOpenAlbumMovieStream(u64 *stream, const CapsApplicationAlbumFileEntry *entry) { + Result rc=0; + + if (!serviceIsActive(&g_capsuAccessor)) rc =_capsuOpenAccessorSessionForApplication(&g_capsuAccessor, entry); + + if (R_SUCCEEDED(rc)) rc = _capsuOpenAlbumMovieReadStream(stream, entry); + + return rc; +} + +Result capsuCloseAlbumMovieStream(u64 stream) { + if (!serviceIsActive(&g_capsuAccessor)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _capsuCmdInU64(&g_capsuAccessor, stream, 2002); +} + +Result capsuGetAlbumMovieStreamSize(u64 stream, u64 *size) { + if (!serviceIsActive(&g_capsuAccessor)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _capsuGetAlbumMovieReadStreamMovieDataSize(stream, size); +} + +Result capsuReadAlbumMovieStream(u64 stream, s64 offset, void* buffer, size_t size, u64 *actual_size) { + if (!serviceIsActive(&g_capsuAccessor)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _capsuReadMovieDataFromAlbumMovieReadStream(stream, offset, buffer, size, actual_size); +} + +Result capsuGetAlbumMovieStreamBrokenReason(u64 stream) { + if (!serviceIsActive(&g_capsuAccessor)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _capsuCmdInU64(&g_capsuAccessor, stream, 2005); +} +