diff options
Diffstat (limited to 'src')
30 files changed, 1234 insertions, 304 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4a9c6fd2f..299f1f261 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -17,13 +17,16 @@ set(SRCS | |||
| 17 | core_timing.cpp | 17 | core_timing.cpp |
| 18 | file_sys/archive_backend.cpp | 18 | file_sys/archive_backend.cpp |
| 19 | file_sys/archive_extsavedata.cpp | 19 | file_sys/archive_extsavedata.cpp |
| 20 | file_sys/archive_ncch.cpp | ||
| 20 | file_sys/archive_romfs.cpp | 21 | file_sys/archive_romfs.cpp |
| 21 | file_sys/archive_savedata.cpp | 22 | file_sys/archive_savedata.cpp |
| 22 | file_sys/archive_savedatacheck.cpp | ||
| 23 | file_sys/archive_sdmc.cpp | 23 | file_sys/archive_sdmc.cpp |
| 24 | file_sys/archive_sdmcwriteonly.cpp | ||
| 24 | file_sys/archive_systemsavedata.cpp | 25 | file_sys/archive_systemsavedata.cpp |
| 25 | file_sys/disk_archive.cpp | 26 | file_sys/disk_archive.cpp |
| 26 | file_sys/ivfc_archive.cpp | 27 | file_sys/ivfc_archive.cpp |
| 28 | file_sys/path_parser.cpp | ||
| 29 | file_sys/savedata_archive.cpp | ||
| 27 | gdbstub/gdbstub.cpp | 30 | gdbstub/gdbstub.cpp |
| 28 | hle/config_mem.cpp | 31 | hle/config_mem.cpp |
| 29 | hle/hle.cpp | 32 | hle/hle.cpp |
| @@ -159,15 +162,18 @@ set(HEADERS | |||
| 159 | core_timing.h | 162 | core_timing.h |
| 160 | file_sys/archive_backend.h | 163 | file_sys/archive_backend.h |
| 161 | file_sys/archive_extsavedata.h | 164 | file_sys/archive_extsavedata.h |
| 165 | file_sys/archive_ncch.h | ||
| 162 | file_sys/archive_romfs.h | 166 | file_sys/archive_romfs.h |
| 163 | file_sys/archive_savedata.h | 167 | file_sys/archive_savedata.h |
| 164 | file_sys/archive_savedatacheck.h | ||
| 165 | file_sys/archive_sdmc.h | 168 | file_sys/archive_sdmc.h |
| 169 | file_sys/archive_sdmcwriteonly.h | ||
| 166 | file_sys/archive_systemsavedata.h | 170 | file_sys/archive_systemsavedata.h |
| 167 | file_sys/directory_backend.h | 171 | file_sys/directory_backend.h |
| 168 | file_sys/disk_archive.h | 172 | file_sys/disk_archive.h |
| 169 | file_sys/file_backend.h | 173 | file_sys/file_backend.h |
| 170 | file_sys/ivfc_archive.h | 174 | file_sys/ivfc_archive.h |
| 175 | file_sys/path_parser.h | ||
| 176 | file_sys/savedata_archive.h | ||
| 171 | gdbstub/gdbstub.h | 177 | gdbstub/gdbstub.h |
| 172 | hle/config_mem.h | 178 | hle/config_mem.h |
| 173 | hle/function_wrappers.h | 179 | hle/function_wrappers.h |
diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h index 06b8f2ed7..58f6c150c 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/archive_backend.h | |||
| @@ -87,7 +87,7 @@ public: | |||
| 87 | * @return Opened file, or error code | 87 | * @return Opened file, or error code |
| 88 | */ | 88 | */ |
| 89 | virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | 89 | virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |
| 90 | const Mode mode) const = 0; | 90 | const Mode& mode) const = 0; |
| 91 | 91 | ||
| 92 | /** | 92 | /** |
| 93 | * Delete a file specified by its path | 93 | * Delete a file specified by its path |
| @@ -100,53 +100,53 @@ public: | |||
| 100 | * Rename a File specified by its path | 100 | * Rename a File specified by its path |
| 101 | * @param src_path Source path relative to the archive | 101 | * @param src_path Source path relative to the archive |
| 102 | * @param dest_path Destination path relative to the archive | 102 | * @param dest_path Destination path relative to the archive |
| 103 | * @return Whether rename succeeded | 103 | * @return Result of the operation |
| 104 | */ | 104 | */ |
| 105 | virtual bool RenameFile(const Path& src_path, const Path& dest_path) const = 0; | 105 | virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0; |
| 106 | 106 | ||
| 107 | /** | 107 | /** |
| 108 | * Delete a directory specified by its path | 108 | * Delete a directory specified by its path |
| 109 | * @param path Path relative to the archive | 109 | * @param path Path relative to the archive |
| 110 | * @return Whether the directory could be deleted | 110 | * @return Result of the operation |
| 111 | */ | 111 | */ |
| 112 | virtual bool DeleteDirectory(const Path& path) const = 0; | 112 | virtual ResultCode DeleteDirectory(const Path& path) const = 0; |
| 113 | 113 | ||
| 114 | /** | 114 | /** |
| 115 | * Delete a directory specified by its path and anything under it | 115 | * Delete a directory specified by its path and anything under it |
| 116 | * @param path Path relative to the archive | 116 | * @param path Path relative to the archive |
| 117 | * @return Whether the directory could be deleted | 117 | * @return Result of the operation |
| 118 | */ | 118 | */ |
| 119 | virtual bool DeleteDirectoryRecursively(const Path& path) const = 0; | 119 | virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0; |
| 120 | 120 | ||
| 121 | /** | 121 | /** |
| 122 | * Create a file specified by its path | 122 | * Create a file specified by its path |
| 123 | * @param path Path relative to the Archive | 123 | * @param path Path relative to the Archive |
| 124 | * @param size The size of the new file, filled with zeroes | 124 | * @param size The size of the new file, filled with zeroes |
| 125 | * @return File creation result code | 125 | * @return Result of the operation |
| 126 | */ | 126 | */ |
| 127 | virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; | 127 | virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; |
| 128 | 128 | ||
| 129 | /** | 129 | /** |
| 130 | * Create a directory specified by its path | 130 | * Create a directory specified by its path |
| 131 | * @param path Path relative to the archive | 131 | * @param path Path relative to the archive |
| 132 | * @return Whether the directory could be created | 132 | * @return Result of the operation |
| 133 | */ | 133 | */ |
| 134 | virtual bool CreateDirectory(const Path& path) const = 0; | 134 | virtual ResultCode CreateDirectory(const Path& path) const = 0; |
| 135 | 135 | ||
| 136 | /** | 136 | /** |
| 137 | * Rename a Directory specified by its path | 137 | * Rename a Directory specified by its path |
| 138 | * @param src_path Source path relative to the archive | 138 | * @param src_path Source path relative to the archive |
| 139 | * @param dest_path Destination path relative to the archive | 139 | * @param dest_path Destination path relative to the archive |
| 140 | * @return Whether rename succeeded | 140 | * @return Result of the operation |
| 141 | */ | 141 | */ |
| 142 | virtual bool RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; | 142 | virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; |
| 143 | 143 | ||
| 144 | /** | 144 | /** |
| 145 | * Open a directory specified by its path | 145 | * Open a directory specified by its path |
| 146 | * @param path Path relative to the archive | 146 | * @param path Path relative to the archive |
| 147 | * @return Opened directory, or nullptr | 147 | * @return Opened directory, or error code |
| 148 | */ | 148 | */ |
| 149 | virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0; | 149 | virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0; |
| 150 | 150 | ||
| 151 | /** | 151 | /** |
| 152 | * Get the free space | 152 | * Get the free space |
diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index e1d29efd3..e1c4931ec 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp | |||
| @@ -11,6 +11,9 @@ | |||
| 11 | #include "common/string_util.h" | 11 | #include "common/string_util.h" |
| 12 | #include "core/file_sys/archive_extsavedata.h" | 12 | #include "core/file_sys/archive_extsavedata.h" |
| 13 | #include "core/file_sys/disk_archive.h" | 13 | #include "core/file_sys/disk_archive.h" |
| 14 | #include "core/file_sys/errors.h" | ||
| 15 | #include "core/file_sys/path_parser.h" | ||
| 16 | #include "core/file_sys/savedata_archive.h" | ||
| 14 | #include "core/hle/service/fs/archive.h" | 17 | #include "core/hle/service/fs/archive.h" |
| 15 | 18 | ||
| 16 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 19 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| @@ -18,6 +21,116 @@ | |||
| 18 | 21 | ||
| 19 | namespace FileSys { | 22 | namespace FileSys { |
| 20 | 23 | ||
| 24 | /** | ||
| 25 | * A modified version of DiskFile for fixed-size file used by ExtSaveData | ||
| 26 | * The file size can't be changed by SetSize or Write. | ||
| 27 | */ | ||
| 28 | class FixSizeDiskFile : public DiskFile { | ||
| 29 | public: | ||
| 30 | FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode) : DiskFile(std::move(file), mode) { | ||
| 31 | size = GetSize(); | ||
| 32 | } | ||
| 33 | |||
| 34 | bool SetSize(u64 size) const override { | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | |||
| 38 | ResultVal<size_t> Write(u64 offset, size_t length, bool flush, | ||
| 39 | const u8* buffer) const override { | ||
| 40 | if (offset > size) { | ||
| 41 | return ResultCode(ErrorDescription::FS_WriteBeyondEnd, ErrorModule::FS, | ||
| 42 | ErrorSummary::InvalidArgument, ErrorLevel::Usage); | ||
| 43 | } else if (offset == size) { | ||
| 44 | return MakeResult<size_t>(0); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (offset + length > size) { | ||
| 48 | length = size - offset; | ||
| 49 | } | ||
| 50 | |||
| 51 | return DiskFile::Write(offset, length, flush, buffer); | ||
| 52 | } | ||
| 53 | |||
| 54 | private: | ||
| 55 | u64 size{}; | ||
| 56 | }; | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Archive backend for general extsave data archive type. | ||
| 60 | * The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for | ||
| 61 | * - file size can't be changed once created (thus creating zero-size file and openning with create | ||
| 62 | * flag are prohibited); | ||
| 63 | * - always open a file with read+write permission. | ||
| 64 | */ | ||
| 65 | class ExtSaveDataArchive : public SaveDataArchive { | ||
| 66 | public: | ||
| 67 | ExtSaveDataArchive(const std::string& mount_point) : SaveDataArchive(mount_point) {} | ||
| 68 | |||
| 69 | std::string GetName() const override { | ||
| 70 | return "ExtSaveDataArchive: " + mount_point; | ||
| 71 | } | ||
| 72 | |||
| 73 | ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||
| 74 | const Mode& mode) const override { | ||
| 75 | LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); | ||
| 76 | |||
| 77 | const PathParser path_parser(path); | ||
| 78 | |||
| 79 | if (!path_parser.IsValid()) { | ||
| 80 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 81 | return ERROR_INVALID_PATH; | ||
| 82 | } | ||
| 83 | |||
| 84 | if (mode.hex == 0) { | ||
| 85 | LOG_ERROR(Service_FS, "Empty open mode"); | ||
| 86 | return ERROR_UNSUPPORTED_OPEN_FLAGS; | ||
| 87 | } | ||
| 88 | |||
| 89 | if (mode.create_flag) { | ||
| 90 | LOG_ERROR(Service_FS, "Create flag is not supported"); | ||
| 91 | return ERROR_UNSUPPORTED_OPEN_FLAGS; | ||
| 92 | } | ||
| 93 | |||
| 94 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 95 | |||
| 96 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 97 | case PathParser::InvalidMountPoint: | ||
| 98 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 99 | return ERROR_FILE_NOT_FOUND; | ||
| 100 | case PathParser::PathNotFound: | ||
| 101 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 102 | return ERROR_PATH_NOT_FOUND; | ||
| 103 | case PathParser::FileInPath: | ||
| 104 | case PathParser::DirectoryFound: | ||
| 105 | LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str()); | ||
| 106 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; | ||
| 107 | case PathParser::NotFound: | ||
| 108 | LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); | ||
| 109 | return ERROR_FILE_NOT_FOUND; | ||
| 110 | } | ||
| 111 | |||
| 112 | FileUtil::IOFile file(full_path, "r+b"); | ||
| 113 | if (!file.IsOpen()) { | ||
| 114 | LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); | ||
| 115 | return ERROR_FILE_NOT_FOUND; | ||
| 116 | } | ||
| 117 | |||
| 118 | Mode rwmode; | ||
| 119 | rwmode.write_flag.Assign(1); | ||
| 120 | rwmode.read_flag.Assign(1); | ||
| 121 | auto disk_file = std::make_unique<FixSizeDiskFile>(std::move(file), rwmode); | ||
| 122 | return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file)); | ||
| 123 | } | ||
| 124 | |||
| 125 | ResultCode CreateFile(const Path& path, u64 size) const override { | ||
| 126 | if (size == 0) { | ||
| 127 | LOG_ERROR(Service_FS, "Zero-size file is not supported"); | ||
| 128 | return ERROR_UNSUPPORTED_OPEN_FLAGS; | ||
| 129 | } | ||
| 130 | return SaveDataArchive::CreateFile(path, size); | ||
| 131 | } | ||
| 132 | }; | ||
| 133 | |||
| 21 | std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) { | 134 | std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) { |
| 22 | std::vector<u8> vec_data = path.AsBinary(); | 135 | std::vector<u8> vec_data = path.AsBinary(); |
| 23 | const u32* data = reinterpret_cast<const u32*>(vec_data.data()); | 136 | const u32* data = reinterpret_cast<const u32*>(vec_data.data()); |
| @@ -84,7 +197,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons | |||
| 84 | ErrorSummary::InvalidState, ErrorLevel::Status); | 197 | ErrorSummary::InvalidState, ErrorLevel::Status); |
| 85 | } | 198 | } |
| 86 | } | 199 | } |
| 87 | auto archive = std::make_unique<DiskArchive>(fullpath); | 200 | auto archive = std::make_unique<ExtSaveDataArchive>(fullpath); |
| 88 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | 201 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
| 89 | } | 202 | } |
| 90 | 203 | ||
diff --git a/src/core/file_sys/archive_savedatacheck.cpp b/src/core/file_sys/archive_ncch.cpp index 6c4542b7d..6f1aadfc3 100644 --- a/src/core/file_sys/archive_savedatacheck.cpp +++ b/src/core/file_sys/archive_ncch.cpp | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "common/file_util.h" | 9 | #include "common/file_util.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/string_util.h" | 11 | #include "common/string_util.h" |
| 12 | #include "core/file_sys/archive_savedatacheck.h" | 12 | #include "core/file_sys/archive_ncch.h" |
| 13 | #include "core/file_sys/ivfc_archive.h" | 13 | #include "core/file_sys/ivfc_archive.h" |
| 14 | #include "core/hle/service/fs/archive.h" | 14 | #include "core/hle/service/fs/archive.h" |
| 15 | 15 | ||
| @@ -18,22 +18,22 @@ | |||
| 18 | 18 | ||
| 19 | namespace FileSys { | 19 | namespace FileSys { |
| 20 | 20 | ||
| 21 | static std::string GetSaveDataCheckContainerPath(const std::string& nand_directory) { | 21 | static std::string GetNCCHContainerPath(const std::string& nand_directory) { |
| 22 | return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str()); | 22 | return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str()); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | static std::string GetSaveDataCheckPath(const std::string& mount_point, u32 high, u32 low) { | 25 | static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) { |
| 26 | return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(), | 26 | return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(), |
| 27 | high, low); | 27 | high, low); |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory) | 30 | ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory) |
| 31 | : mount_point(GetSaveDataCheckContainerPath(nand_directory)) {} | 31 | : mount_point(GetNCCHContainerPath(nand_directory)) {} |
| 32 | 32 | ||
| 33 | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(const Path& path) { | 33 | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) { |
| 34 | auto vec = path.AsBinary(); | 34 | auto vec = path.AsBinary(); |
| 35 | const u32* data = reinterpret_cast<u32*>(vec.data()); | 35 | const u32* data = reinterpret_cast<u32*>(vec.data()); |
| 36 | std::string file_path = GetSaveDataCheckPath(mount_point, data[1], data[0]); | 36 | std::string file_path = GetNCCHPath(mount_point, data[1], data[0]); |
| 37 | auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb"); | 37 | auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb"); |
| 38 | 38 | ||
| 39 | if (!file->IsOpen()) { | 39 | if (!file->IsOpen()) { |
| @@ -45,15 +45,15 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(co | |||
| 45 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | 45 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path, | 48 | ResultCode ArchiveFactory_NCCH::Format(const Path& path, |
| 49 | const FileSys::ArchiveFormatInfo& format_info) { | 49 | const FileSys::ArchiveFormatInfo& format_info) { |
| 50 | LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive."); | 50 | LOG_ERROR(Service_FS, "Attempted to format a NCCH archive."); |
| 51 | // TODO: Verify error code | 51 | // TODO: Verify error code |
| 52 | return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, | 52 | return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, |
| 53 | ErrorLevel::Permanent); | 53 | ErrorLevel::Permanent); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const { | 56 | ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const { |
| 57 | // TODO(Subv): Implement | 57 | // TODO(Subv): Implement |
| 58 | LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); | 58 | LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); |
| 59 | return ResultCode(-1); | 59 | return ResultCode(-1); |
diff --git a/src/core/file_sys/archive_savedatacheck.h b/src/core/file_sys/archive_ncch.h index e9cafbed9..66b8ce75d 100644 --- a/src/core/file_sys/archive_savedatacheck.h +++ b/src/core/file_sys/archive_ncch.h | |||
| @@ -14,13 +14,13 @@ | |||
| 14 | 14 | ||
| 15 | namespace FileSys { | 15 | namespace FileSys { |
| 16 | 16 | ||
| 17 | /// File system interface to the SaveDataCheck archive | 17 | /// File system interface to the NCCH archive |
| 18 | class ArchiveFactory_SaveDataCheck final : public ArchiveFactory { | 18 | class ArchiveFactory_NCCH final : public ArchiveFactory { |
| 19 | public: | 19 | public: |
| 20 | ArchiveFactory_SaveDataCheck(const std::string& mount_point); | 20 | ArchiveFactory_NCCH(const std::string& mount_point); |
| 21 | 21 | ||
| 22 | std::string GetName() const override { | 22 | std::string GetName() const override { |
| 23 | return "SaveDataCheck"; | 23 | return "NCCH"; |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; | 26 | ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; |
diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp index 6711035ec..ecb44a215 100644 --- a/src/core/file_sys/archive_savedata.cpp +++ b/src/core/file_sys/archive_savedata.cpp | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 10 | #include "common/string_util.h" | 10 | #include "common/string_util.h" |
| 11 | #include "core/file_sys/archive_savedata.h" | 11 | #include "core/file_sys/archive_savedata.h" |
| 12 | #include "core/file_sys/disk_archive.h" | 12 | #include "core/file_sys/savedata_archive.h" |
| 13 | #include "core/hle/kernel/process.h" | 13 | #include "core/hle/kernel/process.h" |
| 14 | #include "core/hle/service/fs/archive.h" | 14 | #include "core/hle/service/fs/archive.h" |
| 15 | 15 | ||
| @@ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P | |||
| 54 | ErrorSummary::InvalidState, ErrorLevel::Status); | 54 | ErrorSummary::InvalidState, ErrorLevel::Status); |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point)); | 57 | auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point)); |
| 58 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | 58 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
| 59 | } | 59 | } |
| 60 | 60 | ||
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index bcb03ed36..333dfb92e 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp | |||
| @@ -8,6 +8,8 @@ | |||
| 8 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 9 | #include "core/file_sys/archive_sdmc.h" | 9 | #include "core/file_sys/archive_sdmc.h" |
| 10 | #include "core/file_sys/disk_archive.h" | 10 | #include "core/file_sys/disk_archive.h" |
| 11 | #include "core/file_sys/errors.h" | ||
| 12 | #include "core/file_sys/path_parser.h" | ||
| 11 | #include "core/settings.h" | 13 | #include "core/settings.h" |
| 12 | 14 | ||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 15 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| @@ -15,6 +17,281 @@ | |||
| 15 | 17 | ||
| 16 | namespace FileSys { | 18 | namespace FileSys { |
| 17 | 19 | ||
| 20 | ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path, | ||
| 21 | const Mode& mode) const { | ||
| 22 | Mode modified_mode; | ||
| 23 | modified_mode.hex = mode.hex; | ||
| 24 | |||
| 25 | // SDMC archive always opens a file with at least read permission | ||
| 26 | modified_mode.read_flag.Assign(1); | ||
| 27 | |||
| 28 | return OpenFileBase(path, modified_mode); | ||
| 29 | } | ||
| 30 | |||
| 31 | ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& path, | ||
| 32 | const Mode& mode) const { | ||
| 33 | LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); | ||
| 34 | |||
| 35 | const PathParser path_parser(path); | ||
| 36 | |||
| 37 | if (!path_parser.IsValid()) { | ||
| 38 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 39 | return ERROR_INVALID_PATH; | ||
| 40 | } | ||
| 41 | |||
| 42 | if (mode.hex == 0) { | ||
| 43 | LOG_ERROR(Service_FS, "Empty open mode"); | ||
| 44 | return ERROR_INVALID_OPEN_FLAGS; | ||
| 45 | } | ||
| 46 | |||
| 47 | if (mode.create_flag && !mode.write_flag) { | ||
| 48 | LOG_ERROR(Service_FS, "Create flag set but write flag not set"); | ||
| 49 | return ERROR_INVALID_OPEN_FLAGS; | ||
| 50 | } | ||
| 51 | |||
| 52 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 53 | |||
| 54 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 55 | case PathParser::InvalidMountPoint: | ||
| 56 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 57 | return ERROR_NOT_FOUND; | ||
| 58 | case PathParser::PathNotFound: | ||
| 59 | case PathParser::FileInPath: | ||
| 60 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 61 | return ERROR_NOT_FOUND; | ||
| 62 | case PathParser::DirectoryFound: | ||
| 63 | LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str()); | ||
| 64 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; | ||
| 65 | case PathParser::NotFound: | ||
| 66 | if (!mode.create_flag) { | ||
| 67 | LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", | ||
| 68 | full_path.c_str()); | ||
| 69 | return ERROR_NOT_FOUND; | ||
| 70 | } else { | ||
| 71 | // Create the file | ||
| 72 | FileUtil::CreateEmptyFile(full_path); | ||
| 73 | } | ||
| 74 | break; | ||
| 75 | } | ||
| 76 | |||
| 77 | FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); | ||
| 78 | if (!file.IsOpen()) { | ||
| 79 | LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); | ||
| 80 | return ERROR_NOT_FOUND; | ||
| 81 | } | ||
| 82 | |||
| 83 | auto disk_file = std::make_unique<DiskFile>(std::move(file), mode); | ||
| 84 | return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file)); | ||
| 85 | } | ||
| 86 | |||
| 87 | ResultCode SDMCArchive::DeleteFile(const Path& path) const { | ||
| 88 | const PathParser path_parser(path); | ||
| 89 | |||
| 90 | if (!path_parser.IsValid()) { | ||
| 91 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 92 | return ERROR_INVALID_PATH; | ||
| 93 | } | ||
| 94 | |||
| 95 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 96 | |||
| 97 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 98 | case PathParser::InvalidMountPoint: | ||
| 99 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 100 | return ERROR_NOT_FOUND; | ||
| 101 | case PathParser::PathNotFound: | ||
| 102 | case PathParser::FileInPath: | ||
| 103 | case PathParser::NotFound: | ||
| 104 | LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); | ||
| 105 | return ERROR_NOT_FOUND; | ||
| 106 | case PathParser::DirectoryFound: | ||
| 107 | LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str()); | ||
| 108 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; | ||
| 109 | } | ||
| 110 | |||
| 111 | if (FileUtil::Delete(full_path)) { | ||
| 112 | return RESULT_SUCCESS; | ||
| 113 | } | ||
| 114 | |||
| 115 | LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str()); | ||
| 116 | return ERROR_NOT_FOUND; | ||
| 117 | } | ||
| 118 | |||
| 119 | ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { | ||
| 120 | if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) { | ||
| 121 | return RESULT_SUCCESS; | ||
| 122 | } | ||
| 123 | |||
| 124 | // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't | ||
| 125 | // exist or similar. Verify. | ||
| 126 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 127 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 128 | } | ||
| 129 | |||
| 130 | template <typename T> | ||
| 131 | static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point, | ||
| 132 | T deleter) { | ||
| 133 | const PathParser path_parser(path); | ||
| 134 | |||
| 135 | if (!path_parser.IsValid()) { | ||
| 136 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 137 | return ERROR_INVALID_PATH; | ||
| 138 | } | ||
| 139 | |||
| 140 | if (path_parser.IsRootDirectory()) | ||
| 141 | return ERROR_NOT_FOUND; | ||
| 142 | |||
| 143 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 144 | |||
| 145 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 146 | case PathParser::InvalidMountPoint: | ||
| 147 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 148 | return ERROR_NOT_FOUND; | ||
| 149 | case PathParser::PathNotFound: | ||
| 150 | case PathParser::NotFound: | ||
| 151 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 152 | return ERROR_NOT_FOUND; | ||
| 153 | case PathParser::FileInPath: | ||
| 154 | case PathParser::FileFound: | ||
| 155 | LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); | ||
| 156 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; | ||
| 157 | } | ||
| 158 | |||
| 159 | if (deleter(full_path)) { | ||
| 160 | return RESULT_SUCCESS; | ||
| 161 | } | ||
| 162 | |||
| 163 | LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str()); | ||
| 164 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; | ||
| 165 | } | ||
| 166 | |||
| 167 | ResultCode SDMCArchive::DeleteDirectory(const Path& path) const { | ||
| 168 | return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir); | ||
| 169 | } | ||
| 170 | |||
| 171 | ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const { | ||
| 172 | return DeleteDirectoryHelper( | ||
| 173 | path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); | ||
| 174 | } | ||
| 175 | |||
| 176 | ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { | ||
| 177 | const PathParser path_parser(path); | ||
| 178 | |||
| 179 | if (!path_parser.IsValid()) { | ||
| 180 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 181 | return ERROR_INVALID_PATH; | ||
| 182 | } | ||
| 183 | |||
| 184 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 185 | |||
| 186 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 187 | case PathParser::InvalidMountPoint: | ||
| 188 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 189 | return ERROR_NOT_FOUND; | ||
| 190 | case PathParser::PathNotFound: | ||
| 191 | case PathParser::FileInPath: | ||
| 192 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 193 | return ERROR_NOT_FOUND; | ||
| 194 | case PathParser::DirectoryFound: | ||
| 195 | LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); | ||
| 196 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; | ||
| 197 | case PathParser::FileFound: | ||
| 198 | LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); | ||
| 199 | return ERROR_ALREADY_EXISTS; | ||
| 200 | } | ||
| 201 | |||
| 202 | if (size == 0) { | ||
| 203 | FileUtil::CreateEmptyFile(full_path); | ||
| 204 | return RESULT_SUCCESS; | ||
| 205 | } | ||
| 206 | |||
| 207 | FileUtil::IOFile file(full_path, "wb"); | ||
| 208 | // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) | ||
| 209 | // We do this by seeking to the right size, then writing a single null byte. | ||
| 210 | if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { | ||
| 211 | return RESULT_SUCCESS; | ||
| 212 | } | ||
| 213 | |||
| 214 | LOG_ERROR(Service_FS, "Too large file"); | ||
| 215 | return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, | ||
| 216 | ErrorLevel::Info); | ||
| 217 | } | ||
| 218 | |||
| 219 | ResultCode SDMCArchive::CreateDirectory(const Path& path) const { | ||
| 220 | const PathParser path_parser(path); | ||
| 221 | |||
| 222 | if (!path_parser.IsValid()) { | ||
| 223 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 224 | return ERROR_INVALID_PATH; | ||
| 225 | } | ||
| 226 | |||
| 227 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 228 | |||
| 229 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 230 | case PathParser::InvalidMountPoint: | ||
| 231 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 232 | return ERROR_NOT_FOUND; | ||
| 233 | case PathParser::PathNotFound: | ||
| 234 | case PathParser::FileInPath: | ||
| 235 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 236 | return ERROR_NOT_FOUND; | ||
| 237 | case PathParser::DirectoryFound: | ||
| 238 | case PathParser::FileFound: | ||
| 239 | LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); | ||
| 240 | return ERROR_ALREADY_EXISTS; | ||
| 241 | } | ||
| 242 | |||
| 243 | if (FileUtil::CreateDir(mount_point + path.AsString())) { | ||
| 244 | return RESULT_SUCCESS; | ||
| 245 | } | ||
| 246 | |||
| 247 | LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str()); | ||
| 248 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled, | ||
| 249 | ErrorLevel::Status); | ||
| 250 | } | ||
| 251 | |||
| 252 | ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { | ||
| 253 | if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) | ||
| 254 | return RESULT_SUCCESS; | ||
| 255 | |||
| 256 | // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't | ||
| 257 | // exist or similar. Verify. | ||
| 258 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 259 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 260 | } | ||
| 261 | |||
| 262 | ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) const { | ||
| 263 | const PathParser path_parser(path); | ||
| 264 | |||
| 265 | if (!path_parser.IsValid()) { | ||
| 266 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 267 | return ERROR_INVALID_PATH; | ||
| 268 | } | ||
| 269 | |||
| 270 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 271 | |||
| 272 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 273 | case PathParser::InvalidMountPoint: | ||
| 274 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 275 | return ERROR_NOT_FOUND; | ||
| 276 | case PathParser::PathNotFound: | ||
| 277 | case PathParser::NotFound: | ||
| 278 | case PathParser::FileFound: | ||
| 279 | LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); | ||
| 280 | return ERROR_NOT_FOUND; | ||
| 281 | case PathParser::FileInPath: | ||
| 282 | LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); | ||
| 283 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; | ||
| 284 | } | ||
| 285 | |||
| 286 | auto directory = std::make_unique<DiskDirectory>(full_path); | ||
| 287 | return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); | ||
| 288 | } | ||
| 289 | |||
| 290 | u64 SDMCArchive::GetFreeBytes() const { | ||
| 291 | // TODO: Stubbed to return 1GiB | ||
| 292 | return 1024 * 1024 * 1024; | ||
| 293 | } | ||
| 294 | |||
| 18 | ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory) | 295 | ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory) |
| 19 | : sdmc_directory(sdmc_directory) { | 296 | : sdmc_directory(sdmc_directory) { |
| 20 | LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); | 297 | LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); |
| @@ -35,7 +312,7 @@ bool ArchiveFactory_SDMC::Initialize() { | |||
| 35 | } | 312 | } |
| 36 | 313 | ||
| 37 | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) { | 314 | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) { |
| 38 | auto archive = std::make_unique<DiskArchive>(sdmc_directory); | 315 | auto archive = std::make_unique<SDMCArchive>(sdmc_directory); |
| 39 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | 316 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
| 40 | } | 317 | } |
| 41 | 318 | ||
diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h index 88e855351..9d99b110c 100644 --- a/src/core/file_sys/archive_sdmc.h +++ b/src/core/file_sys/archive_sdmc.h | |||
| @@ -14,6 +14,32 @@ | |||
| 14 | 14 | ||
| 15 | namespace FileSys { | 15 | namespace FileSys { |
| 16 | 16 | ||
| 17 | /// Archive backend for SDMC archive | ||
| 18 | class SDMCArchive : public ArchiveBackend { | ||
| 19 | public: | ||
| 20 | SDMCArchive(const std::string& mount_point_) : mount_point(mount_point_) {} | ||
| 21 | |||
| 22 | std::string GetName() const override { | ||
| 23 | return "SDMCArchive: " + mount_point; | ||
| 24 | } | ||
| 25 | |||
| 26 | ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||
| 27 | const Mode& mode) const override; | ||
| 28 | ResultCode DeleteFile(const Path& path) const override; | ||
| 29 | ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; | ||
| 30 | ResultCode DeleteDirectory(const Path& path) const override; | ||
| 31 | ResultCode DeleteDirectoryRecursively(const Path& path) const override; | ||
| 32 | ResultCode CreateFile(const Path& path, u64 size) const override; | ||
| 33 | ResultCode CreateDirectory(const Path& path) const override; | ||
| 34 | ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||
| 35 | ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||
| 36 | u64 GetFreeBytes() const override; | ||
| 37 | |||
| 38 | protected: | ||
| 39 | ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const; | ||
| 40 | std::string mount_point; | ||
| 41 | }; | ||
| 42 | |||
| 17 | /// File system interface to the SDMC archive | 43 | /// File system interface to the SDMC archive |
| 18 | class ArchiveFactory_SDMC final : public ArchiveFactory { | 44 | class ArchiveFactory_SDMC final : public ArchiveFactory { |
| 19 | public: | 45 | public: |
diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp new file mode 100644 index 000000000..2aafc9b1d --- /dev/null +++ b/src/core/file_sys/archive_sdmcwriteonly.cpp | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <memory> | ||
| 6 | #include "common/file_util.h" | ||
| 7 | #include "core/file_sys/archive_sdmcwriteonly.h" | ||
| 8 | #include "core/file_sys/directory_backend.h" | ||
| 9 | #include "core/file_sys/errors.h" | ||
| 10 | #include "core/file_sys/file_backend.h" | ||
| 11 | #include "core/settings.h" | ||
| 12 | |||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 14 | // FileSys namespace | ||
| 15 | |||
| 16 | namespace FileSys { | ||
| 17 | |||
| 18 | ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path, | ||
| 19 | const Mode& mode) const { | ||
| 20 | if (mode.read_flag) { | ||
| 21 | LOG_ERROR(Service_FS, "Read flag is not supported"); | ||
| 22 | return ERROR_INVALID_READ_FLAG; | ||
| 23 | } | ||
| 24 | return SDMCArchive::OpenFileBase(path, mode); | ||
| 25 | } | ||
| 26 | |||
| 27 | ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory( | ||
| 28 | const Path& path) const { | ||
| 29 | LOG_ERROR(Service_FS, "Not supported"); | ||
| 30 | return ERROR_UNSUPPORTED_OPEN_FLAGS; | ||
| 31 | } | ||
| 32 | |||
| 33 | ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point) | ||
| 34 | : sdmc_directory(mount_point) { | ||
| 35 | LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str()); | ||
| 36 | } | ||
| 37 | |||
| 38 | bool ArchiveFactory_SDMCWriteOnly::Initialize() { | ||
| 39 | if (!Settings::values.use_virtual_sd) { | ||
| 40 | LOG_WARNING(Service_FS, "SDMC disabled by config."); | ||
| 41 | return false; | ||
| 42 | } | ||
| 43 | |||
| 44 | if (!FileUtil::CreateFullPath(sdmc_directory)) { | ||
| 45 | LOG_ERROR(Service_FS, "Unable to create SDMC path."); | ||
| 46 | return false; | ||
| 47 | } | ||
| 48 | |||
| 49 | return true; | ||
| 50 | } | ||
| 51 | |||
| 52 | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) { | ||
| 53 | auto archive = std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory); | ||
| 54 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||
| 55 | } | ||
| 56 | |||
| 57 | ResultCode ArchiveFactory_SDMCWriteOnly::Format(const Path& path, | ||
| 58 | const FileSys::ArchiveFormatInfo& format_info) { | ||
| 59 | // TODO(wwylele): hwtest this | ||
| 60 | LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive."); | ||
| 61 | return ResultCode(-1); | ||
| 62 | } | ||
| 63 | |||
| 64 | ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path) const { | ||
| 65 | // TODO(Subv): Implement | ||
| 66 | LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); | ||
| 67 | return ResultCode(-1); | ||
| 68 | } | ||
| 69 | |||
| 70 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/archive_sdmcwriteonly.h b/src/core/file_sys/archive_sdmcwriteonly.h new file mode 100644 index 000000000..ed977485a --- /dev/null +++ b/src/core/file_sys/archive_sdmcwriteonly.h | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "core/file_sys/archive_sdmc.h" | ||
| 8 | |||
| 9 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 10 | // FileSys namespace | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Archive backend for SDMC write-only archive. | ||
| 16 | * The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for | ||
| 17 | * - OpenDirectory is unsupported; | ||
| 18 | * - OpenFile with read flag is unsupported. | ||
| 19 | */ | ||
| 20 | class SDMCWriteOnlyArchive : public SDMCArchive { | ||
| 21 | public: | ||
| 22 | SDMCWriteOnlyArchive(const std::string& mount_point) : SDMCArchive(mount_point) {} | ||
| 23 | |||
| 24 | std::string GetName() const override { | ||
| 25 | return "SDMCWriteOnlyArchive: " + mount_point; | ||
| 26 | } | ||
| 27 | |||
| 28 | ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||
| 29 | const Mode& mode) const override; | ||
| 30 | |||
| 31 | ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||
| 32 | }; | ||
| 33 | |||
| 34 | /// File system interface to the SDMC write-only archive | ||
| 35 | class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory { | ||
| 36 | public: | ||
| 37 | ArchiveFactory_SDMCWriteOnly(const std::string& mount_point); | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Initialize the archive. | ||
| 41 | * @return true if it initialized successfully | ||
| 42 | */ | ||
| 43 | bool Initialize(); | ||
| 44 | |||
| 45 | std::string GetName() const override { | ||
| 46 | return "SDMCWriteOnly"; | ||
| 47 | } | ||
| 48 | |||
| 49 | ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; | ||
| 50 | ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; | ||
| 51 | ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; | ||
| 52 | |||
| 53 | private: | ||
| 54 | std::string sdmc_directory; | ||
| 55 | }; | ||
| 56 | |||
| 57 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index 48ebc0ed4..54e7793e0 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | #include "common/file_util.h" | 9 | #include "common/file_util.h" |
| 10 | #include "common/string_util.h" | 10 | #include "common/string_util.h" |
| 11 | #include "core/file_sys/archive_systemsavedata.h" | 11 | #include "core/file_sys/archive_systemsavedata.h" |
| 12 | #include "core/file_sys/disk_archive.h" | 12 | #include "core/file_sys/savedata_archive.h" |
| 13 | #include "core/hle/service/fs/archive.h" | 13 | #include "core/hle/service/fs/archive.h" |
| 14 | 14 | ||
| 15 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 15 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| @@ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c | |||
| 56 | return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, | 56 | return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, |
| 57 | ErrorSummary::InvalidState, ErrorLevel::Status); | 57 | ErrorSummary::InvalidState, ErrorLevel::Status); |
| 58 | } | 58 | } |
| 59 | auto archive = std::make_unique<DiskArchive>(fullpath); | 59 | auto archive = std::make_unique<SaveDataArchive>(fullpath); |
| 60 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | 60 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
| 61 | } | 61 | } |
| 62 | 62 | ||
diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h index b55e382ef..0c93f2074 100644 --- a/src/core/file_sys/directory_backend.h +++ b/src/core/file_sys/directory_backend.h | |||
| @@ -41,12 +41,6 @@ public: | |||
| 41 | virtual ~DirectoryBackend() {} | 41 | virtual ~DirectoryBackend() {} |
| 42 | 42 | ||
| 43 | /** | 43 | /** |
| 44 | * Open the directory | ||
| 45 | * @return true if the directory opened correctly | ||
| 46 | */ | ||
| 47 | virtual bool Open() = 0; | ||
| 48 | |||
| 49 | /** | ||
| 50 | * List files contained in the directory | 44 | * List files contained in the directory |
| 51 | * @param count Number of entries to return at once in entries | 45 | * @param count Number of entries to return at once in entries |
| 52 | * @param entries Buffer to read data into | 46 | * @param entries Buffer to read data into |
diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index 2f05af361..a243d9a13 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp | |||
| @@ -15,144 +15,8 @@ | |||
| 15 | 15 | ||
| 16 | namespace FileSys { | 16 | namespace FileSys { |
| 17 | 17 | ||
| 18 | ResultVal<std::unique_ptr<FileBackend>> DiskArchive::OpenFile(const Path& path, | ||
| 19 | const Mode mode) const { | ||
| 20 | LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); | ||
| 21 | auto file = std::make_unique<DiskFile>(*this, path, mode); | ||
| 22 | ResultCode result = file->Open(); | ||
| 23 | if (result.IsError()) | ||
| 24 | return result; | ||
| 25 | return MakeResult<std::unique_ptr<FileBackend>>(std::move(file)); | ||
| 26 | } | ||
| 27 | |||
| 28 | ResultCode DiskArchive::DeleteFile(const Path& path) const { | ||
| 29 | std::string file_path = mount_point + path.AsString(); | ||
| 30 | |||
| 31 | if (FileUtil::IsDirectory(file_path)) | ||
| 32 | return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, | ||
| 33 | ErrorLevel::Status); | ||
| 34 | |||
| 35 | if (!FileUtil::Exists(file_path)) | ||
| 36 | return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, | ||
| 37 | ErrorLevel::Status); | ||
| 38 | |||
| 39 | if (FileUtil::Delete(file_path)) | ||
| 40 | return RESULT_SUCCESS; | ||
| 41 | |||
| 42 | return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, | ||
| 43 | ErrorLevel::Status); | ||
| 44 | } | ||
| 45 | |||
| 46 | bool DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const { | ||
| 47 | return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()); | ||
| 48 | } | ||
| 49 | |||
| 50 | bool DiskArchive::DeleteDirectory(const Path& path) const { | ||
| 51 | return FileUtil::DeleteDir(mount_point + path.AsString()); | ||
| 52 | } | ||
| 53 | |||
| 54 | bool DiskArchive::DeleteDirectoryRecursively(const Path& path) const { | ||
| 55 | return FileUtil::DeleteDirRecursively(mount_point + path.AsString()); | ||
| 56 | } | ||
| 57 | |||
| 58 | ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const { | ||
| 59 | std::string full_path = mount_point + path.AsString(); | ||
| 60 | |||
| 61 | if (FileUtil::IsDirectory(full_path)) | ||
| 62 | return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, | ||
| 63 | ErrorLevel::Status); | ||
| 64 | |||
| 65 | if (FileUtil::Exists(full_path)) | ||
| 66 | return ResultCode(ErrorDescription::FS_AlreadyExists, ErrorModule::FS, | ||
| 67 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 68 | |||
| 69 | if (size == 0) { | ||
| 70 | FileUtil::CreateEmptyFile(full_path); | ||
| 71 | return RESULT_SUCCESS; | ||
| 72 | } | ||
| 73 | |||
| 74 | FileUtil::IOFile file(full_path, "wb"); | ||
| 75 | // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) | ||
| 76 | // We do this by seeking to the right size, then writing a single null byte. | ||
| 77 | if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) | ||
| 78 | return RESULT_SUCCESS; | ||
| 79 | |||
| 80 | return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, | ||
| 81 | ErrorLevel::Info); | ||
| 82 | } | ||
| 83 | |||
| 84 | bool DiskArchive::CreateDirectory(const Path& path) const { | ||
| 85 | return FileUtil::CreateDir(mount_point + path.AsString()); | ||
| 86 | } | ||
| 87 | |||
| 88 | bool DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { | ||
| 89 | return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()); | ||
| 90 | } | ||
| 91 | |||
| 92 | std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) const { | ||
| 93 | LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); | ||
| 94 | auto directory = std::make_unique<DiskDirectory>(*this, path); | ||
| 95 | if (!directory->Open()) | ||
| 96 | return nullptr; | ||
| 97 | return std::move(directory); | ||
| 98 | } | ||
| 99 | |||
| 100 | u64 DiskArchive::GetFreeBytes() const { | ||
| 101 | // TODO: Stubbed to return 1GiB | ||
| 102 | return 1024 * 1024 * 1024; | ||
| 103 | } | ||
| 104 | |||
| 105 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 106 | |||
| 107 | DiskFile::DiskFile(const DiskArchive& archive, const Path& path, const Mode mode) { | ||
| 108 | // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass | ||
| 109 | // the root directory we set while opening the archive. | ||
| 110 | // For example, opening /../../etc/passwd can give the emulated program your users list. | ||
| 111 | this->path = archive.mount_point + path.AsString(); | ||
| 112 | this->mode.hex = mode.hex; | ||
| 113 | } | ||
| 114 | |||
| 115 | ResultCode DiskFile::Open() { | ||
| 116 | if (FileUtil::IsDirectory(path)) | ||
| 117 | return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, | ||
| 118 | ErrorLevel::Status); | ||
| 119 | |||
| 120 | // Specifying only the Create flag is invalid | ||
| 121 | if (mode.create_flag && !mode.read_flag && !mode.write_flag) { | ||
| 122 | return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, | ||
| 123 | ErrorSummary::Canceled, ErrorLevel::Status); | ||
| 124 | } | ||
| 125 | |||
| 126 | if (!FileUtil::Exists(path)) { | ||
| 127 | if (!mode.create_flag) { | ||
| 128 | LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", | ||
| 129 | path.c_str()); | ||
| 130 | return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, | ||
| 131 | ErrorSummary::NotFound, ErrorLevel::Status); | ||
| 132 | } else { | ||
| 133 | // Create the file | ||
| 134 | FileUtil::CreateEmptyFile(path); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | std::string mode_string = ""; | ||
| 139 | if (mode.write_flag) | ||
| 140 | mode_string += "r+"; // Files opened with Write access can be read from | ||
| 141 | else if (mode.read_flag) | ||
| 142 | mode_string += "r"; | ||
| 143 | |||
| 144 | // Open the file in binary mode, to avoid problems with CR/LF on Windows systems | ||
| 145 | mode_string += "b"; | ||
| 146 | |||
| 147 | file = std::make_unique<FileUtil::IOFile>(path, mode_string.c_str()); | ||
| 148 | if (file->IsOpen()) | ||
| 149 | return RESULT_SUCCESS; | ||
| 150 | return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, | ||
| 151 | ErrorLevel::Status); | ||
| 152 | } | ||
| 153 | |||
| 154 | ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { | 18 | ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { |
| 155 | if (!mode.read_flag && !mode.write_flag) | 19 | if (!mode.read_flag) |
| 156 | return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, | 20 | return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, |
| 157 | ErrorSummary::Canceled, ErrorLevel::Status); | 21 | ErrorSummary::Canceled, ErrorLevel::Status); |
| 158 | 22 | ||
| @@ -189,21 +53,11 @@ bool DiskFile::Close() const { | |||
| 189 | 53 | ||
| 190 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 54 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 191 | 55 | ||
| 192 | DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() { | 56 | DiskDirectory::DiskDirectory(const std::string& path) : directory() { |
| 193 | // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass | ||
| 194 | // the root directory we set while opening the archive. | ||
| 195 | // For example, opening /../../usr/bin can give the emulated program your installed programs. | ||
| 196 | this->path = archive.mount_point + path.AsString(); | ||
| 197 | } | ||
| 198 | |||
| 199 | bool DiskDirectory::Open() { | ||
| 200 | if (!FileUtil::IsDirectory(path)) | ||
| 201 | return false; | ||
| 202 | unsigned size = FileUtil::ScanDirectoryTree(path, directory); | 57 | unsigned size = FileUtil::ScanDirectoryTree(path, directory); |
| 203 | directory.size = size; | 58 | directory.size = size; |
| 204 | directory.isDirectory = true; | 59 | directory.isDirectory = true; |
| 205 | children_iterator = directory.children.begin(); | 60 | children_iterator = directory.children.begin(); |
| 206 | return true; | ||
| 207 | } | 61 | } |
| 208 | 62 | ||
| 209 | u32 DiskDirectory::Read(const u32 count, Entry* entries) { | 63 | u32 DiskDirectory::Read(const u32 count, Entry* entries) { |
diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index 59ebb2002..eb9166df6 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h | |||
| @@ -20,43 +20,13 @@ | |||
| 20 | 20 | ||
| 21 | namespace FileSys { | 21 | namespace FileSys { |
| 22 | 22 | ||
| 23 | /** | ||
| 24 | * Helper which implements a backend accessing the host machine's filesystem. | ||
| 25 | * This should be subclassed by concrete archive types, which will provide the | ||
| 26 | * base directory on the host filesystem and override any required functionality. | ||
| 27 | */ | ||
| 28 | class DiskArchive : public ArchiveBackend { | ||
| 29 | public: | ||
| 30 | DiskArchive(const std::string& mount_point_) : mount_point(mount_point_) {} | ||
| 31 | |||
| 32 | virtual std::string GetName() const override { | ||
| 33 | return "DiskArchive: " + mount_point; | ||
| 34 | } | ||
| 35 | |||
| 36 | ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||
| 37 | const Mode mode) const override; | ||
| 38 | ResultCode DeleteFile(const Path& path) const override; | ||
| 39 | bool RenameFile(const Path& src_path, const Path& dest_path) const override; | ||
| 40 | bool DeleteDirectory(const Path& path) const override; | ||
| 41 | bool DeleteDirectoryRecursively(const Path& path) const override; | ||
| 42 | ResultCode CreateFile(const Path& path, u64 size) const override; | ||
| 43 | bool CreateDirectory(const Path& path) const override; | ||
| 44 | bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||
| 45 | std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; | ||
| 46 | u64 GetFreeBytes() const override; | ||
| 47 | |||
| 48 | protected: | ||
| 49 | friend class DiskFile; | ||
| 50 | friend class DiskDirectory; | ||
| 51 | |||
| 52 | std::string mount_point; | ||
| 53 | }; | ||
| 54 | |||
| 55 | class DiskFile : public FileBackend { | 23 | class DiskFile : public FileBackend { |
| 56 | public: | 24 | public: |
| 57 | DiskFile(const DiskArchive& archive, const Path& path, const Mode mode); | 25 | DiskFile(FileUtil::IOFile&& file_, const Mode& mode_) |
| 26 | : file(new FileUtil::IOFile(std::move(file_))) { | ||
| 27 | mode.hex = mode_.hex; | ||
| 28 | } | ||
| 58 | 29 | ||
| 59 | ResultCode Open() override; | ||
| 60 | ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | 30 | ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; |
| 61 | ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | 31 | ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; |
| 62 | u64 GetSize() const override; | 32 | u64 GetSize() const override; |
| @@ -68,20 +38,18 @@ public: | |||
| 68 | } | 38 | } |
| 69 | 39 | ||
| 70 | protected: | 40 | protected: |
| 71 | std::string path; | ||
| 72 | Mode mode; | 41 | Mode mode; |
| 73 | std::unique_ptr<FileUtil::IOFile> file; | 42 | std::unique_ptr<FileUtil::IOFile> file; |
| 74 | }; | 43 | }; |
| 75 | 44 | ||
| 76 | class DiskDirectory : public DirectoryBackend { | 45 | class DiskDirectory : public DirectoryBackend { |
| 77 | public: | 46 | public: |
| 78 | DiskDirectory(const DiskArchive& archive, const Path& path); | 47 | DiskDirectory(const std::string& path); |
| 79 | 48 | ||
| 80 | ~DiskDirectory() override { | 49 | ~DiskDirectory() override { |
| 81 | Close(); | 50 | Close(); |
| 82 | } | 51 | } |
| 83 | 52 | ||
| 84 | bool Open() override; | ||
| 85 | u32 Read(const u32 count, Entry* entries) override; | 53 | u32 Read(const u32 count, Entry* entries) override; |
| 86 | 54 | ||
| 87 | bool Close() const override { | 55 | bool Close() const override { |
| @@ -89,7 +57,6 @@ public: | |||
| 89 | } | 57 | } |
| 90 | 58 | ||
| 91 | protected: | 59 | protected: |
| 92 | std::string path; | ||
| 93 | u32 total_entries_in_directory; | 60 | u32 total_entries_in_directory; |
| 94 | FileUtil::FSTEntry directory; | 61 | FileUtil::FSTEntry directory; |
| 95 | 62 | ||
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h new file mode 100644 index 000000000..fd1b07df0 --- /dev/null +++ b/src/core/file_sys/errors.h | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "core/hle/result.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS, | ||
| 10 | ErrorSummary::InvalidArgument, ErrorLevel::Usage); | ||
| 11 | const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags, | ||
| 12 | ErrorModule::FS, ErrorSummary::NotSupported, | ||
| 13 | ErrorLevel::Usage); | ||
| 14 | const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, | ||
| 15 | ErrorSummary::Canceled, ErrorLevel::Status); | ||
| 16 | const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS, | ||
| 17 | ErrorSummary::InvalidArgument, ErrorLevel::Usage); | ||
| 18 | const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS, | ||
| 19 | ErrorSummary::NotFound, ErrorLevel::Status); | ||
| 20 | const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS, | ||
| 21 | ErrorSummary::NotFound, ErrorLevel::Status); | ||
| 22 | const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS, | ||
| 23 | ErrorSummary::NotFound, ErrorLevel::Status); | ||
| 24 | const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory, | ||
| 25 | ErrorModule::FS, ErrorSummary::NotSupported, | ||
| 26 | ErrorLevel::Usage); | ||
| 27 | const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile, | ||
| 28 | ErrorModule::FS, ErrorSummary::Canceled, | ||
| 29 | ErrorLevel::Status); | ||
| 30 | const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists, | ||
| 31 | ErrorModule::FS, ErrorSummary::NothingHappened, | ||
| 32 | ErrorLevel::Status); | ||
| 33 | const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS, | ||
| 34 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 35 | const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS, | ||
| 36 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 37 | const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS, | ||
| 38 | ErrorSummary::Canceled, ErrorLevel::Status); | ||
| 39 | |||
| 40 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h index ed997537f..5e7c2bab4 100644 --- a/src/core/file_sys/file_backend.h +++ b/src/core/file_sys/file_backend.h | |||
| @@ -19,12 +19,6 @@ public: | |||
| 19 | virtual ~FileBackend() {} | 19 | virtual ~FileBackend() {} |
| 20 | 20 | ||
| 21 | /** | 21 | /** |
| 22 | * Open the file | ||
| 23 | * @return Result of the file operation | ||
| 24 | */ | ||
| 25 | virtual ResultCode Open() = 0; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Read data from the file | 22 | * Read data from the file |
| 29 | * @param offset Offset in bytes to start reading data from | 23 | * @param offset Offset in bytes to start reading data from |
| 30 | * @param length Length in bytes of data to read from file | 24 | * @param length Length in bytes of data to read from file |
diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp index af59d296d..2735d2e3c 100644 --- a/src/core/file_sys/ivfc_archive.cpp +++ b/src/core/file_sys/ivfc_archive.cpp | |||
| @@ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const { | |||
| 18 | } | 18 | } |
| 19 | 19 | ||
| 20 | ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, | 20 | ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, |
| 21 | const Mode mode) const { | 21 | const Mode& mode) const { |
| 22 | return MakeResult<std::unique_ptr<FileBackend>>( | 22 | return MakeResult<std::unique_ptr<FileBackend>>( |
| 23 | std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); | 23 | std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); |
| 24 | } | 24 | } |
| @@ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const { | |||
| 31 | ErrorLevel::Status); | 31 | ErrorLevel::Status); |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | bool IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { | 34 | ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { |
| 35 | LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", | 35 | LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", |
| 36 | GetName().c_str()); | 36 | GetName().c_str()); |
| 37 | return false; | 37 | // TODO(wwylele): Use correct error code |
| 38 | return ResultCode(-1); | ||
| 38 | } | 39 | } |
| 39 | 40 | ||
| 40 | bool IVFCArchive::DeleteDirectory(const Path& path) const { | 41 | ResultCode IVFCArchive::DeleteDirectory(const Path& path) const { |
| 41 | LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", | 42 | LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", |
| 42 | GetName().c_str()); | 43 | GetName().c_str()); |
| 43 | return false; | 44 | // TODO(wwylele): Use correct error code |
| 45 | return ResultCode(-1); | ||
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | bool IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { | 48 | ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { |
| 47 | LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", | 49 | LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", |
| 48 | GetName().c_str()); | 50 | GetName().c_str()); |
| 49 | return false; | 51 | // TODO(wwylele): Use correct error code |
| 52 | return ResultCode(-1); | ||
| 50 | } | 53 | } |
| 51 | 54 | ||
| 52 | ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { | 55 | ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { |
| @@ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { | |||
| 57 | ErrorLevel::Permanent); | 60 | ErrorLevel::Permanent); |
| 58 | } | 61 | } |
| 59 | 62 | ||
| 60 | bool IVFCArchive::CreateDirectory(const Path& path) const { | 63 | ResultCode IVFCArchive::CreateDirectory(const Path& path) const { |
| 61 | LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).", | 64 | LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).", |
| 62 | GetName().c_str()); | 65 | GetName().c_str()); |
| 63 | return false; | 66 | // TODO(wwylele): Use correct error code |
| 67 | return ResultCode(-1); | ||
| 64 | } | 68 | } |
| 65 | 69 | ||
| 66 | bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { | 70 | ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { |
| 67 | LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", | 71 | LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", |
| 68 | GetName().c_str()); | 72 | GetName().c_str()); |
| 69 | return false; | 73 | // TODO(wwylele): Use correct error code |
| 74 | return ResultCode(-1); | ||
| 70 | } | 75 | } |
| 71 | 76 | ||
| 72 | std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const { | 77 | ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const { |
| 73 | return std::make_unique<IVFCDirectory>(); | 78 | return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>()); |
| 74 | } | 79 | } |
| 75 | 80 | ||
| 76 | u64 IVFCArchive::GetFreeBytes() const { | 81 | u64 IVFCArchive::GetFreeBytes() const { |
diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h index 2fbb3a568..e6fbdfb1f 100644 --- a/src/core/file_sys/ivfc_archive.h +++ b/src/core/file_sys/ivfc_archive.h | |||
| @@ -33,15 +33,15 @@ public: | |||
| 33 | std::string GetName() const override; | 33 | std::string GetName() const override; |
| 34 | 34 | ||
| 35 | ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | 35 | ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |
| 36 | const Mode mode) const override; | 36 | const Mode& mode) const override; |
| 37 | ResultCode DeleteFile(const Path& path) const override; | 37 | ResultCode DeleteFile(const Path& path) const override; |
| 38 | bool RenameFile(const Path& src_path, const Path& dest_path) const override; | 38 | ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; |
| 39 | bool DeleteDirectory(const Path& path) const override; | 39 | ResultCode DeleteDirectory(const Path& path) const override; |
| 40 | bool DeleteDirectoryRecursively(const Path& path) const override; | 40 | ResultCode DeleteDirectoryRecursively(const Path& path) const override; |
| 41 | ResultCode CreateFile(const Path& path, u64 size) const override; | 41 | ResultCode CreateFile(const Path& path, u64 size) const override; |
| 42 | bool CreateDirectory(const Path& path) const override; | 42 | ResultCode CreateDirectory(const Path& path) const override; |
| 43 | bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; | 43 | ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; |
| 44 | std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; | 44 | ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; |
| 45 | u64 GetFreeBytes() const override; | 45 | u64 GetFreeBytes() const override; |
| 46 | 46 | ||
| 47 | protected: | 47 | protected: |
| @@ -55,9 +55,6 @@ public: | |||
| 55 | IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) | 55 | IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) |
| 56 | : romfs_file(file), data_offset(offset), data_size(size) {} | 56 | : romfs_file(file), data_offset(offset), data_size(size) {} |
| 57 | 57 | ||
| 58 | ResultCode Open() override { | ||
| 59 | return RESULT_SUCCESS; | ||
| 60 | } | ||
| 61 | ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | 58 | ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; |
| 62 | ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | 59 | ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; |
| 63 | u64 GetSize() const override; | 60 | u64 GetSize() const override; |
| @@ -75,9 +72,6 @@ private: | |||
| 75 | 72 | ||
| 76 | class IVFCDirectory : public DirectoryBackend { | 73 | class IVFCDirectory : public DirectoryBackend { |
| 77 | public: | 74 | public: |
| 78 | bool Open() override { | ||
| 79 | return false; | ||
| 80 | } | ||
| 81 | u32 Read(const u32 count, Entry* entries) override { | 75 | u32 Read(const u32 count, Entry* entries) override { |
| 82 | return 0; | 76 | return 0; |
| 83 | } | 77 | } |
diff --git a/src/core/file_sys/path_parser.cpp b/src/core/file_sys/path_parser.cpp new file mode 100644 index 000000000..5a89b02b8 --- /dev/null +++ b/src/core/file_sys/path_parser.cpp | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <set> | ||
| 7 | #include "common/file_util.h" | ||
| 8 | #include "common/string_util.h" | ||
| 9 | #include "core/file_sys/path_parser.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | PathParser::PathParser(const Path& path) { | ||
| 14 | if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) { | ||
| 15 | is_valid = false; | ||
| 16 | return; | ||
| 17 | } | ||
| 18 | |||
| 19 | auto path_string = path.AsString(); | ||
| 20 | if (path_string.size() == 0 || path_string[0] != '/') { | ||
| 21 | is_valid = false; | ||
| 22 | return; | ||
| 23 | } | ||
| 24 | |||
| 25 | // Filter out invalid characters for the host system. | ||
| 26 | // Although some of these characters are valid on 3DS, they are unlikely to be used by games. | ||
| 27 | if (std::find_if(path_string.begin(), path_string.end(), [](char c) { | ||
| 28 | static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'}; | ||
| 29 | return invalid_chars.find(c) != invalid_chars.end(); | ||
| 30 | }) != path_string.end()) { | ||
| 31 | is_valid = false; | ||
| 32 | return; | ||
| 33 | } | ||
| 34 | |||
| 35 | Common::SplitString(path_string, '/', path_sequence); | ||
| 36 | |||
| 37 | auto begin = path_sequence.begin(); | ||
| 38 | auto end = path_sequence.end(); | ||
| 39 | end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; }); | ||
| 40 | path_sequence = std::vector<std::string>(begin, end); | ||
| 41 | |||
| 42 | // checks if the path is out of bounds. | ||
| 43 | int level = 0; | ||
| 44 | for (auto& node : path_sequence) { | ||
| 45 | if (node == "..") { | ||
| 46 | --level; | ||
| 47 | if (level < 0) { | ||
| 48 | is_valid = false; | ||
| 49 | return; | ||
| 50 | } | ||
| 51 | } else { | ||
| 52 | ++level; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | is_valid = true; | ||
| 57 | is_root = level == 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const { | ||
| 61 | auto path = mount_point; | ||
| 62 | if (!FileUtil::IsDirectory(path)) | ||
| 63 | return InvalidMountPoint; | ||
| 64 | if (path_sequence.empty()) { | ||
| 65 | return DirectoryFound; | ||
| 66 | } | ||
| 67 | |||
| 68 | for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) { | ||
| 69 | if (path.back() != '/') | ||
| 70 | path += '/'; | ||
| 71 | path += *iter; | ||
| 72 | |||
| 73 | if (!FileUtil::Exists(path)) | ||
| 74 | return PathNotFound; | ||
| 75 | if (FileUtil::IsDirectory(path)) | ||
| 76 | continue; | ||
| 77 | return FileInPath; | ||
| 78 | } | ||
| 79 | |||
| 80 | path += "/" + path_sequence.back(); | ||
| 81 | if (!FileUtil::Exists(path)) | ||
| 82 | return NotFound; | ||
| 83 | if (FileUtil::IsDirectory(path)) | ||
| 84 | return DirectoryFound; | ||
| 85 | return FileFound; | ||
| 86 | } | ||
| 87 | |||
| 88 | std::string PathParser::BuildHostPath(const std::string& mount_point) const { | ||
| 89 | std::string path = mount_point; | ||
| 90 | for (auto& node : path_sequence) { | ||
| 91 | if (path.back() != '/') | ||
| 92 | path += '/'; | ||
| 93 | path += node; | ||
| 94 | } | ||
| 95 | return path; | ||
| 96 | } | ||
| 97 | |||
| 98 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h new file mode 100644 index 000000000..990802579 --- /dev/null +++ b/src/core/file_sys/path_parser.h | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <string> | ||
| 8 | #include <vector> | ||
| 9 | #include "core/file_sys/archive_backend.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * A helper class parsing and verifying a string-type Path. | ||
| 15 | * Every archives with a sub file system should use this class to parse the path argument and check | ||
| 16 | * the status of the file / directory in question on the host file system. | ||
| 17 | */ | ||
| 18 | class PathParser { | ||
| 19 | public: | ||
| 20 | PathParser(const Path& path); | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Checks if the Path is valid. | ||
| 24 | * This function should be called once a PathParser is constructed. | ||
| 25 | * A Path is valid if: | ||
| 26 | * - it is a string path (with type LowPathType::Char or LowPathType::Wchar), | ||
| 27 | * - it starts with "/" (this seems a hard requirement in real 3DS), | ||
| 28 | * - it doesn't contain invalid characters, and | ||
| 29 | * - it doesn't go out of the root directory using "..". | ||
| 30 | */ | ||
| 31 | bool IsValid() const { | ||
| 32 | return is_valid; | ||
| 33 | } | ||
| 34 | |||
| 35 | /// Checks if the Path represents the root directory. | ||
| 36 | bool IsRootDirectory() const { | ||
| 37 | return is_root; | ||
| 38 | } | ||
| 39 | |||
| 40 | enum HostStatus { | ||
| 41 | InvalidMountPoint, | ||
| 42 | PathNotFound, // "/a/b/c" when "a" doesn't exist | ||
| 43 | FileInPath, // "/a/b/c" when "a" is a file | ||
| 44 | FileFound, // "/a/b/c" when "c" is a file | ||
| 45 | DirectoryFound, // "/a/b/c" when "c" is a directory | ||
| 46 | NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist | ||
| 47 | }; | ||
| 48 | |||
| 49 | /// Checks the status of the specified file / directory by the Path on the host file system. | ||
| 50 | HostStatus GetHostStatus(const std::string& mount_point) const; | ||
| 51 | |||
| 52 | /// Builds a full path on the host file system. | ||
| 53 | std::string BuildHostPath(const std::string& mount_point) const; | ||
| 54 | |||
| 55 | private: | ||
| 56 | std::vector<std::string> path_sequence; | ||
| 57 | bool is_valid{}; | ||
| 58 | bool is_root{}; | ||
| 59 | }; | ||
| 60 | |||
| 61 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp new file mode 100644 index 000000000..f2e6a06bc --- /dev/null +++ b/src/core/file_sys/savedata_archive.cpp | |||
| @@ -0,0 +1,283 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/file_util.h" | ||
| 6 | #include "core/file_sys/disk_archive.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/path_parser.h" | ||
| 9 | #include "core/file_sys/savedata_archive.h" | ||
| 10 | |||
| 11 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 12 | // FileSys namespace | ||
| 13 | |||
| 14 | namespace FileSys { | ||
| 15 | |||
| 16 | ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path, | ||
| 17 | const Mode& mode) const { | ||
| 18 | LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); | ||
| 19 | |||
| 20 | const PathParser path_parser(path); | ||
| 21 | |||
| 22 | if (!path_parser.IsValid()) { | ||
| 23 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 24 | return ERROR_INVALID_PATH; | ||
| 25 | } | ||
| 26 | |||
| 27 | if (mode.hex == 0) { | ||
| 28 | LOG_ERROR(Service_FS, "Empty open mode"); | ||
| 29 | return ERROR_UNSUPPORTED_OPEN_FLAGS; | ||
| 30 | } | ||
| 31 | |||
| 32 | if (mode.create_flag && !mode.write_flag) { | ||
| 33 | LOG_ERROR(Service_FS, "Create flag set but write flag not set"); | ||
| 34 | return ERROR_UNSUPPORTED_OPEN_FLAGS; | ||
| 35 | } | ||
| 36 | |||
| 37 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 38 | |||
| 39 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 40 | case PathParser::InvalidMountPoint: | ||
| 41 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 42 | return ERROR_FILE_NOT_FOUND; | ||
| 43 | case PathParser::PathNotFound: | ||
| 44 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 45 | return ERROR_PATH_NOT_FOUND; | ||
| 46 | case PathParser::FileInPath: | ||
| 47 | case PathParser::DirectoryFound: | ||
| 48 | LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str()); | ||
| 49 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; | ||
| 50 | case PathParser::NotFound: | ||
| 51 | if (!mode.create_flag) { | ||
| 52 | LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", | ||
| 53 | full_path.c_str()); | ||
| 54 | return ERROR_FILE_NOT_FOUND; | ||
| 55 | } else { | ||
| 56 | // Create the file | ||
| 57 | FileUtil::CreateEmptyFile(full_path); | ||
| 58 | } | ||
| 59 | break; | ||
| 60 | } | ||
| 61 | |||
| 62 | FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); | ||
| 63 | if (!file.IsOpen()) { | ||
| 64 | LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); | ||
| 65 | return ERROR_FILE_NOT_FOUND; | ||
| 66 | } | ||
| 67 | |||
| 68 | auto disk_file = std::make_unique<DiskFile>(std::move(file), mode); | ||
| 69 | return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file)); | ||
| 70 | } | ||
| 71 | |||
| 72 | ResultCode SaveDataArchive::DeleteFile(const Path& path) const { | ||
| 73 | const PathParser path_parser(path); | ||
| 74 | |||
| 75 | if (!path_parser.IsValid()) { | ||
| 76 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 77 | return ERROR_INVALID_PATH; | ||
| 78 | } | ||
| 79 | |||
| 80 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 81 | |||
| 82 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 83 | case PathParser::InvalidMountPoint: | ||
| 84 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 85 | return ERROR_FILE_NOT_FOUND; | ||
| 86 | case PathParser::PathNotFound: | ||
| 87 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 88 | return ERROR_PATH_NOT_FOUND; | ||
| 89 | case PathParser::FileInPath: | ||
| 90 | case PathParser::DirectoryFound: | ||
| 91 | case PathParser::NotFound: | ||
| 92 | LOG_ERROR(Service_FS, "File not found %s", full_path.c_str()); | ||
| 93 | return ERROR_FILE_NOT_FOUND; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (FileUtil::Delete(full_path)) { | ||
| 97 | return RESULT_SUCCESS; | ||
| 98 | } | ||
| 99 | |||
| 100 | LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str()); | ||
| 101 | return ERROR_FILE_NOT_FOUND; | ||
| 102 | } | ||
| 103 | |||
| 104 | ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const { | ||
| 105 | if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) { | ||
| 106 | return RESULT_SUCCESS; | ||
| 107 | } | ||
| 108 | |||
| 109 | // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't | ||
| 110 | // exist or similar. Verify. | ||
| 111 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 112 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 113 | } | ||
| 114 | |||
| 115 | template <typename T> | ||
| 116 | static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point, | ||
| 117 | T deleter) { | ||
| 118 | const PathParser path_parser(path); | ||
| 119 | |||
| 120 | if (!path_parser.IsValid()) { | ||
| 121 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 122 | return ERROR_INVALID_PATH; | ||
| 123 | } | ||
| 124 | |||
| 125 | if (path_parser.IsRootDirectory()) | ||
| 126 | return ERROR_DIRECTORY_NOT_EMPTY; | ||
| 127 | |||
| 128 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 129 | |||
| 130 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 131 | case PathParser::InvalidMountPoint: | ||
| 132 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 133 | return ERROR_PATH_NOT_FOUND; | ||
| 134 | case PathParser::PathNotFound: | ||
| 135 | case PathParser::NotFound: | ||
| 136 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 137 | return ERROR_PATH_NOT_FOUND; | ||
| 138 | case PathParser::FileInPath: | ||
| 139 | case PathParser::FileFound: | ||
| 140 | LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str()); | ||
| 141 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; | ||
| 142 | } | ||
| 143 | |||
| 144 | if (deleter(full_path)) { | ||
| 145 | return RESULT_SUCCESS; | ||
| 146 | } | ||
| 147 | |||
| 148 | LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str()); | ||
| 149 | return ERROR_DIRECTORY_NOT_EMPTY; | ||
| 150 | } | ||
| 151 | |||
| 152 | ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const { | ||
| 153 | return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir); | ||
| 154 | } | ||
| 155 | |||
| 156 | ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const { | ||
| 157 | return DeleteDirectoryHelper( | ||
| 158 | path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); | ||
| 159 | } | ||
| 160 | |||
| 161 | ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { | ||
| 162 | const PathParser path_parser(path); | ||
| 163 | |||
| 164 | if (!path_parser.IsValid()) { | ||
| 165 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 166 | return ERROR_INVALID_PATH; | ||
| 167 | } | ||
| 168 | |||
| 169 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 170 | |||
| 171 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 172 | case PathParser::InvalidMountPoint: | ||
| 173 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 174 | return ERROR_FILE_NOT_FOUND; | ||
| 175 | case PathParser::PathNotFound: | ||
| 176 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 177 | return ERROR_PATH_NOT_FOUND; | ||
| 178 | case PathParser::FileInPath: | ||
| 179 | LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); | ||
| 180 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; | ||
| 181 | case PathParser::DirectoryFound: | ||
| 182 | case PathParser::FileFound: | ||
| 183 | LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); | ||
| 184 | return ERROR_FILE_ALREADY_EXISTS; | ||
| 185 | } | ||
| 186 | |||
| 187 | if (size == 0) { | ||
| 188 | FileUtil::CreateEmptyFile(full_path); | ||
| 189 | return RESULT_SUCCESS; | ||
| 190 | } | ||
| 191 | |||
| 192 | FileUtil::IOFile file(full_path, "wb"); | ||
| 193 | // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) | ||
| 194 | // We do this by seeking to the right size, then writing a single null byte. | ||
| 195 | if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { | ||
| 196 | return RESULT_SUCCESS; | ||
| 197 | } | ||
| 198 | |||
| 199 | LOG_ERROR(Service_FS, "Too large file"); | ||
| 200 | return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, | ||
| 201 | ErrorLevel::Info); | ||
| 202 | } | ||
| 203 | |||
| 204 | ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { | ||
| 205 | const PathParser path_parser(path); | ||
| 206 | |||
| 207 | if (!path_parser.IsValid()) { | ||
| 208 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 209 | return ERROR_INVALID_PATH; | ||
| 210 | } | ||
| 211 | |||
| 212 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 213 | |||
| 214 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 215 | case PathParser::InvalidMountPoint: | ||
| 216 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 217 | return ERROR_FILE_NOT_FOUND; | ||
| 218 | case PathParser::PathNotFound: | ||
| 219 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 220 | return ERROR_PATH_NOT_FOUND; | ||
| 221 | case PathParser::FileInPath: | ||
| 222 | LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); | ||
| 223 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; | ||
| 224 | case PathParser::DirectoryFound: | ||
| 225 | case PathParser::FileFound: | ||
| 226 | LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); | ||
| 227 | return ERROR_DIRECTORY_ALREADY_EXISTS; | ||
| 228 | } | ||
| 229 | |||
| 230 | if (FileUtil::CreateDir(mount_point + path.AsString())) { | ||
| 231 | return RESULT_SUCCESS; | ||
| 232 | } | ||
| 233 | |||
| 234 | LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str()); | ||
| 235 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled, | ||
| 236 | ErrorLevel::Status); | ||
| 237 | } | ||
| 238 | |||
| 239 | ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { | ||
| 240 | if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) | ||
| 241 | return RESULT_SUCCESS; | ||
| 242 | |||
| 243 | // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't | ||
| 244 | // exist or similar. Verify. | ||
| 245 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 246 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 247 | } | ||
| 248 | |||
| 249 | ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory( | ||
| 250 | const Path& path) const { | ||
| 251 | const PathParser path_parser(path); | ||
| 252 | |||
| 253 | if (!path_parser.IsValid()) { | ||
| 254 | LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); | ||
| 255 | return ERROR_INVALID_PATH; | ||
| 256 | } | ||
| 257 | |||
| 258 | const auto full_path = path_parser.BuildHostPath(mount_point); | ||
| 259 | |||
| 260 | switch (path_parser.GetHostStatus(mount_point)) { | ||
| 261 | case PathParser::InvalidMountPoint: | ||
| 262 | LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); | ||
| 263 | return ERROR_FILE_NOT_FOUND; | ||
| 264 | case PathParser::PathNotFound: | ||
| 265 | case PathParser::NotFound: | ||
| 266 | LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); | ||
| 267 | return ERROR_PATH_NOT_FOUND; | ||
| 268 | case PathParser::FileInPath: | ||
| 269 | case PathParser::FileFound: | ||
| 270 | LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); | ||
| 271 | return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; | ||
| 272 | } | ||
| 273 | |||
| 274 | auto directory = std::make_unique<DiskDirectory>(full_path); | ||
| 275 | return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); | ||
| 276 | } | ||
| 277 | |||
| 278 | u64 SaveDataArchive::GetFreeBytes() const { | ||
| 279 | // TODO: Stubbed to return 1GiB | ||
| 280 | return 1024 * 1024 * 1024; | ||
| 281 | } | ||
| 282 | |||
| 283 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h new file mode 100644 index 000000000..2fb6c452a --- /dev/null +++ b/src/core/file_sys/savedata_archive.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <string> | ||
| 8 | #include "core/file_sys/archive_backend.h" | ||
| 9 | #include "core/file_sys/directory_backend.h" | ||
| 10 | #include "core/file_sys/file_backend.h" | ||
| 11 | #include "core/hle/result.h" | ||
| 12 | |||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 14 | // FileSys namespace | ||
| 15 | |||
| 16 | namespace FileSys { | ||
| 17 | |||
| 18 | /// Archive backend for general save data archive type (SaveData and SystemSaveData) | ||
| 19 | class SaveDataArchive : public ArchiveBackend { | ||
| 20 | public: | ||
| 21 | SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {} | ||
| 22 | |||
| 23 | std::string GetName() const override { | ||
| 24 | return "SaveDataArchive: " + mount_point; | ||
| 25 | } | ||
| 26 | |||
| 27 | ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||
| 28 | const Mode& mode) const override; | ||
| 29 | ResultCode DeleteFile(const Path& path) const override; | ||
| 30 | ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; | ||
| 31 | ResultCode DeleteDirectory(const Path& path) const override; | ||
| 32 | ResultCode DeleteDirectoryRecursively(const Path& path) const override; | ||
| 33 | ResultCode CreateFile(const Path& path, u64 size) const override; | ||
| 34 | ResultCode CreateDirectory(const Path& path) const override; | ||
| 35 | ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||
| 36 | ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||
| 37 | u64 GetFreeBytes() const override; | ||
| 38 | |||
| 39 | protected: | ||
| 40 | std::string mount_point; | ||
| 41 | }; | ||
| 42 | |||
| 43 | } // namespace FileSys | ||
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 7f8d8e00d..f7356f9d8 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -20,15 +20,24 @@ enum class ErrorDescription : u32 { | |||
| 20 | OS_InvalidBufferDescriptor = 48, | 20 | OS_InvalidBufferDescriptor = 48, |
| 21 | WrongAddress = 53, | 21 | WrongAddress = 53, |
| 22 | FS_ArchiveNotMounted = 101, | 22 | FS_ArchiveNotMounted = 101, |
| 23 | FS_FileNotFound = 112, | ||
| 24 | FS_PathNotFound = 113, | ||
| 23 | FS_NotFound = 120, | 25 | FS_NotFound = 120, |
| 26 | FS_FileAlreadyExists = 180, | ||
| 27 | FS_DirectoryAlreadyExists = 185, | ||
| 24 | FS_AlreadyExists = 190, | 28 | FS_AlreadyExists = 190, |
| 25 | FS_InvalidOpenFlags = 230, | 29 | FS_InvalidOpenFlags = 230, |
| 30 | FS_DirectoryNotEmpty = 240, | ||
| 26 | FS_NotAFile = 250, | 31 | FS_NotAFile = 250, |
| 27 | FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive | 32 | FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive |
| 28 | OutofRangeOrMisalignedAddress = | 33 | OutofRangeOrMisalignedAddress = |
| 29 | 513, // TODO(purpasmart): Check if this name fits its actual usage | 34 | 513, // TODO(purpasmart): Check if this name fits its actual usage |
| 30 | GPU_FirstInitialization = 519, | 35 | GPU_FirstInitialization = 519, |
| 36 | FS_InvalidReadFlag = 700, | ||
| 31 | FS_InvalidPath = 702, | 37 | FS_InvalidPath = 702, |
| 38 | FS_WriteBeyondEnd = 705, | ||
| 39 | FS_UnsupportedOpenFlags = 760, | ||
| 40 | FS_UnexpectedFileOrDirectory = 770, | ||
| 32 | InvalidSection = 1000, | 41 | InvalidSection = 1000, |
| 33 | TooLarge = 1001, | 42 | TooLarge = 1001, |
| 34 | NotAuthorized = 1002, | 43 | NotAuthorized = 1002, |
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index d3d0f3b55..d554c3f54 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp | |||
| @@ -360,7 +360,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da | |||
| 360 | } | 360 | } |
| 361 | 361 | ||
| 362 | ResultCode DeleteConfigNANDSaveFile() { | 362 | ResultCode DeleteConfigNANDSaveFile() { |
| 363 | FileSys::Path path("config"); | 363 | FileSys::Path path("/config"); |
| 364 | return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); | 364 | return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); |
| 365 | } | 365 | } |
| 366 | 366 | ||
| @@ -369,7 +369,7 @@ ResultCode UpdateConfigNANDSavegame() { | |||
| 369 | mode.write_flag.Assign(1); | 369 | mode.write_flag.Assign(1); |
| 370 | mode.create_flag.Assign(1); | 370 | mode.create_flag.Assign(1); |
| 371 | 371 | ||
| 372 | FileSys::Path path("config"); | 372 | FileSys::Path path("/config"); |
| 373 | 373 | ||
| 374 | auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode); | 374 | auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode); |
| 375 | ASSERT_MSG(config_result.Succeeded(), "could not open file"); | 375 | ASSERT_MSG(config_result.Succeeded(), "could not open file"); |
| @@ -383,8 +383,9 @@ ResultCode UpdateConfigNANDSavegame() { | |||
| 383 | ResultCode FormatConfig() { | 383 | ResultCode FormatConfig() { |
| 384 | ResultCode res = DeleteConfigNANDSaveFile(); | 384 | ResultCode res = DeleteConfigNANDSaveFile(); |
| 385 | // The delete command fails if the file doesn't exist, so we have to check that too | 385 | // The delete command fails if the file doesn't exist, so we have to check that too |
| 386 | if (!res.IsSuccess() && res.description != ErrorDescription::FS_NotFound) | 386 | if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) { |
| 387 | return res; | 387 | return res; |
| 388 | } | ||
| 388 | // Delete the old data | 389 | // Delete the old data |
| 389 | cfg_config_file_buffer.fill(0); | 390 | cfg_config_file_buffer.fill(0); |
| 390 | // Create the header | 391 | // Create the header |
| @@ -510,7 +511,7 @@ ResultCode LoadConfigNANDSaveFile() { | |||
| 510 | 511 | ||
| 511 | cfg_system_save_data_archive = *archive_result; | 512 | cfg_system_save_data_archive = *archive_result; |
| 512 | 513 | ||
| 513 | FileSys::Path config_path("config"); | 514 | FileSys::Path config_path("/config"); |
| 514 | FileSys::Mode open_mode = {}; | 515 | FileSys::Mode open_mode = {}; |
| 515 | open_mode.read_flag.Assign(1); | 516 | open_mode.read_flag.Assign(1); |
| 516 | 517 | ||
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 7f9696bfb..4c29784e8 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp | |||
| @@ -15,9 +15,10 @@ | |||
| 15 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 16 | #include "core/file_sys/archive_backend.h" | 16 | #include "core/file_sys/archive_backend.h" |
| 17 | #include "core/file_sys/archive_extsavedata.h" | 17 | #include "core/file_sys/archive_extsavedata.h" |
| 18 | #include "core/file_sys/archive_ncch.h" | ||
| 18 | #include "core/file_sys/archive_savedata.h" | 19 | #include "core/file_sys/archive_savedata.h" |
| 19 | #include "core/file_sys/archive_savedatacheck.h" | ||
| 20 | #include "core/file_sys/archive_sdmc.h" | 20 | #include "core/file_sys/archive_sdmc.h" |
| 21 | #include "core/file_sys/archive_sdmcwriteonly.h" | ||
| 21 | #include "core/file_sys/archive_systemsavedata.h" | 22 | #include "core/file_sys/archive_systemsavedata.h" |
| 22 | #include "core/file_sys/directory_backend.h" | 23 | #include "core/file_sys/directory_backend.h" |
| 23 | #include "core/file_sys/file_backend.h" | 24 | #include "core/file_sys/file_backend.h" |
| @@ -338,17 +339,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, | |||
| 338 | return ERR_INVALID_ARCHIVE_HANDLE; | 339 | return ERR_INVALID_ARCHIVE_HANDLE; |
| 339 | 340 | ||
| 340 | if (src_archive == dest_archive) { | 341 | if (src_archive == dest_archive) { |
| 341 | if (src_archive->RenameFile(src_path, dest_path)) | 342 | return src_archive->RenameFile(src_path, dest_path); |
| 342 | return RESULT_SUCCESS; | ||
| 343 | } else { | 343 | } else { |
| 344 | // TODO: Implement renaming across archives | 344 | // TODO: Implement renaming across archives |
| 345 | return UnimplementedFunction(ErrorModule::FS); | 345 | return UnimplementedFunction(ErrorModule::FS); |
| 346 | } | 346 | } |
| 347 | |||
| 348 | // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't | ||
| 349 | // exist or similar. Verify. | ||
| 350 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 351 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 352 | } | 347 | } |
| 353 | 348 | ||
| 354 | ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { | 349 | ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { |
| @@ -356,10 +351,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy | |||
| 356 | if (archive == nullptr) | 351 | if (archive == nullptr) |
| 357 | return ERR_INVALID_ARCHIVE_HANDLE; | 352 | return ERR_INVALID_ARCHIVE_HANDLE; |
| 358 | 353 | ||
| 359 | if (archive->DeleteDirectory(path)) | 354 | return archive->DeleteDirectory(path); |
| 360 | return RESULT_SUCCESS; | ||
| 361 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 362 | ErrorSummary::Canceled, ErrorLevel::Status); | ||
| 363 | } | 355 | } |
| 364 | 356 | ||
| 365 | ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, | 357 | ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, |
| @@ -368,10 +360,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, | |||
| 368 | if (archive == nullptr) | 360 | if (archive == nullptr) |
| 369 | return ERR_INVALID_ARCHIVE_HANDLE; | 361 | return ERR_INVALID_ARCHIVE_HANDLE; |
| 370 | 362 | ||
| 371 | if (archive->DeleteDirectoryRecursively(path)) | 363 | return archive->DeleteDirectoryRecursively(path); |
| 372 | return RESULT_SUCCESS; | ||
| 373 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 374 | ErrorSummary::Canceled, ErrorLevel::Status); | ||
| 375 | } | 364 | } |
| 376 | 365 | ||
| 377 | ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | 366 | ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, |
| @@ -388,10 +377,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy | |||
| 388 | if (archive == nullptr) | 377 | if (archive == nullptr) |
| 389 | return ERR_INVALID_ARCHIVE_HANDLE; | 378 | return ERR_INVALID_ARCHIVE_HANDLE; |
| 390 | 379 | ||
| 391 | if (archive->CreateDirectory(path)) | 380 | return archive->CreateDirectory(path); |
| 392 | return RESULT_SUCCESS; | ||
| 393 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 394 | ErrorSummary::Canceled, ErrorLevel::Status); | ||
| 395 | } | 381 | } |
| 396 | 382 | ||
| 397 | ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | 383 | ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, |
| @@ -404,17 +390,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | |||
| 404 | return ERR_INVALID_ARCHIVE_HANDLE; | 390 | return ERR_INVALID_ARCHIVE_HANDLE; |
| 405 | 391 | ||
| 406 | if (src_archive == dest_archive) { | 392 | if (src_archive == dest_archive) { |
| 407 | if (src_archive->RenameDirectory(src_path, dest_path)) | 393 | return src_archive->RenameDirectory(src_path, dest_path); |
| 408 | return RESULT_SUCCESS; | ||
| 409 | } else { | 394 | } else { |
| 410 | // TODO: Implement renaming across archives | 395 | // TODO: Implement renaming across archives |
| 411 | return UnimplementedFunction(ErrorModule::FS); | 396 | return UnimplementedFunction(ErrorModule::FS); |
| 412 | } | 397 | } |
| 413 | |||
| 414 | // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't | ||
| 415 | // exist or similar. Verify. | ||
| 416 | return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description | ||
| 417 | ErrorSummary::NothingHappened, ErrorLevel::Status); | ||
| 418 | } | 398 | } |
| 419 | 399 | ||
| 420 | ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, | 400 | ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, |
| @@ -423,13 +403,11 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a | |||
| 423 | if (archive == nullptr) | 403 | if (archive == nullptr) |
| 424 | return ERR_INVALID_ARCHIVE_HANDLE; | 404 | return ERR_INVALID_ARCHIVE_HANDLE; |
| 425 | 405 | ||
| 426 | std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path); | 406 | auto backend = archive->OpenDirectory(path); |
| 427 | if (backend == nullptr) { | 407 | if (backend.Failed()) |
| 428 | return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, | 408 | return backend.Code(); |
| 429 | ErrorLevel::Permanent); | ||
| 430 | } | ||
| 431 | 409 | ||
| 432 | auto directory = Kernel::SharedPtr<Directory>(new Directory(std::move(backend), path)); | 410 | auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path)); |
| 433 | return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); | 411 | return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); |
| 434 | } | 412 | } |
| 435 | 413 | ||
| @@ -549,6 +527,13 @@ void RegisterArchiveTypes() { | |||
| 549 | LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", | 527 | LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", |
| 550 | sdmc_directory.c_str()); | 528 | sdmc_directory.c_str()); |
| 551 | 529 | ||
| 530 | auto sdmcwo_factory = std::make_unique<FileSys::ArchiveFactory_SDMCWriteOnly>(sdmc_directory); | ||
| 531 | if (sdmcwo_factory->Initialize()) | ||
| 532 | RegisterArchiveType(std::move(sdmcwo_factory), ArchiveIdCode::SDMCWriteOnly); | ||
| 533 | else | ||
| 534 | LOG_ERROR(Service_FS, "Can't instantiate SDMCWriteOnly archive with path %s", | ||
| 535 | sdmc_directory.c_str()); | ||
| 536 | |||
| 552 | // Create the SaveData archive | 537 | // Create the SaveData archive |
| 553 | auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); | 538 | auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); |
| 554 | RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); | 539 | RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); |
| @@ -569,10 +554,9 @@ void RegisterArchiveTypes() { | |||
| 569 | LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s", | 554 | LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s", |
| 570 | sharedextsavedata_factory->GetMountPoint().c_str()); | 555 | sharedextsavedata_factory->GetMountPoint().c_str()); |
| 571 | 556 | ||
| 572 | // Create the SaveDataCheck archive, basically a small variation of the RomFS archive | 557 | // Create the NCCH archive, basically a small variation of the RomFS archive |
| 573 | auto savedatacheck_factory = | 558 | auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_NCCH>(nand_directory); |
| 574 | std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory); | 559 | RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH); |
| 575 | RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck); | ||
| 576 | 560 | ||
| 577 | auto systemsavedata_factory = | 561 | auto systemsavedata_factory = |
| 578 | std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); | 562 | std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); |
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 41a76285c..21ed9717b 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h | |||
| @@ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 { | |||
| 33 | SystemSaveData = 0x00000008, | 33 | SystemSaveData = 0x00000008, |
| 34 | SDMC = 0x00000009, | 34 | SDMC = 0x00000009, |
| 35 | SDMCWriteOnly = 0x0000000A, | 35 | SDMCWriteOnly = 0x0000000A, |
| 36 | SaveDataCheck = 0x2345678A, | 36 | NCCH = 0x2345678A, |
| 37 | }; | 37 | }; |
| 38 | 38 | ||
| 39 | /// Media types for the archives | 39 | /// Media types for the archives |
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 6e6b63329..cc859c14c 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp | |||
| @@ -128,7 +128,7 @@ void Init() { | |||
| 128 | Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); | 128 | Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); |
| 129 | ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); | 129 | ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); |
| 130 | 130 | ||
| 131 | FileSys::Path gamecoin_path("gamecoin.dat"); | 131 | FileSys::Path gamecoin_path("/gamecoin.dat"); |
| 132 | FileSys::Mode open_mode = {}; | 132 | FileSys::Mode open_mode = {}; |
| 133 | open_mode.write_flag.Assign(1); | 133 | open_mode.write_flag.Assign(1); |
| 134 | open_mode.create_flag.Assign(1); | 134 | open_mode.create_flag.Assign(1); |
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 457c55571..89237e477 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt | |||
| @@ -1,5 +1,7 @@ | |||
| 1 | set(SRCS | 1 | set(SRCS |
| 2 | glad.cpp | ||
| 2 | tests.cpp | 3 | tests.cpp |
| 4 | core/file_sys/path_parser.cpp | ||
| 3 | ) | 5 | ) |
| 4 | 6 | ||
| 5 | set(HEADERS | 7 | set(HEADERS |
diff --git a/src/tests/core/file_sys/path_parser.cpp b/src/tests/core/file_sys/path_parser.cpp new file mode 100644 index 000000000..2b543e438 --- /dev/null +++ b/src/tests/core/file_sys/path_parser.cpp | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <catch.hpp> | ||
| 6 | #include "common/file_util.h" | ||
| 7 | #include "core/file_sys/path_parser.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | TEST_CASE("PathParser", "[core][file_sys]") { | ||
| 12 | REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid()); | ||
| 13 | REQUIRE(!PathParser(Path("a")).IsValid()); | ||
| 14 | REQUIRE(!PathParser(Path("/|")).IsValid()); | ||
| 15 | REQUIRE(PathParser(Path("/a")).IsValid()); | ||
| 16 | REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid()); | ||
| 17 | REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid()); | ||
| 18 | REQUIRE(PathParser(Path("/")).IsRootDirectory()); | ||
| 19 | REQUIRE(!PathParser(Path("/a")).IsRootDirectory()); | ||
| 20 | REQUIRE(PathParser(Path("/a/..")).IsRootDirectory()); | ||
| 21 | } | ||
| 22 | |||
| 23 | TEST_CASE("PathParser - Host file system", "[core][file_sys]") { | ||
| 24 | std::string test_dir = "./test"; | ||
| 25 | FileUtil::CreateDir(test_dir); | ||
| 26 | FileUtil::CreateDir(test_dir + "/z"); | ||
| 27 | FileUtil::CreateEmptyFile(test_dir + "/a"); | ||
| 28 | |||
| 29 | REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound); | ||
| 30 | REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound); | ||
| 31 | REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound); | ||
| 32 | REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath); | ||
| 33 | REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound); | ||
| 34 | |||
| 35 | FileUtil::DeleteDirRecursively(test_dir); | ||
| 36 | } | ||
| 37 | |||
| 38 | } // namespace FileSys | ||
diff --git a/src/tests/glad.cpp b/src/tests/glad.cpp new file mode 100644 index 000000000..b0b016440 --- /dev/null +++ b/src/tests/glad.cpp | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <catch.hpp> | ||
| 6 | #include <glad/glad.h> | ||
| 7 | |||
| 8 | // This is not an actual test, but a work-around for issue #2183. | ||
| 9 | // If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS | ||
| 10 | // will error about undefined references from video_core to glad. So we explicitly use a glad | ||
| 11 | // function here to shut up the linker. | ||
| 12 | TEST_CASE("glad fake test", "[dummy]") { | ||
| 13 | REQUIRE(&gladLoadGL != nullptr); | ||
| 14 | } | ||