ExtSaveData work

This commit is contained in:
wheremyfoodat 2023-01-22 14:23:28 +02:00
parent 902b877c40
commit 995dfb81df
12 changed files with 68 additions and 28 deletions

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cassert> #include <cassert>
#include <cstdio>
#include <optional> #include <optional>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -72,14 +73,24 @@ struct FSPath {
} }
}; };
class ArchiveBase; struct FilePerms {
u32 raw;
FilePerms(u32 val) : raw(val) {}
bool read() const { return (raw & 1) != 0; }
bool write() const { return (raw & 2) != 0; }
bool create() const { return (raw & 4) != 0; }
};
class ArchiveBase;
struct FileSession { struct FileSession {
ArchiveBase* archive = nullptr; ArchiveBase* archive = nullptr;
FILE* fd = nullptr; // File descriptor for file sessions that require them.
FSPath path; FSPath path;
bool isOpen; bool isOpen;
FileSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(path), isOpen(isOpen) {} FileSession(ArchiveBase* archive, const FSPath& filePath, FILE* fd, bool isOpen = true) :
archive(archive), path(path), fd(fd), isOpen(isOpen) {}
}; };
struct ArchiveSession { struct ArchiveSession {
@ -90,9 +101,16 @@ struct ArchiveSession {
ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(path), isOpen(isOpen) {} ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(path), isOpen(isOpen) {}
}; };
// Represents a file descriptor obtained from OpenFile. If the optional is nullopt, opening the file failed.
// Otherwise the fd of the opened file is returned (or nullptr if the opened file doesn't require one)
using FileDescriptor = std::optional<FILE*>;
class ArchiveBase { class ArchiveBase {
protected: protected:
using Handle = u32; using Handle = u32;
static constexpr FileDescriptor NoFile = nullptr;
static constexpr FileDescriptor FileError = std::nullopt;
Memory& mem; Memory& mem;
// Returns if a specified 3DS path in UTF16 or ASCII format is safe or not // Returns if a specified 3DS path in UTF16 or ASCII format is safe or not
@ -143,7 +161,8 @@ protected:
public: public:
virtual std::string name() = 0; virtual std::string name() = 0;
virtual u64 getFreeBytes() = 0; virtual u64 getFreeBytes() = 0;
virtual bool openFile(const FSPath& path) = 0; // 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 ArchiveBase* openArchive(const FSPath& path) = 0; virtual ArchiveBase* openArchive(const FSPath& path) = 0;

View file

@ -8,8 +8,8 @@ public:
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"; } std::string name() override { return "ExtSaveData"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override; ArchiveBase* openArchive(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; std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
bool isShared = false; bool isShared = false;

View file

@ -8,8 +8,8 @@ public:
u64 getFreeBytes() override { return 0; } u64 getFreeBytes() override { return 0; }
std::string name() override { return "SelfNCCH"; } std::string name() override { return "SelfNCCH"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override; ArchiveBase* openArchive(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; std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
// Returns whether the cart has a RomFS // Returns whether the cart has a RomFS

View file

@ -8,8 +8,8 @@ public:
u64 getFreeBytes() override { Helpers::panic("SaveData::GetFreeBytes unimplemented"); return 0; } u64 getFreeBytes() override { Helpers::panic("SaveData::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "SaveData"; } std::string name() override { return "SaveData"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override; ArchiveBase* openArchive(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; std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
// Returns whether the cart has save data or not // Returns whether the cart has save data or not

View file

@ -8,7 +8,7 @@ public:
u64 getFreeBytes() override { Helpers::panic("SDMC::GetFreeBytes unimplemented"); return 0; } u64 getFreeBytes() override { Helpers::panic("SDMC::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "SDMC"; } std::string name() override { return "SDMC"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override; ArchiveBase* openArchive(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; std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
}; };

View file

@ -24,6 +24,9 @@ class IOFile {
public: public:
IOFile() {} IOFile() {}
IOFile(FILE* handle) : handle(handle) {} IOFile(FILE* handle) : handle(handle) {}
IOFile(const std::filesystem::path& path, const char* permissions = "rb") {
open(path, permissions);
}
bool isOpen() { bool isOpen() {
return handle != nullptr; return handle != nullptr;
@ -86,6 +89,10 @@ public:
return seek(0, SEEK_SET); return seek(0, SEEK_SET);
} }
FILE* getHandle() {
return handle;
}
static void setAppDataDir(const char* dir) { static void setAppDataDir(const char* dir) {
if (!dir) Helpers::panic("Failed to set app data directory"); if (!dir) Helpers::panic("Failed to set app data directory");
appData = std::filesystem::path(dir); appData = std::filesystem::path(dir);

View file

@ -26,7 +26,7 @@ class FSService {
ArchiveBase* getArchiveFromID(u32 id); ArchiveBase* getArchiveFromID(u32 id);
std::optional<Handle> openArchiveHandle(u32 archiveID, const FSPath& path); std::optional<Handle> openArchiveHandle(u32 archiveID, const FSPath& path);
std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path); std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path, const FilePerms& perms);
FSPath readPath(u32 type, u32 pointer, u32 size); FSPath readPath(u32 type, u32 pointer, u32 size);
// Service commands // Service commands

View file

@ -3,18 +3,27 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
bool ExtSaveDataArchive::openFile(const FSPath& path) { FileDescriptor ExtSaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (path.type == PathType::UTF16) { if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::OpenFile"); Helpers::panic("Unsafe path in ExtSaveData::OpenFile");
if (perms.create())
Helpers::panic("[ExtSaveData] CAn't open file with create flag");
fs::path p = IOFile::getAppData() / "NAND"; fs::path p = IOFile::getAppData() / "NAND";
p += fs::path(path.utf16_string).make_preferred(); p += fs::path(path.utf16_string).make_preferred();
return false;
if (fs::exists(p)) { // Return file descriptor if the file exists
IOFile file(p.string().c_str(), "r+b"); // According to Citra, this ignores the OpenFile flags and always opens as r+b? TODO: Check
return file.isOpen() ? file.getHandle() : FileError;
} else {
return FileError;
}
} }
Helpers::panic("ExtSaveDataArchive::OpenFile: Failed"); Helpers::panic("ExtSaveDataArchive::OpenFile: Failed");
return false; return FileError;
} }
ArchiveBase* ExtSaveDataArchive::openArchive(const FSPath& path) { ArchiveBase* ExtSaveDataArchive::openArchive(const FSPath& path) {
@ -22,6 +31,8 @@ ArchiveBase* ExtSaveDataArchive::openArchive(const FSPath& path) {
Helpers::panic("ExtSaveData accessed with an invalid path in OpenArchive"); Helpers::panic("ExtSaveData accessed with an invalid path in OpenArchive");
} }
if (path.binary[0] != 0) Helpers::panic("ExtSaveData: Tried to access something other than NAND");
return this; return this;
} }

View file

@ -1,15 +1,15 @@
#include "fs/archive_ncch.hpp" #include "fs/archive_ncch.hpp"
#include <memory> #include <memory>
bool SelfNCCHArchive::openFile(const FSPath& path) { FileDescriptor SelfNCCHArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (!hasRomFS()) { if (!hasRomFS()) {
printf("Tried to open a SelfNCCH file without a RomFS\n"); printf("Tried to open a SelfNCCH file without a RomFS\n");
return false; return FileError;
} }
if (path.type != PathType::Binary || path.binary.size() != 12) { if (path.type != PathType::Binary || path.binary.size() != 12) {
printf("Invalid SelfNCCH path type\n"); printf("Invalid SelfNCCH path type\n");
return false; return FileError;
} }
// Where to read the file from. (https://www.3dbrew.org/wiki/Filesystem_services#SelfNCCH_File_Path_Data_Format) // Where to read the file from. (https://www.3dbrew.org/wiki/Filesystem_services#SelfNCCH_File_Path_Data_Format)
@ -19,7 +19,7 @@ bool SelfNCCHArchive::openFile(const FSPath& path) {
Helpers::panic("Read from NCCH's non-RomFS section!"); Helpers::panic("Read from NCCH's non-RomFS section!");
} }
return true; return NoFile; // No file descriptor needed for RomFS
} }
ArchiveBase* SelfNCCHArchive::openArchive(const FSPath& path) { ArchiveBase* SelfNCCHArchive::openArchive(const FSPath& path) {

View file

@ -2,23 +2,23 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
bool SaveDataArchive::openFile(const FSPath& path) { FileDescriptor SaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (!cartHasSaveData()) { if (!cartHasSaveData()) {
printf("Tried to read SaveData FS without save data\n"); printf("Tried to read SaveData FS without save data\n");
return false; return FileError;
} }
if (path.type == PathType::UTF16 /* && path.utf16_string == u"/game_header" */) { if (path.type == PathType::UTF16 /* && path.utf16_string == u"/game_header" */) {
printf("Opened file from the SaveData archive \n"); printf("Opened file from the SaveData archive \n");
return true; return NoFile;
} }
if (path.type != PathType::Binary) { if (path.type != PathType::Binary) {
printf("Unimplemented SaveData path type: %d\n", path.type); printf("Unimplemented SaveData path type: %d\n", path.type);
return false; return FileError;
} }
return true; return NoFile;
} }
ArchiveBase* SaveDataArchive::openArchive(const FSPath& path) { ArchiveBase* SaveDataArchive::openArchive(const FSPath& path) {

View file

@ -1,9 +1,9 @@
#include "fs/archive_sdmc.hpp" #include "fs/archive_sdmc.hpp"
#include <memory> #include <memory>
bool SDMCArchive::openFile(const FSPath& path) { FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) {
printf("SDMCArchive::OpenFile: Failed"); printf("SDMCArchive::OpenFile: Failed");
return false; return FileError;
} }
ArchiveBase* SDMCArchive::openArchive(const FSPath& path) { ArchiveBase* SDMCArchive::openArchive(const FSPath& path) {

View file

@ -39,12 +39,13 @@ ArchiveBase* FSService::getArchiveFromID(u32 id) {
} }
} }
std::optional<Handle> FSService::openFileHandle(ArchiveBase* archive, const FSPath& path) { std::optional<Handle> FSService::openFileHandle(ArchiveBase* archive, const FSPath& path, const FilePerms& perms) {
bool opened = archive->openFile(path); FileDescriptor opened = archive->openFile(path, perms);
if (opened) { if (opened.has_value()) { // If opened doesn't have a value, we failed to open the file
auto handle = kernel.makeObject(KernelObjectType::File); auto handle = kernel.makeObject(KernelObjectType::File);
auto& file = kernel.getObjects()[handle]; auto& file = kernel.getObjects()[handle];
file.data = new FileSession(archive, path); file.data = new FileSession(archive, path, opened.value());
return handle; return handle;
} else { } else {
@ -164,8 +165,9 @@ void FSService::openFile(u32 messagePointer) {
ArchiveBase* archive = archiveObject->getData<ArchiveSession>()->archive; ArchiveBase* archive = archiveObject->getData<ArchiveSession>()->archive;
auto filePath = readPath(filePathType, filePathPointer, filePathSize); auto filePath = readPath(filePathType, filePathPointer, filePathSize);
const FilePerms perms(openFlags);
std::optional<Handle> handle = openFileHandle(archive, filePath); std::optional<Handle> handle = openFileHandle(archive, filePath, perms);
if (!handle.has_value()) { if (!handle.has_value()) {
printf("OpenFile failed\n"); printf("OpenFile failed\n");
mem.write32(messagePointer + 4, Result::FileNotFound); mem.write32(messagePointer + 4, Result::FileNotFound);
@ -196,13 +198,14 @@ void FSService::openFileDirectly(u32 messagePointer) {
auto archivePath = readPath(archivePathType, archivePathPointer, archivePathSize); auto archivePath = readPath(archivePathType, archivePathPointer, archivePathSize);
auto filePath = readPath(filePathType, filePathPointer, filePathSize); auto filePath = readPath(filePathType, filePathPointer, filePathSize);
const FilePerms perms(openFlags);
archive = archive->openArchive(archivePath); archive = archive->openArchive(archivePath);
if (archive == nullptr) [[unlikely]] { if (archive == nullptr) [[unlikely]] {
Helpers::panic("OpenFileDirectly: Failed to open archive with given path"); Helpers::panic("OpenFileDirectly: Failed to open archive with given path");
} }
std::optional<Handle> handle = openFileHandle(archive, filePath); std::optional<Handle> handle = openFileHandle(archive, filePath, perms);
if (!handle.has_value()) { if (!handle.has_value()) {
Helpers::panic("OpenFileDirectly: Failed to open file with given path"); Helpers::panic("OpenFileDirectly: Failed to open file with given path");
} else { } else {