#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; if (good) good = mi.FinishInputParameters(); if (!good) return; ArticBaseCommon::Buffer* tid_buffer = mi.ReserveResultBuffer(0, sizeof(u64)); if (!tid_buffer) { return; } s64 out; svcGetProcessInfo(&out, CUR_PROCESS_HANDLE, 0x10001); memcpy(tid_buffer->data, &out, sizeof(s64)); mi.FinishGood(0); } void Process_GetProductInfo(ArticBaseServer::MethodInterface& mi) { bool good = true; if (good) good = mi.FinishInputParameters(); if (!good) return; ArticBaseCommon::Buffer* prod_code_buffer = mi.ReserveResultBuffer(0, sizeof(FS_ProductInfo)); if (!prod_code_buffer) { return; } u32 pid; Result res = svcGetProcessId(&pid, CUR_PROCESS_HANDLE); if (R_SUCCEEDED(res)) res = FSUSER_GetProductInfo((FS_ProductInfo*)prod_code_buffer->data, pid); if (R_FAILED(res)) { logger.Error("Process_GetProductInfo: 0x%08X", res); mi.FinishInternalError(); return; } mi.FinishGood(0); } void Process_GetExheader(ArticBaseServer::MethodInterface& mi) { bool good = true; if (good) good = mi.FinishInputParameters(); if (!good) return; ArticBaseCommon::Buffer* exheader_buf = mi.ReserveResultBuffer(0, sizeof(lastAppExheader)); if (!exheader_buf) { return; } memcpy(exheader_buf->data, &lastAppExheader, exheader_buf->bufferSize); mi.FinishGood(0); } void Process_ReadCode(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 offset, size; if (good) good = mi.GetParameterS32(offset); if (good) good = mi.GetParameterS32(size); if (good) good = mi.FinishInputParameters(); if (!good) return; s64 out; if (R_FAILED(svcGetProcessInfo(&out, CUR_PROCESS_HANDLE, 0x10005))) { mi.FinishInternalError(); return; } u8* start_addr = reinterpret_cast(out); ArticBaseCommon::Buffer* code_buf = mi.ReserveResultBuffer(0, size); if (!code_buf) { return; } memcpy(code_buf->data, start_addr + offset, size); mi.FinishGood(0); } static void _Process_ReadExefs(ArticBaseServer::MethodInterface& mi, const char* section) { bool good = true; if (good) good = mi.FinishInputParameters(); if (!good) return; // Set up FS_Path structures u8 path[0xC] = {0}; u32* type = (u32*)path; char* name = (char*)(path + sizeof(u32)); *type = 0x2; // ExeFS strcpy(name, section); // Icon FS_Path archPath = { PATH_EMPTY, 1, "" }; FS_Path filePath = { PATH_BINARY, sizeof(path), path }; // Open the RomFS file and mount it Handle fd = 0; Result rc = FSUSER_OpenFileDirectly(&fd, ARCHIVE_ROMFS, archPath, filePath, FS_OPEN_READ, 0); if (R_FAILED(rc)) { mi.FinishGood(rc); return; } u64 file_size; rc = FSFILE_GetSize(fd, &file_size); if (R_FAILED(rc)) { FSFILE_Close(fd); mi.FinishGood(rc); return; } ArticBaseCommon::Buffer* icon_buf = mi.ReserveResultBuffer(0, static_cast(file_size)); if (!icon_buf) { FSFILE_Close(fd); return; } u32 bytes_read; rc = FSFILE_Read(fd, &bytes_read, 0, icon_buf->data, icon_buf->bufferSize); if (R_FAILED(rc)) { FSFILE_Close(fd); mi.ResizeLastResultBuffer(icon_buf, 0); mi.FinishGood(rc); return; } mi.ResizeLastResultBuffer(icon_buf, bytes_read); FSFILE_Close(fd); mi.FinishGood(0); } void Process_ReadIcon(ArticBaseServer::MethodInterface& mi) { _Process_ReadExefs(mi, "icon"); } void Process_ReadBanner(ArticBaseServer::MethodInterface& mi) { _Process_ReadExefs(mi, "banner"); } void Process_ReadLogo(ArticBaseServer::MethodInterface& mi) { _Process_ReadExefs(mi, "logo"); } bool GetFSPath(ArticBaseServer::MethodInterface& mi, FS_Path& path) { void* pathPtr; size_t pathSize; if (!mi.GetParameterBuffer(pathPtr, pathSize)) return false; path.type = reinterpret_cast(pathPtr)->type; path.size = reinterpret_cast(pathPtr)->size; if (pathSize < 8 || path.size != pathSize - 0x8) { mi.FinishInternalError(); return false; } path.data = (u8*)pathPtr + 0x8; return true; } void FSUSER_OpenFileDirectly_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 archiveID; FS_Path archPath; FS_Path filePath; s32 openFlags; s32 attributes; if (good) good = mi.GetParameterS32(archiveID); if (good) good = GetFSPath(mi, archPath); if (good) good = GetFSPath(mi, filePath); if (good) good = mi.GetParameterS32(openFlags); if (good) good = mi.GetParameterS32(attributes); if (good) good = mi.FinishInputParameters(); if (!good) return; Handle out; Result res = FSUSER_OpenFileDirectly(&out, (FS_ArchiveID)archiveID, archPath, filePath, openFlags, attributes); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* handle_buf = mi.ReserveResultBuffer(0, sizeof(Handle)); if (!handle_buf) { FSFILE_Close(out); return; } *reinterpret_cast(handle_buf->data) = out; openHandles[(u64)out] = HandleType::FILE; mi.FinishGood(res); } void FSUSER_OpenArchive_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 archiveID; FS_Path archPath; if (good) good = mi.GetParameterS32(archiveID); if (good) good = GetFSPath(mi, archPath); if (good) good = mi.FinishInputParameters(); if (!good) return; FS_Archive out; Result res = FSUSER_OpenArchive(&out, (FS_ArchiveID)archiveID, archPath); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* handle_buf = mi.ReserveResultBuffer(0, sizeof(FS_Archive)); if (!handle_buf) { FSUSER_CloseArchive(out); return; } *reinterpret_cast(handle_buf->data) = out; openHandles[(u64)out] = HandleType::ARCHIVE; mi.FinishGood(res); } void FSUSER_CloseArchive_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_CloseArchive(archive); openHandles.erase((u64)archive); mi.FinishGood(res); } void FSUSER_OpenFile_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_Path filePath; s32 openFlags; s32 attributes; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = GetFSPath(mi, filePath); if (good) good = mi.GetParameterS32(openFlags); if (good) good = mi.GetParameterS32(attributes); if (good) good = mi.FinishInputParameters(); if (!good) return; Handle out; Result res = FSUSER_OpenFile(&out, archive, filePath, openFlags, attributes); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* handle_buf = mi.ReserveResultBuffer(0, sizeof(Handle)); if (!handle_buf) { FSFILE_Close(out); return; } *reinterpret_cast(handle_buf->data) = out; // Citra always asks for the size after opening a file, provided it here. u64 fileSize; Result res2 = FSFILE_GetSize(out, &fileSize); if (R_SUCCEEDED(res2)) { ArticBaseCommon::Buffer* size_buf = mi.ReserveResultBuffer(1, sizeof(u64)); if (!size_buf) { FSFILE_Close(out); return; } *reinterpret_cast(size_buf->data) = fileSize; } openHandles[(u64)out] = HandleType::FILE; mi.FinishGood(res); } void FSUSER_CreateFile_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_Path filePath; s32 attributes; s64 fileSize; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = GetFSPath(mi, filePath); if (good) good = mi.GetParameterS32(attributes); if (good) good = mi.GetParameterS64(fileSize); if (good) good = mi.FinishInputParameters(); if (!good) return; Handle out; Result res = FSUSER_CreateFile(archive, filePath, attributes, fileSize); mi.FinishGood(res); } void FSUSER_DeleteFile_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_Path filePath; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = GetFSPath(mi, filePath); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_DeleteFile(archive, filePath); mi.FinishGood(res); } void FSUSER_RenameFile_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive srcarchive; FS_Path srcfilePath; FS_Archive dstarchive; FS_Path dstfilePath; if (good) good = mi.GetParameterS64(*reinterpret_cast(&srcarchive)); if (good) good = GetFSPath(mi, srcfilePath); if (good) good = mi.GetParameterS64(*reinterpret_cast(&dstarchive)); if (good) good = GetFSPath(mi, dstfilePath); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_RenameFile(srcarchive, srcfilePath, dstarchive, dstfilePath); mi.FinishGood(res); } void FSUSER_OpenDirectory_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_Path dirPath; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = GetFSPath(mi, dirPath); if (good) good = mi.FinishInputParameters(); if (!good) return; Handle out; Result res = FSUSER_OpenDirectory(&out, archive, dirPath); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* handle_buf = mi.ReserveResultBuffer(0, sizeof(Handle)); if (!handle_buf) { FSDIR_Close(out); return; } *reinterpret_cast(handle_buf->data) = out; openHandles[(u64)out] = HandleType::DIR; mi.FinishGood(res); } void FSUSER_CreateDirectory_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_Path dirPath; s32 attributes; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = GetFSPath(mi, dirPath); if (good) good = mi.GetParameterS32(attributes); if (good) good = mi.FinishInputParameters(); if (!good) return; Handle out; Result res = FSUSER_CreateDirectory(archive, dirPath, attributes); mi.FinishGood(res); } void FSUSER_DeleteDirectory_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_Path dirPath; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = GetFSPath(mi, dirPath); if (good) good = mi.FinishInputParameters(); if (!good) return; Handle out; Result res = FSUSER_DeleteDirectory(archive, dirPath); mi.FinishGood(res); } void FSUSER_DeleteDirectoryRecursively_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_Path dirPath; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = GetFSPath(mi, dirPath); if (good) good = mi.FinishInputParameters(); if (!good) return; Handle out; Result res = FSUSER_DeleteDirectoryRecursively(archive, dirPath); mi.FinishGood(res); } void FSUSER_RenameDirectory_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive srcarchive; FS_Path srcdirPath; FS_Archive dstarchive; FS_Path dstdirPath; if (good) good = mi.GetParameterS64(*reinterpret_cast(&srcarchive)); if (good) good = GetFSPath(mi, srcdirPath); if (good) good = mi.GetParameterS64(*reinterpret_cast(&dstarchive)); if (good) good = GetFSPath(mi, dstdirPath); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_RenameDirectory(srcarchive, srcdirPath, dstarchive, dstdirPath); mi.FinishGood(res); } void FSUSER_ControlArchive_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; FS_ArchiveAction action; void* input; size_t inputSize; s32 outputSize; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = mi.GetParameterS32(*reinterpret_cast(&action)); if (good) good = mi.GetParameterBuffer(input, inputSize); if (good) good = mi.GetParameterS32(outputSize); if (good) good = mi.FinishInputParameters(); if (outputSize > 0x1000) { mi.FinishInternalError(); return; } // Cannot use output buffer while using input at the same time, need to allocate void* output = malloc(outputSize); Result res = FSUSER_ControlArchive(archive, action, input, inputSize, output, outputSize); ArticBaseCommon::Buffer* out_buf = mi.ReserveResultBuffer(0, outputSize); if (!out_buf) { free(output); return; } memcpy(out_buf->data, output, outputSize); mi.FinishGood(res); free(output); } void FSUSER_GetFreeBytes_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = mi.FinishInputParameters(); if (!good) return; u64 freeBytes; Result res = FSUSER_GetFreeBytes(&freeBytes, archive); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* size_buf = mi.ReserveResultBuffer(0, sizeof(u64)); if (!size_buf) { return; } *reinterpret_cast(size_buf->data) = freeBytes; mi.FinishGood(res); } void FSUSER_GetFormatInfo_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 archiveID; FS_Path path; if (good) good = mi.GetParameterS32(archiveID); if (good) good = GetFSPath(mi, path); if (good) good = mi.FinishInputParameters(); if (!good) return; u32 totalSize; u32 directories; u32 files; bool duplicateData; Result res = FSUSER_GetFormatInfo(&totalSize, &directories, &files, &duplicateData, (FS_ArchiveID)archiveID, path); struct { u32 total_size; u32 number_directories; u32 number_files; u8 duplicate_data; } archive_format_info; static_assert(sizeof(archive_format_info) == 16); archive_format_info.total_size = totalSize; archive_format_info.number_directories = directories; archive_format_info.number_files = files; archive_format_info.duplicate_data = duplicateData; ArticBaseCommon::Buffer* format_info_buf = mi.ReserveResultBuffer(0, sizeof(archive_format_info)); if (!format_info_buf) { return; } memcpy(format_info_buf->data, &archive_format_info, sizeof(archive_format_info)); mi.FinishGood(res); } void FSUSER_FormatSaveData_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 archiveID; FS_Path path; s32 blocks; s32 directories; s32 files; s32 directoryBuckets; s32 fileBuckets; bool duplicateData; if (good) good = mi.GetParameterS32(archiveID); if (good) good = GetFSPath(mi, path); if (good) good = mi.GetParameterS32(blocks); if (good) good = mi.GetParameterS32(directories); if (good) good = mi.GetParameterS32(files); if (good) good = mi.GetParameterS32(directoryBuckets); if (good) good = mi.GetParameterS32(fileBuckets); if (good) good = mi.GetParameterS8(*reinterpret_cast(&duplicateData)); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_FormatSaveData((FS_ArchiveID)archiveID, path, blocks, directories, files, directoryBuckets, fileBuckets, duplicateData); mi.FinishGood(res); } void FSUSER_ObsoletedSetSaveDataSecureValue_(ArticBaseServer::MethodInterface& mi) { bool good = true; s64 secure_value; s32 slot; s32 title_id; s8 title_variation; if (good) good = mi.GetParameterS64(secure_value); if (good) good = mi.GetParameterS32(slot); if (good) good = mi.GetParameterS32(title_id); if (good) good = mi.GetParameterS8(title_variation); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_SetSaveDataSecureValue((u64)secure_value, (FS_SecureValueSlot)slot, title_id, title_variation); mi.FinishGood(res); } void FSUSER_ObsoletedGetSaveDataSecureValue_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 slot; s32 title_id; s8 title_variation; if (good) good = mi.GetParameterS32(slot); if (good) good = mi.GetParameterS32(title_id); if (good) good = mi.GetParameterS8(title_variation); if (good) good = mi.FinishInputParameters(); if (!good) return; bool exists; u64 secure_value; Result res = FSUSER_GetSaveDataSecureValue(&exists, &secure_value, (FS_SecureValueSlot)slot, title_id, title_variation); if (R_FAILED(res)) { mi.FinishGood(res); return; } struct { bool exists; u64 secure_value; } secure_value_result; static_assert(sizeof(secure_value_result) == 0x10); ArticBaseCommon::Buffer* sec_val_buf = mi.ReserveResultBuffer(0, sizeof(secure_value_result)); if (!sec_val_buf) { return; } secure_value_result.exists = exists; secure_value_result.secure_value = secure_value; memcpy(sec_val_buf->data, &secure_value_result, sizeof(secure_value_result)); mi.FinishGood(res); } void FSUSER_ControlSecureSave_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 action; void* input; size_t inputSize; s32 outputSize; if (good) good = mi.GetParameterS32(action); if (good) good = mi.GetParameterBuffer(input, inputSize); if (good) good = mi.GetParameterS32(outputSize); if (good) good = mi.FinishInputParameters(); if (outputSize > 0x1000) { mi.FinishInternalError(); return; } // Cannot use output buffer while using input at the same time, need to allocate void* output = malloc(outputSize); Result res = FSUSER_ControlSecureSave((FS_SecureSaveAction)action, input, inputSize, output, outputSize); ArticBaseCommon::Buffer* out_buf = mi.ReserveResultBuffer(0, outputSize); if (!out_buf) { free(output); return; } memcpy(out_buf->data, output, outputSize); mi.FinishGood(res); free(output); } void FSUSER_SetSaveDataSecureValue_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; s32 slot; s64 secure_value; s8 flush; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = mi.GetParameterS32(slot); if (good) good = mi.GetParameterS64(secure_value); if (good) good = mi.GetParameterS8(flush); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_NewSetSaveDataSecureValue(archive, (u64)secure_value, (FS_SecureValueSlot)slot, flush != 0); mi.FinishGood(res); } void FSUSER_GetSaveDataSecureValue_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_Archive archive; s32 slot; if (good) good = mi.GetParameterS64(*reinterpret_cast(&archive)); if (good) good = mi.GetParameterS32(slot); if (good) good = mi.FinishInputParameters(); if (!good) return; bool exists, isGamecard; u64 secure_value; Result res = FSUSER_NewGetSaveDataSecureValue(&exists, &isGamecard, &secure_value, archive, (FS_SecureValueSlot)slot); if (R_FAILED(res)) { mi.FinishGood(res); return; } struct { bool exists; bool isGamecard; u64 secure_value; } secure_value_result; static_assert(sizeof(secure_value_result) == 0x10); ArticBaseCommon::Buffer* sec_val_buf = mi.ReserveResultBuffer(0, sizeof(secure_value_result)); if (!sec_val_buf) { return; } secure_value_result.exists = exists; secure_value_result.isGamecard = isGamecard; secure_value_result.secure_value = secure_value; memcpy(sec_val_buf->data, &secure_value_result, sizeof(secure_value_result)); mi.FinishGood(res); } void FSUSER_SetThisSaveDataSecureValue_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 slot; s64 secure_value; if (good) good = mi.GetParameterS32(slot); if (good) good = mi.GetParameterS64(secure_value); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSUSER_SetThisSaveDataSecureValue((u64)secure_value, (FS_SecureValueSlot)slot); mi.FinishGood(res); } void FSUSER_GetThisSaveDataSecureValue_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 slot; if (good) good = mi.GetParameterS32(slot); if (good) good = mi.FinishInputParameters(); if (!good) return; bool exists, isGamecard; u64 secure_value; Result res = FSUSER_GetThisSaveDataSecureValue(&exists, &isGamecard, &secure_value, (FS_SecureValueSlot)slot); if (R_FAILED(res)) { mi.FinishGood(res); return; } struct { bool exists; bool isGamecard; u64 secure_value; } secure_value_result; static_assert(sizeof(secure_value_result) == 0x10); ArticBaseCommon::Buffer* sec_val_buf = mi.ReserveResultBuffer(0, sizeof(secure_value_result)); if (!sec_val_buf) { return; } secure_value_result.exists = exists; secure_value_result.isGamecard = isGamecard; secure_value_result.secure_value = secure_value; memcpy(sec_val_buf->data, &secure_value_result, sizeof(secure_value_result)); mi.FinishGood(res); } void FSUSER_CreateExtSaveData_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_ExtSaveDataInfo info; void* formatInfoPtr; size_t formatInfoPtrSize; s32 directories, files; s64 size_limit; void* smdhIconPtr; size_t smdhIconPtrSize; if (good) good = mi.GetParameterBuffer(formatInfoPtr, formatInfoPtrSize); if (good) good = mi.GetParameterS32(directories); if (good) good = mi.GetParameterS32(files); if (good) good = mi.GetParameterS64(size_limit); if (good) good = mi.GetParameterBuffer(smdhIconPtr, smdhIconPtrSize); if (good) good = mi.FinishInputParameters(); if (!good) return; if (formatInfoPtrSize != sizeof(FS_ExtSaveDataInfo)) { mi.FinishInternalError(); return; } memcpy(&info, formatInfoPtr, formatInfoPtrSize); Result res = FSUSER_CreateExtSaveData(info, directories, files, size_limit, static_cast(smdhIconPtrSize), reinterpret_cast(smdhIconPtr)); mi.FinishGood(res); } void FSUSER_DeleteExtSaveData_(ArticBaseServer::MethodInterface& mi) { bool good = true; FS_ExtSaveDataInfo info; void* formatInfoPtr; size_t formatInfoPtrSize; if (good) good = mi.GetParameterBuffer(formatInfoPtr, formatInfoPtrSize); if (good) good = mi.FinishInputParameters(); if (!good) return; if (formatInfoPtrSize != sizeof(FS_ExtSaveDataInfo)) { mi.FinishInternalError(); return; } memcpy(&info, formatInfoPtr, formatInfoPtrSize); Result res = FSUSER_DeleteExtSaveData(info); 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; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSFILE_Close(handle); openHandles.erase((u64)handle); mi.FinishGood(res); } void FSFILE_SetSize_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; s64 size; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.GetParameterS64(size); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSFILE_SetSize(handle, size); mi.FinishGood(res); } void FSFILE_GetSize_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.FinishInputParameters(); if (!good) return; u64 fileSize; Result res = FSFILE_GetSize(handle, &fileSize); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* size_buf = mi.ReserveResultBuffer(0, sizeof(u64)); if (!size_buf) { return; } *reinterpret_cast(size_buf->data) = fileSize; mi.FinishGood(res); } void FSFILE_SetAttributes_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; s32 attributes; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.GetParameterS32(attributes); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSFILE_SetAttributes(handle, attributes); mi.FinishGood(res); } void FSFILE_GetAttributes_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.FinishInputParameters(); if (!good) return; u32 attributes; Result res = FSFILE_GetAttributes(handle, &attributes); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* size_buf = mi.ReserveResultBuffer(0, sizeof(u32)); if (!size_buf) { return; } *reinterpret_cast(size_buf->data) = attributes; mi.FinishGood(res); } void FSFILE_Read_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle, size; s64 offset; u32 bytes_read; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.GetParameterS64(offset); if (good) good = mi.GetParameterS32(size); if (good) good = mi.FinishInputParameters(); if (!good) return; logger.Debug("Read o=0x%08X, l=0x%08X", (u32)offset, (u32)size); ArticBaseCommon::Buffer* read_buf = mi.ReserveResultBuffer(0, size); if (!read_buf) { return; } Result res = FSFILE_Read(handle, &bytes_read, offset, read_buf->data, read_buf->bufferSize); if (R_FAILED(res)) { mi.ResizeLastResultBuffer(read_buf, 0); mi.FinishGood(res); return; } mi.ResizeLastResultBuffer(read_buf, bytes_read); mi.FinishGood(res); } void FSFILE_Write_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle, size, flags; s64 offset; u32 bytes_written; void* dataPtr; size_t dataPtrSize; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.GetParameterS64(offset); if (good) good = mi.GetParameterS32(size); if (good) good = mi.GetParameterS32(flags); if (good) good = mi.GetParameterBuffer(dataPtr, dataPtrSize); if (good) good = mi.FinishInputParameters(); if (!good) return; if (dataPtrSize != size) { mi.FinishInternalError(); return; } Result res = FSFILE_Write(handle, &bytes_written, offset, dataPtr, size, flags); if (R_FAILED(res)) { mi.FinishGood(res); return; } ArticBaseCommon::Buffer* bytes_written_buf = mi.ReserveResultBuffer(0, sizeof(u32)); if (!bytes_written_buf) { return; } *reinterpret_cast(bytes_written_buf->data) = bytes_written; mi.FinishGood(res); } void FSFILE_Flush_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSFILE_Flush(handle); mi.FinishGood(res); } void FSDIR_Read_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; s32 entryCount; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.GetParameterS32(entryCount); if (good) good = mi.FinishInputParameters(); if (!good) return; ArticBaseCommon::Buffer* read_dir_buf = mi.ReserveResultBuffer(0, entryCount * sizeof(FS_DirectoryEntry)); if (!read_dir_buf) { return; } u32 entries_read; Result res = FSDIR_Read(handle, &entries_read, entryCount, reinterpret_cast(read_dir_buf->data)); if (R_FAILED(res)) { mi.ResizeLastResultBuffer(read_dir_buf, 0); mi.FinishGood(res); return; } mi.ResizeLastResultBuffer(read_dir_buf, entries_read * sizeof(FS_DirectoryEntry)); mi.FinishGood(res); } void FSDIR_Close_(ArticBaseServer::MethodInterface& mi) { bool good = true; s32 handle; if (good) good = mi.GetParameterS32(handle); if (good) good = mi.FinishInputParameters(); if (!good) return; Result res = FSDIR_Close(handle); openHandles.erase((u64)handle); 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!"); return s; } std::map functionHandlers = { {METHOD_NAME("Process_GetTitleID"), Process_GetTitleID}, {METHOD_NAME("Process_GetProductInfo"), Process_GetProductInfo}, {METHOD_NAME("Process_GetExheader"), Process_GetExheader}, {METHOD_NAME("Process_ReadCode"), Process_ReadCode}, {METHOD_NAME("Process_ReadIcon"), Process_ReadIcon}, {METHOD_NAME("Process_ReadBanner"), Process_ReadBanner}, {METHOD_NAME("Process_ReadLogo"), Process_ReadLogo}, {METHOD_NAME("FSUSER_OpenFileDirectly"), FSUSER_OpenFileDirectly_}, {METHOD_NAME("FSUSER_OpenArchive"), FSUSER_OpenArchive_}, {METHOD_NAME("FSUSER_CloseArchive"), FSUSER_CloseArchive_}, {METHOD_NAME("FSUSER_OpenFile"), FSUSER_OpenFile_}, {METHOD_NAME("FSUSER_CreateFile"), FSUSER_CreateFile_}, {METHOD_NAME("FSUSER_DeleteFile"), FSUSER_DeleteFile_}, {METHOD_NAME("FSUSER_RenameFile"), FSUSER_RenameFile_}, {METHOD_NAME("FSUSER_OpenDirectory"), FSUSER_OpenDirectory_}, {METHOD_NAME("FSUSER_CreateDirectory"), FSUSER_CreateDirectory_}, {METHOD_NAME("FSUSER_DeleteDirectory"), FSUSER_DeleteDirectory_}, {METHOD_NAME("FSUSER_DeleteDirectoryRec"), FSUSER_DeleteDirectoryRecursively_}, {METHOD_NAME("FSUSER_RenameDirectory"), FSUSER_RenameDirectory_}, {METHOD_NAME("FSUSER_ControlArchive"), FSUSER_ControlArchive_}, {METHOD_NAME("FSUSER_GetFreeBytes"), FSUSER_GetFreeBytes_}, {METHOD_NAME("FSUSER_GetFormatInfo"), FSUSER_GetFormatInfo_}, {METHOD_NAME("FSUSER_FormatSaveData"), FSUSER_FormatSaveData_}, {METHOD_NAME("FSUSER_ObsSetSaveDataSecureVal"), FSUSER_ObsoletedSetSaveDataSecureValue_}, {METHOD_NAME("FSUSER_ObsGetSaveDataSecureVal"), FSUSER_ObsoletedGetSaveDataSecureValue_}, {METHOD_NAME("FSUSER_ControlSecureSave"), FSUSER_ControlSecureSave_}, {METHOD_NAME("FSUSER_SetSaveDataSecureValue"), FSUSER_SetSaveDataSecureValue_}, {METHOD_NAME("FSUSER_GetSaveDataSecureValue"), FSUSER_GetSaveDataSecureValue_}, {METHOD_NAME("FSUSER_SetThisSaveDataSecVal"), FSUSER_SetThisSaveDataSecureValue_}, {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_}, {METHOD_NAME("FSFILE_SetSize"), FSFILE_SetSize_}, {METHOD_NAME("FSFILE_GetSize"), FSFILE_GetSize_}, {METHOD_NAME("FSFILE_Read"), FSFILE_Read_}, {METHOD_NAME("FSFILE_Write"), FSFILE_Write_}, {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() { Result loaderInitCustom(void); void loaderExitCustom(void); Result LOADER_GetLastApplicationProgramInfo(ExHeader_Info* exheaderInfo); Result res = loaderInitCustom(); if (R_SUCCEEDED(res)) res = LOADER_GetLastApplicationProgramInfo(&lastAppExheader); loaderExitCustom(); if (R_FAILED(res)) { logger.Error("Failed to get ExHeader. Luma3DS may be outdated."); return false; } return true; } static bool closeHandles() { auto CloseHandle = [](u64 handle, HandleType type) { switch (type) { case HandleType::FILE: logger.Debug("Call pending FSFILE_Close"); FSFILE_Close((Handle)handle); break; case HandleType::DIR: logger.Debug("Call pending FSDIR_Close"); FSDIR_Close((Handle)handle); break; case HandleType::ARCHIVE: logger.Debug("Call pending FSUSER_CloseArchive"); FSUSER_CloseArchive((FS_Archive)handle); break; default: break; } }; for (auto it = openHandles.begin(); it != openHandles.end(); it++) { CloseHandle(it->first, it->second); } openHandles.clear(); return true; } std::vector setupFunctions { obtainExheader, }; std::vector destructFunctions { closeHandles, }; }