mirror of
https://github.com/Lime3DS/Lime3DS
synced 2024-12-27 17:42:40 -06:00
Merge pull request #2132 from wwylele/fix-fs-err
Correct FS error codes & add path boundary checks
This commit is contained in:
commit
4ba5acdaff
30 changed files with 1234 additions and 304 deletions
|
@ -17,13 +17,16 @@ set(SRCS
|
||||||
core_timing.cpp
|
core_timing.cpp
|
||||||
file_sys/archive_backend.cpp
|
file_sys/archive_backend.cpp
|
||||||
file_sys/archive_extsavedata.cpp
|
file_sys/archive_extsavedata.cpp
|
||||||
|
file_sys/archive_ncch.cpp
|
||||||
file_sys/archive_romfs.cpp
|
file_sys/archive_romfs.cpp
|
||||||
file_sys/archive_savedata.cpp
|
file_sys/archive_savedata.cpp
|
||||||
file_sys/archive_savedatacheck.cpp
|
|
||||||
file_sys/archive_sdmc.cpp
|
file_sys/archive_sdmc.cpp
|
||||||
|
file_sys/archive_sdmcwriteonly.cpp
|
||||||
file_sys/archive_systemsavedata.cpp
|
file_sys/archive_systemsavedata.cpp
|
||||||
file_sys/disk_archive.cpp
|
file_sys/disk_archive.cpp
|
||||||
file_sys/ivfc_archive.cpp
|
file_sys/ivfc_archive.cpp
|
||||||
|
file_sys/path_parser.cpp
|
||||||
|
file_sys/savedata_archive.cpp
|
||||||
gdbstub/gdbstub.cpp
|
gdbstub/gdbstub.cpp
|
||||||
hle/config_mem.cpp
|
hle/config_mem.cpp
|
||||||
hle/hle.cpp
|
hle/hle.cpp
|
||||||
|
@ -159,15 +162,18 @@ set(HEADERS
|
||||||
core_timing.h
|
core_timing.h
|
||||||
file_sys/archive_backend.h
|
file_sys/archive_backend.h
|
||||||
file_sys/archive_extsavedata.h
|
file_sys/archive_extsavedata.h
|
||||||
|
file_sys/archive_ncch.h
|
||||||
file_sys/archive_romfs.h
|
file_sys/archive_romfs.h
|
||||||
file_sys/archive_savedata.h
|
file_sys/archive_savedata.h
|
||||||
file_sys/archive_savedatacheck.h
|
|
||||||
file_sys/archive_sdmc.h
|
file_sys/archive_sdmc.h
|
||||||
|
file_sys/archive_sdmcwriteonly.h
|
||||||
file_sys/archive_systemsavedata.h
|
file_sys/archive_systemsavedata.h
|
||||||
file_sys/directory_backend.h
|
file_sys/directory_backend.h
|
||||||
file_sys/disk_archive.h
|
file_sys/disk_archive.h
|
||||||
file_sys/file_backend.h
|
file_sys/file_backend.h
|
||||||
file_sys/ivfc_archive.h
|
file_sys/ivfc_archive.h
|
||||||
|
file_sys/path_parser.h
|
||||||
|
file_sys/savedata_archive.h
|
||||||
gdbstub/gdbstub.h
|
gdbstub/gdbstub.h
|
||||||
hle/config_mem.h
|
hle/config_mem.h
|
||||||
hle/function_wrappers.h
|
hle/function_wrappers.h
|
||||||
|
|
|
@ -87,7 +87,7 @@ public:
|
||||||
* @return Opened file, or error code
|
* @return Opened file, or error code
|
||||||
*/
|
*/
|
||||||
virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
||||||
const Mode mode) const = 0;
|
const Mode& mode) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a file specified by its path
|
* Delete a file specified by its path
|
||||||
|
@ -100,53 +100,53 @@ public:
|
||||||
* Rename a File specified by its path
|
* Rename a File specified by its path
|
||||||
* @param src_path Source path relative to the archive
|
* @param src_path Source path relative to the archive
|
||||||
* @param dest_path Destination path relative to the archive
|
* @param dest_path Destination path relative to the archive
|
||||||
* @return Whether rename succeeded
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual bool RenameFile(const Path& src_path, const Path& dest_path) const = 0;
|
virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a directory specified by its path
|
* Delete a directory specified by its path
|
||||||
* @param path Path relative to the archive
|
* @param path Path relative to the archive
|
||||||
* @return Whether the directory could be deleted
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual bool DeleteDirectory(const Path& path) const = 0;
|
virtual ResultCode DeleteDirectory(const Path& path) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a directory specified by its path and anything under it
|
* Delete a directory specified by its path and anything under it
|
||||||
* @param path Path relative to the archive
|
* @param path Path relative to the archive
|
||||||
* @return Whether the directory could be deleted
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual bool DeleteDirectoryRecursively(const Path& path) const = 0;
|
virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a file specified by its path
|
* Create a file specified by its path
|
||||||
* @param path Path relative to the Archive
|
* @param path Path relative to the Archive
|
||||||
* @param size The size of the new file, filled with zeroes
|
* @param size The size of the new file, filled with zeroes
|
||||||
* @return File creation result code
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual ResultCode CreateFile(const Path& path, u64 size) const = 0;
|
virtual ResultCode CreateFile(const Path& path, u64 size) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a directory specified by its path
|
* Create a directory specified by its path
|
||||||
* @param path Path relative to the archive
|
* @param path Path relative to the archive
|
||||||
* @return Whether the directory could be created
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual bool CreateDirectory(const Path& path) const = 0;
|
virtual ResultCode CreateDirectory(const Path& path) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename a Directory specified by its path
|
* Rename a Directory specified by its path
|
||||||
* @param src_path Source path relative to the archive
|
* @param src_path Source path relative to the archive
|
||||||
* @param dest_path Destination path relative to the archive
|
* @param dest_path Destination path relative to the archive
|
||||||
* @return Whether rename succeeded
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual bool RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
|
virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a directory specified by its path
|
* Open a directory specified by its path
|
||||||
* @param path Path relative to the archive
|
* @param path Path relative to the archive
|
||||||
* @return Opened directory, or nullptr
|
* @return Opened directory, or error code
|
||||||
*/
|
*/
|
||||||
virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0;
|
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the free space
|
* Get the free space
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/file_sys/archive_extsavedata.h"
|
#include "core/file_sys/archive_extsavedata.h"
|
||||||
#include "core/file_sys/disk_archive.h"
|
#include "core/file_sys/disk_archive.h"
|
||||||
|
#include "core/file_sys/errors.h"
|
||||||
|
#include "core/file_sys/path_parser.h"
|
||||||
|
#include "core/file_sys/savedata_archive.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -18,6 +21,116 @@
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A modified version of DiskFile for fixed-size file used by ExtSaveData
|
||||||
|
* The file size can't be changed by SetSize or Write.
|
||||||
|
*/
|
||||||
|
class FixSizeDiskFile : public DiskFile {
|
||||||
|
public:
|
||||||
|
FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode) : DiskFile(std::move(file), mode) {
|
||||||
|
size = GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetSize(u64 size) const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<size_t> Write(u64 offset, size_t length, bool flush,
|
||||||
|
const u8* buffer) const override {
|
||||||
|
if (offset > size) {
|
||||||
|
return ResultCode(ErrorDescription::FS_WriteBeyondEnd, ErrorModule::FS,
|
||||||
|
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
|
||||||
|
} else if (offset == size) {
|
||||||
|
return MakeResult<size_t>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + length > size) {
|
||||||
|
length = size - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DiskFile::Write(offset, length, flush, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
u64 size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Archive backend for general extsave data archive type.
|
||||||
|
* The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for
|
||||||
|
* - file size can't be changed once created (thus creating zero-size file and openning with create
|
||||||
|
* flag are prohibited);
|
||||||
|
* - always open a file with read+write permission.
|
||||||
|
*/
|
||||||
|
class ExtSaveDataArchive : public SaveDataArchive {
|
||||||
|
public:
|
||||||
|
ExtSaveDataArchive(const std::string& mount_point) : SaveDataArchive(mount_point) {}
|
||||||
|
|
||||||
|
std::string GetName() const override {
|
||||||
|
return "ExtSaveDataArchive: " + mount_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
||||||
|
const Mode& mode) const override {
|
||||||
|
LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
|
||||||
|
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.hex == 0) {
|
||||||
|
LOG_ERROR(Service_FS, "Empty open mode");
|
||||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.create_flag) {
|
||||||
|
LOG_ERROR(Service_FS, "Create flag is not supported");
|
||||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
|
||||||
|
case PathParser::NotFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil::IOFile file(full_path, "r+b");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode rwmode;
|
||||||
|
rwmode.write_flag.Assign(1);
|
||||||
|
rwmode.read_flag.Assign(1);
|
||||||
|
auto disk_file = std::make_unique<FixSizeDiskFile>(std::move(file), rwmode);
|
||||||
|
return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode CreateFile(const Path& path, u64 size) const override {
|
||||||
|
if (size == 0) {
|
||||||
|
LOG_ERROR(Service_FS, "Zero-size file is not supported");
|
||||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
return SaveDataArchive::CreateFile(path, size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) {
|
std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) {
|
||||||
std::vector<u8> vec_data = path.AsBinary();
|
std::vector<u8> vec_data = path.AsBinary();
|
||||||
const u32* data = reinterpret_cast<const u32*>(vec_data.data());
|
const u32* data = reinterpret_cast<const u32*>(vec_data.data());
|
||||||
|
@ -84,7 +197,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons
|
||||||
ErrorSummary::InvalidState, ErrorLevel::Status);
|
ErrorSummary::InvalidState, ErrorLevel::Status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto archive = std::make_unique<DiskArchive>(fullpath);
|
auto archive = std::make_unique<ExtSaveDataArchive>(fullpath);
|
||||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/file_sys/archive_savedatacheck.h"
|
#include "core/file_sys/archive_ncch.h"
|
||||||
#include "core/file_sys/ivfc_archive.h"
|
#include "core/file_sys/ivfc_archive.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
|
||||||
|
@ -18,22 +18,22 @@
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
static std::string GetSaveDataCheckContainerPath(const std::string& nand_directory) {
|
static std::string GetNCCHContainerPath(const std::string& nand_directory) {
|
||||||
return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str());
|
return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string GetSaveDataCheckPath(const std::string& mount_point, u32 high, u32 low) {
|
static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) {
|
||||||
return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(),
|
return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(),
|
||||||
high, low);
|
high, low);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory)
|
ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory)
|
||||||
: mount_point(GetSaveDataCheckContainerPath(nand_directory)) {}
|
: mount_point(GetNCCHContainerPath(nand_directory)) {}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(const Path& path) {
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) {
|
||||||
auto vec = path.AsBinary();
|
auto vec = path.AsBinary();
|
||||||
const u32* data = reinterpret_cast<u32*>(vec.data());
|
const u32* data = reinterpret_cast<u32*>(vec.data());
|
||||||
std::string file_path = GetSaveDataCheckPath(mount_point, data[1], data[0]);
|
std::string file_path = GetNCCHPath(mount_point, data[1], data[0]);
|
||||||
auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb");
|
auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb");
|
||||||
|
|
||||||
if (!file->IsOpen()) {
|
if (!file->IsOpen()) {
|
||||||
|
@ -45,15 +45,15 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(co
|
||||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path,
|
ResultCode ArchiveFactory_NCCH::Format(const Path& path,
|
||||||
const FileSys::ArchiveFormatInfo& format_info) {
|
const FileSys::ArchiveFormatInfo& format_info) {
|
||||||
LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive.");
|
LOG_ERROR(Service_FS, "Attempted to format a NCCH archive.");
|
||||||
// TODO: Verify error code
|
// TODO: Verify error code
|
||||||
return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
||||||
ErrorLevel::Permanent);
|
ErrorLevel::Permanent);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const {
|
ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const {
|
||||||
// TODO(Subv): Implement
|
// TODO(Subv): Implement
|
||||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
|
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
|
||||||
return ResultCode(-1);
|
return ResultCode(-1);
|
|
@ -14,13 +14,13 @@
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
/// File system interface to the SaveDataCheck archive
|
/// File system interface to the NCCH archive
|
||||||
class ArchiveFactory_SaveDataCheck final : public ArchiveFactory {
|
class ArchiveFactory_NCCH final : public ArchiveFactory {
|
||||||
public:
|
public:
|
||||||
ArchiveFactory_SaveDataCheck(const std::string& mount_point);
|
ArchiveFactory_NCCH(const std::string& mount_point);
|
||||||
|
|
||||||
std::string GetName() const override {
|
std::string GetName() const override {
|
||||||
return "SaveDataCheck";
|
return "NCCH";
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
|
|
@ -9,7 +9,7 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/file_sys/archive_savedata.h"
|
#include "core/file_sys/archive_savedata.h"
|
||||||
#include "core/file_sys/disk_archive.h"
|
#include "core/file_sys/savedata_archive.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P
|
||||||
ErrorSummary::InvalidState, ErrorLevel::Status);
|
ErrorSummary::InvalidState, ErrorLevel::Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point));
|
auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
|
||||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/file_sys/archive_sdmc.h"
|
#include "core/file_sys/archive_sdmc.h"
|
||||||
#include "core/file_sys/disk_archive.h"
|
#include "core/file_sys/disk_archive.h"
|
||||||
|
#include "core/file_sys/errors.h"
|
||||||
|
#include "core/file_sys/path_parser.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -15,6 +17,281 @@
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path,
|
||||||
|
const Mode& mode) const {
|
||||||
|
Mode modified_mode;
|
||||||
|
modified_mode.hex = mode.hex;
|
||||||
|
|
||||||
|
// SDMC archive always opens a file with at least read permission
|
||||||
|
modified_mode.read_flag.Assign(1);
|
||||||
|
|
||||||
|
return OpenFileBase(path, modified_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& path,
|
||||||
|
const Mode& mode) const {
|
||||||
|
LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
|
||||||
|
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.hex == 0) {
|
||||||
|
LOG_ERROR(Service_FS, "Empty open mode");
|
||||||
|
return ERROR_INVALID_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.create_flag && !mode.write_flag) {
|
||||||
|
LOG_ERROR(Service_FS, "Create flag set but write flag not set");
|
||||||
|
return ERROR_INVALID_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
|
||||||
|
case PathParser::NotFound:
|
||||||
|
if (!mode.create_flag) {
|
||||||
|
LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
|
||||||
|
full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
} else {
|
||||||
|
// Create the file
|
||||||
|
FileUtil::CreateEmptyFile(full_path);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
|
||||||
|
return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SDMCArchive::DeleteFile(const Path& path) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
case PathParser::NotFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FileUtil::Delete(full_path)) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||||
|
if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||||
|
// exist or similar. Verify.
|
||||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
|
||||||
|
T deleter) {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path_parser.IsRootDirectory())
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::NotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleter(full_path)) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SDMCArchive::DeleteDirectory(const Path& path) const {
|
||||||
|
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
|
return DeleteDirectoryHelper(
|
||||||
|
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
|
||||||
|
return ERROR_ALREADY_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
FileUtil::CreateEmptyFile(full_path);
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil::IOFile file(full_path, "wb");
|
||||||
|
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
||||||
|
// We do this by seeking to the right size, then writing a single null byte.
|
||||||
|
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_FS, "Too large file");
|
||||||
|
return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
|
||||||
|
ErrorLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
|
||||||
|
return ERROR_ALREADY_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FileUtil::CreateDir(mount_point + path.AsString())) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
|
||||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
|
||||||
|
ErrorLevel::Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||||
|
if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
|
||||||
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||||
|
// exist or similar. Verify.
|
||||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::NotFound:
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto directory = std::make_unique<DiskDirectory>(full_path);
|
||||||
|
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 SDMCArchive::GetFreeBytes() const {
|
||||||
|
// TODO: Stubbed to return 1GiB
|
||||||
|
return 1024 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory)
|
ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory)
|
||||||
: sdmc_directory(sdmc_directory) {
|
: sdmc_directory(sdmc_directory) {
|
||||||
LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str());
|
LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str());
|
||||||
|
@ -35,7 +312,7 @@ bool ArchiveFactory_SDMC::Initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) {
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) {
|
||||||
auto archive = std::make_unique<DiskArchive>(sdmc_directory);
|
auto archive = std::make_unique<SDMCArchive>(sdmc_directory);
|
||||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,32 @@
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
|
/// Archive backend for SDMC archive
|
||||||
|
class SDMCArchive : public ArchiveBackend {
|
||||||
|
public:
|
||||||
|
SDMCArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
|
||||||
|
|
||||||
|
std::string GetName() const override {
|
||||||
|
return "SDMCArchive: " + mount_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
||||||
|
const Mode& mode) const override;
|
||||||
|
ResultCode DeleteFile(const Path& path) const override;
|
||||||
|
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
|
ResultCode DeleteDirectory(const Path& path) const override;
|
||||||
|
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
|
ResultCode CreateFile(const Path& path, u64 size) const override;
|
||||||
|
ResultCode CreateDirectory(const Path& path) const override;
|
||||||
|
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
||||||
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const;
|
||||||
|
std::string mount_point;
|
||||||
|
};
|
||||||
|
|
||||||
/// File system interface to the SDMC archive
|
/// File system interface to the SDMC archive
|
||||||
class ArchiveFactory_SDMC final : public ArchiveFactory {
|
class ArchiveFactory_SDMC final : public ArchiveFactory {
|
||||||
public:
|
public:
|
||||||
|
|
70
src/core/file_sys/archive_sdmcwriteonly.cpp
Normal file
70
src/core/file_sys/archive_sdmcwriteonly.cpp
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "core/file_sys/archive_sdmcwriteonly.h"
|
||||||
|
#include "core/file_sys/directory_backend.h"
|
||||||
|
#include "core/file_sys/errors.h"
|
||||||
|
#include "core/file_sys/file_backend.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// FileSys namespace
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
|
||||||
|
const Mode& mode) const {
|
||||||
|
if (mode.read_flag) {
|
||||||
|
LOG_ERROR(Service_FS, "Read flag is not supported");
|
||||||
|
return ERROR_INVALID_READ_FLAG;
|
||||||
|
}
|
||||||
|
return SDMCArchive::OpenFileBase(path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(
|
||||||
|
const Path& path) const {
|
||||||
|
LOG_ERROR(Service_FS, "Not supported");
|
||||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point)
|
||||||
|
: sdmc_directory(mount_point) {
|
||||||
|
LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArchiveFactory_SDMCWriteOnly::Initialize() {
|
||||||
|
if (!Settings::values.use_virtual_sd) {
|
||||||
|
LOG_WARNING(Service_FS, "SDMC disabled by config.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FileUtil::CreateFullPath(sdmc_directory)) {
|
||||||
|
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) {
|
||||||
|
auto archive = std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory);
|
||||||
|
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode ArchiveFactory_SDMCWriteOnly::Format(const Path& path,
|
||||||
|
const FileSys::ArchiveFormatInfo& format_info) {
|
||||||
|
// TODO(wwylele): hwtest this
|
||||||
|
LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive.");
|
||||||
|
return ResultCode(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path) const {
|
||||||
|
// TODO(Subv): Implement
|
||||||
|
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
|
||||||
|
return ResultCode(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FileSys
|
57
src/core/file_sys/archive_sdmcwriteonly.h
Normal file
57
src/core/file_sys/archive_sdmcwriteonly.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/file_sys/archive_sdmc.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// FileSys namespace
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Archive backend for SDMC write-only archive.
|
||||||
|
* The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for
|
||||||
|
* - OpenDirectory is unsupported;
|
||||||
|
* - OpenFile with read flag is unsupported.
|
||||||
|
*/
|
||||||
|
class SDMCWriteOnlyArchive : public SDMCArchive {
|
||||||
|
public:
|
||||||
|
SDMCWriteOnlyArchive(const std::string& mount_point) : SDMCArchive(mount_point) {}
|
||||||
|
|
||||||
|
std::string GetName() const override {
|
||||||
|
return "SDMCWriteOnlyArchive: " + mount_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
||||||
|
const Mode& mode) const override;
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// File system interface to the SDMC write-only archive
|
||||||
|
class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory {
|
||||||
|
public:
|
||||||
|
ArchiveFactory_SDMCWriteOnly(const std::string& mount_point);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the archive.
|
||||||
|
* @return true if it initialized successfully
|
||||||
|
*/
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
std::string GetName() const override {
|
||||||
|
return "SDMCWriteOnly";
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
|
||||||
|
ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override;
|
||||||
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string sdmc_directory;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FileSys
|
|
@ -9,7 +9,7 @@
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/file_sys/archive_systemsavedata.h"
|
#include "core/file_sys/archive_systemsavedata.h"
|
||||||
#include "core/file_sys/disk_archive.h"
|
#include "core/file_sys/savedata_archive.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c
|
||||||
return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS,
|
return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS,
|
||||||
ErrorSummary::InvalidState, ErrorLevel::Status);
|
ErrorSummary::InvalidState, ErrorLevel::Status);
|
||||||
}
|
}
|
||||||
auto archive = std::make_unique<DiskArchive>(fullpath);
|
auto archive = std::make_unique<SaveDataArchive>(fullpath);
|
||||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,12 +40,6 @@ public:
|
||||||
DirectoryBackend() {}
|
DirectoryBackend() {}
|
||||||
virtual ~DirectoryBackend() {}
|
virtual ~DirectoryBackend() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the directory
|
|
||||||
* @return true if the directory opened correctly
|
|
||||||
*/
|
|
||||||
virtual bool Open() = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List files contained in the directory
|
* List files contained in the directory
|
||||||
* @param count Number of entries to return at once in entries
|
* @param count Number of entries to return at once in entries
|
||||||
|
|
|
@ -15,144 +15,8 @@
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> DiskArchive::OpenFile(const Path& path,
|
|
||||||
const Mode mode) const {
|
|
||||||
LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
|
|
||||||
auto file = std::make_unique<DiskFile>(*this, path, mode);
|
|
||||||
ResultCode result = file->Open();
|
|
||||||
if (result.IsError())
|
|
||||||
return result;
|
|
||||||
return MakeResult<std::unique_ptr<FileBackend>>(std::move(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultCode DiskArchive::DeleteFile(const Path& path) const {
|
|
||||||
std::string file_path = mount_point + path.AsString();
|
|
||||||
|
|
||||||
if (FileUtil::IsDirectory(file_path))
|
|
||||||
return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
|
|
||||||
ErrorLevel::Status);
|
|
||||||
|
|
||||||
if (!FileUtil::Exists(file_path))
|
|
||||||
return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
|
|
||||||
ErrorLevel::Status);
|
|
||||||
|
|
||||||
if (FileUtil::Delete(file_path))
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
|
|
||||||
return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
|
|
||||||
ErrorLevel::Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
|
||||||
return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DiskArchive::DeleteDirectory(const Path& path) const {
|
|
||||||
return FileUtil::DeleteDir(mount_point + path.AsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DiskArchive::DeleteDirectoryRecursively(const Path& path) const {
|
|
||||||
return FileUtil::DeleteDirRecursively(mount_point + path.AsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const {
|
|
||||||
std::string full_path = mount_point + path.AsString();
|
|
||||||
|
|
||||||
if (FileUtil::IsDirectory(full_path))
|
|
||||||
return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
|
|
||||||
ErrorLevel::Status);
|
|
||||||
|
|
||||||
if (FileUtil::Exists(full_path))
|
|
||||||
return ResultCode(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
|
|
||||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
FileUtil::CreateEmptyFile(full_path);
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileUtil::IOFile file(full_path, "wb");
|
|
||||||
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
|
||||||
// We do this by seeking to the right size, then writing a single null byte.
|
|
||||||
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1)
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
|
|
||||||
return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
|
|
||||||
ErrorLevel::Info);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DiskArchive::CreateDirectory(const Path& path) const {
|
|
||||||
return FileUtil::CreateDir(mount_point + path.AsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
|
||||||
return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) const {
|
|
||||||
LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str());
|
|
||||||
auto directory = std::make_unique<DiskDirectory>(*this, path);
|
|
||||||
if (!directory->Open())
|
|
||||||
return nullptr;
|
|
||||||
return std::move(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 DiskArchive::GetFreeBytes() const {
|
|
||||||
// TODO: Stubbed to return 1GiB
|
|
||||||
return 1024 * 1024 * 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
DiskFile::DiskFile(const DiskArchive& archive, const Path& path, const Mode mode) {
|
|
||||||
// TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
|
|
||||||
// the root directory we set while opening the archive.
|
|
||||||
// For example, opening /../../etc/passwd can give the emulated program your users list.
|
|
||||||
this->path = archive.mount_point + path.AsString();
|
|
||||||
this->mode.hex = mode.hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultCode DiskFile::Open() {
|
|
||||||
if (FileUtil::IsDirectory(path))
|
|
||||||
return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
|
|
||||||
ErrorLevel::Status);
|
|
||||||
|
|
||||||
// Specifying only the Create flag is invalid
|
|
||||||
if (mode.create_flag && !mode.read_flag && !mode.write_flag) {
|
|
||||||
return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
|
|
||||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FileUtil::Exists(path)) {
|
|
||||||
if (!mode.create_flag) {
|
|
||||||
LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
|
|
||||||
path.c_str());
|
|
||||||
return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS,
|
|
||||||
ErrorSummary::NotFound, ErrorLevel::Status);
|
|
||||||
} else {
|
|
||||||
// Create the file
|
|
||||||
FileUtil::CreateEmptyFile(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string mode_string = "";
|
|
||||||
if (mode.write_flag)
|
|
||||||
mode_string += "r+"; // Files opened with Write access can be read from
|
|
||||||
else if (mode.read_flag)
|
|
||||||
mode_string += "r";
|
|
||||||
|
|
||||||
// Open the file in binary mode, to avoid problems with CR/LF on Windows systems
|
|
||||||
mode_string += "b";
|
|
||||||
|
|
||||||
file = std::make_unique<FileUtil::IOFile>(path, mode_string.c_str());
|
|
||||||
if (file->IsOpen())
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
|
|
||||||
ErrorLevel::Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {
|
ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {
|
||||||
if (!mode.read_flag && !mode.write_flag)
|
if (!mode.read_flag)
|
||||||
return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
|
return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
|
||||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
ErrorSummary::Canceled, ErrorLevel::Status);
|
||||||
|
|
||||||
|
@ -189,21 +53,11 @@ bool DiskFile::Close() const {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() {
|
DiskDirectory::DiskDirectory(const std::string& path) : directory() {
|
||||||
// TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
|
|
||||||
// the root directory we set while opening the archive.
|
|
||||||
// For example, opening /../../usr/bin can give the emulated program your installed programs.
|
|
||||||
this->path = archive.mount_point + path.AsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DiskDirectory::Open() {
|
|
||||||
if (!FileUtil::IsDirectory(path))
|
|
||||||
return false;
|
|
||||||
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
|
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
|
||||||
directory.size = size;
|
directory.size = size;
|
||||||
directory.isDirectory = true;
|
directory.isDirectory = true;
|
||||||
children_iterator = directory.children.begin();
|
children_iterator = directory.children.begin();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 DiskDirectory::Read(const u32 count, Entry* entries) {
|
u32 DiskDirectory::Read(const u32 count, Entry* entries) {
|
||||||
|
|
|
@ -20,43 +20,13 @@
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper which implements a backend accessing the host machine's filesystem.
|
|
||||||
* This should be subclassed by concrete archive types, which will provide the
|
|
||||||
* base directory on the host filesystem and override any required functionality.
|
|
||||||
*/
|
|
||||||
class DiskArchive : public ArchiveBackend {
|
|
||||||
public:
|
|
||||||
DiskArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
|
|
||||||
|
|
||||||
virtual std::string GetName() const override {
|
|
||||||
return "DiskArchive: " + mount_point;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
|
||||||
const Mode mode) const override;
|
|
||||||
ResultCode DeleteFile(const Path& path) const override;
|
|
||||||
bool RenameFile(const Path& src_path, const Path& dest_path) const override;
|
|
||||||
bool DeleteDirectory(const Path& path) const override;
|
|
||||||
bool DeleteDirectoryRecursively(const Path& path) const override;
|
|
||||||
ResultCode CreateFile(const Path& path, u64 size) const override;
|
|
||||||
bool CreateDirectory(const Path& path) const override;
|
|
||||||
bool RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
|
||||||
std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override;
|
|
||||||
u64 GetFreeBytes() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
friend class DiskFile;
|
|
||||||
friend class DiskDirectory;
|
|
||||||
|
|
||||||
std::string mount_point;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DiskFile : public FileBackend {
|
class DiskFile : public FileBackend {
|
||||||
public:
|
public:
|
||||||
DiskFile(const DiskArchive& archive, const Path& path, const Mode mode);
|
DiskFile(FileUtil::IOFile&& file_, const Mode& mode_)
|
||||||
|
: file(new FileUtil::IOFile(std::move(file_))) {
|
||||||
|
mode.hex = mode_.hex;
|
||||||
|
}
|
||||||
|
|
||||||
ResultCode Open() override;
|
|
||||||
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
|
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
|
||||||
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
|
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
|
@ -68,20 +38,18 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string path;
|
|
||||||
Mode mode;
|
Mode mode;
|
||||||
std::unique_ptr<FileUtil::IOFile> file;
|
std::unique_ptr<FileUtil::IOFile> file;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DiskDirectory : public DirectoryBackend {
|
class DiskDirectory : public DirectoryBackend {
|
||||||
public:
|
public:
|
||||||
DiskDirectory(const DiskArchive& archive, const Path& path);
|
DiskDirectory(const std::string& path);
|
||||||
|
|
||||||
~DiskDirectory() override {
|
~DiskDirectory() override {
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Open() override;
|
|
||||||
u32 Read(const u32 count, Entry* entries) override;
|
u32 Read(const u32 count, Entry* entries) override;
|
||||||
|
|
||||||
bool Close() const override {
|
bool Close() const override {
|
||||||
|
@ -89,7 +57,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string path;
|
|
||||||
u32 total_entries_in_directory;
|
u32 total_entries_in_directory;
|
||||||
FileUtil::FSTEntry directory;
|
FileUtil::FSTEntry directory;
|
||||||
|
|
||||||
|
|
40
src/core/file_sys/errors.h
Normal file
40
src/core/file_sys/errors.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS,
|
||||||
|
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
|
||||||
|
const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags,
|
||||||
|
ErrorModule::FS, ErrorSummary::NotSupported,
|
||||||
|
ErrorLevel::Usage);
|
||||||
|
const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
|
||||||
|
ErrorSummary::Canceled, ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS,
|
||||||
|
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
|
||||||
|
const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS,
|
||||||
|
ErrorSummary::NotFound, ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS,
|
||||||
|
ErrorSummary::NotFound, ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS,
|
||||||
|
ErrorSummary::NotFound, ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory,
|
||||||
|
ErrorModule::FS, ErrorSummary::NotSupported,
|
||||||
|
ErrorLevel::Usage);
|
||||||
|
const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile,
|
||||||
|
ErrorModule::FS, ErrorSummary::Canceled,
|
||||||
|
ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists,
|
||||||
|
ErrorModule::FS, ErrorSummary::NothingHappened,
|
||||||
|
ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS,
|
||||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
|
||||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
|
const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS,
|
||||||
|
ErrorSummary::Canceled, ErrorLevel::Status);
|
||||||
|
|
||||||
|
} // namespace FileSys
|
|
@ -18,12 +18,6 @@ public:
|
||||||
FileBackend() {}
|
FileBackend() {}
|
||||||
virtual ~FileBackend() {}
|
virtual ~FileBackend() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the file
|
|
||||||
* @return Result of the file operation
|
|
||||||
*/
|
|
||||||
virtual ResultCode Open() = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read data from the file
|
* Read data from the file
|
||||||
* @param offset Offset in bytes to start reading data from
|
* @param offset Offset in bytes to start reading data from
|
||||||
|
|
|
@ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path,
|
||||||
const Mode mode) const {
|
const Mode& mode) const {
|
||||||
return MakeResult<std::unique_ptr<FileBackend>>(
|
return MakeResult<std::unique_ptr<FileBackend>>(
|
||||||
std::make_unique<IVFCFile>(romfs_file, data_offset, data_size));
|
std::make_unique<IVFCFile>(romfs_file, data_offset, data_size));
|
||||||
}
|
}
|
||||||
|
@ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const {
|
||||||
ErrorLevel::Status);
|
ErrorLevel::Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
|
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
|
||||||
GetName().c_str());
|
GetName().c_str());
|
||||||
return false;
|
// TODO(wwylele): Use correct error code
|
||||||
|
return ResultCode(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IVFCArchive::DeleteDirectory(const Path& path) const {
|
ResultCode IVFCArchive::DeleteDirectory(const Path& path) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
|
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
|
||||||
GetName().c_str());
|
GetName().c_str());
|
||||||
return false;
|
// TODO(wwylele): Use correct error code
|
||||||
|
return ResultCode(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
|
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
|
||||||
GetName().c_str());
|
GetName().c_str());
|
||||||
return false;
|
// TODO(wwylele): Use correct error code
|
||||||
|
return ResultCode(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
|
ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
|
||||||
|
@ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
|
||||||
ErrorLevel::Permanent);
|
ErrorLevel::Permanent);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IVFCArchive::CreateDirectory(const Path& path) const {
|
ResultCode IVFCArchive::CreateDirectory(const Path& path) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).",
|
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).",
|
||||||
GetName().c_str());
|
GetName().c_str());
|
||||||
return false;
|
// TODO(wwylele): Use correct error code
|
||||||
|
return ResultCode(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
|
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
|
||||||
GetName().c_str());
|
GetName().c_str());
|
||||||
return false;
|
// TODO(wwylele): Use correct error code
|
||||||
|
return ResultCode(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const {
|
ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const {
|
||||||
return std::make_unique<IVFCDirectory>();
|
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>());
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 IVFCArchive::GetFreeBytes() const {
|
u64 IVFCArchive::GetFreeBytes() const {
|
||||||
|
|
|
@ -33,15 +33,15 @@ public:
|
||||||
std::string GetName() const override;
|
std::string GetName() const override;
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
||||||
const Mode mode) const override;
|
const Mode& mode) const override;
|
||||||
ResultCode DeleteFile(const Path& path) const override;
|
ResultCode DeleteFile(const Path& path) const override;
|
||||||
bool RenameFile(const Path& src_path, const Path& dest_path) const override;
|
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
bool DeleteDirectory(const Path& path) const override;
|
ResultCode DeleteDirectory(const Path& path) const override;
|
||||||
bool DeleteDirectoryRecursively(const Path& path) const override;
|
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
ResultCode CreateFile(const Path& path, u64 size) const override;
|
ResultCode CreateFile(const Path& path, u64 size) const override;
|
||||||
bool CreateDirectory(const Path& path) const override;
|
ResultCode CreateDirectory(const Path& path) const override;
|
||||||
bool RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override;
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
||||||
u64 GetFreeBytes() const override;
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -55,9 +55,6 @@ public:
|
||||||
IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
|
IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
|
||||||
: romfs_file(file), data_offset(offset), data_size(size) {}
|
: romfs_file(file), data_offset(offset), data_size(size) {}
|
||||||
|
|
||||||
ResultCode Open() override {
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
}
|
|
||||||
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
|
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
|
||||||
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
|
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
|
@ -75,9 +72,6 @@ private:
|
||||||
|
|
||||||
class IVFCDirectory : public DirectoryBackend {
|
class IVFCDirectory : public DirectoryBackend {
|
||||||
public:
|
public:
|
||||||
bool Open() override {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
u32 Read(const u32 count, Entry* entries) override {
|
u32 Read(const u32 count, Entry* entries) override {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
98
src/core/file_sys/path_parser.cpp
Normal file
98
src/core/file_sys/path_parser.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <set>
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "core/file_sys/path_parser.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
PathParser::PathParser(const Path& path) {
|
||||||
|
if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) {
|
||||||
|
is_valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path_string = path.AsString();
|
||||||
|
if (path_string.size() == 0 || path_string[0] != '/') {
|
||||||
|
is_valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out invalid characters for the host system.
|
||||||
|
// Although some of these characters are valid on 3DS, they are unlikely to be used by games.
|
||||||
|
if (std::find_if(path_string.begin(), path_string.end(), [](char c) {
|
||||||
|
static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'};
|
||||||
|
return invalid_chars.find(c) != invalid_chars.end();
|
||||||
|
}) != path_string.end()) {
|
||||||
|
is_valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::SplitString(path_string, '/', path_sequence);
|
||||||
|
|
||||||
|
auto begin = path_sequence.begin();
|
||||||
|
auto end = path_sequence.end();
|
||||||
|
end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; });
|
||||||
|
path_sequence = std::vector<std::string>(begin, end);
|
||||||
|
|
||||||
|
// checks if the path is out of bounds.
|
||||||
|
int level = 0;
|
||||||
|
for (auto& node : path_sequence) {
|
||||||
|
if (node == "..") {
|
||||||
|
--level;
|
||||||
|
if (level < 0) {
|
||||||
|
is_valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
++level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid = true;
|
||||||
|
is_root = level == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const {
|
||||||
|
auto path = mount_point;
|
||||||
|
if (!FileUtil::IsDirectory(path))
|
||||||
|
return InvalidMountPoint;
|
||||||
|
if (path_sequence.empty()) {
|
||||||
|
return DirectoryFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) {
|
||||||
|
if (path.back() != '/')
|
||||||
|
path += '/';
|
||||||
|
path += *iter;
|
||||||
|
|
||||||
|
if (!FileUtil::Exists(path))
|
||||||
|
return PathNotFound;
|
||||||
|
if (FileUtil::IsDirectory(path))
|
||||||
|
continue;
|
||||||
|
return FileInPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
path += "/" + path_sequence.back();
|
||||||
|
if (!FileUtil::Exists(path))
|
||||||
|
return NotFound;
|
||||||
|
if (FileUtil::IsDirectory(path))
|
||||||
|
return DirectoryFound;
|
||||||
|
return FileFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PathParser::BuildHostPath(const std::string& mount_point) const {
|
||||||
|
std::string path = mount_point;
|
||||||
|
for (auto& node : path_sequence) {
|
||||||
|
if (path.back() != '/')
|
||||||
|
path += '/';
|
||||||
|
path += node;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FileSys
|
61
src/core/file_sys/path_parser.h
Normal file
61
src/core/file_sys/path_parser.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class parsing and verifying a string-type Path.
|
||||||
|
* Every archives with a sub file system should use this class to parse the path argument and check
|
||||||
|
* the status of the file / directory in question on the host file system.
|
||||||
|
*/
|
||||||
|
class PathParser {
|
||||||
|
public:
|
||||||
|
PathParser(const Path& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Path is valid.
|
||||||
|
* This function should be called once a PathParser is constructed.
|
||||||
|
* A Path is valid if:
|
||||||
|
* - it is a string path (with type LowPathType::Char or LowPathType::Wchar),
|
||||||
|
* - it starts with "/" (this seems a hard requirement in real 3DS),
|
||||||
|
* - it doesn't contain invalid characters, and
|
||||||
|
* - it doesn't go out of the root directory using "..".
|
||||||
|
*/
|
||||||
|
bool IsValid() const {
|
||||||
|
return is_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the Path represents the root directory.
|
||||||
|
bool IsRootDirectory() const {
|
||||||
|
return is_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HostStatus {
|
||||||
|
InvalidMountPoint,
|
||||||
|
PathNotFound, // "/a/b/c" when "a" doesn't exist
|
||||||
|
FileInPath, // "/a/b/c" when "a" is a file
|
||||||
|
FileFound, // "/a/b/c" when "c" is a file
|
||||||
|
DirectoryFound, // "/a/b/c" when "c" is a directory
|
||||||
|
NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Checks the status of the specified file / directory by the Path on the host file system.
|
||||||
|
HostStatus GetHostStatus(const std::string& mount_point) const;
|
||||||
|
|
||||||
|
/// Builds a full path on the host file system.
|
||||||
|
std::string BuildHostPath(const std::string& mount_point) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> path_sequence;
|
||||||
|
bool is_valid{};
|
||||||
|
bool is_root{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FileSys
|
283
src/core/file_sys/savedata_archive.cpp
Normal file
283
src/core/file_sys/savedata_archive.cpp
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "core/file_sys/disk_archive.h"
|
||||||
|
#include "core/file_sys/errors.h"
|
||||||
|
#include "core/file_sys/path_parser.h"
|
||||||
|
#include "core/file_sys/savedata_archive.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// FileSys namespace
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
|
||||||
|
const Mode& mode) const {
|
||||||
|
LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
|
||||||
|
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.hex == 0) {
|
||||||
|
LOG_ERROR(Service_FS, "Empty open mode");
|
||||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.create_flag && !mode.write_flag) {
|
||||||
|
LOG_ERROR(Service_FS, "Create flag set but write flag not set");
|
||||||
|
return ERROR_UNSUPPORTED_OPEN_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
|
||||||
|
case PathParser::NotFound:
|
||||||
|
if (!mode.create_flag) {
|
||||||
|
LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
|
||||||
|
full_path.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
} else {
|
||||||
|
// Create the file
|
||||||
|
FileUtil::CreateEmptyFile(full_path);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
|
||||||
|
return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
case PathParser::NotFound:
|
||||||
|
LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FileUtil::Delete(full_path)) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||||
|
if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||||
|
// exist or similar. Verify.
|
||||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
|
||||||
|
T deleter) {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path_parser.IsRootDirectory())
|
||||||
|
return ERROR_DIRECTORY_NOT_EMPTY;
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::NotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleter(full_path)) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
|
||||||
|
return ERROR_DIRECTORY_NOT_EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
|
||||||
|
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
|
return DeleteDirectoryHelper(
|
||||||
|
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
|
||||||
|
return ERROR_FILE_ALREADY_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
FileUtil::CreateEmptyFile(full_path);
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil::IOFile file(full_path, "wb");
|
||||||
|
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
||||||
|
// We do this by seeking to the right size, then writing a single null byte.
|
||||||
|
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_FS, "Too large file");
|
||||||
|
return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
|
||||||
|
ErrorLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
|
||||||
|
case PathParser::DirectoryFound:
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
|
||||||
|
return ERROR_DIRECTORY_ALREADY_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FileUtil::CreateDir(mount_point + path.AsString())) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
|
||||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
|
||||||
|
ErrorLevel::Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||||
|
if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
|
||||||
|
return RESULT_SUCCESS;
|
||||||
|
|
||||||
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||||
|
// exist or similar. Verify.
|
||||||
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||||
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
|
||||||
|
const Path& path) const {
|
||||||
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
|
if (!path_parser.IsValid()) {
|
||||||
|
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
|
||||||
|
return ERROR_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||||
|
|
||||||
|
switch (path_parser.GetHostStatus(mount_point)) {
|
||||||
|
case PathParser::InvalidMountPoint:
|
||||||
|
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
|
||||||
|
return ERROR_FILE_NOT_FOUND;
|
||||||
|
case PathParser::PathNotFound:
|
||||||
|
case PathParser::NotFound:
|
||||||
|
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
|
||||||
|
return ERROR_PATH_NOT_FOUND;
|
||||||
|
case PathParser::FileInPath:
|
||||||
|
case PathParser::FileFound:
|
||||||
|
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
|
||||||
|
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto directory = std::make_unique<DiskDirectory>(full_path);
|
||||||
|
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 SaveDataArchive::GetFreeBytes() const {
|
||||||
|
// TODO: Stubbed to return 1GiB
|
||||||
|
return 1024 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FileSys
|
43
src/core/file_sys/savedata_archive.h
Normal file
43
src/core/file_sys/savedata_archive.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/file_sys/directory_backend.h"
|
||||||
|
#include "core/file_sys/file_backend.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// FileSys namespace
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
/// Archive backend for general save data archive type (SaveData and SystemSaveData)
|
||||||
|
class SaveDataArchive : public ArchiveBackend {
|
||||||
|
public:
|
||||||
|
SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
|
||||||
|
|
||||||
|
std::string GetName() const override {
|
||||||
|
return "SaveDataArchive: " + mount_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
||||||
|
const Mode& mode) const override;
|
||||||
|
ResultCode DeleteFile(const Path& path) const override;
|
||||||
|
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
|
ResultCode DeleteDirectory(const Path& path) const override;
|
||||||
|
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
|
ResultCode CreateFile(const Path& path, u64 size) const override;
|
||||||
|
ResultCode CreateDirectory(const Path& path) const override;
|
||||||
|
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
||||||
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string mount_point;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FileSys
|
|
@ -20,15 +20,24 @@ enum class ErrorDescription : u32 {
|
||||||
OS_InvalidBufferDescriptor = 48,
|
OS_InvalidBufferDescriptor = 48,
|
||||||
WrongAddress = 53,
|
WrongAddress = 53,
|
||||||
FS_ArchiveNotMounted = 101,
|
FS_ArchiveNotMounted = 101,
|
||||||
|
FS_FileNotFound = 112,
|
||||||
|
FS_PathNotFound = 113,
|
||||||
FS_NotFound = 120,
|
FS_NotFound = 120,
|
||||||
|
FS_FileAlreadyExists = 180,
|
||||||
|
FS_DirectoryAlreadyExists = 185,
|
||||||
FS_AlreadyExists = 190,
|
FS_AlreadyExists = 190,
|
||||||
FS_InvalidOpenFlags = 230,
|
FS_InvalidOpenFlags = 230,
|
||||||
|
FS_DirectoryNotEmpty = 240,
|
||||||
FS_NotAFile = 250,
|
FS_NotAFile = 250,
|
||||||
FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
|
FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
|
||||||
OutofRangeOrMisalignedAddress =
|
OutofRangeOrMisalignedAddress =
|
||||||
513, // TODO(purpasmart): Check if this name fits its actual usage
|
513, // TODO(purpasmart): Check if this name fits its actual usage
|
||||||
GPU_FirstInitialization = 519,
|
GPU_FirstInitialization = 519,
|
||||||
|
FS_InvalidReadFlag = 700,
|
||||||
FS_InvalidPath = 702,
|
FS_InvalidPath = 702,
|
||||||
|
FS_WriteBeyondEnd = 705,
|
||||||
|
FS_UnsupportedOpenFlags = 760,
|
||||||
|
FS_UnexpectedFileOrDirectory = 770,
|
||||||
InvalidSection = 1000,
|
InvalidSection = 1000,
|
||||||
TooLarge = 1001,
|
TooLarge = 1001,
|
||||||
NotAuthorized = 1002,
|
NotAuthorized = 1002,
|
||||||
|
|
|
@ -360,7 +360,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode DeleteConfigNANDSaveFile() {
|
ResultCode DeleteConfigNANDSaveFile() {
|
||||||
FileSys::Path path("config");
|
FileSys::Path path("/config");
|
||||||
return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path);
|
return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ ResultCode UpdateConfigNANDSavegame() {
|
||||||
mode.write_flag.Assign(1);
|
mode.write_flag.Assign(1);
|
||||||
mode.create_flag.Assign(1);
|
mode.create_flag.Assign(1);
|
||||||
|
|
||||||
FileSys::Path path("config");
|
FileSys::Path path("/config");
|
||||||
|
|
||||||
auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
|
auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
|
||||||
ASSERT_MSG(config_result.Succeeded(), "could not open file");
|
ASSERT_MSG(config_result.Succeeded(), "could not open file");
|
||||||
|
@ -383,8 +383,9 @@ ResultCode UpdateConfigNANDSavegame() {
|
||||||
ResultCode FormatConfig() {
|
ResultCode FormatConfig() {
|
||||||
ResultCode res = DeleteConfigNANDSaveFile();
|
ResultCode res = DeleteConfigNANDSaveFile();
|
||||||
// The delete command fails if the file doesn't exist, so we have to check that too
|
// The delete command fails if the file doesn't exist, so we have to check that too
|
||||||
if (!res.IsSuccess() && res.description != ErrorDescription::FS_NotFound)
|
if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) {
|
||||||
return res;
|
return res;
|
||||||
|
}
|
||||||
// Delete the old data
|
// Delete the old data
|
||||||
cfg_config_file_buffer.fill(0);
|
cfg_config_file_buffer.fill(0);
|
||||||
// Create the header
|
// Create the header
|
||||||
|
@ -510,7 +511,7 @@ ResultCode LoadConfigNANDSaveFile() {
|
||||||
|
|
||||||
cfg_system_save_data_archive = *archive_result;
|
cfg_system_save_data_archive = *archive_result;
|
||||||
|
|
||||||
FileSys::Path config_path("config");
|
FileSys::Path config_path("/config");
|
||||||
FileSys::Mode open_mode = {};
|
FileSys::Mode open_mode = {};
|
||||||
open_mode.read_flag.Assign(1);
|
open_mode.read_flag.Assign(1);
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/file_sys/archive_backend.h"
|
#include "core/file_sys/archive_backend.h"
|
||||||
#include "core/file_sys/archive_extsavedata.h"
|
#include "core/file_sys/archive_extsavedata.h"
|
||||||
|
#include "core/file_sys/archive_ncch.h"
|
||||||
#include "core/file_sys/archive_savedata.h"
|
#include "core/file_sys/archive_savedata.h"
|
||||||
#include "core/file_sys/archive_savedatacheck.h"
|
|
||||||
#include "core/file_sys/archive_sdmc.h"
|
#include "core/file_sys/archive_sdmc.h"
|
||||||
|
#include "core/file_sys/archive_sdmcwriteonly.h"
|
||||||
#include "core/file_sys/archive_systemsavedata.h"
|
#include "core/file_sys/archive_systemsavedata.h"
|
||||||
#include "core/file_sys/directory_backend.h"
|
#include "core/file_sys/directory_backend.h"
|
||||||
#include "core/file_sys/file_backend.h"
|
#include "core/file_sys/file_backend.h"
|
||||||
|
@ -338,17 +339,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle,
|
||||||
return ERR_INVALID_ARCHIVE_HANDLE;
|
return ERR_INVALID_ARCHIVE_HANDLE;
|
||||||
|
|
||||||
if (src_archive == dest_archive) {
|
if (src_archive == dest_archive) {
|
||||||
if (src_archive->RenameFile(src_path, dest_path))
|
return src_archive->RenameFile(src_path, dest_path);
|
||||||
return RESULT_SUCCESS;
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: Implement renaming across archives
|
// TODO: Implement renaming across archives
|
||||||
return UnimplementedFunction(ErrorModule::FS);
|
return UnimplementedFunction(ErrorModule::FS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
|
||||||
// exist or similar. Verify.
|
|
||||||
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
||||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
|
ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
|
||||||
|
@ -356,10 +351,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
|
||||||
if (archive == nullptr)
|
if (archive == nullptr)
|
||||||
return ERR_INVALID_ARCHIVE_HANDLE;
|
return ERR_INVALID_ARCHIVE_HANDLE;
|
||||||
|
|
||||||
if (archive->DeleteDirectory(path))
|
return archive->DeleteDirectory(path);
|
||||||
return RESULT_SUCCESS;
|
|
||||||
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
||||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
|
ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
|
||||||
|
@ -368,10 +360,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
|
||||||
if (archive == nullptr)
|
if (archive == nullptr)
|
||||||
return ERR_INVALID_ARCHIVE_HANDLE;
|
return ERR_INVALID_ARCHIVE_HANDLE;
|
||||||
|
|
||||||
if (archive->DeleteDirectoryRecursively(path))
|
return archive->DeleteDirectoryRecursively(path);
|
||||||
return RESULT_SUCCESS;
|
|
||||||
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
||||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
||||||
|
@ -388,10 +377,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
|
||||||
if (archive == nullptr)
|
if (archive == nullptr)
|
||||||
return ERR_INVALID_ARCHIVE_HANDLE;
|
return ERR_INVALID_ARCHIVE_HANDLE;
|
||||||
|
|
||||||
if (archive->CreateDirectory(path))
|
return archive->CreateDirectory(path);
|
||||||
return RESULT_SUCCESS;
|
|
||||||
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
||||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
|
ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
|
||||||
|
@ -404,17 +390,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
|
||||||
return ERR_INVALID_ARCHIVE_HANDLE;
|
return ERR_INVALID_ARCHIVE_HANDLE;
|
||||||
|
|
||||||
if (src_archive == dest_archive) {
|
if (src_archive == dest_archive) {
|
||||||
if (src_archive->RenameDirectory(src_path, dest_path))
|
return src_archive->RenameDirectory(src_path, dest_path);
|
||||||
return RESULT_SUCCESS;
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: Implement renaming across archives
|
// TODO: Implement renaming across archives
|
||||||
return UnimplementedFunction(ErrorModule::FS);
|
return UnimplementedFunction(ErrorModule::FS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
|
||||||
// exist or similar. Verify.
|
|
||||||
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
||||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle,
|
ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle,
|
||||||
|
@ -423,13 +403,11 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
|
||||||
if (archive == nullptr)
|
if (archive == nullptr)
|
||||||
return ERR_INVALID_ARCHIVE_HANDLE;
|
return ERR_INVALID_ARCHIVE_HANDLE;
|
||||||
|
|
||||||
std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path);
|
auto backend = archive->OpenDirectory(path);
|
||||||
if (backend == nullptr) {
|
if (backend.Failed())
|
||||||
return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
|
return backend.Code();
|
||||||
ErrorLevel::Permanent);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto directory = Kernel::SharedPtr<Directory>(new Directory(std::move(backend), path));
|
auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path));
|
||||||
return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory));
|
return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,6 +527,13 @@ void RegisterArchiveTypes() {
|
||||||
LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s",
|
LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s",
|
||||||
sdmc_directory.c_str());
|
sdmc_directory.c_str());
|
||||||
|
|
||||||
|
auto sdmcwo_factory = std::make_unique<FileSys::ArchiveFactory_SDMCWriteOnly>(sdmc_directory);
|
||||||
|
if (sdmcwo_factory->Initialize())
|
||||||
|
RegisterArchiveType(std::move(sdmcwo_factory), ArchiveIdCode::SDMCWriteOnly);
|
||||||
|
else
|
||||||
|
LOG_ERROR(Service_FS, "Can't instantiate SDMCWriteOnly archive with path %s",
|
||||||
|
sdmc_directory.c_str());
|
||||||
|
|
||||||
// Create the SaveData archive
|
// Create the SaveData archive
|
||||||
auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory);
|
auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory);
|
||||||
RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData);
|
RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData);
|
||||||
|
@ -569,10 +554,9 @@ void RegisterArchiveTypes() {
|
||||||
LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s",
|
LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s",
|
||||||
sharedextsavedata_factory->GetMountPoint().c_str());
|
sharedextsavedata_factory->GetMountPoint().c_str());
|
||||||
|
|
||||||
// Create the SaveDataCheck archive, basically a small variation of the RomFS archive
|
// Create the NCCH archive, basically a small variation of the RomFS archive
|
||||||
auto savedatacheck_factory =
|
auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_NCCH>(nand_directory);
|
||||||
std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory);
|
RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH);
|
||||||
RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck);
|
|
||||||
|
|
||||||
auto systemsavedata_factory =
|
auto systemsavedata_factory =
|
||||||
std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory);
|
std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory);
|
||||||
|
|
|
@ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 {
|
||||||
SystemSaveData = 0x00000008,
|
SystemSaveData = 0x00000008,
|
||||||
SDMC = 0x00000009,
|
SDMC = 0x00000009,
|
||||||
SDMCWriteOnly = 0x0000000A,
|
SDMCWriteOnly = 0x0000000A,
|
||||||
SaveDataCheck = 0x2345678A,
|
NCCH = 0x2345678A,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Media types for the archives
|
/// Media types for the archives
|
||||||
|
|
|
@ -128,7 +128,7 @@ void Init() {
|
||||||
Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path);
|
Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path);
|
||||||
ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!");
|
ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!");
|
||||||
|
|
||||||
FileSys::Path gamecoin_path("gamecoin.dat");
|
FileSys::Path gamecoin_path("/gamecoin.dat");
|
||||||
FileSys::Mode open_mode = {};
|
FileSys::Mode open_mode = {};
|
||||||
open_mode.write_flag.Assign(1);
|
open_mode.write_flag.Assign(1);
|
||||||
open_mode.create_flag.Assign(1);
|
open_mode.create_flag.Assign(1);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
set(SRCS
|
set(SRCS
|
||||||
|
glad.cpp
|
||||||
tests.cpp
|
tests.cpp
|
||||||
|
core/file_sys/path_parser.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
|
|
38
src/tests/core/file_sys/path_parser.cpp
Normal file
38
src/tests/core/file_sys/path_parser.cpp
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "core/file_sys/path_parser.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
TEST_CASE("PathParser", "[core][file_sys]") {
|
||||||
|
REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid());
|
||||||
|
REQUIRE(!PathParser(Path("a")).IsValid());
|
||||||
|
REQUIRE(!PathParser(Path("/|")).IsValid());
|
||||||
|
REQUIRE(PathParser(Path("/a")).IsValid());
|
||||||
|
REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid());
|
||||||
|
REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid());
|
||||||
|
REQUIRE(PathParser(Path("/")).IsRootDirectory());
|
||||||
|
REQUIRE(!PathParser(Path("/a")).IsRootDirectory());
|
||||||
|
REQUIRE(PathParser(Path("/a/..")).IsRootDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("PathParser - Host file system", "[core][file_sys]") {
|
||||||
|
std::string test_dir = "./test";
|
||||||
|
FileUtil::CreateDir(test_dir);
|
||||||
|
FileUtil::CreateDir(test_dir + "/z");
|
||||||
|
FileUtil::CreateEmptyFile(test_dir + "/a");
|
||||||
|
|
||||||
|
REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound);
|
||||||
|
REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound);
|
||||||
|
REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound);
|
||||||
|
REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath);
|
||||||
|
REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound);
|
||||||
|
|
||||||
|
FileUtil::DeleteDirRecursively(test_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FileSys
|
14
src/tests/glad.cpp
Normal file
14
src/tests/glad.cpp
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
// This is not an actual test, but a work-around for issue #2183.
|
||||||
|
// If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS
|
||||||
|
// will error about undefined references from video_core to glad. So we explicitly use a glad
|
||||||
|
// function here to shut up the linker.
|
||||||
|
TEST_CASE("glad fake test", "[dummy]") {
|
||||||
|
REQUIRE(&gladLoadGL != nullptr);
|
||||||
|
}
|
Loading…
Reference in a new issue