ExtData support

This commit is contained in:
Masamune3210 2026-05-12 00:33:45 -05:00
parent 78e6aef690
commit 8d71f4afa3
4 changed files with 348 additions and 4 deletions

View file

@ -290,6 +290,117 @@ Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u8 unknown, u32 h
return ext_savedata->DeleteExtData(media_type, unknown, high, low);
}
ResultVal<u32> 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<u32>(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<u64>(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<u32> ArchiveManager::ReadExtSaveDataIcon(const ExtSaveDataInfo& info, u32 buffer_size,
u8* buffer) {
auto archive = id_code_map.find(
static_cast<MediaType>(info.media_type) == MediaType::NAND
? ArchiveIdCode::SharedExtSaveData
: ArchiveIdCode::ExtSaveData);
if (archive == id_code_map.end()) {
return UnimplementedFunction(ErrorModule::FS);
}
auto* ext_savedata =
static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get());
FileSys::Path path = FileSys::ConstructExtDataBinaryPath(
static_cast<u32>(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<u32>(std::min(static_cast<u64>(buffer_size), file.GetSize()));
if (file.ReadBytes(buffer, bytes_to_read) != bytes_to_read) {
return ResultUnknown;
}
return bytes_to_read;
}
ResultVal<ArchiveManager::ExtDataBlockInfo> ArchiveManager::GetExtDataBlockSize(
const ExtSaveDataInfo& info) {
auto archive = id_code_map.find(
static_cast<MediaType>(info.media_type) == MediaType::NAND
? ArchiveIdCode::SharedExtSaveData
: ArchiveIdCode::ExtSaveData);
if (archive == id_code_map.end()) {
return UnimplementedFunction(ErrorModule::FS);
}
auto* ext_savedata =
static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get());
FileSys::Path path = FileSys::ConstructExtDataBinaryPath(
static_cast<u32>(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<u64>(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);

View file

@ -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<u32> 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<u32> 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<ExtDataBlockInfo> 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

View file

@ -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<u32>();
u8 media_type = rp.Pop<u8>();
u32 id_size = rp.Pop<u32>();
bool shared = rp.Pop<bool>();
auto& output_buffer = rp.PopMappedBuffer();
u32 max_count = id_size > 0 ? ids_size / id_size : 0;
std::vector<u8> output(ids_size);
auto result = archives.EnumerateExtSaveData(static_cast<MediaType>(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<u32>(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<u32>();
u8 media_type = rp.Pop<u8>();
auto& output_buffer = rp.PopMappedBuffer();
constexpr u32 id_size = sizeof(u64);
u32 max_count = ids_size / id_size;
std::vector<u8> output(ids_size);
auto result = archives.EnumerateExtSaveData(static_cast<MediaType>(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<u32>(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<ExtSaveDataInfo>();
u32 smdh_size = rp.Pop<u32>();
auto& output_buffer = rp.PopMappedBuffer();
std::vector<u8> 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<u32>(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<ExtSaveDataInfo>();
auto result = archives.GetExtDataBlockSize(info);
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
rb.Push<u32>(0);
rb.Push<u32>(0);
rb.Push<u32>(0);
rb.Push<u32>(0);
} else {
rb.Push(ResultSuccess);
rb.Push(static_cast<u32>(result->total_blocks & 0xFFFFFFFF));
rb.Push(static_cast<u32>(result->total_blocks >> 32));
rb.Push(static_cast<u32>(result->free_blocks & 0xFFFFFFFF));
rb.Push(static_cast<u32>(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<FS::ArchiveIdCode>();
const auto archivename_type = rp.PopEnum<FileSys::LowPathType>();
const auto archivename_size = rp.Pop<u32>();
std::vector<u8> 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<ArchiveHandle> 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"},

View file

@ -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: