From e440076eb9c5a1a40d6833120d20de7a2ee1459e Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 9 Jul 2024 22:04:35 +0200 Subject: [PATCH] Implement DLC support & others things --- Makefile | 4 +- app/Makefile | 4 +- plugin/Makefile | 4 +- plugin/includes/ArticBaseServer.hpp | 2 +- plugin/includes/amExtension.hpp | 21 ++ plugin/sources/ArticBaseFunctions.cpp | 445 ++++++++++++++++++++++++++ plugin/sources/amExtension.cpp | 97 ++++++ 7 files changed, 570 insertions(+), 7 deletions(-) create mode 100644 plugin/includes/amExtension.hpp create mode 100644 plugin/sources/amExtension.cpp diff --git a/Makefile b/Makefile index ae46d35..8181621 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ VERSION_MAJOR := 1 -VERSION_MINOR := 0 -VERSION_REVISION := 1 +VERSION_MINOR := 1 +VERSION_REVISION := 0 all: mkdir -p plugin/build diff --git a/app/Makefile b/app/Makefile index 969affc..6b5351a 100644 --- a/app/Makefile +++ b/app/Makefile @@ -25,8 +25,8 @@ LIBRARY_DIRS := $(PORTLIBS) $(CTRULIB) $(DEVKITPRO)/libcwav $(DEVKITPRO)/libncsn LIBRARIES := ctru VERSION_MAJOR := 1 -VERSION_MINOR := 0 -VERSION_MICRO := 1 +VERSION_MINOR := 1 +VERSION_MICRO := 0 BUILD_FLAGS := -march=armv6k -mtune=mpcore -mfloat-abi=hard BUILD_FLAGS_CC := -g -Wall -Wno-strict-aliasing -O3 -mword-relocations \ diff --git a/plugin/Makefile b/plugin/Makefile index c95c575..fc7103c 100644 --- a/plugin/Makefile +++ b/plugin/Makefile @@ -15,8 +15,8 @@ INCLUDES := includes SOURCES := sources sources/CTRPluginFramework VERSION_MAJOR := 1 -VERSION_MINOR := 0 -VERSION_REVISION := 1 +VERSION_MINOR := 1 +VERSION_REVISION := 0 SERVER_PORT := 5543 IP := 19 diff --git a/plugin/includes/ArticBaseServer.hpp b/plugin/includes/ArticBaseServer.hpp index f7e1cbf..0267ae9 100644 --- a/plugin/includes/ArticBaseServer.hpp +++ b/plugin/includes/ArticBaseServer.hpp @@ -45,7 +45,7 @@ private: std::vector reqParameters; }; - static constexpr const char* VERSION = "0"; + static constexpr const char* VERSION = "1"; int socketFd; bool run = true; diff --git a/plugin/includes/amExtension.hpp b/plugin/includes/amExtension.hpp new file mode 100644 index 0000000..1a5352e --- /dev/null +++ b/plugin/includes/amExtension.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "3ds.h" + +typedef struct +{ + u64 title_id; + u64 ticket_id; + u16 version; + u16 unused; + u32 size; +} AM_TicketInfo; + +Result AM_GetTitleInfoIgnorePlatform(FS_MediaType mediatype, u32 titleCount, u64 *titleIds, AM_TitleEntry *titleInfo); + +Result AMAPP_FindDLCContentInfos(FS_MediaType mediatype, u64 title_id, u32 contentCount, u16 *contentIds, AM_ContentInfo *contentInfos); + +Result AMAPP_GetDLCTitleInfos(FS_MediaType mediatype, u32 titleCount, u64 *titleIds, AM_TitleEntry *titleInfo); + +Result AMAPP_ListDataTitleTicketInfos(u32* ticketReadCount, u64 titleID, u32 ticketInfoCount, u32 offset, AM_TicketInfo* ticketInfos); + +Result AMAPP_GetPatchTitleInfos(FS_MediaType mediatype, u32 titleCount, u64 *titleIds, AM_TitleEntry *titleInfo); \ No newline at end of file diff --git a/plugin/sources/ArticBaseFunctions.cpp b/plugin/sources/ArticBaseFunctions.cpp index c6568c0..c588594 100644 --- a/plugin/sources/ArticBaseFunctions.cpp +++ b/plugin/sources/ArticBaseFunctions.cpp @@ -1,11 +1,15 @@ #include "ArticBaseFunctions.hpp" #include "Main.hpp" +#include "amExtension.hpp" #include "fsExtension.hpp" +#include "CTRPluginFramework/CTRPluginFramework.hpp" namespace ArticBaseFunctions { ExHeader_Info lastAppExheader; std::map openHandles; + CTRPluginFramework::Mutex amMutex; + CTRPluginFramework::Mutex cfgMutex; void Process_GetTitleID(ArticBaseServer::MethodInterface& mi) { bool good = true; @@ -884,6 +888,34 @@ namespace ArticBaseFunctions { mi.FinishGood(res); } + void FSUSER_CreateSysSaveData_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s8 duplicate_data; + s32 high, low, total_size, block_size, number_directories, number_files, number_directory_buckets, number_file_buckets; + + if (good) good = mi.GetParameterS32(high); + if (good) good = mi.GetParameterS32(low); + if (good) good = mi.GetParameterS32(total_size); + if (good) good = mi.GetParameterS32(block_size); + if (good) good = mi.GetParameterS32(number_directories); + if (good) good = mi.GetParameterS32(number_files); + if (good) good = mi.GetParameterS32(number_directory_buckets); + if (good) good = mi.GetParameterS32(number_file_buckets); + if (good) good = mi.GetParameterS8(duplicate_data); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + // Citra has this structure wrong, high is actually the first 4 bytes of FS_SystemSaveDataInfo and low is saveId + FS_SystemSaveDataInfo sinfo; + memcpy(&sinfo, &high, sizeof(high)); + sinfo.saveId = low; + + Result res = FSUSER_CreateSystemSaveData(sinfo, (u32)total_size, (u32)block_size, (u32)number_directories, (u32)number_files, (u32)number_directory_buckets, (u32)number_file_buckets, duplicate_data != 0); + mi.FinishGood(res); + } + void FSFILE_Close_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; @@ -1011,6 +1043,7 @@ namespace ArticBaseFunctions { if (R_FAILED(res)) { mi.ResizeLastResultBuffer(read_buf, 0); mi.FinishGood(res); + return; } mi.ResizeLastResultBuffer(read_buf, bytes_read); @@ -1091,6 +1124,7 @@ namespace ArticBaseFunctions { if (R_FAILED(res)) { mi.ResizeLastResultBuffer(read_dir_buf, 0); mi.FinishGood(res); + return; } mi.ResizeLastResultBuffer(read_dir_buf, entries_read * sizeof(FS_DirectoryEntry)); @@ -1113,6 +1147,406 @@ namespace ArticBaseFunctions { mi.FinishGood(res); } + void AM_GetTitleCount_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s8 mediatype; + + if (good) good = mi.GetParameterS8(mediatype); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + CTRPluginFramework::Lock l(amMutex); + Result res = amInit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + u32 count = 0; + res = AM_GetTitleCount(static_cast(mediatype), &count); + amExit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + ArticBaseCommon::Buffer* count_buf = mi.ReserveResultBuffer(0, sizeof(u32)); + if (!count_buf) { + return; + } + + *reinterpret_cast(count_buf->data) = count; + mi.FinishGood(res); + } + + void AM_GetTitleList_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s32 count; + s8 mediatype; + + if (good) good = mi.GetParameterS32(count); + if (good) good = mi.GetParameterS8(mediatype); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + ArticBaseCommon::Buffer* title_buf = mi.ReserveResultBuffer(0, count * sizeof(u64)); + if (!title_buf) { + return; + } + + CTRPluginFramework::Lock l(amMutex); + Result res = amInit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(title_buf, 0); + mi.FinishGood(res); + return; + } + + u32 titlesRead = 0; + res = AM_GetTitleList(&titlesRead, static_cast(mediatype), count, reinterpret_cast(title_buf->data)); + amExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(title_buf, 0); + mi.FinishGood(res); + return; + } + + mi.ResizeLastResultBuffer(title_buf, sizeof(u64) * titlesRead); + mi.FinishGood(res); + } + + void AM_GetTitleInfo_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s8 mediatype; + s8 ignorePlatform; + void* titleList; size_t titleListSize; + + if (good) good = mi.GetParameterS8(mediatype); + if (good) good = mi.GetParameterBuffer(titleList, titleListSize); + if (good) good = mi.GetParameterS8(ignorePlatform); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + CTRPluginFramework::Lock l(amMutex); + Result res = amInit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + // Cannot use output buffer while using input at the same time, need to allocate + u64* titleIDs = (u64*)malloc(titleListSize); + memcpy(titleIDs, titleList, titleListSize); + u32 count = titleListSize / sizeof(u64); + + ArticBaseCommon::Buffer* title_buf = mi.ReserveResultBuffer(0, count * sizeof(AM_TitleEntry)); + if (!title_buf) { + free(titleIDs); + return; + } + + if (ignorePlatform) { + res = AM_GetTitleInfoIgnorePlatform(static_cast(mediatype), count, titleIDs, reinterpret_cast(title_buf->data)); + } else { + res = AM_GetTitleInfo(static_cast(mediatype), count, titleIDs, reinterpret_cast(title_buf->data)); + } + free(titleIDs); + amExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(title_buf, 0); + mi.FinishGood(res); + return; + } + + mi.FinishGood(res); + } + + void AMAPP_GetDLCContentInfoCount_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s8 mediatype; + s64 title_id; + + if (good) good = mi.GetParameterS8(mediatype); + if (good) good = mi.GetParameterS64(title_id); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + CTRPluginFramework::Lock l(amMutex); + Result res = amAppInit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + u32 count = 0; + res = AMAPP_GetDLCContentInfoCount(&count, static_cast(mediatype), static_cast(title_id)); + amExit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + ArticBaseCommon::Buffer* count_buf = mi.ReserveResultBuffer(0, sizeof(u32)); + if (!count_buf) { + return; + } + + *reinterpret_cast(count_buf->data) = count; + mi.FinishGood(res); + } + + void AMAPP_FindDLCContentInfos_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s8 mediatype; + s64 title_id; + void* contentList; size_t contentListSize; + + if (good) good = mi.GetParameterS8(mediatype); + if (good) good = mi.GetParameterS64(title_id); + if (good) good = mi.GetParameterBuffer(contentList, contentListSize); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + CTRPluginFramework::Lock l(amMutex); + Result res = amAppInit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + // Cannot use output buffer while using input at the same time, need to allocate + u16* contentIDs = (u16*)malloc(contentListSize); + memcpy(contentIDs, contentList, contentListSize); + u32 count = contentListSize / sizeof(u16); + + ArticBaseCommon::Buffer* title_buf = mi.ReserveResultBuffer(0, count * sizeof(AM_ContentInfo)); + if (!title_buf) { + free(contentIDs); + return; + } + + res = AMAPP_FindDLCContentInfos(static_cast(mediatype), title_id, count, contentIDs, reinterpret_cast(title_buf->data)); + free(contentIDs); + amExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(title_buf, 0); + mi.FinishGood(res); + return; + } + + mi.FinishGood(res); + } + + void AMAPP_ListDLCContentInfos_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s32 count; + s8 mediatype; + s64 title_id; + s32 start_index; + + if (good) good = mi.GetParameterS32(count); + if (good) good = mi.GetParameterS8(mediatype); + if (good) good = mi.GetParameterS64(title_id); + if (good) good = mi.GetParameterS32(start_index); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + ArticBaseCommon::Buffer* content_buf = mi.ReserveResultBuffer(0, count * sizeof(AM_ContentInfo)); + if (!content_buf) { + return; + } + + CTRPluginFramework::Lock l(amMutex); + Result res = amAppInit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(content_buf, 0); + mi.FinishGood(res); + return; + } + + u32 contentRead = 0; + res = AMAPP_ListDLCContentInfos(&contentRead, static_cast(mediatype), title_id, count, start_index, reinterpret_cast(content_buf->data)); + amExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(content_buf, 0); + mi.FinishGood(res); + return; + } + + mi.ResizeLastResultBuffer(content_buf, sizeof(AM_ContentInfo) * contentRead); + mi.FinishGood(res); + } + + void AMAPP_GetDLCTitleInfos_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s8 mediatype; + void* titleList; size_t titleListSize; + + if (good) good = mi.GetParameterS8(mediatype); + if (good) good = mi.GetParameterBuffer(titleList, titleListSize); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + CTRPluginFramework::Lock l(amMutex); + Result res = amAppInit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + // Cannot use output buffer while using input at the same time, need to allocate + u64* titleIDs = (u64*)malloc(titleListSize); + memcpy(titleIDs, titleList, titleListSize); + u32 count = titleListSize / sizeof(u64); + + ArticBaseCommon::Buffer* title_buf = mi.ReserveResultBuffer(0, count * sizeof(AM_TitleEntry)); + if (!title_buf) { + free(titleIDs); + return; + } + + res = AMAPP_GetDLCTitleInfos(static_cast(mediatype), count, titleIDs, reinterpret_cast(title_buf->data)); + free(titleIDs); + amExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(title_buf, 0); + mi.FinishGood(res); + return; + } + + mi.FinishGood(res); + } + + void AMAPP_ListDataTitleTicketInfos_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s32 count; + s64 title_id; + s32 start_index; + + if (good) good = mi.GetParameterS32(count); + if (good) good = mi.GetParameterS64(title_id); + if (good) good = mi.GetParameterS32(start_index); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + ArticBaseCommon::Buffer* content_buf = mi.ReserveResultBuffer(0, count * sizeof(AM_TicketInfo)); + if (!content_buf) { + return; + } + + CTRPluginFramework::Lock l(amMutex); + Result res = amAppInit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(content_buf, 0); + mi.FinishGood(res); + return; + } + + u32 ticketsRead = 0; + res = AMAPP_ListDataTitleTicketInfos(&ticketsRead, title_id, count, start_index, reinterpret_cast(content_buf->data)); + amExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(content_buf, 0); + mi.FinishGood(res); + return; + } + + mi.ResizeLastResultBuffer(content_buf, sizeof(AM_TicketInfo) * ticketsRead); + mi.FinishGood(res); + } + + void AMAPP_GetPatchTitleInfos_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s8 mediatype; + void* titleList; size_t titleListSize; + + if (good) good = mi.GetParameterS8(mediatype); + if (good) good = mi.GetParameterBuffer(titleList, titleListSize); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + CTRPluginFramework::Lock l(amMutex); + Result res = amAppInit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + // Cannot use output buffer while using input at the same time, need to allocate + u64* titleIDs = (u64*)malloc(titleListSize); + memcpy(titleIDs, titleList, titleListSize); + u32 count = titleListSize / sizeof(u64); + + ArticBaseCommon::Buffer* title_buf = mi.ReserveResultBuffer(0, count * sizeof(AM_TitleEntry)); + if (!title_buf) { + free(titleIDs); + return; + } + + res = AMAPP_GetPatchTitleInfos(static_cast(mediatype), count, titleIDs, reinterpret_cast(title_buf->data)); + free(titleIDs); + amExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(title_buf, 0); + mi.FinishGood(res); + return; + } + + mi.FinishGood(res); + } + + void CFGU_GetConfigInfoBlk2_(ArticBaseServer::MethodInterface& mi) { + bool good = true; + s32 block_id, size; + + if (good) good = mi.GetParameterS32(block_id); + if (good) good = mi.GetParameterS32(size); + + if (good) good = mi.FinishInputParameters(); + + if (!good) return; + + CTRPluginFramework::Lock l(cfgMutex); + Result res = cfguInit(); + if (R_FAILED(res)) { + mi.FinishGood(res); + return; + } + + ArticBaseCommon::Buffer* conf_buf = mi.ReserveResultBuffer(0, size); + if (!conf_buf) { + return; + } + + res = CFGU_GetConfigInfoBlk2(static_cast(size), static_cast(block_id), conf_buf->data); + cfguExit(); + if (R_FAILED(res)) { + mi.ResizeLastResultBuffer(conf_buf, 0); + mi.FinishGood(res); + return; + } + + mi.FinishGood(res); + } + template constexpr auto& METHOD_NAME(char const (&s)[N]) { static_assert(N < sizeof(ArticBaseCommon::RequestPacket::method), "String exceeds 10 bytes!"); @@ -1152,6 +1586,7 @@ namespace ArticBaseFunctions { {METHOD_NAME("FSUSER_GetThisSaveDataSecVal"), FSUSER_GetThisSaveDataSecureValue_}, {METHOD_NAME("FSUSER_CreateExtSaveData"), FSUSER_CreateExtSaveData_}, {METHOD_NAME("FSUSER_DeleteExtSaveData"), FSUSER_DeleteExtSaveData_}, + {METHOD_NAME("FSUSER_CreateSysSaveData"), FSUSER_CreateSysSaveData_}, {METHOD_NAME("FSFILE_Close"), FSFILE_Close_}, {METHOD_NAME("FSFILE_SetAttributes"), FSFILE_SetAttributes_}, {METHOD_NAME("FSFILE_GetAttributes"), FSFILE_GetAttributes_}, @@ -1162,6 +1597,16 @@ namespace ArticBaseFunctions { {METHOD_NAME("FSFILE_Flush"), FSFILE_Flush_}, {METHOD_NAME("FSDIR_Read"), FSDIR_Read_}, {METHOD_NAME("FSDIR_Close"), FSDIR_Close_}, + {METHOD_NAME("AM_GetTitleCount"), AM_GetTitleCount_}, + {METHOD_NAME("AM_GetTitleList"), AM_GetTitleList_}, + {METHOD_NAME("AM_GetTitleInfo"), AM_GetTitleInfo_}, + {METHOD_NAME("AMAPP_GetDLCContentInfoCount"), AMAPP_GetDLCContentInfoCount_}, + {METHOD_NAME("AMAPP_FindDLCContentInfos"), AMAPP_FindDLCContentInfos_}, + {METHOD_NAME("AMAPP_ListDLCContentInfos"), AMAPP_ListDLCContentInfos_}, + {METHOD_NAME("AMAPP_GetDLCTitleInfos"), AMAPP_GetDLCTitleInfos_}, + {METHOD_NAME("AMAPP_ListDataTitleTicketInfos"), AMAPP_ListDataTitleTicketInfos_}, + {METHOD_NAME("AMAPP_GetPatchTitleInfos"), AMAPP_GetPatchTitleInfos_}, + {METHOD_NAME("CFGU_GetConfigInfoBlk2"), CFGU_GetConfigInfoBlk2_}, }; bool obtainExheader() { diff --git a/plugin/sources/amExtension.cpp b/plugin/sources/amExtension.cpp new file mode 100644 index 0000000..574dacf --- /dev/null +++ b/plugin/sources/amExtension.cpp @@ -0,0 +1,97 @@ +#include "3ds.h" +#include <3ds/result.h> +#include <3ds/svc.h> +#include "amExtension.hpp" + +Result AM_GetTitleInfoIgnorePlatform(FS_MediaType mediatype, u32 titleCount, u64 *titleIds, AM_TitleEntry *titleInfo) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x2C,2,4); // 0x002C0084 + cmdbuf[1] = mediatype; + cmdbuf[2] = titleCount; + cmdbuf[3] = IPC_Desc_Buffer(titleCount*sizeof(u64),IPC_BUFFER_R); + cmdbuf[4] = (u32)titleIds; + cmdbuf[5] = IPC_Desc_Buffer(titleCount*sizeof(AM_TitleEntry),IPC_BUFFER_W); + cmdbuf[6] = (u32)titleInfo; + + if(R_FAILED(ret = svcSendSyncRequest(*amGetSessionHandle()))) return ret; + + return (Result)cmdbuf[1]; +} + +Result AMAPP_FindDLCContentInfos(FS_MediaType mediatype, u64 title_id, u32 contentCount, u16 *contentIds, AM_ContentInfo *contentInfos) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x1002,4,4); // 0x10020104 + cmdbuf[1] = mediatype; + *(u64*)(&cmdbuf[2]) = title_id; + cmdbuf[4] = contentCount; + cmdbuf[5] = IPC_Desc_Buffer(contentCount*sizeof(u16),IPC_BUFFER_R); + cmdbuf[6] = (u32)contentIds; + cmdbuf[7] = IPC_Desc_Buffer(contentCount*sizeof(AM_ContentInfo),IPC_BUFFER_W); + cmdbuf[8] = (u32)contentInfos; + + if(R_FAILED(ret = svcSendSyncRequest(*amGetSessionHandle()))) return ret; + + return (Result)cmdbuf[1]; +} + +Result AMAPP_GetDLCTitleInfos(FS_MediaType mediatype, u32 titleCount, u64 *titleIds, AM_TitleEntry *titleInfo) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x1005,2,4); // 0x10050084 + cmdbuf[1] = mediatype; + cmdbuf[2] = titleCount; + cmdbuf[3] = IPC_Desc_Buffer(titleCount*sizeof(u64),IPC_BUFFER_R); + cmdbuf[4] = (u32)titleIds; + cmdbuf[5] = IPC_Desc_Buffer(titleCount*sizeof(AM_TitleEntry),IPC_BUFFER_W); + cmdbuf[6] = (u32)titleInfo; + + if(R_FAILED(ret = svcSendSyncRequest(*amGetSessionHandle()))) return ret; + + return (Result)cmdbuf[1]; +} + +Result AMAPP_ListDataTitleTicketInfos(u32* ticketReadCount, u64 titleID, u32 ticketInfoCount, u32 offset, AM_TicketInfo* ticketInfos) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = (IPC_MakeHeader(0x1007,4,2)); // 0x10070102 + cmdbuf[1] = ticketInfoCount; + cmdbuf[2] = titleID & 0xffffffff; + cmdbuf[3] = (u32)(titleID >> 32); + cmdbuf[4] = offset; + cmdbuf[5] = IPC_Desc_Buffer(ticketInfoCount * sizeof(AM_TicketInfo), IPC_BUFFER_W); + cmdbuf[6] = (u32)ticketInfos; + + if(R_FAILED(ret = svcSendSyncRequest(*amGetSessionHandle()))) return ret; + + *ticketReadCount = cmdbuf[2]; + + return (Result)cmdbuf[1]; +} + +Result AMAPP_GetPatchTitleInfos(FS_MediaType mediatype, u32 titleCount, u64 *titleIds, AM_TitleEntry *titleInfo) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x100D,2,4); // 0x100D0084 + cmdbuf[1] = mediatype; + cmdbuf[2] = titleCount; + cmdbuf[3] = IPC_Desc_Buffer(titleCount*sizeof(u64),IPC_BUFFER_R); + cmdbuf[4] = (u32)titleIds; + cmdbuf[5] = IPC_Desc_Buffer(titleCount*sizeof(AM_TitleEntry),IPC_BUFFER_W); + cmdbuf[6] = (u32)titleInfo; + + if(R_FAILED(ret = svcSendSyncRequest(*amGetSessionHandle()))) return ret; + + return (Result)cmdbuf[1]; +} \ No newline at end of file