From a4e44cf06093f07bf4787d21f3a64952331de659 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 7 Mar 2024 15:37:43 +0000 Subject: [PATCH] Partially implement ExtSaveData --- include/fs/archive_base.hpp | 18 +++- include/fs/archive_ext_save_data.hpp | 14 +-- include/services/fs.hpp | 3 +- src/core/fs/archive_ext_save_data.cpp | 143 +++++++++++++++++++++++--- src/core/services/fs.cpp | 40 ++++++- 5 files changed, 188 insertions(+), 30 deletions(-) diff --git a/include/fs/archive_base.hpp b/include/fs/archive_base.hpp index 2843be68..d95d8afe 100644 --- a/include/fs/archive_base.hpp +++ b/include/fs/archive_base.hpp @@ -228,9 +228,19 @@ public: return Ok(FormatInfo{ .size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false }); } - virtual HorizonResult createDirectory(const FSPath& path) { - Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str()); - return Result::FS::AlreadyExists; + virtual HorizonResult createDirectory(const FSPath& path) { + Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str()); + return Result::FS::AlreadyExists; + } + + virtual HorizonResult deleteDirectory(const FSPath& path) { + Helpers::warn("Stubbed DeleteDirectory for %s archive", name().c_str()); + return Result::Success; + } + + virtual HorizonResult deleteDirectoryRecursively(const FSPath& path) { + Helpers::warn("Stubbed DeleteDirectoryRecursively for %s archive", name().c_str()); + return Result::Success; } // Returns nullopt if opening the file failed, otherwise returns a file descriptor to it (nullptr if none is needed) @@ -263,4 +273,4 @@ struct ArchiveResource { u32 clusterSize; // Size of a cluster in bytes u32 partitionCapacityInClusters; u32 freeSpaceInClusters; -}; \ No newline at end of file +}; diff --git a/include/fs/archive_ext_save_data.hpp b/include/fs/archive_ext_save_data.hpp index 7c8c7503..e9142012 100644 --- a/include/fs/archive_ext_save_data.hpp +++ b/include/fs/archive_ext_save_data.hpp @@ -10,6 +10,8 @@ public: std::string name() override { return "ExtSaveData::" + backingFolder; } HorizonResult createDirectory(const FSPath& path) override; + HorizonResult deleteDirectory(const FSPath& path) override; + HorizonResult deleteDirectoryRecursively(const FSPath& path) override; HorizonResult createFile(const FSPath& path, u64 size) override; HorizonResult deleteFile(const FSPath& path) override; HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) override; @@ -18,16 +20,16 @@ public: Rust::Result openDirectory(const FSPath& path) override; FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override; std::optional readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override; + void format(const FSPath& path, const FormatInfo& info) override; + Rust::Result getFormatInfo(const FSPath& path) override; - Rust::Result getFormatInfo(const FSPath& path) override { - Helpers::warn("Stubbed ExtSaveData::GetFormatInfo"); - return Ok(FormatInfo{.size = 1_GB, .numOfDirectories = 255, .numOfFiles = 255, .duplicateData = false}); - } + std::filesystem::path getFormatInfoPath(const FSPath& path) const; + std::filesystem::path getUserDataPath() const; // Takes in a binary ExtSaveData path, outputs a combination of the backing folder with the low and high save entries of the path // Used for identifying the archive format info files - std::string getExtSaveDataPathFromBinary(const FSPath& path); + std::string getExtSaveDataPathFromBinary(const FSPath& path) const; bool isShared = false; std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path. -}; \ No newline at end of file +}; diff --git a/include/services/fs.hpp b/include/services/fs.hpp index 4a613121..d75db962 100644 --- a/include/services/fs.hpp +++ b/include/services/fs.hpp @@ -54,6 +54,7 @@ class FSService { void closeArchive(u32 messagePointer); void controlArchive(u32 messagePointer); void deleteDirectory(u32 messagePointer); + void deleteDirectoryRecursively(u32 messagePointer); void deleteExtSaveData(u32 messagePointer); void deleteFile(u32 messagePointer); void formatSaveData(u32 messagePointer); @@ -91,4 +92,4 @@ public: void handleSyncRequest(u32 messagePointer); // Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM. void initializeFilesystem(); -}; \ No newline at end of file +}; diff --git a/src/core/fs/archive_ext_save_data.cpp b/src/core/fs/archive_ext_save_data.cpp index 4b57f245..c872351d 100644 --- a/src/core/fs/archive_ext_save_data.cpp +++ b/src/core/fs/archive_ext_save_data.cpp @@ -11,7 +11,8 @@ HorizonResult ExtSaveDataArchive::createFile(const FSPath& path, u64 size) { if (!isPathSafe(path)) Helpers::panic("Unsafe path in ExtSaveData::CreateFile"); - fs::path p = IOFile::getAppData() / backingFolder; + fs::path p = getUserDataPath(); + p += fs::path(path.utf16_string).make_preferred(); if (fs::exists(p)) @@ -37,7 +38,8 @@ HorizonResult ExtSaveDataArchive::deleteFile(const FSPath& path) { if (!isPathSafe(path)) Helpers::panic("Unsafe path in ExtSaveData::DeleteFile"); - fs::path p = IOFile::getAppData() / backingFolder; + fs::path p = getUserDataPath(); + p += fs::path(path.utf16_string).make_preferred(); if (fs::is_directory(p)) { @@ -72,7 +74,8 @@ FileDescriptor ExtSaveDataArchive::openFile(const FSPath& path, const FilePerms& if (perms.create()) Helpers::panic("[ExtSaveData] Can't open file with create flag"); - fs::path p = IOFile::getAppData() / backingFolder; + fs::path p = getUserDataPath(); + p += fs::path(path.utf16_string).make_preferred(); if (fs::exists(p)) { // Return file descriptor if the file exists @@ -97,7 +100,8 @@ HorizonResult ExtSaveDataArchive::renameFile(const FSPath& oldPath, const FSPath } // Construct host filesystem paths - fs::path sourcePath = IOFile::getAppData() / backingFolder; + fs::path sourcePath = getUserDataPath(); + fs::path destPath = sourcePath; sourcePath += fs::path(oldPath.utf16_string).make_preferred(); @@ -130,7 +134,7 @@ HorizonResult ExtSaveDataArchive::createDirectory(const FSPath& path) { Helpers::panic("Unsafe path in ExtSaveData::OpenFile"); } - fs::path p = IOFile::getAppData() / backingFolder; + fs::path p = getUserDataPath(); p += fs::path(path.utf16_string).make_preferred(); if (fs::is_directory(p)) return Result::FS::AlreadyExists; @@ -145,14 +149,124 @@ HorizonResult ExtSaveDataArchive::createDirectory(const FSPath& path) { } } -std::string ExtSaveDataArchive::getExtSaveDataPathFromBinary(const FSPath& path) { +HorizonResult ExtSaveDataArchive::deleteDirectory(const FSPath& path) { + if (path.type == PathType::UTF16) { + if (!isPathSafe(path)) { + Helpers::panic("Unsafe path in ExtSaveData::DeleteDirectory"); + } + + fs::path p = getUserDataPath(); + p += fs::path(path.utf16_string).make_preferred(); + + if (!fs::is_directory(p)) { + return Result::FS::NotFoundInvalid; + } + + if (fs::is_regular_file(p)) { + Helpers::panic("File path passed to ExtSaveData::DeleteDirectory"); + } + + Helpers::warn("Stubbed DeleteDirectory for %s archive", name().c_str()); + bool success = fs::remove(p); + return success ? Result::Success : Result::FS::UnexpectedFileOrDir; + } else { + Helpers::panic("Unimplemented ExtSaveData::DeleteDirectory"); + } +} + + +HorizonResult ExtSaveDataArchive::deleteDirectoryRecursively(const FSPath& path) { + if (path.type == PathType::UTF16) { + if (!isPathSafe(path)) { + Helpers::panic("Unsafe path in ExtSaveData::DeleteDirectoryRecursively"); + } + + fs::path p = getUserDataPath(); + p += fs::path(path.utf16_string).make_preferred(); + + if (!fs::is_directory(p)) { + return Result::FS::NotFoundInvalid; + } + + if (fs::is_regular_file(p)) { + Helpers::panic("File path passed to ExtSaveData::DeleteDirectoryRecursively"); + } + + Helpers::warn("Stubbed DeleteDirectoryRecursively for %s archive", name().c_str()); + bool success = fs::remove_all(p); + return success ? Result::Success : Result::FS::UnexpectedFileOrDir; + } else { + Helpers::panic("Unimplemented ExtSaveData::DeleteDirectoryRecursively"); + } +} + +void ExtSaveDataArchive::format(const FSPath& path, const FormatInfo& info) { + const fs::path saveDataPath = IOFile::getAppData() / backingFolder; + const fs::path formatInfoPath = getFormatInfoPath(path); + + // Delete all contents by deleting the directory then recreating it + fs::remove_all(saveDataPath); + fs::create_directories(saveDataPath); + + if(!isShared) { + fs::create_directories(saveDataPath / "user"); + fs::create_directories(saveDataPath / "boss"); + // todo: save icon. + } + + // Write format info on disk + IOFile file(formatInfoPath, "wb"); + file.writeBytes(&info, sizeof(info)); + file.flush(); + file.close(); +} + +std::filesystem::path ExtSaveDataArchive::getFormatInfoPath(const FSPath& path) const { + return IOFile::getAppData() / "FormatInfo" / (getExtSaveDataPathFromBinary(path) + ".format"); +} + +std::filesystem::path ExtSaveDataArchive::getUserDataPath() const { + fs::path p = IOFile::getAppData() / backingFolder; + if(!isShared) { // todo: "boss"? + p /= "user"; + } + return p; +} + + +Rust::Result ExtSaveDataArchive::getFormatInfo(const FSPath& path) { + const fs::path formatInfoPath = getFormatInfoPath(path); + IOFile file(formatInfoPath, "rb"); + + // If the file failed to open somehow, we return that the archive is not formatted + if (!file.isOpen()) { + return Err(Result::FS::NotFormatted); + } + + FormatInfo ret; + auto [success, bytesRead] = file.readBytes(&ret, sizeof(FormatInfo)); + file.close(); + + if (!success || bytesRead != sizeof(FormatInfo)) { + Helpers::warn("ExtSaveDataArchive::GetFormatInfo: Format file exists but was not properly read into the FormatInfo struct"); + return Err(Result::FS::NotFormatted); + } + + return Ok(ret); +} + +std::string ExtSaveDataArchive::getExtSaveDataPathFromBinary(const FSPath& path) const { + if(path.type != PathType::Binary) { + Helpers::panic("GetExtSaveDataPathFromBinary called without a Bianry FSPath!"); + } + // TODO: Remove punning here const u32 mediaType = *(u32*)&path.binary[0]; const u32 saveLow = *(u32*)&path.binary[4]; const u32 saveHigh = *(u32*)&path.binary[8]; - // TODO: Should the media type be used here - return backingFolder + std::to_string(saveLow) + std::to_string(saveHigh); + // TODO: Should the media type be used here, using it just to be safe. + return backingFolder + std::to_string(mediaType) + "_" + std::to_string(saveLow) + "_" + std::to_string(saveHigh); } Rust::Result ExtSaveDataArchive::openArchive(const FSPath& path) { @@ -160,13 +274,12 @@ Rust::Result ExtSaveDataArchive::openArchive(const Helpers::panic("ExtSaveData accessed with an invalid path in OpenArchive"); } - // TODO: Readd the format check. I didn't manage to fix it sadly // Create a format info path in the style of AppData/FormatInfo/Cartridge10390390194.format - // fs::path formatInfopath = IOFile::getAppData() / "FormatInfo" / (getExtSaveDataPathFromBinary(path) + ".format"); + const fs::path formatInfoPath = getFormatInfoPath(path); // Format info not found so the archive is not formatted - // if (!fs::is_regular_file(formatInfopath)) { - // return isShared ? Err(Result::FS::NotFormatted) : Err(Result::FS::NotFoundInvalid); - //} + if (!fs::is_regular_file(formatInfoPath)) { + return isShared ? Err(Result::FS::NotFormatted) : Err(Result::FS::NotFoundInvalid); + } return Ok((ArchiveBase*)this); } @@ -176,7 +289,7 @@ Rust::Result ExtSaveDataArchive::openDirectory( if (!isPathSafe(path)) Helpers::panic("Unsafe path in ExtSaveData::OpenDirectory"); - fs::path p = IOFile::getAppData() / backingFolder; + fs::path p = getUserDataPath(); p += fs::path(path.utf16_string).make_preferred(); if (fs::is_regular_file(p)) { @@ -198,4 +311,4 @@ Rust::Result ExtSaveDataArchive::openDirectory( std::optional ExtSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) { Helpers::panic("ExtSaveDataArchive::ReadFile: Failed"); return std::nullopt; -} \ No newline at end of file +} diff --git a/src/core/services/fs.cpp b/src/core/services/fs.cpp index 2e102958..69155093 100644 --- a/src/core/services/fs.cpp +++ b/src/core/services/fs.cpp @@ -173,6 +173,7 @@ void FSService::handleSyncRequest(u32 messagePointer) { case FSCommands::ControlArchive: controlArchive(messagePointer); break; case FSCommands::CloseArchive: closeArchive(messagePointer); break; case FSCommands::DeleteDirectory: deleteDirectory(messagePointer); break; + case FSCommands::DeleteDirectoryRecursively: deleteDirectoryRecursively(messagePointer); break; case FSCommands::DeleteExtSaveData: deleteExtSaveData(messagePointer); break; case FSCommands::DeleteFile: deleteFile(messagePointer); break; case FSCommands::FormatSaveData: formatSaveData(messagePointer); break; @@ -431,11 +432,23 @@ void FSService::deleteDirectory(u32 messagePointer) { const u32 filePathPointer = mem.read32(messagePointer + 28); log("FS::DeleteDirectory\n"); - Helpers::warn("Stubbed FS::DeleteDirectory call!"); + Helpers::warn("Stubbed FS::DeleteDirectory call!"); // note: should we ensure the system isn't about to delete things outside of the VFS? mem.write32(messagePointer, IPC::responseHeader(0x806, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } +void FSService::deleteDirectoryRecursively(u32 messagePointer) { + const Handle archiveHandle = Handle(mem.read64(messagePointer + 8)); + const u32 filePathType = mem.read32(messagePointer + 16); + const u32 filePathSize = mem.read32(messagePointer + 20); + const u32 filePathPointer = mem.read32(messagePointer + 28); + log("FS::DeleteDirectoryRecursively\n"); + + Helpers::warn("Stubbed FS::DeleteDirectoryRecursively call!"); // note: should we ensure the system isn't about to delete things outside of the VFS? + mem.write32(messagePointer, IPC::responseHeader(0x807, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void FSService::getFormatInfo(u32 messagePointer) { const u32 archiveID = mem.read32(messagePointer + 4); const u32 pathType = mem.read32(messagePointer + 8); @@ -530,10 +543,29 @@ void FSService::createExtSaveData(u32 messagePointer) { const u32 smdhSize = mem.read32(messagePointer + 36); const u32 smdhPointer = mem.read32(messagePointer + 44); - log("FS::CreateExtSaveData (stubbed)\n"); + log("FS::CreateExtSaveData\n"); + ArchiveBase::FormatInfo info { + .size = (u32) (sizeLimit * 0x200), + .numOfDirectories = numOfDirectories, + .numOfFiles = numOfFiles, + .duplicateData = false + }; + FSPath path = readPath(PathType::Binary, messagePointer + 4, 32); + FSPath smdh = readPath(PathType::Binary, smdhPointer, smdhSize); + + switch(mediaType) { + case 0: + sharedExtSaveData_nand.format(path, info); + break; + case 1: + extSaveData_sdmc.format(path, info); + break; + default: + Helpers::warn("Unhanled ExtSaveData MediaType %d", static_cast(mediaType)); + break; + } mem.write32(messagePointer, IPC::responseHeader(0x0851, 1, 0)); - // TODO: Similar to DeleteExtSaveData, we need to refactor how our ExtSaveData stuff works before properly implementing this mem.write32(messagePointer + 4, Result::Success); } @@ -785,4 +817,4 @@ void FSService::getSdmcArchiveResource(u32 messagePointer) { mem.write32(messagePointer + 12, resource.clusterSize); mem.write32(messagePointer + 16, resource.partitionCapacityInClusters); mem.write32(messagePointer + 20, resource.freeSpaceInClusters); -} \ No newline at end of file +}