This commit is contained in:
Ada Ahmed 2024-12-06 20:11:54 +00:00 committed by GitHub
commit 88782aab09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 309 additions and 62 deletions

View file

@ -57,6 +57,14 @@ namespace ArchiveID {
} }
} }
namespace MediaType {
enum : u8 {
NAND = 0,
SD = 1,
Gamecard = 2,
};
};
struct FSPath { struct FSPath {
u32 type = PathType::Invalid; u32 type = PathType::Invalid;
@ -222,16 +230,26 @@ public:
virtual HorizonResult createFile(const FSPath& path, u64 size) = 0; virtual HorizonResult createFile(const FSPath& path, u64 size) = 0;
virtual HorizonResult deleteFile(const FSPath& path) = 0; virtual HorizonResult deleteFile(const FSPath& path) = 0;
virtual Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) { virtual Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) {
Helpers::panic("Unimplemented GetFormatInfo for %s archive", name().c_str()); Helpers::panic("Unimplemented GetFormatInfo for %s archive", name().c_str());
// Return a dummy struct just to avoid the UB of not returning anything, even if we panic // Return a dummy struct just to avoid the UB of not returning anything, even if we panic
return Ok(FormatInfo{ .size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false }); return Ok(FormatInfo{.size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false});
} }
virtual HorizonResult createDirectory(const FSPath& path) { virtual HorizonResult createDirectory(const FSPath& path) {
Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str()); Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str());
return Result::FS::AlreadyExists; 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) // Returns nullopt if opening the file failed, otherwise returns a file descriptor to it (nullptr if none is needed)
virtual FileDescriptor openFile(const FSPath& path, const FilePerms& perms) = 0; virtual FileDescriptor openFile(const FSPath& path, const FilePerms& perms) = 0;
@ -263,4 +281,4 @@ struct ArchiveResource {
u32 clusterSize; // Size of a cluster in bytes u32 clusterSize; // Size of a cluster in bytes
u32 partitionCapacityInClusters; u32 partitionCapacityInClusters;
u32 freeSpaceInClusters; u32 freeSpaceInClusters;
}; };

View file

@ -1,15 +1,26 @@
#pragma once #pragma once
#include <unordered_map>
#include "archive_base.hpp" #include "archive_base.hpp"
#pragma pack(push, 1)
struct ExtSaveDataInfo {
u8 media_type;
u8 unknown;
u16 reserved;
u64 save_id;
};
#pragma pack(pop)
class ExtSaveDataArchive : public ArchiveBase { class ExtSaveDataArchive : public ArchiveBase {
public: public:
ExtSaveDataArchive(Memory& mem, const std::string& folder, bool isShared = false) : ArchiveBase(mem), ExtSaveDataArchive(Memory& mem, const std::string& folder, u64 saveId, bool isShared = false, bool isNAND = false) : ArchiveBase(mem),
isShared(isShared), backingFolder(folder) {} archiveSaveId(saveId), isShared(isShared), isNAND(isNAND), backingFolder(folder) {}
u64 getFreeBytes() override { Helpers::panic("ExtSaveData::GetFreeBytes unimplemented"); return 0; } u64 getFreeBytes() override { Helpers::panic("ExtSaveData::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "ExtSaveData::" + backingFolder; } std::string name() override { return "ExtSaveData::" + backingFolder + "::" + std::to_string(archiveSaveId); }
HorizonResult createDirectory(const FSPath& path) override; 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 createFile(const FSPath& path, u64 size) override;
HorizonResult deleteFile(const FSPath& path) override; HorizonResult deleteFile(const FSPath& path) override;
HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) override; HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) override;
@ -18,16 +29,20 @@ public:
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override; Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override; FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override; std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
void format(const FSPath& path, const FormatInfo& info) override;
Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) override;
void clear(const FSPath& path) const;
Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) override { std::filesystem::path getFormatInfoPath(const FSPath& path) const;
Helpers::warn("Stubbed ExtSaveData::GetFormatInfo"); std::filesystem::path getUserDataPath() const;
return Ok(FormatInfo{.size = 1_GB, .numOfDirectories = 255, .numOfFiles = 255, .duplicateData = false});
}
// Takes in a binary ExtSaveData path, outputs a combination of the backing folder with the low and high save entries of the path // 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 // Used for identifying the archive format info files
std::string getExtSaveDataPathFromBinary(const FSPath& path); std::string getExtSaveDataPathFromBinary(const FSPath& path) const;
std::string getExtSaveDataPath() const;
u64 archiveSaveId = 0;
bool isShared = false; bool isShared = false;
bool isNAND = false;
std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path. std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path.
}; };

View file

@ -35,11 +35,13 @@ class FSService {
UserSaveDataArchive userSaveData1; UserSaveDataArchive userSaveData1;
UserSaveDataArchive userSaveData2; UserSaveDataArchive userSaveData2;
ExtSaveDataArchive extSaveData_sdmc; std::unordered_map<u64, ExtSaveDataArchive> extSaveData_sdmc;
ExtSaveDataArchive sharedExtSaveData_nand; std::unordered_map<u64, ExtSaveDataArchive> nandExtSaveData_nand;
SystemSaveDataArchive systemSaveData; SystemSaveDataArchive systemSaveData;
ArchiveBase* getArchiveFromID(u32 id, const FSPath& archivePath); ArchiveBase* getArchiveFromID(u32 id, const FSPath& archivePath);
ExtSaveDataArchive* getExtArchiveFromID(u64 saveId, bool isShared);
ExtSaveDataArchive* getNANDExtArchiveFromID(u64 saveId, bool isShared);
Rust::Result<Handle, HorizonResult> openArchiveHandle(u32 archiveID, const FSPath& path); Rust::Result<Handle, HorizonResult> openArchiveHandle(u32 archiveID, const FSPath& path);
Rust::Result<Handle, HorizonResult> openDirectoryHandle(ArchiveBase* archive, const FSPath& path); Rust::Result<Handle, HorizonResult> openDirectoryHandle(ArchiveBase* archive, const FSPath& path);
std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path, const FSPath& archivePath, const FilePerms& perms); std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path, const FSPath& archivePath, const FilePerms& perms);
@ -56,6 +58,7 @@ class FSService {
void closeArchive(u32 messagePointer); void closeArchive(u32 messagePointer);
void controlArchive(u32 messagePointer); void controlArchive(u32 messagePointer);
void deleteDirectory(u32 messagePointer); void deleteDirectory(u32 messagePointer);
void deleteDirectoryRecursively(u32 messagePointer);
void deleteExtSaveData(u32 messagePointer); void deleteExtSaveData(u32 messagePointer);
void deleteFile(u32 messagePointer); void deleteFile(u32 messagePointer);
void formatSaveData(u32 messagePointer); void formatSaveData(u32 messagePointer);
@ -79,13 +82,14 @@ class FSService {
void setArchivePriority(u32 messagePointer); void setArchivePriority(u32 messagePointer);
void setPriority(u32 messagePointer); void setPriority(u32 messagePointer);
void setThisSaveDataSecureValue(u32 messagePointer); void setThisSaveDataSecureValue(u32 messagePointer);
void readExtSaveDataIcon(u32 messagePointer);
// Used for set/get priority: Not sure what sort of priority this is referring to // Used for set/get priority: Not sure what sort of priority this is referring to
u32 priority; u32 priority;
public: public:
FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config) FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config)
: mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), : mem(mem), saveData(mem), sdmc(mem),
sdmcWriteOnly(mem, true), selfNcch(mem), ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), sdmcWriteOnly(mem, true), selfNcch(mem), ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1),
userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config), systemSaveData(mem) {} userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config), systemSaveData(mem) {}
@ -93,4 +97,4 @@ class FSService {
void handleSyncRequest(u32 messagePointer); void handleSyncRequest(u32 messagePointer);
// Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM. // Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM.
void initializeFilesystem(); void initializeFilesystem();
}; };

View file

@ -11,7 +11,8 @@ HorizonResult ExtSaveDataArchive::createFile(const FSPath& path, u64 size) {
if (!isPathSafe<PathType::UTF16>(path)) if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::CreateFile"); 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(); p += fs::path(path.utf16_string).make_preferred();
if (fs::exists(p)) if (fs::exists(p))
@ -37,7 +38,8 @@ HorizonResult ExtSaveDataArchive::deleteFile(const FSPath& path) {
if (!isPathSafe<PathType::UTF16>(path)) if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::DeleteFile"); 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(); p += fs::path(path.utf16_string).make_preferred();
if (fs::is_directory(p)) { if (fs::is_directory(p)) {
@ -72,7 +74,8 @@ FileDescriptor ExtSaveDataArchive::openFile(const FSPath& path, const FilePerms&
if (perms.create()) if (perms.create())
Helpers::panic("[ExtSaveData] Can't open file with create flag"); 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(); p += fs::path(path.utf16_string).make_preferred();
if (fs::exists(p)) { // Return file descriptor if the file exists 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 // Construct host filesystem paths
fs::path sourcePath = IOFile::getAppData() / backingFolder; fs::path sourcePath = getUserDataPath();
fs::path destPath = sourcePath; fs::path destPath = sourcePath;
sourcePath += fs::path(oldPath.utf16_string).make_preferred(); 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"); 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(); p += fs::path(path.utf16_string).make_preferred();
if (fs::is_directory(p)) return Result::FS::AlreadyExists; if (fs::is_directory(p)) return Result::FS::AlreadyExists;
@ -145,14 +149,135 @@ HorizonResult ExtSaveDataArchive::createDirectory(const FSPath& path) {
} }
} }
std::string ExtSaveDataArchive::getExtSaveDataPathFromBinary(const FSPath& path) { HorizonResult ExtSaveDataArchive::deleteDirectory(const FSPath& path) {
// TODO: Remove punning here if (path.type == PathType::UTF16) {
const u32 mediaType = *(u32*)&path.binary[0]; if (!isPathSafe<PathType::UTF16>(path)) {
const u32 saveLow = *(u32*)&path.binary[4]; Helpers::panic("Unsafe path in ExtSaveData::DeleteDirectory");
const u32 saveHigh = *(u32*)&path.binary[8]; }
// TODO: Should the media type be used here fs::path p = getUserDataPath();
return backingFolder + std::to_string(saveLow) + std::to_string(saveHigh); 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<PathType::UTF16>(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() / getExtSaveDataPath();
const fs::path formatInfoPath = getFormatInfoPath(path);
if (!isShared) {
// Delete all contents by deleting the directory then recreating it
fs::remove_all(saveDataPath);
fs::create_directories(saveDataPath);
fs::create_directories(saveDataPath / "user");
fs::create_directories(saveDataPath / "boss");
}
// Write format info on disk
IOFile file(formatInfoPath, "wb");
file.writeBytes(&info, sizeof(info));
file.flush();
file.close();
}
void ExtSaveDataArchive::clear(const FSPath& path) const {
const fs::path saveDataPath = IOFile::getAppData() / getExtSaveDataPath();
const fs::path formatInfoPath = getFormatInfoPath(path);
fs::remove_all(saveDataPath);
fs::remove(formatInfoPath);
}
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() / getExtSaveDataPath();
if (!isShared) { // todo: "boss"?
p /= "user";
}
return p;
}
Rust::Result<ArchiveBase::FormatInfo, HorizonResult> 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);
}
ret.size = 0;
return Ok(ret);
}
std::string ExtSaveDataArchive::getExtSaveDataPathFromBinary(const FSPath& path) const {
if (path.type != PathType::Binary) {
Helpers::panic("GetExtSaveDataPathFromBinary called without a Binary FSPath!");
}
const ExtSaveDataInfo info = *reinterpret_cast<const ExtSaveDataInfo*>(&path.binary[0]);
return fs::path(backingFolder).filename().string() + "_" + std::to_string(info.save_id);
}
std::string ExtSaveDataArchive::getExtSaveDataPath() const {
if (isShared) {
return backingFolder + "/" + std::to_string(mem.getProgramID().value_or(0)) + "/" + std::to_string(archiveSaveId);
}
return backingFolder + "/" + std::to_string(archiveSaveId);
} }
Rust::Result<ArchiveBase*, HorizonResult> ExtSaveDataArchive::openArchive(const FSPath& path) { Rust::Result<ArchiveBase*, HorizonResult> ExtSaveDataArchive::openArchive(const FSPath& path) {
@ -160,13 +285,19 @@ Rust::Result<ArchiveBase*, HorizonResult> ExtSaveDataArchive::openArchive(const
Helpers::panic("ExtSaveData accessed with an invalid path in OpenArchive"); Helpers::panic("ExtSaveData accessed with an invalid path in OpenArchive");
} }
// TODO: Readd the format check. I didn't manage to fix it sadly if (isShared) {
const fs::path saveDataPath = IOFile::getAppData() / getExtSaveDataPath();
if (!fs::exists(saveDataPath))
fs::create_directories(saveDataPath);
return Ok((ArchiveBase*) this);
}
// Create a format info path in the style of AppData/FormatInfo/Cartridge10390390194.format // 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 // Format info not found so the archive is not formatted
// if (!fs::is_regular_file(formatInfopath)) { if (!fs::is_regular_file(formatInfoPath)) {
// return isShared ? Err(Result::FS::NotFormatted) : Err(Result::FS::NotFoundInvalid); return isNAND ? Err(Result::FS::NotFormatted) : Err(Result::FS::NotFoundInvalid);
//} }
return Ok((ArchiveBase*)this); return Ok((ArchiveBase*)this);
} }
@ -176,7 +307,7 @@ Rust::Result<DirectorySession, HorizonResult> ExtSaveDataArchive::openDirectory(
if (!isPathSafe<PathType::UTF16>(path)) if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::OpenDirectory"); 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(); p += fs::path(path.utf16_string).make_preferred();
if (fs::is_regular_file(p)) { if (fs::is_regular_file(p)) {
@ -198,4 +329,4 @@ Rust::Result<DirectorySession, HorizonResult> ExtSaveDataArchive::openDirectory(
std::optional<u32> ExtSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) { std::optional<u32> ExtSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
Helpers::panic("ExtSaveDataArchive::ReadFile: Failed"); Helpers::panic("ExtSaveDataArchive::ReadFile: Failed");
return std::nullopt; return std::nullopt;
} }

View file

@ -13,14 +13,6 @@ namespace PathType {
}; };
}; };
namespace MediaType {
enum : u8 {
NAND = 0,
SD = 1,
Gamecard = 2
};
};
HorizonResult NCCHArchive::createFile(const FSPath& path, u64 size) { HorizonResult NCCHArchive::createFile(const FSPath& path, u64 size) {
Helpers::panic("[NCCH] CreateFile not yet supported"); Helpers::panic("[NCCH] CreateFile not yet supported");
return Result::Success; return Result::Success;

View file

@ -37,6 +37,7 @@ namespace FSCommands {
FormatSaveData = 0x084C0242, FormatSaveData = 0x084C0242,
CreateExtSaveData = 0x08510242, CreateExtSaveData = 0x08510242,
DeleteExtSaveData = 0x08520100, DeleteExtSaveData = 0x08520100,
ReadExtSaveDataIcon = 0x08530142,
SetArchivePriority = 0x085A00C0, SetArchivePriority = 0x085A00C0,
InitializeWithSdkVersion = 0x08610042, InitializeWithSdkVersion = 0x08610042,
SetPriority = 0x08620040, SetPriority = 0x08620040,
@ -54,6 +55,8 @@ void FSService::reset() {
// Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM. // Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM.
void FSService::initializeFilesystem() { void FSService::initializeFilesystem() {
const auto sdmcPath = IOFile::getAppData() / "SDMC"; // Create SDMC directory const auto sdmcPath = IOFile::getAppData() / "SDMC"; // Create SDMC directory
const auto nandPath = IOFile::getAppData() / "NAND";
const auto smdcSharedpath = IOFile::getAppData() / ".." / "SharedFiles" / "SDMC";
const auto nandSharedpath = IOFile::getAppData() / ".." / "SharedFiles" / "NAND"; const auto nandSharedpath = IOFile::getAppData() / ".." / "SharedFiles" / "NAND";
const auto savePath = IOFile::getAppData() / "SaveData"; // Create SaveData const auto savePath = IOFile::getAppData() / "SaveData"; // Create SaveData
@ -62,6 +65,10 @@ void FSService::initializeFilesystem() {
namespace fs = std::filesystem; namespace fs = std::filesystem;
if (!fs::is_directory(smdcSharedpath)) {
fs::create_directories(smdcSharedpath);
}
if (!fs::is_directory(nandSharedpath)) { if (!fs::is_directory(nandSharedpath)) {
fs::create_directories(nandSharedpath); fs::create_directories(nandSharedpath);
} }
@ -70,6 +77,10 @@ void FSService::initializeFilesystem() {
fs::create_directories(sdmcPath); fs::create_directories(sdmcPath);
} }
if (!fs::is_directory(nandPath)) {
fs::create_directories(nandPath);
}
if (!fs::is_directory(savePath)) { if (!fs::is_directory(savePath)) {
fs::create_directories(savePath); fs::create_directories(savePath);
} }
@ -83,17 +94,52 @@ void FSService::initializeFilesystem() {
} }
} }
ExtSaveDataArchive* FSService::getExtArchiveFromID(u64 saveId, bool isShared) {
if (const auto entry = extSaveData_sdmc.find(saveId); entry == extSaveData_sdmc.end()) {
extSaveData_sdmc.emplace(saveId, ExtSaveDataArchive(mem, isShared ? "../SharedFiles/SDMC" : "SDMC", saveId, isShared, false));
}
return &extSaveData_sdmc.at(saveId);
}
ExtSaveDataArchive* FSService::getNANDExtArchiveFromID(u64 saveId, bool isShared) {
if (const auto entry = nandExtSaveData_nand.find(saveId); entry == nandExtSaveData_nand.end()) {
nandExtSaveData_nand.emplace(saveId, ExtSaveDataArchive(mem, isShared ? "../SharedFiles/NAND" : "NAND", saveId, isShared, true));
}
return &nandExtSaveData_nand.at(saveId);
}
ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) { ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
switch (id) { switch (id) {
case ArchiveID::SelfNCCH: return &selfNcch; case ArchiveID::SelfNCCH: return &selfNcch;
case ArchiveID::SaveData: return &saveData; case ArchiveID::SaveData: return &saveData;
case ArchiveID::UserSaveData2: return &userSaveData2; case ArchiveID::UserSaveData2: return &userSaveData2;
case ArchiveID::ExtSaveData: case ArchiveID::ExtSaveData: {
return &extSaveData_sdmc; const ExtSaveDataInfo info = *reinterpret_cast<const ExtSaveDataInfo*>(&archivePath.binary[0]);
switch(info.media_type) {
case MediaType::NAND:
return getNANDExtArchiveFromID(info.save_id, false);
case MediaType::SD:
return getExtArchiveFromID(info.save_id, false);
default:
Helpers::panic("Unknown archive media type. ID: %d\n", info.media_type);
return nullptr;
}
}
case ArchiveID::SharedExtSaveData: case ArchiveID::SharedExtSaveData: {
return &sharedExtSaveData_nand; const ExtSaveDataInfo info = *reinterpret_cast<const ExtSaveDataInfo*>(&archivePath.binary[0]);
switch(info.media_type) {
case MediaType::NAND:
return getNANDExtArchiveFromID(info.save_id, true);
case MediaType::SD:
return getExtArchiveFromID(info.save_id, true);
default:
Helpers::panic("Unknown archive media type. ID: %d\n", info.media_type);
return nullptr;
}
}
case ArchiveID::SystemSaveData: return &systemSaveData; case ArchiveID::SystemSaveData: return &systemSaveData;
case ArchiveID::SDMC: return &sdmc; case ArchiveID::SDMC: return &sdmc;
@ -173,6 +219,7 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::ControlArchive: controlArchive(messagePointer); break; case FSCommands::ControlArchive: controlArchive(messagePointer); break;
case FSCommands::CloseArchive: closeArchive(messagePointer); break; case FSCommands::CloseArchive: closeArchive(messagePointer); break;
case FSCommands::DeleteDirectory: deleteDirectory(messagePointer); break; case FSCommands::DeleteDirectory: deleteDirectory(messagePointer); break;
case FSCommands::DeleteDirectoryRecursively: deleteDirectoryRecursively(messagePointer); break;
case FSCommands::DeleteExtSaveData: deleteExtSaveData(messagePointer); break; case FSCommands::DeleteExtSaveData: deleteExtSaveData(messagePointer); break;
case FSCommands::DeleteFile: deleteFile(messagePointer); break; case FSCommands::DeleteFile: deleteFile(messagePointer); break;
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break; case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
@ -436,6 +483,18 @@ void FSService::deleteDirectory(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success); 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!");
mem.write32(messagePointer, IPC::responseHeader(0x807, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::getFormatInfo(u32 messagePointer) { void FSService::getFormatInfo(u32 messagePointer) {
const u32 archiveID = mem.read32(messagePointer + 4); const u32 archiveID = mem.read32(messagePointer + 4);
const u32 pathType = mem.read32(messagePointer + 8); const u32 pathType = mem.read32(messagePointer + 8);
@ -511,29 +570,57 @@ void FSService::deleteExtSaveData(u32 messagePointer) {
const u64 saveID = mem.read64(messagePointer + 8); const u64 saveID = mem.read64(messagePointer + 8);
log("FS::DeleteExtSaveData (media type = %d, saveID = %llx) (stubbed)\n", mediaType, saveID); log("FS::DeleteExtSaveData (media type = %d, saveID = %llx) (stubbed)\n", mediaType, saveID);
mem.write32(messagePointer, IPC::responseHeader(0x0852, 1, 0));
// TODO: We can't properly implement this yet until we properly support title/save IDs. We will stub this and insert a warning for now. Required for Planet Robobot // TODO: We can't properly implement this yet until we properly support title/save IDs. We will stub this and insert a warning for now. Required for Planet Robobot
// When we properly implement it, it will just be a recursive directory deletion /*
FSPath path = readPath(PathType::Binary, messagePointer + 4, 8);
switch (mediaType) {
case MediaType::NAND: sharedExtSaveData_nand.clear(path); break;
case MediaType::SD: extSaveData_sdmc.clear(path); break;
default: Helpers::warn("FS::DeleteExtSaveData: Unhandled ExtSaveData MediaType %d", static_cast<s32>(mediaType)); break;
}
*/
mem.write32(messagePointer, IPC::responseHeader(0x0852, 1, 0));
mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 4, Result::Success);
} }
void FSService::createExtSaveData(u32 messagePointer) { void FSService::createExtSaveData(u32 messagePointer) {
Helpers::warn("Stubbed call to FS::CreateExtSaveData!"); log("FS::CreateExtSaveData\n");
// First 4 words of parameters are the ExtSaveData info // First 4 words of parameters are the ExtSaveData info
// https://www.3dbrew.org/wiki/Filesystem_services#ExtSaveDataInfo // https://www.3dbrew.org/wiki/Filesystem_services#ExtSaveDataInfo
// This creates the ExtSaveData with the specified saveid in the specified media type. It stores the SMDH as "icon" in the root of the created directory. // This creates the ExtSaveData with the specified saveid in the specified media type. It stores the SMDH as "icon" in the root of the created directory.
const u8 mediaType = mem.read8(messagePointer + 4); const u8 mediaType = mem.read8(messagePointer + 4);
const u64 saveID = mem.read64(messagePointer + 8); const u64 saveID = mem.read64(messagePointer + 8); // todo: <-- how should this be used? ATM everything is in the same space.
const u32 numOfDirectories = mem.read32(messagePointer + 20); const u32 numOfDirectories = mem.read32(messagePointer + 20);
const u32 numOfFiles = mem.read32(messagePointer + 24); const u32 numOfFiles = mem.read32(messagePointer + 24);
const u64 sizeLimit = mem.read64(messagePointer + 28); const u64 sizeLimit = mem.read64(messagePointer + 28);
const u32 smdhSize = mem.read32(messagePointer + 36); const u32 smdhSize = mem.read32(messagePointer + 36);
const u32 smdhPointer = mem.read32(messagePointer + 44); const u32 smdhPointer = mem.read32(messagePointer + 44);
log("FS::CreateExtSaveData (stubbed)\n"); ArchiveBase::FormatInfo info {
.size = 0,
.numOfDirectories = numOfDirectories,
.numOfFiles = numOfFiles,
.duplicateData = false
};
FSPath path = readPath(PathType::Binary, messagePointer + 4, 32);
ExtSaveDataArchive* selected = nullptr;
switch(mediaType) {
// is there ever a situation where it formats a shared archive?
case MediaType::NAND: selected = getNANDExtArchiveFromID(saveID, false); break;
case MediaType::SD: selected = getExtArchiveFromID(saveID, false); break;
default: Helpers::warn("FS::CreateExtSaveData - Unhandled ExtSaveData MediaType %d", static_cast<s32>(mediaType)); break;
}
if (selected != nullptr) {
selected->format(path, info);
const FSPath smdh = readPath(PathType::Binary, smdhPointer, smdhSize);
// selected->saveIcon(smdh.binary);
}
mem.write32(messagePointer, IPC::responseHeader(0x0851, 1, 0)); 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); mem.write32(messagePointer + 4, Result::Success);
} }
@ -785,4 +872,4 @@ void FSService::getSdmcArchiveResource(u32 messagePointer) {
mem.write32(messagePointer + 12, resource.clusterSize); mem.write32(messagePointer + 12, resource.clusterSize);
mem.write32(messagePointer + 16, resource.partitionCapacityInClusters); mem.write32(messagePointer + 16, resource.partitionCapacityInClusters);
mem.write32(messagePointer + 20, resource.freeSpaceInClusters); mem.write32(messagePointer + 20, resource.freeSpaceInClusters);
} }