Merge pull request #7 from wheremyfoodat/DSP-n-FS-work

Better DSP & FS HLE
This commit is contained in:
wheremyfoodat 2023-06-03 22:59:45 +03:00 committed by GitHub
commit 9ca66f4157
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 234 additions and 58 deletions

View file

@ -204,10 +204,11 @@ public:
virtual u64 getFreeBytes() = 0;
virtual FSResult createFile(const FSPath& path, u64 size) = 0;
virtual FSResult deleteFile(const FSPath& path) = 0;
virtual FormatInfo getFormatInfo(const FSPath& path) {
virtual Rust::Result<FormatInfo, FSResult> getFormatInfo(const FSPath& path) {
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 FormatInfo{ .size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false };
return Ok(FormatInfo{ .size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false });
}
virtual FSResult createDirectory(const FSPath& path) {
@ -224,6 +225,10 @@ public:
return Err(FSResult::FileNotFound);
}
virtual void format(const FSPath& path, const FormatInfo& info) {
Helpers::panic("Unimplemented Format for %s archive", name().c_str());
}
// Read size bytes from a file starting at offset "offset" into a certain buffer in memory
// Returns the number of bytes read, or nullopt if the read failed
virtual std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0;

View file

@ -17,6 +17,10 @@ public:
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
// 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);
bool isShared = false;
std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path.
};

View file

@ -11,13 +11,19 @@ public:
FSResult createDirectory(const FSPath& path) override;
FSResult createFile(const FSPath& path, u64 size) override;
FSResult deleteFile(const FSPath& path) override;
FormatInfo getFormatInfo(const FSPath& path) override;
Rust::Result<ArchiveBase*, FSResult> openArchive(const FSPath& path) override;
Rust::Result<DirectorySession, FSResult> openDirectory(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) 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, FSResult> getFormatInfo(const FSPath& path) override;
std::filesystem::path getFormatInfoPath() {
return IOFile::getAppData() / "FormatInfo" / "SaveData.format";
}
// Returns whether the cart has save data or not
bool cartHasSaveData() {
auto cxi = mem.getCXI();

View file

@ -146,6 +146,7 @@ private:
// File operations
void handleFileOperation(u32 messagePointer, Handle file);
void closeFile(u32 messagePointer, Handle file);
void flushFile(u32 messagePointer, Handle file);
void readFile(u32 messagePointer, Handle file);
void writeFile(u32 messagePointer, Handle file);
void getFileSize(u32 messagePointer, Handle file);

View file

@ -25,13 +25,11 @@ class FSService {
SDMCArchive sdmc;
NCCHArchive ncch;
ExtSaveDataArchive extSaveData_nand;
ExtSaveDataArchive extSaveData_cart;
ExtSaveDataArchive extSaveData_sdmc;
ExtSaveDataArchive sharedExtSaveData_nand;
ExtSaveDataArchive sharedExtSaveData_cart;
ArchiveBase* getArchiveFromID(u32 id, const FSPath& archivePath);
std::optional<Handle> openArchiveHandle(u32 archiveID, const FSPath& path);
Rust::Result<Handle, FSResult> openArchiveHandle(u32 archiveID, const FSPath& path);
Rust::Result<Handle, FSResult> openDirectoryHandle(ArchiveBase* archive, const FSPath& path);
std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path, const FSPath& archivePath, const FilePerms& perms);
FSPath readPath(u32 type, u32 pointer, u32 size);
@ -43,6 +41,7 @@ class FSService {
void controlArchive(u32 messagePointer);
void deleteFile(u32 messagePointer);
void formatSaveData(u32 messagePointer);
void formatThisUserSaveData(u32 messagePointer);
void getFreeBytes(u32 messagePointer);
void getFormatInfo(u32 messagePointer);
void getPriority(u32 messagePointer);
@ -59,8 +58,8 @@ class FSService {
u32 priority;
public:
FSService(Memory& mem, Kernel& kernel) : mem(mem), saveData(mem), extSaveData_nand(mem, "NAND"),
sharedExtSaveData_nand(mem, "NAND", true), extSaveData_cart(mem, "CartSave"), sharedExtSaveData_cart(mem, "CartSave", true),
FSService(Memory& mem, Kernel& kernel) : mem(mem), saveData(mem),
sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"),
sdmc(mem), selfNcch(mem), ncch(mem), kernel(kernel)
{}

View file

@ -53,4 +53,4 @@ Simply drag and drop a ROM to the executable if supported, or invoke the executa
Nintendo 3DS is a registered trademark of Nintendo Co., Ltd.
![pamda](docs/img/panda.jpg)
Here's a panda it go blep
Here's a panda it go blep

View file

@ -38,12 +38,27 @@ FSResult ExtSaveDataArchive::deleteFile(const FSPath& path) {
fs::path p = IOFile::getAppData() / backingFolder;
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_directory(p)) {
Helpers::panic("ExtSaveData::DeleteFile: Tried to delete directory");
}
if (!fs::is_regular_file(p)) {
return FSResult::FileNotFound;
}
std::error_code ec;
bool success = fs::remove(p, ec);
return success ? FSResult::Success : FSResult::FileNotFound;
// It might still be possible for fs::remove to fail, if there's eg an open handle to a file being deleted
// In this case, print a warning, but still return success for now
if (!success) {
Helpers::warn("ExtSaveData::DeleteFile: fs::remove failed\n");
}
return FSResult::Success;
}
Helpers::panic("ExtSaveDataArchive::DeleteFile: Failed");
Helpers::panic("ExtSaveDataArchive::DeleteFile: Unknown path type");
return FSResult::Success;
}
@ -70,11 +85,29 @@ FileDescriptor ExtSaveDataArchive::openFile(const FSPath& path, const FilePerms&
return FileError;
}
std::string ExtSaveDataArchive::getExtSaveDataPathFromBinary(const FSPath& path) {
// 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);
}
Rust::Result<ArchiveBase*, FSResult> ExtSaveDataArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Binary || path.binary.size() != 12) {
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");
// Format info not found so the archive is not formatted
// if (!fs::is_regular_file(formatInfopath)) {
// return isShared ? Err(FSResult::NotFormatted) : Err(FSResult::NotFoundInvalid);
//}
return Ok((ArchiveBase*)this);
}

View file

@ -31,7 +31,34 @@ FSResult SaveDataArchive::createDirectory(const FSPath& path) {
}
FSResult SaveDataArchive::deleteFile(const FSPath& path) {
Helpers::panic("[SaveData] Unimplemented DeleteFile");
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in SaveData::DeleteFile");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_directory(p)) {
Helpers::panic("SaveData::DeleteFile: Tried to delete directory");
}
if (!fs::is_regular_file(p)) {
return FSResult::FileNotFound;
}
std::error_code ec;
bool success = fs::remove(p, ec);
// It might still be possible for fs::remove to fail, if there's eg an open handle to a file being deleted
// In this case, print a warning, but still return success for now
if (!success) {
Helpers::warn("SaveData::DeleteFile: fs::remove failed\n");
}
return FSResult::Success;
}
Helpers::panic("SaveDataArchive::DeleteFile: Unknown path type");
return FSResult::Success;
}
@ -93,9 +120,36 @@ Rust::Result<DirectorySession, FSResult> SaveDataArchive::openDirectory(const FS
return Err(FSResult::Success);
}
ArchiveBase::FormatInfo SaveDataArchive::getFormatInfo(const FSPath& path) {
//Helpers::panic("Unimplemented SaveData::GetFormatInfo");
return FormatInfo{ .size = 0, .numOfDirectories = 255, .numOfFiles = 255, .duplicateData = false };
Rust::Result<ArchiveBase::FormatInfo, FSResult> SaveDataArchive::getFormatInfo(const FSPath& path) {
const fs::path formatInfoPath = getFormatInfoPath();
IOFile file(formatInfoPath, "rb");
// If the file failed to open somehow, we return that the archive is not formatted
if (!file.isOpen()) {
return Err(FSResult::NotFormatted);
}
FormatInfo ret;
auto [success, bytesRead] = file.readBytes(&ret, sizeof(FormatInfo));
if (!success || bytesRead != sizeof(FormatInfo)) {
Helpers::warn("SaveData::GetFormatInfo: Format file exists but was not properly read into the FormatInfo struct");
return Err(FSResult::NotFormatted);
}
return Ok(ret);
}
void SaveDataArchive::format(const FSPath& path, const ArchiveBase::FormatInfo& info) {
const fs::path saveDataPath = IOFile::getAppData() / "SaveData";
const fs::path formatInfoPath = getFormatInfoPath();
// Delete all contents by deleting the directory then recreating it
fs::remove_all(saveDataPath);
fs::create_directories(saveDataPath);
// Write format info on disk
IOFile file(formatInfoPath, "wb");
file.writeBytes(&info, sizeof(info));
}
Rust::Result<ArchiveBase*, FSResult> SaveDataArchive::openArchive(const FSPath& path) {
@ -104,6 +158,12 @@ Rust::Result<ArchiveBase*, FSResult> SaveDataArchive::openArchive(const FSPath&
return Err(FSResult::NotFoundInvalid);
}
const fs::path formatInfoPath = getFormatInfoPath();
// Format info not found so the archive is not formatted
if (!fs::is_regular_file(formatInfoPath)) {
return Err(FSResult::NotFormatted);
}
return Ok((ArchiveBase*)this);
}

View file

@ -18,7 +18,7 @@ FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms)
Rust::Result<ArchiveBase*, FSResult> SDMCArchive::openArchive(const FSPath& path) {
printf("SDMCArchive::OpenArchive: Failed\n");
return Ok((ArchiveBase*)nullptr);
return Err(FSResult::NotFormatted);
}
std::optional<u32> SDMCArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {

View file

@ -1,3 +1,4 @@
#include "ipc.hpp"
#include "kernel.hpp"
namespace FileOps {
@ -7,6 +8,7 @@ namespace FileOps {
GetSize = 0x08040000,
SetSize = 0x08050080,
Close = 0x08080000,
Flush = 0x08090000,
SetPriority = 0x080A0040,
OpenLinkFile = 0x080C0000
};
@ -41,7 +43,30 @@ void Kernel::closeFile(u32 messagePointer, Handle fileHandle) {
Helpers::panic("Called CloseFile on non-existent file");
}
p->getData<FileSession>()->isOpen = false;
FileSession* session = p->getData<FileSession>();
session->isOpen = false;
if (session->fd != nullptr) {
fclose(session->fd);
}
mem.write32(messagePointer, IPC::responseHeader(0x0808, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void Kernel::flushFile(u32 messagePointer, Handle fileHandle) {
logFileIO("Flushed file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called FlushFile on non-existent file");
}
FileSession* session = p->getData<FileSession>();
if (session->fd != nullptr) {
fflush(session->fd);
}
mem.write32(messagePointer, IPC::responseHeader(0x0809, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -58,6 +83,8 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
Helpers::panic("Called ReadFile on non-existent file");
}
mem.write32(messagePointer, IPC::responseHeader(0x0802, 2, 2));
FileSession* file = p->getData<FileSession>();
if (!file->isOpen) {
Helpers::panic("Tried to read closed file");
@ -126,6 +153,7 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) {
IOFile f(file->fd);
auto [success, bytesWritten] = f.writeBytes(data.get(), size);
mem.write32(messagePointer, IPC::responseHeader(0x0803, 2, 2));
if (!success) {
Helpers::panic("Kernel::WriteFile failed");
} else {
@ -146,6 +174,7 @@ void Kernel::setFileSize(u32 messagePointer, Handle fileHandle) {
if (!file->isOpen) {
Helpers::panic("Tried to get size of closed file");
}
mem.write32(messagePointer, IPC::responseHeader(0x0805, 1, 0));
if (file->fd) {
const u64 newSize = mem.read64(messagePointer + 4);
@ -174,6 +203,7 @@ void Kernel::getFileSize(u32 messagePointer, Handle fileHandle) {
if (!file->isOpen) {
Helpers::panic("Tried to get size of closed file");
}
mem.write32(messagePointer, IPC::responseHeader(0x0804, 3, 0));
if (file->fd) {
IOFile f(file->fd);
@ -212,6 +242,7 @@ void Kernel::openLinkFile(u32 messagePointer, Handle fileHandle) {
// However we do seek properly on every file access so this shouldn't matter
cloneFile.data = new FileSession(*file);
mem.write32(messagePointer, IPC::responseHeader(0x080C, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 12, handle);
}
@ -231,5 +262,6 @@ void Kernel::setFilePriority(u32 messagePointer, Handle fileHandle) {
}
file->priority = priority;
mem.write32(messagePointer, IPC::responseHeader(0x080A, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -21,6 +21,7 @@ namespace FSCommands {
OpenArchive = 0x080C00C2,
ControlArchive = 0x080D0144,
CloseArchive = 0x080E0080,
FormatThisUserSaveData = 0x080F0180,
GetFreeBytes = 0x08120080,
IsSdmcDetected = 0x08170000,
GetFormatInfo = 0x084500C2,
@ -45,23 +46,29 @@ void FSService::reset() {
// Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM.
void FSService::initializeFilesystem() {
const auto nandPath = IOFile::getAppData() / "NAND"; // Create NAND
const auto cartPath = IOFile::getAppData() / "CartSave"; // Create cartridge save folder for use with ExtSaveData
const auto savePath = IOFile::getAppData() / "SaveData"; // Create SaveData
namespace fs = std::filesystem;
// TODO: SDMC, etc
const auto sdmcPath = IOFile::getAppData() / "SDMC"; // Create SDMC directory
const auto nandSharedpath = IOFile::getAppData() / ".." / "SharedFiles" / "NAND";
if (!fs::is_directory(nandPath)) {
fs::create_directories(nandPath);
const auto savePath = IOFile::getAppData() / "SaveData"; // Create SaveData
const auto formatPath = IOFile::getAppData() / "FormatInfo"; // Create folder for storing archive formatting info
namespace fs = std::filesystem;
if (!fs::is_directory(nandSharedpath)) {
fs::create_directories(nandSharedpath);
}
if (!fs::is_directory(cartPath)) {
fs::create_directories(cartPath);
if (!fs::is_directory(sdmcPath)) {
fs::create_directories(sdmcPath);
}
if (!fs::is_directory(savePath)) {
fs::create_directories(savePath);
}
if (!fs::is_directory(formatPath)) {
fs::create_directories(formatPath);
}
}
ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
@ -69,22 +76,10 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
case ArchiveID::SelfNCCH: return &selfNcch;
case ArchiveID::SaveData: return &saveData;
case ArchiveID::ExtSaveData:
if (archivePath.type == PathType::Binary) {
switch (archivePath.binary[0]) {
case 0: return &extSaveData_nand;
case 1: return &extSaveData_cart;
}
}
return nullptr;
return &extSaveData_sdmc;
case ArchiveID::SharedExtSaveData:
if (archivePath.type == PathType::Binary) {
switch (archivePath.binary[0]) {
case 0: return &sharedExtSaveData_nand;
case 1: return &sharedExtSaveData_cart;
}
}
return nullptr;
return &sharedExtSaveData_nand;
case ArchiveID::SDMC: return &sdmc;
case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI
@ -121,12 +116,12 @@ Rust::Result<Handle, FSResult> FSService::openDirectoryHandle(ArchiveBase* archi
}
}
std::optional<Handle> FSService::openArchiveHandle(u32 archiveID, const FSPath& path) {
Rust::Result<Handle, FSResult> FSService::openArchiveHandle(u32 archiveID, const FSPath& path) {
ArchiveBase* archive = getArchiveFromID(archiveID, path);
if (archive == nullptr) [[unlikely]] {
Helpers::panic("OpenArchive: Tried to open unknown archive %d.", archiveID);
return std::nullopt;
return Err(FSResult::NotFormatted);
}
Rust::Result<ArchiveBase*, FSResult> res = archive->openArchive(path);
@ -135,10 +130,10 @@ std::optional<Handle> FSService::openArchiveHandle(u32 archiveID, const FSPath&
auto& archiveObject = kernel.getObjects()[handle];
archiveObject.data = new ArchiveSession(res.unwrap(), path);
return handle;
return Ok(handle);
}
else {
return std::nullopt;
return Err(res.unwrapErr());
}
}
@ -161,6 +156,7 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::CloseArchive: closeArchive(messagePointer); break;
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break;
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
case FSCommands::GetPriority: getPriority(messagePointer); break;
@ -216,14 +212,15 @@ void FSService::openArchive(u32 messagePointer) {
auto archivePath = readPath(archivePathType, archivePathPointer, archivePathSize);
log("FS::OpenArchive(archive ID = %d, archive path type = %d)\n", archiveID, archivePathType);
std::optional<Handle> handle = openArchiveHandle(archiveID, archivePath);
Rust::Result<Handle, FSResult> res = openArchiveHandle(archiveID, archivePath);
mem.write32(messagePointer, IPC::responseHeader(0x80C, 3, 0));
if (handle.has_value()) {
if (res.isOk()) {
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write64(messagePointer + 8, handle.value());
mem.write64(messagePointer + 8, res.unwrap());
} else {
log("FS::OpenArchive: Failed to open archive with id = %d\n", archiveID);
mem.write32(messagePointer + 4, ResultCode::Failure);
log("FS::OpenArchive: Failed to open archive with id = %d. Error %08X\n", archiveID, (u32)res.unwrapErr());
mem.write32(messagePointer + 4, static_cast<u32>(res.unwrapErr()));
mem.write64(messagePointer + 8, 0);
}
}
@ -411,16 +408,25 @@ void FSService::getFormatInfo(u32 messagePointer) {
Helpers::panic("OpenArchive: Tried to open unknown archive %d.", archiveID);
}
ArchiveBase::FormatInfo info = archive->getFormatInfo(path);
mem.write32(messagePointer, IPC::responseHeader(0x845, 5, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 8, info.size);
mem.write32(messagePointer + 12, info.numOfDirectories);
mem.write32(messagePointer + 16, info.numOfFiles);
mem.write8(messagePointer + 20, info.duplicateData ? 1 : 0);
Rust::Result<ArchiveBase::FormatInfo, FSResult> res = archive->getFormatInfo(path);
// If the FormatInfo was returned, write them to the output buffer. Otherwise, write an error code.
if (res.isOk()) {
ArchiveBase::FormatInfo info = res.unwrap();
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 8, info.size);
mem.write32(messagePointer + 12, info.numOfDirectories);
mem.write32(messagePointer + 16, info.numOfFiles);
mem.write8(messagePointer + 20, info.duplicateData ? 1 : 0);
} else {
mem.write32(messagePointer + 4, static_cast<u32>(res.unwrapErr()));
}
}
void FSService::formatSaveData(u32 messagePointer) {
log("FS::FormatSaveData\n");
const u32 archiveID = mem.read32(messagePointer + 4);
if (archiveID != ArchiveID::SaveData)
Helpers::panic("FS::FormatSaveData: Archive is not SaveData");
@ -440,13 +446,43 @@ void FSService::formatSaveData(u32 messagePointer) {
const u32 fileNum = mem.read32(messagePointer + 24); // Max number of files
const u32 directoryBucketNum = mem.read32(messagePointer + 28); // Not sure what a directory bucket is...?
const u32 fileBucketNum = mem.read32(messagePointer + 32); // Same here
const bool duplicateData = mem.read8(messagePointer + 36) != 0;
const bool duplicateData = mem.read8(messagePointer + 36) != 0;
ArchiveBase::FormatInfo info {
.size = blockSize * 0x200,
.numOfDirectories = directoryNum,
.numOfFiles = fileNum,
.duplicateData = duplicateData
};
saveData.format(path, info);
printf("Stubbed FS::FormatSaveData. File num: %d, directory num: %d\n", fileNum, directoryNum);
mem.write32(messagePointer, IPC::responseHeader(0x84C, 1, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
}
void FSService::formatThisUserSaveData(u32 messagePointer) {
log("FS::FormatThisUserSaveData\n");
const u32 blockSize = mem.read32(messagePointer + 4);
const u32 directoryNum = mem.read32(messagePointer + 8); // Max number of directories
const u32 fileNum = mem.read32(messagePointer + 12); // Max number of files
const u32 directoryBucketNum = mem.read32(messagePointer + 16); // Not sure what a directory bucket is...?
const u32 fileBucketNum = mem.read32(messagePointer + 20); // Same here
const bool duplicateData = mem.read8(messagePointer + 24) != 0;
ArchiveBase::FormatInfo info {
.size = blockSize * 0x200,
.numOfDirectories = directoryNum,
.numOfFiles = fileNum,
.duplicateData = duplicateData
};
FSPath emptyPath;
mem.write32(messagePointer, IPC::responseHeader(0x080F, 1, 0));
saveData.format(emptyPath, info);
}
void FSService::controlArchive(u32 messagePointer) {
const Handle archiveHandle = mem.read64(messagePointer + 4);
const u32 action = mem.read32(messagePointer + 12);