From 8d71f4afa3ee6f6cecdf37b61e4490ae1125bd6c Mon Sep 17 00:00:00 2001 From: Masamune3210 <1053504+Masamune3210@users.noreply.github.com> Date: Tue, 12 May 2026 00:33:45 -0500 Subject: [PATCH] ExtData support --- src/core/hle/service/fs/archive.cpp | 111 ++++++++++++++++++++++ src/core/hle/service/fs/archive.h | 33 +++++++ src/core/hle/service/fs/fs_user.cpp | 141 +++++++++++++++++++++++++++- src/core/hle/service/fs/fs_user.h | 67 +++++++++++++ 4 files changed, 348 insertions(+), 4 deletions(-) diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 211bf39d6..985bc9106 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -290,6 +290,117 @@ Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u8 unknown, u32 h return ext_savedata->DeleteExtData(media_type, unknown, high, low); } +ResultVal ArchiveManager::EnumerateExtSaveData(MediaType media_type, bool shared, + u32 entry_size, u8* output, u32 max_count) { + std::string media_dir; + bool use_shared = shared || (media_type == MediaType::NAND); + if (media_type == MediaType::SDMC || media_type == MediaType::NAND) { + media_dir = media_type == MediaType::NAND + ? FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + : FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); + } else { + return ResultUnknown; + } + + auto parse_hex = [](const std::string& s, u32& out) -> bool { + char* end = nullptr; + unsigned long val = std::strtoul(s.c_str(), &end, 16); + if (end == s.c_str() || *end != '\0') + return false; + out = static_cast(val); + return true; + }; + + std::string container = FileSys::GetExtDataContainerPath(media_dir, use_shared); + u32 written = 0; + + FileUtil::ForeachDirectoryEntry( + nullptr, container, + [&](u64*, const std::string& dir, const std::string& high_name) { + if (written >= max_count) + return true; + u32 high; + if (!parse_hex(high_name, high)) + return true; + + FileUtil::ForeachDirectoryEntry( + nullptr, dir + high_name + "/", + [&](u64*, const std::string&, const std::string& low_name) { + if (written >= max_count) + return true; + u32 low; + if (!parse_hex(low_name, low)) + return true; + + if (entry_size == sizeof(u64)) { + u64 id = (static_cast(high) << 32) | low; + std::memcpy(output + written * sizeof(u64), &id, sizeof(u64)); + } else if (entry_size == sizeof(u32)) { + std::memcpy(output + written * sizeof(u32), &low, sizeof(u32)); + } + ++written; + return true; + }); + + return true; + }); + + return written; +} + +ResultVal ArchiveManager::ReadExtSaveDataIcon(const ExtSaveDataInfo& info, u32 buffer_size, + u8* buffer) { + auto archive = id_code_map.find( + static_cast(info.media_type) == MediaType::NAND + ? ArchiveIdCode::SharedExtSaveData + : ArchiveIdCode::ExtSaveData); + if (archive == id_code_map.end()) { + return UnimplementedFunction(ErrorModule::FS); + } + + auto* ext_savedata = + static_cast(archive->second.get()); + FileSys::Path path = FileSys::ConstructExtDataBinaryPath( + static_cast(info.media_type), info.save_id_high, info.save_id_low); + std::string icon_path = + FileSys::GetExtSaveDataPath(ext_savedata->GetMountPoint(), path) + "icon"; + + FileUtil::IOFile file(icon_path, "rb"); + if (!file.IsOpen()) { + return FileSys::ResultFileNotFound; + } + + u32 bytes_to_read = + static_cast(std::min(static_cast(buffer_size), file.GetSize())); + if (file.ReadBytes(buffer, bytes_to_read) != bytes_to_read) { + return ResultUnknown; + } + return bytes_to_read; +} + +ResultVal ArchiveManager::GetExtDataBlockSize( + const ExtSaveDataInfo& info) { + auto archive = id_code_map.find( + static_cast(info.media_type) == MediaType::NAND + ? ArchiveIdCode::SharedExtSaveData + : ArchiveIdCode::ExtSaveData); + if (archive == id_code_map.end()) { + return UnimplementedFunction(ErrorModule::FS); + } + + auto* ext_savedata = + static_cast(archive->second.get()); + FileSys::Path path = FileSys::ConstructExtDataBinaryPath( + static_cast(info.media_type), info.save_id_high, info.save_id_low); + + CASCADE_RESULT(FileSys::ArchiveFormatInfo format_info, ext_savedata->GetFormatInfo(path, 0)); + + constexpr u32 block_size = 512; + u64 total_blocks = + (static_cast(format_info.total_size) + block_size - 1) / block_size; + return ExtDataBlockInfo{total_blocks, total_blocks, block_size}; +} + Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) { // Construct the binary path to the archive first const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 2f31acbea..101865987 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -258,6 +258,39 @@ public: */ Result DeleteExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low); + /** + * Enumerates ExtSaveData IDs for the specified media type + * @param media_type The media type to enumerate (NAND / SDMC) + * @param shared Whether to enumerate shared (NAND) ext save data + * @param entry_size Size of each ID entry in bytes (4 or 8) + * @param output Output buffer to write IDs into + * @param max_count Maximum number of IDs to write + * @return Number of IDs written on success, or error code + */ + ResultVal EnumerateExtSaveData(MediaType media_type, bool shared, u32 entry_size, + u8* output, u32 max_count); + + /** + * Reads the SMDH icon from the specified ExtSaveData archive + * @param info ExtSaveData archive identifier + * @param buffer_size Maximum bytes to read into buffer + * @param buffer Output buffer for icon data + * @return Number of bytes read on success, or error code + */ + ResultVal ReadExtSaveDataIcon(const ExtSaveDataInfo& info, u32 buffer_size, u8* buffer); + + /** + * Returns block usage information for the specified ExtSaveData archive + * @param info ExtSaveData archive identifier + * @return Block info on success, or error code + */ + struct ExtDataBlockInfo { + u64 total_blocks; + u64 free_blocks; + u32 block_size; + }; + ResultVal GetExtDataBlockSize(const ExtSaveDataInfo& info); + /** * Deletes the SystemSaveData archive folder for the specified save data id * @param high The high word of the SystemSaveData archive to delete diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index fdbdfbc58..d68202788 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -1016,6 +1016,138 @@ void FS_USER::DeleteExtSaveData(Kernel::HLERequestContext& ctx) { info.save_id_low, info.save_id_high, info.media_type, info.unknown); } +void FS_USER::EnumerateExtSaveData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + u32 ids_size = rp.Pop(); + u8 media_type = rp.Pop(); + u32 id_size = rp.Pop(); + bool shared = rp.Pop(); + auto& output_buffer = rp.PopMappedBuffer(); + + u32 max_count = id_size > 0 ? ids_size / id_size : 0; + std::vector output(ids_size); + auto result = archives.EnumerateExtSaveData(static_cast(media_type), shared, + id_size, output.data(), max_count); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + if (result.Failed()) { + rb.Push(result.Code()); + rb.Push(0); + } else { + u32 count = result.Unwrap(); + output_buffer.Write(output.data(), 0, count * id_size); + rb.Push(ResultSuccess); + rb.Push(count); + } + rb.PushMappedBuffer(output_buffer); + + LOG_DEBUG(Service_FS, "called, media_type={:02X} shared={} id_size={} ids_size={:08X}", + media_type, shared, id_size, ids_size); +} + +void FS_USER::ObsoletedEnumerateExtSaveData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + u32 ids_size = rp.Pop(); + u8 media_type = rp.Pop(); + auto& output_buffer = rp.PopMappedBuffer(); + + constexpr u32 id_size = sizeof(u64); + u32 max_count = ids_size / id_size; + std::vector output(ids_size); + auto result = archives.EnumerateExtSaveData(static_cast(media_type), false, + id_size, output.data(), max_count); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + if (result.Failed()) { + rb.Push(result.Code()); + rb.Push(0); + } else { + u32 count = result.Unwrap(); + output_buffer.Write(output.data(), 0, count * id_size); + rb.Push(ResultSuccess); + rb.Push(count); + } + rb.PushMappedBuffer(output_buffer); + + LOG_DEBUG(Service_FS, "called, media_type={:02X} ids_size={:08X}", media_type, ids_size); +} + +void FS_USER::ReadExtSaveDataIcon(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + ExtSaveDataInfo info = rp.PopRaw(); + u32 smdh_size = rp.Pop(); + auto& output_buffer = rp.PopMappedBuffer(); + + std::vector icon_data(smdh_size); + auto result = archives.ReadExtSaveDataIcon(info, smdh_size, icon_data.data()); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + if (result.Failed()) { + rb.Push(result.Code()); + rb.Push(0); + } else { + output_buffer.Write(icon_data.data(), 0, *result); + rb.Push(ResultSuccess); + rb.Push(*result); + } + rb.PushMappedBuffer(output_buffer); + + LOG_DEBUG(Service_FS, "called, save_low={:08X} save_high={:08X} smdh_size={:08X}", + info.save_id_low, info.save_id_high, smdh_size); +} + +void FS_USER::GetExtDataBlockSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + ExtSaveDataInfo info = rp.PopRaw(); + + auto result = archives.GetExtDataBlockSize(info); + + IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); + if (result.Failed()) { + rb.Push(result.Code()); + rb.Push(0); + rb.Push(0); + rb.Push(0); + rb.Push(0); + rb.Push(0); + } else { + rb.Push(ResultSuccess); + rb.Push(static_cast(result->total_blocks & 0xFFFFFFFF)); + rb.Push(static_cast(result->total_blocks >> 32)); + rb.Push(static_cast(result->free_blocks & 0xFFFFFFFF)); + rb.Push(static_cast(result->free_blocks >> 32)); + rb.Push(result->block_size); + } + + LOG_DEBUG(Service_FS, "called, save_low={:08X} save_high={:08X}", info.save_id_low, + info.save_id_high); +} + +void FS_USER::CheckArchive(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const auto archive_id = rp.PopEnum(); + const auto archivename_type = rp.PopEnum(); + const auto archivename_size = rp.Pop(); + std::vector archivename = rp.PopStaticBuffer(); + ASSERT(archivename.size() == archivename_size); + const FileSys::Path archive_path(archivename_type, std::move(archivename)); + + ClientSlot* slot = GetSessionData(ctx.Session()); + const ResultVal handle = + archives.OpenArchive(archive_id, archive_path, slot->program_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (handle.Succeeded()) { + archives.CloseArchive(*handle); + rb.Push(ResultSuccess); + } else { + rb.Push(handle.Code()); + } + + LOG_DEBUG(Service_FS, "called, archive_id=0x{:08X} archive_path={}", archive_id, + archive_path.DebugStr()); +} + void FS_USER::CardSlotIsInserted(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); @@ -1844,7 +1976,7 @@ FS_USER::FS_USER(Core::System& system) {0x0830, &FS_USER::ObsoletedCreateExtSaveData, "Obsoleted_3_0_CreateExtSaveData"}, {0x0831, nullptr, "CreateSharedExtSaveData"}, {0x0832, nullptr, "ReadExtSaveDataIcon"}, - {0x0833, nullptr, "EnumerateExtSaveData"}, + {0x0833, &FS_USER::ObsoletedEnumerateExtSaveData, "Obsoleted_3_0_EnumerateExtSaveData"}, {0x0834, nullptr, "EnumerateSharedExtSaveData"}, {0x0835, &FS_USER::ObsoletedDeleteExtSaveData, "Obsoleted_3_0_DeleteExtSaveData"}, {0x0836, nullptr, "DeleteSharedExtSaveData"}, @@ -1876,9 +2008,9 @@ FS_USER::FS_USER(Core::System& system) {0x0850, nullptr, "GetSpecialFileSize"}, {0x0851, &FS_USER::CreateExtSaveData, "CreateExtSaveData"}, {0x0852, &FS_USER::DeleteExtSaveData, "DeleteExtSaveData"}, - {0x0853, nullptr, "ReadExtSaveDataIcon"}, - {0x0854, nullptr, "GetExtDataBlockSize"}, - {0x0855, nullptr, "EnumerateExtSaveData"}, + {0x0853, &FS_USER::ReadExtSaveDataIcon, "ReadExtSaveDataIcon"}, + {0x0854, &FS_USER::GetExtDataBlockSize, "GetExtDataBlockSize"}, + {0x0855, &FS_USER::EnumerateExtSaveData, "EnumerateExtSaveData"}, {0x0856, &FS_USER::CreateSystemSaveData, "CreateSystemSaveData"}, {0x0857, &FS_USER::DeleteSystemSaveData, "DeleteSystemSaveData"}, {0x0858, nullptr, "StartDeviceMoveAsSource"}, @@ -1902,6 +2034,7 @@ FS_USER::FS_USER(Core::System& system) {0x086A, nullptr, "ReadNandReport"}, {0x086E, &FS_USER::SetThisSaveDataSecureValue, "SetThisSaveDataSecureValue" }, {0x086F, &FS_USER::GetThisSaveDataSecureValue, "GetThisSaveDataSecureValue" }, + {0x0870, &FS_USER::CheckArchive, "CheckArchive"}, {0x0875, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue" }, {0x0876, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue" }, {0x087A, &FS_USER::AddSeed, "AddSeed"}, diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index 9dd021bdc..fffe16ab0 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -428,6 +428,73 @@ private: */ void DeleteExtSaveData(Kernel::HLERequestContext& ctx); + /** + * FS_User::EnumerateExtSaveData service function (0x08550102) + * Inputs: + * 1 : Output IDs buffer size in bytes + * 2 : Media type (NAND / SDMC) + * 3 : ID entry size (4 or 8) + * 4 : Shared flag (0 = normal, 1 = shared) + * 5 : (IDs buffer size << 4) | 0xC + * 6 : Pointer to output IDs buffer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Number of IDs written + */ + void EnumerateExtSaveData(Kernel::HLERequestContext& ctx); + + /** + * FS_User::Obsoleted_3_0_EnumerateExtSaveData service function (0x08330082) + * Inputs: + * 1 : Output IDs buffer size in bytes + * 2 : Media type (NAND / SDMC) + * 3 : (IDs buffer size << 4) | 0xC + * 4 : Pointer to output IDs buffer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Number of IDs written + */ + void ObsoletedEnumerateExtSaveData(Kernel::HLERequestContext& ctx); + + /** + * FS_User::ReadExtSaveDataIcon service function (0x08530142) + * Inputs: + * 1-4 : ExtSaveDataInfo + * 5 : SMDH buffer size + * 6 : (SMDH size << 4) | 0xC + * 7 : Pointer to output SMDH buffer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Bytes read + */ + void ReadExtSaveDataIcon(Kernel::HLERequestContext& ctx); + + /** + * FS_User::GetExtDataBlockSize service function (0x08540100) + * Inputs: + * 1-4 : ExtSaveDataInfo + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2-3 : Total blocks (u64) + * 4-5 : Free blocks (u64) + * 6 : Block size (u32) + */ + void GetExtDataBlockSize(Kernel::HLERequestContext& ctx); + + /** + * FS_User::CheckArchive service function (0x087000C2) + * Checks whether the specified archive exists and is accessible. + * Inputs: + * 1 : Archive ID code + * 2 : Archive path type (LowPathType) + * 3 : Archive path size + * 4 : Static buffer descriptor + * 5 : Archive path pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void CheckArchive(Kernel::HLERequestContext& ctx); + /** * FS_User::CardSlotIsInserted service function. * Inputs: