mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu.git
				synced 2025-05-12 00:45:25 +00:00 
			
		
		
		
	Virtual Filesystem 2: Electric Boogaloo (#676)
* Virtual Filesystem * Fix delete bug and documentate * Review fixes + other stuff * Fix puyo regression
This commit is contained in:
		
							parent
							
								
									924c473bb3
								
							
						
					
					
						commit
						29aff8d5ab
					
				| @ -2,6 +2,7 @@ | |||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #include <sstream> | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
| #include "common/common_paths.h" | #include "common/common_paths.h" | ||||||
| @ -386,7 +387,7 @@ u64 GetSize(FILE* f) { | |||||||
| bool CreateEmptyFile(const std::string& filename) { | bool CreateEmptyFile(const std::string& filename) { | ||||||
|     LOG_TRACE(Common_Filesystem, "{}", filename); |     LOG_TRACE(Common_Filesystem, "{}", filename); | ||||||
| 
 | 
 | ||||||
|     if (!FileUtil::IOFile(filename, "wb")) { |     if (!FileUtil::IOFile(filename, "wb").IsOpen()) { | ||||||
|         LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); |         LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -750,7 +751,7 @@ size_t WriteStringToFile(bool text_file, const std::string& str, const char* fil | |||||||
| size_t ReadFileToString(bool text_file, const char* filename, std::string& str) { | size_t ReadFileToString(bool text_file, const char* filename, std::string& str) { | ||||||
|     IOFile file(filename, text_file ? "r" : "rb"); |     IOFile file(filename, text_file ? "r" : "rb"); | ||||||
| 
 | 
 | ||||||
|     if (!file) |     if (!file.IsOpen()) | ||||||
|         return false; |         return false; | ||||||
| 
 | 
 | ||||||
|     str.resize(static_cast<u32>(file.GetSize())); |     str.resize(static_cast<u32>(file.GetSize())); | ||||||
| @ -799,6 +800,71 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::vector<std::string> SplitPathComponents(const std::string& filename) { | ||||||
|  |     auto copy(filename); | ||||||
|  |     std::replace(copy.begin(), copy.end(), '\\', '/'); | ||||||
|  |     std::vector<std::string> out; | ||||||
|  | 
 | ||||||
|  |     std::stringstream stream(filename); | ||||||
|  |     std::string item; | ||||||
|  |     while (std::getline(stream, item, '/')) | ||||||
|  |         out.push_back(std::move(item)); | ||||||
|  | 
 | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string GetParentPath(const std::string& path) { | ||||||
|  |     auto out = path; | ||||||
|  |     const auto name_bck_index = out.find_last_of('\\'); | ||||||
|  |     const auto name_fwd_index = out.find_last_of('/'); | ||||||
|  |     size_t name_index; | ||||||
|  |     if (name_bck_index == std::string::npos || name_fwd_index == std::string::npos) | ||||||
|  |         name_index = std::min<size_t>(name_bck_index, name_fwd_index); | ||||||
|  |     else | ||||||
|  |         name_index = std::max<size_t>(name_bck_index, name_fwd_index); | ||||||
|  | 
 | ||||||
|  |     return out.erase(name_index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string GetPathWithoutTop(std::string path) { | ||||||
|  |     if (path.empty()) | ||||||
|  |         return ""; | ||||||
|  |     while (path[0] == '\\' || path[0] == '/') { | ||||||
|  |         path = path.substr(1); | ||||||
|  |         if (path.empty()) | ||||||
|  |             return ""; | ||||||
|  |     } | ||||||
|  |     const auto name_bck_index = path.find_first_of('\\'); | ||||||
|  |     const auto name_fwd_index = path.find_first_of('/'); | ||||||
|  |     return path.substr(std::min<size_t>(name_bck_index, name_fwd_index) + 1); | ||||||
|  |     return path.substr(std::min<size_t>(name_bck_index, name_fwd_index) + 1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string GetFilename(std::string path) { | ||||||
|  |     std::replace(path.begin(), path.end(), '\\', '/'); | ||||||
|  |     auto name_index = path.find_last_of('/'); | ||||||
|  |     if (name_index == std::string::npos) | ||||||
|  |         return ""; | ||||||
|  |     return path.substr(name_index + 1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string GetExtensionFromFilename(const std::string& name) { | ||||||
|  |     size_t index = name.find_last_of('.'); | ||||||
|  |     if (index == std::string::npos) | ||||||
|  |         return ""; | ||||||
|  | 
 | ||||||
|  |     return name.substr(index + 1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string RemoveTrailingSlash(const std::string& path) { | ||||||
|  |     if (path.empty()) | ||||||
|  |         return path; | ||||||
|  |     if (path.back() == '\\' || path.back() == '/') | ||||||
|  |         return path.substr(0, path.size() - 1); | ||||||
|  | 
 | ||||||
|  |     return path; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| IOFile::IOFile() {} | IOFile::IOFile() {} | ||||||
| 
 | 
 | ||||||
| IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { | IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { | ||||||
| @ -820,7 +886,6 @@ IOFile& IOFile::operator=(IOFile&& other) noexcept { | |||||||
| 
 | 
 | ||||||
| void IOFile::Swap(IOFile& other) noexcept { | void IOFile::Swap(IOFile& other) noexcept { | ||||||
|     std::swap(m_file, other.m_file); |     std::swap(m_file, other.m_file); | ||||||
|     std::swap(m_good, other.m_good); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IOFile::Open(const std::string& filename, const char openmode[], int flags) { | bool IOFile::Open(const std::string& filename, const char openmode[], int flags) { | ||||||
| @ -837,16 +902,15 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags) | |||||||
|     m_file = fopen(filename.c_str(), openmode); |     m_file = fopen(filename.c_str(), openmode); | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|     m_good = IsOpen(); |     return IsOpen(); | ||||||
|     return m_good; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IOFile::Close() { | bool IOFile::Close() { | ||||||
|     if (!IsOpen() || 0 != std::fclose(m_file)) |     if (!IsOpen() || 0 != std::fclose(m_file)) | ||||||
|         m_good = false; |         return false; | ||||||
| 
 | 
 | ||||||
|     m_file = nullptr; |     m_file = nullptr; | ||||||
|     return m_good; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u64 IOFile::GetSize() const { | u64 IOFile::GetSize() const { | ||||||
| @ -856,11 +920,8 @@ u64 IOFile::GetSize() const { | |||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IOFile::Seek(s64 off, int origin) { | bool IOFile::Seek(s64 off, int origin) const { | ||||||
|     if (!IsOpen() || 0 != fseeko(m_file, off, origin)) |     return IsOpen() && 0 == fseeko(m_file, off, origin); | ||||||
|         m_good = false; |  | ||||||
| 
 |  | ||||||
|     return m_good; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u64 IOFile::Tell() const { | u64 IOFile::Tell() const { | ||||||
| @ -871,14 +932,11 @@ u64 IOFile::Tell() const { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IOFile::Flush() { | bool IOFile::Flush() { | ||||||
|     if (!IsOpen() || 0 != std::fflush(m_file)) |     return IsOpen() && 0 == std::fflush(m_file); | ||||||
|         m_good = false; |  | ||||||
| 
 |  | ||||||
|     return m_good; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IOFile::Resize(u64 size) { | bool IOFile::Resize(u64 size) { | ||||||
|     if (!IsOpen() || 0 != |     return IsOpen() && 0 == | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|                            // ector: _chsize sucks, not 64-bit safe
 |                            // ector: _chsize sucks, not 64-bit safe
 | ||||||
|                            // F|RES: changed to _chsize_s. i think it is 64-bit safe
 |                            // F|RES: changed to _chsize_s. i think it is 64-bit safe
 | ||||||
| @ -887,10 +945,7 @@ bool IOFile::Resize(u64 size) { | |||||||
|                            // TODO: handle 64bit and growing
 |                            // TODO: handle 64bit and growing
 | ||||||
|                            ftruncate(fileno(m_file), size) |                            ftruncate(fileno(m_file), size) | ||||||
| #endif | #endif | ||||||
|     ) |         ; | ||||||
|         m_good = false; |  | ||||||
| 
 |  | ||||||
|     return m_good; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace FileUtil
 | } // namespace FileUtil
 | ||||||
|  | |||||||
| @ -150,6 +150,34 @@ size_t ReadFileToString(bool text_file, const char* filename, std::string& str); | |||||||
| void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name, | void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name, | ||||||
|                      std::array<char, 4>& extension); |                      std::array<char, 4>& extension); | ||||||
| 
 | 
 | ||||||
|  | // Splits the path on '/' or '\' and put the components into a vector
 | ||||||
|  | // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
 | ||||||
|  | std::vector<std::string> SplitPathComponents(const std::string& filename); | ||||||
|  | 
 | ||||||
|  | // Gets all of the text up to the last '/' or '\' in the path.
 | ||||||
|  | std::string GetParentPath(const std::string& path); | ||||||
|  | 
 | ||||||
|  | // Gets all of the text after the first '/' or '\' in the path.
 | ||||||
|  | std::string GetPathWithoutTop(std::string path); | ||||||
|  | 
 | ||||||
|  | // Gets the filename of the path
 | ||||||
|  | std::string GetFilename(std::string path); | ||||||
|  | 
 | ||||||
|  | // Gets the extension of the filename
 | ||||||
|  | std::string GetExtensionFromFilename(const std::string& name); | ||||||
|  | 
 | ||||||
|  | // Removes the final '/' or '\' if one exists
 | ||||||
|  | std::string RemoveTrailingSlash(const std::string& path); | ||||||
|  | 
 | ||||||
|  | // Creates a new vector containing indices [first, last) from the original.
 | ||||||
|  | template <typename T> | ||||||
|  | std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t last) { | ||||||
|  |     if (first >= last) | ||||||
|  |         return {}; | ||||||
|  |     last = std::min<size_t>(last, vector.size()); | ||||||
|  |     return std::vector<T>(vector.begin() + first, vector.begin() + first + last); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // simple wrapper for cstdlib file functions to
 | // simple wrapper for cstdlib file functions to
 | ||||||
| // hopefully will make error checking easier
 | // hopefully will make error checking easier
 | ||||||
| // and make forgetting an fclose() harder
 | // and make forgetting an fclose() harder
 | ||||||
| @ -172,41 +200,27 @@ public: | |||||||
|     bool Close(); |     bool Close(); | ||||||
| 
 | 
 | ||||||
|     template <typename T> |     template <typename T> | ||||||
|     size_t ReadArray(T* data, size_t length) { |     size_t ReadArray(T* data, size_t length) const { | ||||||
|         static_assert(std::is_trivially_copyable<T>(), |         static_assert(std::is_trivially_copyable<T>(), | ||||||
|                       "Given array does not consist of trivially copyable objects"); |                       "Given array does not consist of trivially copyable objects"); | ||||||
| 
 | 
 | ||||||
|         if (!IsOpen()) { |         if (!IsOpen()) | ||||||
|             m_good = false; |  | ||||||
|             return -1; |             return -1; | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         size_t items_read = std::fread(data, sizeof(T), length, m_file); |         return std::fread(data, sizeof(T), length, m_file); | ||||||
|         if (items_read != length) |  | ||||||
|             m_good = false; |  | ||||||
| 
 |  | ||||||
|         return items_read; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     template <typename T> |     template <typename T> | ||||||
|     size_t WriteArray(const T* data, size_t length) { |     size_t WriteArray(const T* data, size_t length) { | ||||||
|         static_assert(std::is_trivially_copyable<T>(), |         static_assert(std::is_trivially_copyable<T>(), | ||||||
|                       "Given array does not consist of trivially copyable objects"); |                       "Given array does not consist of trivially copyable objects"); | ||||||
| 
 |         if (!IsOpen()) | ||||||
|         if (!IsOpen()) { |  | ||||||
|             m_good = false; |  | ||||||
|             return -1; |             return -1; | ||||||
|         } |         return std::fwrite(data, sizeof(T), length, m_file); | ||||||
| 
 |  | ||||||
|         size_t items_written = std::fwrite(data, sizeof(T), length, m_file); |  | ||||||
|         if (items_written != length) |  | ||||||
|             m_good = false; |  | ||||||
| 
 |  | ||||||
|         return items_written; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     template <typename T> |     template <typename T> | ||||||
|     size_t ReadBytes(T* data, size_t length) { |     size_t ReadBytes(T* data, size_t length) const { | ||||||
|         static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable"); |         static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable"); | ||||||
|         return ReadArray(reinterpret_cast<char*>(data), length); |         return ReadArray(reinterpret_cast<char*>(data), length); | ||||||
|     } |     } | ||||||
| @ -231,15 +245,7 @@ public: | |||||||
|         return nullptr != m_file; |         return nullptr != m_file; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // m_good is set to false when a read, write or other function fails
 |     bool Seek(s64 off, int origin) const; | ||||||
|     bool IsGood() const { |  | ||||||
|         return m_good; |  | ||||||
|     } |  | ||||||
|     explicit operator bool() const { |  | ||||||
|         return IsGood(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     bool Seek(s64 off, int origin); |  | ||||||
|     u64 Tell() const; |     u64 Tell() const; | ||||||
|     u64 GetSize() const; |     u64 GetSize() const; | ||||||
|     bool Resize(u64 size); |     bool Resize(u64 size); | ||||||
| @ -247,13 +253,11 @@ public: | |||||||
| 
 | 
 | ||||||
|     // clear error state
 |     // clear error state
 | ||||||
|     void Clear() { |     void Clear() { | ||||||
|         m_good = true; |  | ||||||
|         std::clearerr(m_file); |         std::clearerr(m_file); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::FILE* m_file = nullptr; |     std::FILE* m_file = nullptr; | ||||||
|     bool m_good = true; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace FileUtil
 | } // namespace FileUtil
 | ||||||
|  | |||||||
| @ -8,27 +8,27 @@ add_library(core STATIC | |||||||
|     core_cpu.h |     core_cpu.h | ||||||
|     core_timing.cpp |     core_timing.cpp | ||||||
|     core_timing.h |     core_timing.h | ||||||
|  |     file_sys/content_archive.cpp | ||||||
|  |     file_sys/content_archive.h | ||||||
|     file_sys/directory.h |     file_sys/directory.h | ||||||
|     file_sys/disk_filesystem.cpp |  | ||||||
|     file_sys/disk_filesystem.h |  | ||||||
|     file_sys/errors.h |     file_sys/errors.h | ||||||
|     file_sys/filesystem.cpp |     file_sys/mode.h | ||||||
|     file_sys/filesystem.h |  | ||||||
|     file_sys/partition_filesystem.cpp |     file_sys/partition_filesystem.cpp | ||||||
|     file_sys/partition_filesystem.h |     file_sys/partition_filesystem.h | ||||||
|     file_sys/path_parser.cpp |  | ||||||
|     file_sys/path_parser.h |  | ||||||
|     file_sys/program_metadata.cpp |     file_sys/program_metadata.cpp | ||||||
|     file_sys/program_metadata.h |     file_sys/program_metadata.h | ||||||
|     file_sys/romfs_factory.cpp |     file_sys/romfs_factory.cpp | ||||||
|     file_sys/romfs_factory.h |     file_sys/romfs_factory.h | ||||||
|     file_sys/romfs_filesystem.cpp |  | ||||||
|     file_sys/romfs_filesystem.h |  | ||||||
|     file_sys/savedata_factory.cpp |     file_sys/savedata_factory.cpp | ||||||
|     file_sys/savedata_factory.h |     file_sys/savedata_factory.h | ||||||
|     file_sys/sdmc_factory.cpp |     file_sys/sdmc_factory.cpp | ||||||
|     file_sys/sdmc_factory.h |     file_sys/sdmc_factory.h | ||||||
|     file_sys/storage.h |     file_sys/vfs.cpp | ||||||
|  |     file_sys/vfs.h | ||||||
|  |     file_sys/vfs_offset.cpp | ||||||
|  |     file_sys/vfs_offset.h | ||||||
|  |     file_sys/vfs_real.cpp | ||||||
|  |     file_sys/vfs_real.h | ||||||
|     frontend/emu_window.cpp |     frontend/emu_window.cpp | ||||||
|     frontend/emu_window.h |     frontend/emu_window.h | ||||||
|     frontend/framebuffer_layout.cpp |     frontend/framebuffer_layout.cpp | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ | |||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| #include "core/memory_setup.h" | #include "core/memory_setup.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
|  | #include "file_sys/vfs_real.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
| 
 | 
 | ||||||
| namespace Core { | namespace Core { | ||||||
| @ -84,7 +85,7 @@ System::ResultStatus System::SingleStep() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) { | System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) { | ||||||
|     app_loader = Loader::GetLoader(filepath); |     app_loader = Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(filepath)); | ||||||
| 
 | 
 | ||||||
|     if (!app_loader) { |     if (!app_loader) { | ||||||
|         LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); |         LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); | ||||||
|  | |||||||
							
								
								
									
										167
									
								
								src/core/file_sys/content_archive.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/core/file_sys/content_archive.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,167 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "core/file_sys/content_archive.h" | ||||||
|  | #include "core/file_sys/vfs_offset.h" | ||||||
|  | #include "core/loader/loader.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | // Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
 | ||||||
|  | constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; | ||||||
|  | 
 | ||||||
|  | constexpr u64 SECTION_HEADER_SIZE = 0x200; | ||||||
|  | constexpr u64 SECTION_HEADER_OFFSET = 0x400; | ||||||
|  | 
 | ||||||
|  | constexpr u32 IVFC_MAX_LEVEL = 6; | ||||||
|  | 
 | ||||||
|  | enum class NCASectionFilesystemType : u8 { | ||||||
|  |     PFS0 = 0x2, | ||||||
|  |     ROMFS = 0x3, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct NCASectionHeaderBlock { | ||||||
|  |     INSERT_PADDING_BYTES(3); | ||||||
|  |     NCASectionFilesystemType filesystem_type; | ||||||
|  |     u8 crypto_type; | ||||||
|  |     INSERT_PADDING_BYTES(3); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | ||||||
|  | 
 | ||||||
|  | struct PFS0Superblock { | ||||||
|  |     NCASectionHeaderBlock header_block; | ||||||
|  |     std::array<u8, 0x20> hash; | ||||||
|  |     u32_le size; | ||||||
|  |     INSERT_PADDING_BYTES(4); | ||||||
|  |     u64_le hash_table_offset; | ||||||
|  |     u64_le hash_table_size; | ||||||
|  |     u64_le pfs0_header_offset; | ||||||
|  |     u64_le pfs0_size; | ||||||
|  |     INSERT_PADDING_BYTES(432); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | ||||||
|  | 
 | ||||||
|  | struct IVFCLevel { | ||||||
|  |     u64_le offset; | ||||||
|  |     u64_le size; | ||||||
|  |     u32_le block_size; | ||||||
|  |     u32_le reserved; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); | ||||||
|  | 
 | ||||||
|  | struct RomFSSuperblock { | ||||||
|  |     NCASectionHeaderBlock header_block; | ||||||
|  |     u32_le magic; | ||||||
|  |     u32_le magic_number; | ||||||
|  |     INSERT_PADDING_BYTES(8); | ||||||
|  |     std::array<IVFCLevel, 6> levels; | ||||||
|  |     INSERT_PADDING_BYTES(64); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); | ||||||
|  | 
 | ||||||
|  | NCA::NCA(VirtualFile file_) : file(file_) { | ||||||
|  |     if (sizeof(NCAHeader) != file->ReadObject(&header)) | ||||||
|  |         LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||||||
|  | 
 | ||||||
|  |     if (!IsValidNCA(header)) { | ||||||
|  |         status = Loader::ResultStatus::ErrorInvalidFormat; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::ptrdiff_t number_sections = | ||||||
|  |         std::count_if(std::begin(header.section_tables), std::end(header.section_tables), | ||||||
|  |                       [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); | ||||||
|  | 
 | ||||||
|  |     for (std::ptrdiff_t i = 0; i < number_sections; ++i) { | ||||||
|  |         // Seek to beginning of this section.
 | ||||||
|  |         NCASectionHeaderBlock block{}; | ||||||
|  |         if (sizeof(NCASectionHeaderBlock) != | ||||||
|  |             file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | ||||||
|  |             LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||||||
|  | 
 | ||||||
|  |         if (block.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||||||
|  |             RomFSSuperblock sb{}; | ||||||
|  |             if (sizeof(RomFSSuperblock) != | ||||||
|  |                 file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | ||||||
|  |                 LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||||||
|  | 
 | ||||||
|  |             const size_t romfs_offset = | ||||||
|  |                 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + | ||||||
|  |                 sb.levels[IVFC_MAX_LEVEL - 1].offset; | ||||||
|  |             const size_t romfs_size = sb.levels[IVFC_MAX_LEVEL - 1].size; | ||||||
|  |             files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset)); | ||||||
|  |             romfs = files.back(); | ||||||
|  |         } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { | ||||||
|  |             PFS0Superblock sb{}; | ||||||
|  |             // Seek back to beginning of this section.
 | ||||||
|  |             if (sizeof(PFS0Superblock) != | ||||||
|  |                 file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | ||||||
|  |                 LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||||||
|  | 
 | ||||||
|  |             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * | ||||||
|  |                           MEDIA_OFFSET_MULTIPLIER) + | ||||||
|  |                          sb.pfs0_header_offset; | ||||||
|  |             u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - | ||||||
|  |                                                   header.section_tables[i].media_offset); | ||||||
|  |             auto npfs = std::make_shared<PartitionFilesystem>( | ||||||
|  |                 std::make_shared<OffsetVfsFile>(file, size, offset)); | ||||||
|  | 
 | ||||||
|  |             if (npfs->GetStatus() == Loader::ResultStatus::Success) { | ||||||
|  |                 dirs.emplace_back(npfs); | ||||||
|  |                 if (IsDirectoryExeFS(dirs.back())) | ||||||
|  |                     exefs = dirs.back(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     status = Loader::ResultStatus::Success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Loader::ResultStatus NCA::GetStatus() const { | ||||||
|  |     return status; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const { | ||||||
|  |     if (status != Loader::ResultStatus::Success) | ||||||
|  |         return {}; | ||||||
|  |     return files; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const { | ||||||
|  |     if (status != Loader::ResultStatus::Success) | ||||||
|  |         return {}; | ||||||
|  |     return dirs; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string NCA::GetName() const { | ||||||
|  |     return file->GetName(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const { | ||||||
|  |     return file->GetContainingDirectory(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | NCAContentType NCA::GetType() const { | ||||||
|  |     return header.content_type; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u64 NCA::GetTitleId() const { | ||||||
|  |     if (status != Loader::ResultStatus::Success) | ||||||
|  |         return {}; | ||||||
|  |     return header.title_id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualFile NCA::GetRomFS() const { | ||||||
|  |     return romfs; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualDir NCA::GetExeFS() const { | ||||||
|  |     return exefs; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										95
									
								
								src/core/file_sys/content_archive.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/core/file_sys/content_archive.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "common/common_funcs.h" | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/swap.h" | ||||||
|  | #include "core/file_sys/partition_filesystem.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | enum class NCAContentType : u8 { | ||||||
|  |     Program = 0, | ||||||
|  |     Meta = 1, | ||||||
|  |     Control = 2, | ||||||
|  |     Manual = 3, | ||||||
|  |     Data = 4, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct NCASectionTableEntry { | ||||||
|  |     u32_le media_offset; | ||||||
|  |     u32_le media_end_offset; | ||||||
|  |     INSERT_PADDING_BYTES(0x8); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size."); | ||||||
|  | 
 | ||||||
|  | struct NCAHeader { | ||||||
|  |     std::array<u8, 0x100> rsa_signature_1; | ||||||
|  |     std::array<u8, 0x100> rsa_signature_2; | ||||||
|  |     u32_le magic; | ||||||
|  |     u8 is_system; | ||||||
|  |     NCAContentType content_type; | ||||||
|  |     u8 crypto_type; | ||||||
|  |     u8 key_index; | ||||||
|  |     u64_le size; | ||||||
|  |     u64_le title_id; | ||||||
|  |     INSERT_PADDING_BYTES(0x4); | ||||||
|  |     u32_le sdk_version; | ||||||
|  |     u8 crypto_type_2; | ||||||
|  |     INSERT_PADDING_BYTES(15); | ||||||
|  |     std::array<u8, 0x10> rights_id; | ||||||
|  |     std::array<NCASectionTableEntry, 0x4> section_tables; | ||||||
|  |     std::array<std::array<u8, 0x20>, 0x4> hash_tables; | ||||||
|  |     std::array<std::array<u8, 0x10>, 0x4> key_area; | ||||||
|  |     INSERT_PADDING_BYTES(0xC0); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | ||||||
|  | 
 | ||||||
|  | inline bool IsDirectoryExeFS(std::shared_ptr<FileSys::VfsDirectory> pfs) { | ||||||
|  |     // According to switchbrew, an exefs must only contain these two files:
 | ||||||
|  |     return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline bool IsValidNCA(const NCAHeader& header) { | ||||||
|  |     return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || | ||||||
|  |            header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
 | ||||||
|  | // After construction, use GetStatus to determine if the file is valid and ready to be used.
 | ||||||
|  | class NCA : public ReadOnlyVfsDirectory { | ||||||
|  | public: | ||||||
|  |     explicit NCA(VirtualFile file); | ||||||
|  |     Loader::ResultStatus GetStatus() const; | ||||||
|  | 
 | ||||||
|  |     std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; | ||||||
|  |     std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; | ||||||
|  |     std::string GetName() const override; | ||||||
|  |     std::shared_ptr<VfsDirectory> GetParentDirectory() const override; | ||||||
|  | 
 | ||||||
|  |     NCAContentType GetType() const; | ||||||
|  |     u64 GetTitleId() const; | ||||||
|  | 
 | ||||||
|  |     VirtualFile GetRomFS() const; | ||||||
|  |     VirtualDir GetExeFS() const; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::vector<VirtualDir> dirs; | ||||||
|  |     std::vector<VirtualFile> files; | ||||||
|  | 
 | ||||||
|  |     VirtualFile romfs = nullptr; | ||||||
|  |     VirtualDir exefs = nullptr; | ||||||
|  |     VirtualFile file; | ||||||
|  | 
 | ||||||
|  |     NCAHeader header{}; | ||||||
|  | 
 | ||||||
|  |     Loader::ResultStatus status{}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
| @ -8,13 +8,17 @@ | |||||||
| #include <cstddef> | #include <cstddef> | ||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| 
 | 
 | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||||
| // FileSys namespace
 | // FileSys namespace
 | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
|  | enum EntryType : u8 { | ||||||
|  |     Directory = 0, | ||||||
|  |     File = 1, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| // Structure of a directory entry, from
 | // Structure of a directory entry, from
 | ||||||
| // http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
 | // http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
 | ||||||
| const size_t FILENAME_LENGTH = 0x300; | const size_t FILENAME_LENGTH = 0x300; | ||||||
|  | |||||||
| @ -1,239 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <cstring> |  | ||||||
| #include <memory> |  | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| #include "core/file_sys/disk_filesystem.h" |  | ||||||
| #include "core/file_sys/errors.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| static std::string ModeFlagsToString(Mode mode) { |  | ||||||
|     std::string mode_str; |  | ||||||
|     u32 mode_flags = static_cast<u32>(mode); |  | ||||||
| 
 |  | ||||||
|     // Calculate the correct open mode for the file.
 |  | ||||||
|     if ((mode_flags & static_cast<u32>(Mode::Read)) && |  | ||||||
|         (mode_flags & static_cast<u32>(Mode::Write))) { |  | ||||||
|         if (mode_flags & static_cast<u32>(Mode::Append)) |  | ||||||
|             mode_str = "a+"; |  | ||||||
|         else |  | ||||||
|             mode_str = "r+"; |  | ||||||
|     } else { |  | ||||||
|         if (mode_flags & static_cast<u32>(Mode::Read)) |  | ||||||
|             mode_str = "r"; |  | ||||||
|         else if (mode_flags & static_cast<u32>(Mode::Append)) |  | ||||||
|             mode_str = "a"; |  | ||||||
|         else if (mode_flags & static_cast<u32>(Mode::Write)) |  | ||||||
|             mode_str = "w"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     mode_str += "b"; |  | ||||||
| 
 |  | ||||||
|     return mode_str; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string Disk_FileSystem::GetName() const { |  | ||||||
|     return "Disk"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path, |  | ||||||
|                                                                      Mode mode) const { |  | ||||||
| 
 |  | ||||||
|     // Calculate the correct open mode for the file.
 |  | ||||||
|     std::string mode_str = ModeFlagsToString(mode); |  | ||||||
| 
 |  | ||||||
|     std::string full_path = base_directory + path; |  | ||||||
|     auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str()); |  | ||||||
| 
 |  | ||||||
|     if (!file->IsOpen()) { |  | ||||||
|         return ERROR_PATH_NOT_FOUND; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return MakeResult<std::unique_ptr<StorageBackend>>( |  | ||||||
|         std::make_unique<Disk_Storage>(std::move(file))); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const { |  | ||||||
|     std::string full_path = base_directory + path; |  | ||||||
| 
 |  | ||||||
|     if (!FileUtil::Exists(full_path)) { |  | ||||||
|         return ERROR_PATH_NOT_FOUND; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     FileUtil::Delete(full_path); |  | ||||||
| 
 |  | ||||||
|     return RESULT_SUCCESS; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode Disk_FileSystem::RenameFile(const std::string& src_path, |  | ||||||
|                                        const std::string& dest_path) const { |  | ||||||
|     const std::string full_src_path = base_directory + src_path; |  | ||||||
|     const std::string full_dest_path = base_directory + dest_path; |  | ||||||
| 
 |  | ||||||
|     if (!FileUtil::Exists(full_src_path)) { |  | ||||||
|         return ERROR_PATH_NOT_FOUND; |  | ||||||
|     } |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return FileUtil::Rename(full_src_path, full_dest_path) ? RESULT_SUCCESS : ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const { |  | ||||||
|     LOG_WARNING(Service_FS, "(STUBBED) called"); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const { |  | ||||||
|     LOG_WARNING(Service_FS, "(STUBBED) called"); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const { |  | ||||||
|     LOG_WARNING(Service_FS, "(STUBBED) called"); |  | ||||||
| 
 |  | ||||||
|     std::string full_path = base_directory + path; |  | ||||||
|     if (size == 0) { |  | ||||||
|         FileUtil::CreateEmptyFile(full_path); |  | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     FileUtil::IOFile file(full_path, "wb"); |  | ||||||
|     // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
 |  | ||||||
|     // We do this by seeking to the right size, then writing a single null byte.
 |  | ||||||
|     if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { |  | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     LOG_ERROR(Service_FS, "Too large file"); |  | ||||||
|     // TODO(Subv): Find out the correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const { |  | ||||||
|     // TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
 |  | ||||||
|     std::string full_path = base_directory + path; |  | ||||||
| 
 |  | ||||||
|     if (FileUtil::CreateDir(full_path)) { |  | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", full_path); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const { |  | ||||||
|     LOG_WARNING(Service_FS, "(STUBBED) called"); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory( |  | ||||||
|     const std::string& path) const { |  | ||||||
| 
 |  | ||||||
|     std::string full_path = base_directory + path; |  | ||||||
| 
 |  | ||||||
|     if (!FileUtil::IsDirectory(full_path)) { |  | ||||||
|         // TODO(Subv): Find the correct error code for this.
 |  | ||||||
|         return ResultCode(-1); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     auto directory = std::make_unique<Disk_Directory>(full_path); |  | ||||||
|     return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Disk_FileSystem::GetFreeSpaceSize() const { |  | ||||||
|     LOG_WARNING(Service_FS, "(STUBBED) called"); |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const { |  | ||||||
|     std::string full_path = base_directory + path; |  | ||||||
|     if (!FileUtil::Exists(full_path)) { |  | ||||||
|         return ERROR_PATH_NOT_FOUND; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (FileUtil::IsDirectory(full_path)) |  | ||||||
|         return MakeResult(EntryType::Directory); |  | ||||||
| 
 |  | ||||||
|     return MakeResult(EntryType::File); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { |  | ||||||
|     LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length); |  | ||||||
|     file->Seek(offset, SEEK_SET); |  | ||||||
|     return MakeResult<size_t>(file->ReadBytes(buffer, length)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush, |  | ||||||
|                                       const u8* buffer) const { |  | ||||||
|     LOG_WARNING(Service_FS, "(STUBBED) called"); |  | ||||||
|     file->Seek(offset, SEEK_SET); |  | ||||||
|     size_t written = file->WriteBytes(buffer, length); |  | ||||||
|     if (flush) { |  | ||||||
|         file->Flush(); |  | ||||||
|     } |  | ||||||
|     return MakeResult<size_t>(written); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Disk_Storage::GetSize() const { |  | ||||||
|     return file->GetSize(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool Disk_Storage::SetSize(const u64 size) const { |  | ||||||
|     file->Resize(size); |  | ||||||
|     file->Flush(); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Disk_Directory::Disk_Directory(const std::string& path) { |  | ||||||
|     unsigned size = FileUtil::ScanDirectoryTree(path, directory); |  | ||||||
|     directory.size = size; |  | ||||||
|     directory.isDirectory = true; |  | ||||||
|     children_iterator = directory.children.begin(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Disk_Directory::Read(const u64 count, Entry* entries) { |  | ||||||
|     u64 entries_read = 0; |  | ||||||
| 
 |  | ||||||
|     while (entries_read < count && children_iterator != directory.children.cend()) { |  | ||||||
|         const FileUtil::FSTEntry& file = *children_iterator; |  | ||||||
|         const std::string& filename = file.virtualName; |  | ||||||
|         Entry& entry = entries[entries_read]; |  | ||||||
| 
 |  | ||||||
|         LOG_TRACE(Service_FS, "File {}: size={} dir={}", filename, file.size, file.isDirectory); |  | ||||||
| 
 |  | ||||||
|         // TODO(Link Mauve): use a proper conversion to UTF-16.
 |  | ||||||
|         for (size_t j = 0; j < FILENAME_LENGTH; ++j) { |  | ||||||
|             entry.filename[j] = filename[j]; |  | ||||||
|             if (!filename[j]) |  | ||||||
|                 break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (file.isDirectory) { |  | ||||||
|             entry.file_size = 0; |  | ||||||
|             entry.type = EntryType::Directory; |  | ||||||
|         } else { |  | ||||||
|             entry.file_size = file.size; |  | ||||||
|             entry.type = EntryType::File; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         ++entries_read; |  | ||||||
|         ++children_iterator; |  | ||||||
|     } |  | ||||||
|     return entries_read; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Disk_Directory::GetEntryCount() const { |  | ||||||
|     // We convert the children iterator into a const_iterator to allow template argument deduction
 |  | ||||||
|     // in std::distance.
 |  | ||||||
|     std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator; |  | ||||||
|     return std::distance(current, directory.children.end()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -1,84 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <cstddef> |  | ||||||
| #include <memory> |  | ||||||
| #include <string> |  | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "common/file_util.h" |  | ||||||
| #include "core/file_sys/directory.h" |  | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/file_sys/storage.h" |  | ||||||
| #include "core/hle/result.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| class Disk_FileSystem : public FileSystemBackend { |  | ||||||
| public: |  | ||||||
|     explicit Disk_FileSystem(std::string base_directory) |  | ||||||
|         : base_directory(std::move(base_directory)) {} |  | ||||||
| 
 |  | ||||||
|     std::string GetName() const override; |  | ||||||
| 
 |  | ||||||
|     ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, |  | ||||||
|                                                         Mode mode) const override; |  | ||||||
|     ResultCode DeleteFile(const std::string& path) const override; |  | ||||||
|     ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override; |  | ||||||
|     ResultCode DeleteDirectory(const Path& path) const override; |  | ||||||
|     ResultCode DeleteDirectoryRecursively(const Path& path) const override; |  | ||||||
|     ResultCode CreateFile(const std::string& path, u64 size) const override; |  | ||||||
|     ResultCode CreateDirectory(const std::string& path) const override; |  | ||||||
|     ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; |  | ||||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( |  | ||||||
|         const std::string& path) const override; |  | ||||||
|     u64 GetFreeSpaceSize() const override; |  | ||||||
|     ResultVal<EntryType> GetEntryType(const std::string& path) const override; |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
|     std::string base_directory; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class Disk_Storage : public StorageBackend { |  | ||||||
| public: |  | ||||||
|     explicit Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {} |  | ||||||
| 
 |  | ||||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; |  | ||||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; |  | ||||||
|     u64 GetSize() const override; |  | ||||||
|     bool SetSize(u64 size) const override; |  | ||||||
|     bool Close() const override { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|     void Flush() const override {} |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::shared_ptr<FileUtil::IOFile> file; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class Disk_Directory : public DirectoryBackend { |  | ||||||
| public: |  | ||||||
|     explicit Disk_Directory(const std::string& path); |  | ||||||
| 
 |  | ||||||
|     ~Disk_Directory() override { |  | ||||||
|         Close(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     u64 Read(const u64 count, Entry* entries) override; |  | ||||||
|     u64 GetEntryCount() const override; |  | ||||||
| 
 |  | ||||||
|     bool Close() const override { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
|     FileUtil::FSTEntry directory; |  | ||||||
| 
 |  | ||||||
|     // We need to remember the last entry we returned, so a subsequent call to Read will continue
 |  | ||||||
|     // from the next one. This iterator will always point to the next unread entry.
 |  | ||||||
|     std::vector<FileUtil::FSTEntry>::iterator children_iterator; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -11,7 +11,7 @@ namespace FileSys { | |||||||
| namespace ErrCodes { | namespace ErrCodes { | ||||||
| enum { | enum { | ||||||
|     NotFound = 1, |     NotFound = 1, | ||||||
|     SaveDataNotFound = 1002, |     TitleNotFound = 1002, | ||||||
|     SdCardNotFound = 2001, |     SdCardNotFound = 2001, | ||||||
|     RomFSNotFound = 2520, |     RomFSNotFound = 2520, | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,122 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <cstddef> |  | ||||||
| #include <iomanip> |  | ||||||
| #include <sstream> |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| #include "common/string_util.h" |  | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/memory.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| Path::Path(LowPathType type, u32 size, u32 pointer) : type(type) { |  | ||||||
|     switch (type) { |  | ||||||
|     case Binary: { |  | ||||||
|         binary.resize(size); |  | ||||||
|         Memory::ReadBlock(pointer, binary.data(), binary.size()); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     case Char: { |  | ||||||
|         string.resize(size - 1); // Data is always null-terminated.
 |  | ||||||
|         Memory::ReadBlock(pointer, &string[0], string.size()); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     case Wchar: { |  | ||||||
|         u16str.resize(size / 2 - 1); // Data is always null-terminated.
 |  | ||||||
|         Memory::ReadBlock(pointer, &u16str[0], u16str.size() * sizeof(char16_t)); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     default: |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string Path::DebugStr() const { |  | ||||||
|     switch (GetType()) { |  | ||||||
|     case Invalid: |  | ||||||
|     default: |  | ||||||
|         return "[Invalid]"; |  | ||||||
|     case Empty: |  | ||||||
|         return "[Empty]"; |  | ||||||
|     case Binary: { |  | ||||||
|         std::stringstream res; |  | ||||||
|         res << "[Binary: "; |  | ||||||
|         for (unsigned byte : binary) |  | ||||||
|             res << std::hex << std::setw(2) << std::setfill('0') << byte; |  | ||||||
|         res << ']'; |  | ||||||
|         return res.str(); |  | ||||||
|     } |  | ||||||
|     case Char: |  | ||||||
|         return "[Char: " + AsString() + ']'; |  | ||||||
|     case Wchar: |  | ||||||
|         return "[Wchar: " + AsString() + ']'; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string Path::AsString() const { |  | ||||||
|     switch (GetType()) { |  | ||||||
|     case Char: |  | ||||||
|         return string; |  | ||||||
|     case Wchar: |  | ||||||
|         return Common::UTF16ToUTF8(u16str); |  | ||||||
|     case Empty: |  | ||||||
|         return {}; |  | ||||||
|     case Invalid: |  | ||||||
|     case Binary: |  | ||||||
|     default: |  | ||||||
|         // TODO(yuriks): Add assert
 |  | ||||||
|         LOG_ERROR(Service_FS, "LowPathType cannot be converted to string!"); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::u16string Path::AsU16Str() const { |  | ||||||
|     switch (GetType()) { |  | ||||||
|     case Char: |  | ||||||
|         return Common::UTF8ToUTF16(string); |  | ||||||
|     case Wchar: |  | ||||||
|         return u16str; |  | ||||||
|     case Empty: |  | ||||||
|         return {}; |  | ||||||
|     case Invalid: |  | ||||||
|     case Binary: |  | ||||||
|         // TODO(yuriks): Add assert
 |  | ||||||
|         LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!"); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     UNREACHABLE(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::vector<u8> Path::AsBinary() const { |  | ||||||
|     switch (GetType()) { |  | ||||||
|     case Binary: |  | ||||||
|         return binary; |  | ||||||
|     case Char: |  | ||||||
|         return std::vector<u8>(string.begin(), string.end()); |  | ||||||
|     case Wchar: { |  | ||||||
|         // use two u8 for each character of u16str
 |  | ||||||
|         std::vector<u8> to_return(u16str.size() * 2); |  | ||||||
|         for (size_t i = 0; i < u16str.size(); ++i) { |  | ||||||
|             u16 tmp_char = u16str.at(i); |  | ||||||
|             to_return[i * 2] = (tmp_char & 0xFF00) >> 8; |  | ||||||
|             to_return[i * 2 + 1] = (tmp_char & 0x00FF); |  | ||||||
|         } |  | ||||||
|         return to_return; |  | ||||||
|     } |  | ||||||
|     case Empty: |  | ||||||
|         return {}; |  | ||||||
|     case Invalid: |  | ||||||
|     default: |  | ||||||
|         // TODO(yuriks): Add assert
 |  | ||||||
|         LOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!"); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -1,170 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <memory> |  | ||||||
| #include <string> |  | ||||||
| #include <utility> |  | ||||||
| #include <vector> |  | ||||||
| #include "common/bit_field.h" |  | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "common/swap.h" |  | ||||||
| #include "core/hle/result.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| class StorageBackend; |  | ||||||
| class DirectoryBackend; |  | ||||||
| 
 |  | ||||||
| // Path string type
 |  | ||||||
| enum LowPathType : u32 { |  | ||||||
|     Invalid = 0, |  | ||||||
|     Empty = 1, |  | ||||||
|     Binary = 2, |  | ||||||
|     Char = 3, |  | ||||||
|     Wchar = 4, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| enum EntryType : u8 { |  | ||||||
|     Directory = 0, |  | ||||||
|     File = 1, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| enum class Mode : u32 { |  | ||||||
|     Read = 1, |  | ||||||
|     Write = 2, |  | ||||||
|     Append = 4, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class Path { |  | ||||||
| public: |  | ||||||
|     Path() : type(Invalid) {} |  | ||||||
|     Path(const char* path) : type(Char), string(path) {} |  | ||||||
|     Path(std::vector<u8> binary_data) : type(Binary), binary(std::move(binary_data)) {} |  | ||||||
|     Path(LowPathType type, u32 size, u32 pointer); |  | ||||||
| 
 |  | ||||||
|     LowPathType GetType() const { |  | ||||||
|         return type; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Gets the string representation of the path for debugging |  | ||||||
|      * @return String representation of the path for debugging |  | ||||||
|      */ |  | ||||||
|     std::string DebugStr() const; |  | ||||||
| 
 |  | ||||||
|     std::string AsString() const; |  | ||||||
|     std::u16string AsU16Str() const; |  | ||||||
|     std::vector<u8> AsBinary() const; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     LowPathType type; |  | ||||||
|     std::vector<u8> binary; |  | ||||||
|     std::string string; |  | ||||||
|     std::u16string u16str; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /// Parameters of the archive, as specified in the Create or Format call.
 |  | ||||||
| struct ArchiveFormatInfo { |  | ||||||
|     u32_le total_size;         ///< The pre-defined size of the archive.
 |  | ||||||
|     u32_le number_directories; ///< The pre-defined number of directories in the archive.
 |  | ||||||
|     u32_le number_files;       ///< The pre-defined number of files in the archive.
 |  | ||||||
|     u8 duplicate_data;         ///< Whether the archive should duplicate the data.
 |  | ||||||
| }; |  | ||||||
| static_assert(std::is_pod<ArchiveFormatInfo>::value, "ArchiveFormatInfo is not POD"); |  | ||||||
| 
 |  | ||||||
| class FileSystemBackend : NonCopyable { |  | ||||||
| public: |  | ||||||
|     virtual ~FileSystemBackend() {} |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) |  | ||||||
|      */ |  | ||||||
|     virtual std::string GetName() const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Create a file specified by its path |  | ||||||
|      * @param path Path relative to the Archive |  | ||||||
|      * @param size The size of the new file, filled with zeroes |  | ||||||
|      * @return Result of the operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Delete a file specified by its path |  | ||||||
|      * @param path Path relative to the archive |  | ||||||
|      * @return Result of the operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode DeleteFile(const std::string& path) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Create a directory specified by its path |  | ||||||
|      * @param path Path relative to the archive |  | ||||||
|      * @return Result of the operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode CreateDirectory(const std::string& path) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Delete a directory specified by its path |  | ||||||
|      * @param path Path relative to the archive |  | ||||||
|      * @return Result of the operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode DeleteDirectory(const Path& path) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Delete a directory specified by its path and anything under it |  | ||||||
|      * @param path Path relative to the archive |  | ||||||
|      * @return Result of the operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Rename a File specified by its path |  | ||||||
|      * @param src_path Source path relative to the archive |  | ||||||
|      * @param dest_path Destination path relative to the archive |  | ||||||
|      * @return Result of the operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode RenameFile(const std::string& src_path, |  | ||||||
|                                   const std::string& dest_path) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Rename a Directory specified by its path |  | ||||||
|      * @param src_path Source path relative to the archive |  | ||||||
|      * @param dest_path Destination path relative to the archive |  | ||||||
|      * @return Result of the operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Open a file specified by its path, using the specified mode |  | ||||||
|      * @param path Path relative to the archive |  | ||||||
|      * @param mode Mode to open the file with |  | ||||||
|      * @return Opened file, or error code |  | ||||||
|      */ |  | ||||||
|     virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, |  | ||||||
|                                                                 Mode mode) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Open a directory specified by its path |  | ||||||
|      * @param path Path relative to the archive |  | ||||||
|      * @return Opened directory, or error code |  | ||||||
|      */ |  | ||||||
|     virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( |  | ||||||
|         const std::string& path) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Get the free space |  | ||||||
|      * @return The number of free bytes in the archive |  | ||||||
|      */ |  | ||||||
|     virtual u64 GetFreeSpaceSize() const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Get the type of the specified path |  | ||||||
|      * @return The type of the specified path or error code |  | ||||||
|      */ |  | ||||||
|     virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
							
								
								
									
										17
									
								
								src/core/file_sys/mode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/core/file_sys/mode.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "common/common_types.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | enum class Mode : u32 { | ||||||
|  |     Read = 1, | ||||||
|  |     Write = 2, | ||||||
|  |     Append = 4, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
| @ -6,29 +6,30 @@ | |||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "core/file_sys/partition_filesystem.h" | #include "core/file_sys/partition_filesystem.h" | ||||||
|  | #include "core/file_sys/vfs_offset.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
| Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, size_t offset) { | PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) { | ||||||
|     FileUtil::IOFile file(file_path, "rb"); |  | ||||||
|     if (!file.IsOpen()) |  | ||||||
|         return Loader::ResultStatus::Error; |  | ||||||
| 
 |  | ||||||
|     // At least be as large as the header
 |     // At least be as large as the header
 | ||||||
|     if (file.GetSize() < sizeof(Header)) |     if (file->GetSize() < sizeof(Header)) { | ||||||
|         return Loader::ResultStatus::Error; |         status = Loader::ResultStatus::Error; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     file.Seek(offset, SEEK_SET); |  | ||||||
|     // For cartridges, HFSs can get very large, so we need to calculate the size up to
 |     // For cartridges, HFSs can get very large, so we need to calculate the size up to
 | ||||||
|     // the actual content itself instead of just blindly reading in the entire file.
 |     // the actual content itself instead of just blindly reading in the entire file.
 | ||||||
|     Header pfs_header; |     Header pfs_header; | ||||||
|     if (!file.ReadBytes(&pfs_header, sizeof(Header))) |     if (sizeof(Header) != file->ReadObject(&pfs_header)) { | ||||||
|         return Loader::ResultStatus::Error; |         status = Loader::ResultStatus::Error; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && |     if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && | ||||||
|         pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { |         pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { | ||||||
|         return Loader::ResultStatus::ErrorInvalidFormat; |         status = Loader::ResultStatus::ErrorInvalidFormat; | ||||||
|  |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); |     bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); | ||||||
| @ -38,99 +39,86 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, siz | |||||||
|         sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; |         sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; | ||||||
| 
 | 
 | ||||||
|     // Actually read in now...
 |     // Actually read in now...
 | ||||||
|     file.Seek(offset, SEEK_SET); |     std::vector<u8> file_data = file->ReadBytes(metadata_size); | ||||||
|     std::vector<u8> file_data(metadata_size); |  | ||||||
| 
 | 
 | ||||||
|     if (!file.ReadBytes(file_data.data(), metadata_size)) |     if (file_data.size() != metadata_size) { | ||||||
|         return Loader::ResultStatus::Error; |         status = Loader::ResultStatus::Error; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     Loader::ResultStatus result = Load(file_data); |     size_t total_size = file_data.size(); | ||||||
|     if (result != Loader::ResultStatus::Success) |     if (total_size < sizeof(Header)) { | ||||||
|         LOG_ERROR(Service_FS, "Failed to load PFS from file {}!", file_path); |         status = Loader::ResultStatus::Error; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     return result; |     memcpy(&pfs_header, file_data.data(), sizeof(Header)); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, size_t offset) { |  | ||||||
|     size_t total_size = file_data.size() - offset; |  | ||||||
|     if (total_size < sizeof(Header)) |  | ||||||
|         return Loader::ResultStatus::Error; |  | ||||||
| 
 |  | ||||||
|     memcpy(&pfs_header, &file_data[offset], sizeof(Header)); |  | ||||||
|     if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && |     if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && | ||||||
|         pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { |         pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { | ||||||
|         return Loader::ResultStatus::ErrorInvalidFormat; |         status = Loader::ResultStatus::ErrorInvalidFormat; | ||||||
|  |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); |     is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); | ||||||
| 
 | 
 | ||||||
|     size_t entries_offset = offset + sizeof(Header); |     size_t entries_offset = sizeof(Header); | ||||||
|     size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); |  | ||||||
|     size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); |     size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); | ||||||
|     for (u16 i = 0; i < pfs_header.num_entries; i++) { |  | ||||||
|         FileEntry entry; |  | ||||||
| 
 |  | ||||||
|         memcpy(&entry.fs_entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry)); |  | ||||||
|         entry.name = std::string(reinterpret_cast<const char*>( |  | ||||||
|             &file_data[strtab_offset + entry.fs_entry.strtab_offset])); |  | ||||||
|         pfs_entries.push_back(std::move(entry)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     content_offset = strtab_offset + pfs_header.strtab_size; |     content_offset = strtab_offset + pfs_header.strtab_size; | ||||||
|  |     for (u16 i = 0; i < pfs_header.num_entries; i++) { | ||||||
|  |         FSEntry entry; | ||||||
| 
 | 
 | ||||||
|     return Loader::ResultStatus::Success; |         memcpy(&entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry)); | ||||||
| } |         std::string name( | ||||||
|  |             reinterpret_cast<const char*>(&file_data[strtab_offset + entry.strtab_offset])); | ||||||
| 
 | 
 | ||||||
| u32 PartitionFilesystem::GetNumEntries() const { |         pfs_files.emplace_back( | ||||||
|     return pfs_header.num_entries; |             std::make_shared<OffsetVfsFile>(file, entry.size, content_offset + entry.offset, name)); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 PartitionFilesystem::GetEntryOffset(u32 index) const { |  | ||||||
|     if (index > GetNumEntries()) |  | ||||||
|         return 0; |  | ||||||
| 
 |  | ||||||
|     return content_offset + pfs_entries[index].fs_entry.offset; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 PartitionFilesystem::GetEntrySize(u32 index) const { |  | ||||||
|     if (index > GetNumEntries()) |  | ||||||
|         return 0; |  | ||||||
| 
 |  | ||||||
|     return pfs_entries[index].fs_entry.size; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string PartitionFilesystem::GetEntryName(u32 index) const { |  | ||||||
|     if (index > GetNumEntries()) |  | ||||||
|         return ""; |  | ||||||
| 
 |  | ||||||
|     return pfs_entries[index].name; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 PartitionFilesystem::GetFileOffset(const std::string& name) const { |  | ||||||
|     for (u32 i = 0; i < pfs_header.num_entries; i++) { |  | ||||||
|         if (pfs_entries[i].name == name) |  | ||||||
|             return content_offset + pfs_entries[i].fs_entry.offset; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return 0; |     status = Loader::ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u64 PartitionFilesystem::GetFileSize(const std::string& name) const { | Loader::ResultStatus PartitionFilesystem::GetStatus() const { | ||||||
|     for (u32 i = 0; i < pfs_header.num_entries; i++) { |     return status; | ||||||
|         if (pfs_entries[i].name == name) |  | ||||||
|             return pfs_entries[i].fs_entry.size; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return 0; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void PartitionFilesystem::Print() const { | std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const { | ||||||
|     LOG_DEBUG(Service_FS, "Magic:                  {}", pfs_header.magic); |     return pfs_files; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const { | ||||||
|  |     return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string PartitionFilesystem::GetName() const { | ||||||
|  |     return is_hfs ? "HFS0" : "PFS0"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const { | ||||||
|  |     // TODO(DarkLordZach): Add support for nested containers.
 | ||||||
|  |     return nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void PartitionFilesystem::PrintDebugInfo() const { | ||||||
|  |     LOG_DEBUG(Service_FS, "Magic:                  {:.4}", pfs_header.magic); | ||||||
|     LOG_DEBUG(Service_FS, "Files:                  {}", pfs_header.num_entries); |     LOG_DEBUG(Service_FS, "Files:                  {}", pfs_header.num_entries); | ||||||
|     for (u32 i = 0; i < pfs_header.num_entries; i++) { |     for (u32 i = 0; i < pfs_header.num_entries; i++) { | ||||||
|         LOG_DEBUG(Service_FS, " > File {}:              {} (0x{:X} bytes, at 0x{:X})", i, |         LOG_DEBUG(Service_FS, " > File {}:              {} (0x{:X} bytes, at 0x{:X})", i, | ||||||
|                   pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size, |                   pfs_files[i]->GetName(), pfs_files[i]->GetSize(), | ||||||
|                   GetFileOffset(pfs_entries[i].name)); |                   dynamic_cast<OffsetVfsFile*>(pfs_files[i].get())->GetOffset()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||||
|  |     auto iter = std::find(pfs_files.begin(), pfs_files.end(), file); | ||||||
|  |     if (iter == pfs_files.end()) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     pfs_files[iter - pfs_files.begin()] = pfs_files.back(); | ||||||
|  |     pfs_files.pop_back(); | ||||||
|  | 
 | ||||||
|  |     pfs_dirs.emplace_back(dir); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
| } // namespace FileSys
 | } // namespace FileSys
 | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ | |||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/swap.h" | #include "common/swap.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
| 
 | 
 | ||||||
| namespace Loader { | namespace Loader { | ||||||
| enum class ResultStatus; | enum class ResultStatus; | ||||||
| @ -21,19 +22,19 @@ namespace FileSys { | |||||||
|  * Helper which implements an interface to parse PFS/HFS filesystems. |  * Helper which implements an interface to parse PFS/HFS filesystems. | ||||||
|  * Data can either be loaded from a file path or data with an offset into it. |  * Data can either be loaded from a file path or data with an offset into it. | ||||||
|  */ |  */ | ||||||
| class PartitionFilesystem { | class PartitionFilesystem : public ReadOnlyVfsDirectory { | ||||||
| public: | public: | ||||||
|     Loader::ResultStatus Load(const std::string& file_path, size_t offset = 0); |     explicit PartitionFilesystem(std::shared_ptr<VfsFile> file); | ||||||
|     Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0); |     Loader::ResultStatus GetStatus() const; | ||||||
| 
 | 
 | ||||||
|     u32 GetNumEntries() const; |     std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; | ||||||
|     u64 GetEntryOffset(u32 index) const; |     std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; | ||||||
|     u64 GetEntrySize(u32 index) const; |     std::string GetName() const override; | ||||||
|     std::string GetEntryName(u32 index) const; |     std::shared_ptr<VfsDirectory> GetParentDirectory() const override; | ||||||
|     u64 GetFileOffset(const std::string& name) const; |     void PrintDebugInfo() const; | ||||||
|     u64 GetFileSize(const std::string& name) const; |  | ||||||
| 
 | 
 | ||||||
|     void Print() const; | protected: | ||||||
|  |     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     struct Header { |     struct Header { | ||||||
| @ -72,16 +73,14 @@ private: | |||||||
| 
 | 
 | ||||||
| #pragma pack(pop) | #pragma pack(pop) | ||||||
| 
 | 
 | ||||||
|     struct FileEntry { |     Loader::ResultStatus status; | ||||||
|         FSEntry fs_entry; |  | ||||||
|         std::string name; |  | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     Header pfs_header; |     Header pfs_header; | ||||||
|     bool is_hfs; |     bool is_hfs; | ||||||
|     size_t content_offset; |     size_t content_offset; | ||||||
| 
 | 
 | ||||||
|     std::vector<FileEntry> pfs_entries; |     std::vector<VirtualFile> pfs_files; | ||||||
|  |     std::vector<VirtualDir> pfs_dirs; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace FileSys
 | } // namespace FileSys
 | ||||||
|  | |||||||
| @ -1,98 +0,0 @@ | |||||||
| // Copyright 2016 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <algorithm> |  | ||||||
| #include <set> |  | ||||||
| #include "common/file_util.h" |  | ||||||
| #include "common/string_util.h" |  | ||||||
| #include "core/file_sys/path_parser.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| PathParser::PathParser(const Path& path) { |  | ||||||
|     if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) { |  | ||||||
|         is_valid = false; |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     auto path_string = path.AsString(); |  | ||||||
|     if (path_string.size() == 0 || path_string[0] != '/') { |  | ||||||
|         is_valid = false; |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Filter out invalid characters for the host system.
 |  | ||||||
|     // Although some of these characters are valid on 3DS, they are unlikely to be used by games.
 |  | ||||||
|     if (std::find_if(path_string.begin(), path_string.end(), [](char c) { |  | ||||||
|             static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'}; |  | ||||||
|             return invalid_chars.find(c) != invalid_chars.end(); |  | ||||||
|         }) != path_string.end()) { |  | ||||||
|         is_valid = false; |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Common::SplitString(path_string, '/', path_sequence); |  | ||||||
| 
 |  | ||||||
|     auto begin = path_sequence.begin(); |  | ||||||
|     auto end = path_sequence.end(); |  | ||||||
|     end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; }); |  | ||||||
|     path_sequence = std::vector<std::string>(begin, end); |  | ||||||
| 
 |  | ||||||
|     // checks if the path is out of bounds.
 |  | ||||||
|     int level = 0; |  | ||||||
|     for (auto& node : path_sequence) { |  | ||||||
|         if (node == "..") { |  | ||||||
|             --level; |  | ||||||
|             if (level < 0) { |  | ||||||
|                 is_valid = false; |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             ++level; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     is_valid = true; |  | ||||||
|     is_root = level == 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const { |  | ||||||
|     auto path = mount_point; |  | ||||||
|     if (!FileUtil::IsDirectory(path)) |  | ||||||
|         return InvalidMountPoint; |  | ||||||
|     if (path_sequence.empty()) { |  | ||||||
|         return DirectoryFound; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) { |  | ||||||
|         if (path.back() != '/') |  | ||||||
|             path += '/'; |  | ||||||
|         path += *iter; |  | ||||||
| 
 |  | ||||||
|         if (!FileUtil::Exists(path)) |  | ||||||
|             return PathNotFound; |  | ||||||
|         if (FileUtil::IsDirectory(path)) |  | ||||||
|             continue; |  | ||||||
|         return FileInPath; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     path += "/" + path_sequence.back(); |  | ||||||
|     if (!FileUtil::Exists(path)) |  | ||||||
|         return NotFound; |  | ||||||
|     if (FileUtil::IsDirectory(path)) |  | ||||||
|         return DirectoryFound; |  | ||||||
|     return FileFound; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string PathParser::BuildHostPath(const std::string& mount_point) const { |  | ||||||
|     std::string path = mount_point; |  | ||||||
|     for (auto& node : path_sequence) { |  | ||||||
|         if (path.back() != '/') |  | ||||||
|             path += '/'; |  | ||||||
|         path += node; |  | ||||||
|     } |  | ||||||
|     return path; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -1,61 +0,0 @@ | |||||||
| // Copyright 2016 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <string> |  | ||||||
| #include <vector> |  | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * A helper class parsing and verifying a string-type Path. |  | ||||||
|  * Every archives with a sub file system should use this class to parse the path argument and check |  | ||||||
|  * the status of the file / directory in question on the host file system. |  | ||||||
|  */ |  | ||||||
| class PathParser { |  | ||||||
| public: |  | ||||||
|     explicit PathParser(const Path& path); |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Checks if the Path is valid. |  | ||||||
|      * This function should be called once a PathParser is constructed. |  | ||||||
|      * A Path is valid if: |  | ||||||
|      *  - it is a string path (with type LowPathType::Char or LowPathType::Wchar), |  | ||||||
|      *  - it starts with "/" (this seems a hard requirement in real 3DS), |  | ||||||
|      *  - it doesn't contain invalid characters, and |  | ||||||
|      *  - it doesn't go out of the root directory using "..". |  | ||||||
|      */ |  | ||||||
|     bool IsValid() const { |  | ||||||
|         return is_valid; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Checks if the Path represents the root directory.
 |  | ||||||
|     bool IsRootDirectory() const { |  | ||||||
|         return is_root; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     enum HostStatus { |  | ||||||
|         InvalidMountPoint, |  | ||||||
|         PathNotFound,   // "/a/b/c" when "a" doesn't exist
 |  | ||||||
|         FileInPath,     // "/a/b/c" when "a" is a file
 |  | ||||||
|         FileFound,      // "/a/b/c" when "c" is a file
 |  | ||||||
|         DirectoryFound, // "/a/b/c" when "c" is a directory
 |  | ||||||
|         NotFound        // "/a/b/c" when "a/b/" exists but "c" doesn't exist
 |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     /// Checks the status of the specified file / directory by the Path on the host file system.
 |  | ||||||
|     HostStatus GetHostStatus(const std::string& mount_point) const; |  | ||||||
| 
 |  | ||||||
|     /// Builds a full path on the host file system.
 |  | ||||||
|     std::string BuildHostPath(const std::string& mount_point) const; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::vector<std::string> path_sequence; |  | ||||||
|     bool is_valid{}; |  | ||||||
|     bool is_root{}; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -9,40 +9,29 @@ | |||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
| Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) { | Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { | ||||||
|     FileUtil::IOFile file(file_path, "rb"); |     size_t total_size = static_cast<size_t>(file->GetSize()); | ||||||
|     if (!file.IsOpen()) |  | ||||||
|         return Loader::ResultStatus::Error; |  | ||||||
| 
 |  | ||||||
|     std::vector<u8> file_data(file.GetSize()); |  | ||||||
| 
 |  | ||||||
|     if (!file.ReadBytes(file_data.data(), file_data.size())) |  | ||||||
|         return Loader::ResultStatus::Error; |  | ||||||
| 
 |  | ||||||
|     Loader::ResultStatus result = Load(file_data); |  | ||||||
|     if (result != Loader::ResultStatus::Success) |  | ||||||
|         LOG_ERROR(Service_FS, "Failed to load NPDM from file {}!", file_path); |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Loader::ResultStatus ProgramMetadata::Load(const std::vector<u8> file_data, size_t offset) { |  | ||||||
|     size_t total_size = static_cast<size_t>(file_data.size() - offset); |  | ||||||
|     if (total_size < sizeof(Header)) |     if (total_size < sizeof(Header)) | ||||||
|         return Loader::ResultStatus::Error; |         return Loader::ResultStatus::Error; | ||||||
| 
 | 
 | ||||||
|     size_t header_offset = offset; |     // TODO(DarkLordZach): Use ReadObject when Header/AcidHeader becomes trivially copyable.
 | ||||||
|     memcpy(&npdm_header, &file_data[offset], sizeof(Header)); |     std::vector<u8> npdm_header_data = file->ReadBytes(sizeof(Header)); | ||||||
|  |     if (sizeof(Header) != npdm_header_data.size()) | ||||||
|  |         return Loader::ResultStatus::Error; | ||||||
|  |     std::memcpy(&npdm_header, npdm_header_data.data(), sizeof(Header)); | ||||||
| 
 | 
 | ||||||
|     size_t aci_offset = header_offset + npdm_header.aci_offset; |     std::vector<u8> acid_header_data = file->ReadBytes(sizeof(AcidHeader), npdm_header.acid_offset); | ||||||
|     size_t acid_offset = header_offset + npdm_header.acid_offset; |     if (sizeof(AcidHeader) != acid_header_data.size()) | ||||||
|     memcpy(&aci_header, &file_data[aci_offset], sizeof(AciHeader)); |         return Loader::ResultStatus::Error; | ||||||
|     memcpy(&acid_header, &file_data[acid_offset], sizeof(AcidHeader)); |     std::memcpy(&acid_header, acid_header_data.data(), sizeof(AcidHeader)); | ||||||
| 
 | 
 | ||||||
|     size_t fac_offset = acid_offset + acid_header.fac_offset; |     if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset)) | ||||||
|     size_t fah_offset = aci_offset + aci_header.fah_offset; |         return Loader::ResultStatus::Error; | ||||||
|     memcpy(&acid_file_access, &file_data[fac_offset], sizeof(FileAccessControl)); | 
 | ||||||
|     memcpy(&aci_file_access, &file_data[fah_offset], sizeof(FileAccessHeader)); |     if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset)) | ||||||
|  |         return Loader::ResultStatus::Error; | ||||||
|  |     if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset)) | ||||||
|  |         return Loader::ResultStatus::Error; | ||||||
| 
 | 
 | ||||||
|     return Loader::ResultStatus::Success; |     return Loader::ResultStatus::Success; | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ | |||||||
| #include "common/bit_field.h" | #include "common/bit_field.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/swap.h" | #include "common/swap.h" | ||||||
|  | #include "partition_filesystem.h" | ||||||
| 
 | 
 | ||||||
| namespace Loader { | namespace Loader { | ||||||
| enum class ResultStatus; | enum class ResultStatus; | ||||||
| @ -37,8 +38,7 @@ enum class ProgramFilePermission : u64 { | |||||||
|  */ |  */ | ||||||
| class ProgramMetadata { | class ProgramMetadata { | ||||||
| public: | public: | ||||||
|     Loader::ResultStatus Load(const std::string& file_path); |     Loader::ResultStatus Load(VirtualFile file); | ||||||
|     Loader::ResultStatus Load(const std::vector<u8> file_data, size_t offset = 0); |  | ||||||
| 
 | 
 | ||||||
|     bool Is64BitProgram() const; |     bool Is64BitProgram() const; | ||||||
|     ProgramAddressSpaceType GetAddressSpaceType() const; |     ProgramAddressSpaceType GetAddressSpaceType() const; | ||||||
| @ -51,6 +51,7 @@ public: | |||||||
|     void Print() const; |     void Print() const; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |     // TODO(DarkLordZach): BitField is not trivially copyable.
 | ||||||
|     struct Header { |     struct Header { | ||||||
|         std::array<char, 4> magic; |         std::array<char, 4> magic; | ||||||
|         std::array<u8, 8> reserved; |         std::array<u8, 8> reserved; | ||||||
| @ -77,6 +78,7 @@ private: | |||||||
| 
 | 
 | ||||||
|     static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong"); |     static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong"); | ||||||
| 
 | 
 | ||||||
|  |     // TODO(DarkLordZach): BitField is not trivially copyable.
 | ||||||
|     struct AcidHeader { |     struct AcidHeader { | ||||||
|         std::array<u8, 0x100> signature; |         std::array<u8, 0x100> signature; | ||||||
|         std::array<u8, 0x100> nca_modulus; |         std::array<u8, 0x100> nca_modulus; | ||||||
|  | |||||||
| @ -7,21 +7,19 @@ | |||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "core/file_sys/romfs_factory.h" | #include "core/file_sys/romfs_factory.h" | ||||||
| #include "core/file_sys/romfs_filesystem.h" |  | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
| RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { | RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { | ||||||
|     // Load the RomFS from the app
 |     // Load the RomFS from the app
 | ||||||
|     if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) { |     if (Loader::ResultStatus::Success != app_loader.ReadRomFS(file)) { | ||||||
|         LOG_ERROR(Service_FS, "Unable to read RomFS!"); |         LOG_ERROR(Service_FS, "Unable to read RomFS!"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileSystemBackend>> RomFSFactory::Open(u64 title_id) { | ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id) { | ||||||
|     // TODO(DarkLordZach): Use title id.
 |     // TODO(DarkLordZach): Use title id.
 | ||||||
|     auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size); |     return MakeResult<VirtualFile>(file); | ||||||
|     return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace FileSys
 | } // namespace FileSys
 | ||||||
|  | |||||||
| @ -5,10 +5,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> |  | ||||||
| #include <vector> |  | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| 
 | 
 | ||||||
| @ -19,12 +16,10 @@ class RomFSFactory { | |||||||
| public: | public: | ||||||
|     explicit RomFSFactory(Loader::AppLoader& app_loader); |     explicit RomFSFactory(Loader::AppLoader& app_loader); | ||||||
| 
 | 
 | ||||||
|     ResultVal<std::unique_ptr<FileSystemBackend>> Open(u64 title_id); |     ResultVal<VirtualFile> Open(u64 title_id); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::shared_ptr<FileUtil::IOFile> romfs_file; |     VirtualFile file; | ||||||
|     u64 data_offset; |  | ||||||
|     u64 data_size; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace FileSys
 | } // namespace FileSys
 | ||||||
|  | |||||||
| @ -1,110 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <cstring> |  | ||||||
| #include <memory> |  | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| #include "core/file_sys/romfs_filesystem.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| std::string RomFS_FileSystem::GetName() const { |  | ||||||
|     return "RomFS"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path, |  | ||||||
|                                                                       Mode mode) const { |  | ||||||
|     return MakeResult<std::unique_ptr<StorageBackend>>( |  | ||||||
|         std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName()); |  | ||||||
|     // TODO(bunnei): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path, |  | ||||||
|                                         const std::string& dest_path) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName()); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).", |  | ||||||
|                  GetName()); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).", |  | ||||||
|                  GetName()); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName()); |  | ||||||
|     // TODO(bunnei): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).", |  | ||||||
|                  GetName()); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName()); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory( |  | ||||||
|     const std::string& path) const { |  | ||||||
|     LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive"); |  | ||||||
|     return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 RomFS_FileSystem::GetFreeSpaceSize() const { |  | ||||||
|     LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive"); |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const { |  | ||||||
|     LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path); |  | ||||||
|     // TODO(wwylele): Use correct error code
 |  | ||||||
|     return ResultCode(-1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { |  | ||||||
|     LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length); |  | ||||||
|     romfs_file->Seek(data_offset + offset, SEEK_SET); |  | ||||||
|     size_t read_length = (size_t)std::min((u64)length, data_size - offset); |  | ||||||
| 
 |  | ||||||
|     return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush, |  | ||||||
|                                        const u8* buffer) const { |  | ||||||
|     LOG_ERROR(Service_FS, "Attempted to write to ROMFS file"); |  | ||||||
|     // TODO(Subv): Find error code
 |  | ||||||
|     return MakeResult<size_t>(0); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 RomFS_Storage::GetSize() const { |  | ||||||
|     return data_size; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool RomFS_Storage::SetSize(const u64 size) const { |  | ||||||
|     LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file"); |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -1,85 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <cstddef> |  | ||||||
| #include <memory> |  | ||||||
| #include <string> |  | ||||||
| #include <vector> |  | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "common/file_util.h" |  | ||||||
| #include "core/file_sys/directory.h" |  | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/file_sys/storage.h" |  | ||||||
| #include "core/hle/result.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * Helper which implements an interface to deal with Switch .istorage ROMFS images used in some |  | ||||||
|  * archives This should be subclassed by concrete archive types, which will provide the input data |  | ||||||
|  * (load the raw ROMFS archive) and override any required methods |  | ||||||
|  */ |  | ||||||
| class RomFS_FileSystem : public FileSystemBackend { |  | ||||||
| public: |  | ||||||
|     RomFS_FileSystem(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) |  | ||||||
|         : romfs_file(file), data_offset(offset), data_size(size) {} |  | ||||||
| 
 |  | ||||||
|     std::string GetName() const override; |  | ||||||
| 
 |  | ||||||
|     ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, |  | ||||||
|                                                         Mode mode) const override; |  | ||||||
|     ResultCode DeleteFile(const std::string& path) const override; |  | ||||||
|     ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override; |  | ||||||
|     ResultCode DeleteDirectory(const Path& path) const override; |  | ||||||
|     ResultCode DeleteDirectoryRecursively(const Path& path) const override; |  | ||||||
|     ResultCode CreateFile(const std::string& path, u64 size) const override; |  | ||||||
|     ResultCode CreateDirectory(const std::string& path) const override; |  | ||||||
|     ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; |  | ||||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( |  | ||||||
|         const std::string& path) const override; |  | ||||||
|     u64 GetFreeSpaceSize() const override; |  | ||||||
|     ResultVal<EntryType> GetEntryType(const std::string& path) const override; |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
|     std::shared_ptr<FileUtil::IOFile> romfs_file; |  | ||||||
|     u64 data_offset; |  | ||||||
|     u64 data_size; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class RomFS_Storage : public StorageBackend { |  | ||||||
| public: |  | ||||||
|     RomFS_Storage(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) |  | ||||||
|         : romfs_file(file), data_offset(offset), data_size(size) {} |  | ||||||
| 
 |  | ||||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; |  | ||||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; |  | ||||||
|     u64 GetSize() const override; |  | ||||||
|     bool SetSize(u64 size) const override; |  | ||||||
|     bool Close() const override { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|     void Flush() const override {} |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::shared_ptr<FileUtil::IOFile> romfs_file; |  | ||||||
|     u64 data_offset; |  | ||||||
|     u64 data_size; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class ROMFSDirectory : public DirectoryBackend { |  | ||||||
| public: |  | ||||||
|     u64 Read(const u64 count, Entry* entries) override { |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|     u64 GetEntryCount() const override { |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|     bool Close() const override { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -6,7 +6,6 @@ | |||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/file_sys/disk_filesystem.h" |  | ||||||
| #include "core/file_sys/savedata_factory.h" | #include "core/file_sys/savedata_factory.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| 
 | 
 | ||||||
| @ -17,11 +16,9 @@ std::string SaveDataDescriptor::DebugInfo() { | |||||||
|                        static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id); |                        static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| SaveDataFactory::SaveDataFactory(std::string nand_directory) | SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {} | ||||||
|     : nand_directory(std::move(nand_directory)) {} |  | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileSystemBackend>> SaveDataFactory::Open(SaveDataSpaceId space, | ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescriptor meta) { | ||||||
|                                                                     SaveDataDescriptor meta) { |  | ||||||
|     if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { |     if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { | ||||||
|         if (meta.zero_1 != 0) { |         if (meta.zero_1 != 0) { | ||||||
|             LOG_WARNING(Service_FS, |             LOG_WARNING(Service_FS, | ||||||
| @ -56,28 +53,23 @@ ResultVal<std::unique_ptr<FileSystemBackend>> SaveDataFactory::Open(SaveDataSpac | |||||||
|     // TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods.
 |     // TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods.
 | ||||||
|     // But, user_ids don't match so this works for now.
 |     // But, user_ids don't match so this works for now.
 | ||||||
| 
 | 
 | ||||||
|     if (!FileUtil::Exists(save_directory)) { |     auto out = dir->GetDirectoryRelative(save_directory); | ||||||
|  | 
 | ||||||
|  |     if (out == nullptr) { | ||||||
|         // TODO(bunnei): This is a work-around to always create a save data directory if it does not
 |         // TODO(bunnei): This is a work-around to always create a save data directory if it does not
 | ||||||
|         // already exist. This is a hack, as we do not understand yet how this works on hardware.
 |         // already exist. This is a hack, as we do not understand yet how this works on hardware.
 | ||||||
|         // Without a save data directory, many games will assert on boot. This should not have any
 |         // Without a save data directory, many games will assert on boot. This should not have any
 | ||||||
|         // bad side-effects.
 |         // bad side-effects.
 | ||||||
|         FileUtil::CreateFullPath(save_directory); |         out = dir->CreateDirectoryRelative(save_directory); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // TODO(DarkLordZach): For some reason, CreateFullPath doesn't create the last bit. Should be
 |  | ||||||
|     // fixed with VFS.
 |  | ||||||
|     if (!FileUtil::IsDirectory(save_directory)) { |  | ||||||
|         FileUtil::CreateDir(save_directory); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Return an error if the save data doesn't actually exist.
 |     // Return an error if the save data doesn't actually exist.
 | ||||||
|     if (!FileUtil::IsDirectory(save_directory)) { |     if (out == nullptr) { | ||||||
|         // TODO(Subv): Find out correct error code.
 |         // TODO(Subv): Find out correct error code.
 | ||||||
|         return ResultCode(-1); |         return ResultCode(-1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto archive = std::make_unique<Disk_FileSystem>(save_directory); |     return MakeResult<VirtualDir>(std::move(out)); | ||||||
|     return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, | std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, | ||||||
| @ -87,14 +79,14 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ | |||||||
|     if (type == SaveDataType::SaveData && title_id == 0) |     if (type == SaveDataType::SaveData && title_id == 0) | ||||||
|         title_id = Core::CurrentProcess()->program_id; |         title_id = Core::CurrentProcess()->program_id; | ||||||
| 
 | 
 | ||||||
|     std::string prefix; |     std::string out; | ||||||
| 
 | 
 | ||||||
|     switch (space) { |     switch (space) { | ||||||
|     case SaveDataSpaceId::NandSystem: |     case SaveDataSpaceId::NandSystem: | ||||||
|         prefix = nand_directory + "system/save/"; |         out = "/system/save/"; | ||||||
|         break; |         break; | ||||||
|     case SaveDataSpaceId::NandUser: |     case SaveDataSpaceId::NandUser: | ||||||
|         prefix = nand_directory + "user/save/"; |         out = "/user/save/"; | ||||||
|         break; |         break; | ||||||
|     default: |     default: | ||||||
|         ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space)); |         ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space)); | ||||||
| @ -102,9 +94,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ | |||||||
| 
 | 
 | ||||||
|     switch (type) { |     switch (type) { | ||||||
|     case SaveDataType::SystemSaveData: |     case SaveDataType::SystemSaveData: | ||||||
|         return fmt::format("{}{:016X}/{:016X}{:016X}", prefix, save_id, user_id[1], user_id[0]); |         return fmt::format("{}{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]); | ||||||
|     case SaveDataType::SaveData: |     case SaveDataType::SaveData: | ||||||
|         return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", prefix, 0, user_id[1], user_id[0], |         return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], | ||||||
|                            title_id); |                            title_id); | ||||||
|     default: |     default: | ||||||
|         ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); |         ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| @ -45,14 +44,12 @@ static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorr | |||||||
| /// File system interface to the SaveData archive
 | /// File system interface to the SaveData archive
 | ||||||
| class SaveDataFactory { | class SaveDataFactory { | ||||||
| public: | public: | ||||||
|     explicit SaveDataFactory(std::string nand_directory); |     explicit SaveDataFactory(VirtualDir dir); | ||||||
| 
 | 
 | ||||||
|     ResultVal<std::unique_ptr<FileSystemBackend>> Open(SaveDataSpaceId space, |     ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta); | ||||||
|                                                        SaveDataDescriptor meta); |  | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::string nand_directory; |     VirtualDir dir; | ||||||
|     std::string sd_directory; |  | ||||||
| 
 | 
 | ||||||
|     std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, |     std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, | ||||||
|                             u64 save_id) const; |                             u64 save_id) const; | ||||||
|  | |||||||
| @ -3,25 +3,15 @@ | |||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| #include "common/string_util.h" |  | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/file_sys/disk_filesystem.h" |  | ||||||
| #include "core/file_sys/sdmc_factory.h" | #include "core/file_sys/sdmc_factory.h" | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
| SDMCFactory::SDMCFactory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {} | SDMCFactory::SDMCFactory(VirtualDir dir) : dir(std::move(dir)) {} | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileSystemBackend>> SDMCFactory::Open() { | ResultVal<VirtualDir> SDMCFactory::Open() { | ||||||
|     // Create the SD Card directory if it doesn't already exist.
 |     return MakeResult<VirtualDir>(dir); | ||||||
|     if (!FileUtil::IsDirectory(sd_directory)) { |  | ||||||
|         FileUtil::CreateFullPath(sd_directory); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     auto archive = std::make_unique<Disk_FileSystem>(sd_directory); |  | ||||||
|     return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace FileSys
 | } // namespace FileSys
 | ||||||
|  | |||||||
| @ -4,10 +4,6 @@ | |||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <memory> |  | ||||||
| #include <string> |  | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| @ -15,12 +11,12 @@ namespace FileSys { | |||||||
| /// File system interface to the SDCard archive
 | /// File system interface to the SDCard archive
 | ||||||
| class SDMCFactory { | class SDMCFactory { | ||||||
| public: | public: | ||||||
|     explicit SDMCFactory(std::string sd_directory); |     explicit SDMCFactory(VirtualDir dir); | ||||||
| 
 | 
 | ||||||
|     ResultVal<std::unique_ptr<FileSystemBackend>> Open(); |     ResultVal<VirtualDir> Open(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::string sd_directory; |     VirtualDir dir; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace FileSys
 | } // namespace FileSys
 | ||||||
|  | |||||||
| @ -1,63 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <cstddef> |  | ||||||
| #include "common/common_types.h" |  | ||||||
| #include "core/hle/result.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| class StorageBackend : NonCopyable { |  | ||||||
| public: |  | ||||||
|     StorageBackend() {} |  | ||||||
|     virtual ~StorageBackend() {} |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Read data from the file |  | ||||||
|      * @param offset Offset in bytes to start reading data from |  | ||||||
|      * @param length Length in bytes of data to read from file |  | ||||||
|      * @param buffer Buffer to read data into |  | ||||||
|      * @return Number of bytes read, or error code |  | ||||||
|      */ |  | ||||||
|     virtual ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Write data to the file |  | ||||||
|      * @param offset Offset in bytes to start writing data to |  | ||||||
|      * @param length Length in bytes of data to write to file |  | ||||||
|      * @param flush The flush parameters (0 == do not flush) |  | ||||||
|      * @param buffer Buffer to read data from |  | ||||||
|      * @return Number of bytes written, or error code |  | ||||||
|      */ |  | ||||||
|     virtual ResultVal<size_t> Write(u64 offset, size_t length, bool flush, |  | ||||||
|                                     const u8* buffer) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Flushes the file |  | ||||||
|      */ |  | ||||||
|     virtual void Flush() const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Set the size of the file in bytes |  | ||||||
|      * @param size New size of the file |  | ||||||
|      * @return true if successful |  | ||||||
|      */ |  | ||||||
|     virtual bool SetSize(u64 size) const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Get the size of the file in bytes |  | ||||||
|      * @return Size of the file in bytes |  | ||||||
|      */ |  | ||||||
|     virtual u64 GetSize() const = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |  | ||||||
|      * Close the file |  | ||||||
|      * @return true if the file closed correctly |  | ||||||
|      */ |  | ||||||
|     virtual bool Close() const = 0; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
							
								
								
									
										238
									
								
								src/core/file_sys/vfs.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/core/file_sys/vfs.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,238 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <algorithm> | ||||||
|  | #include <numeric> | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/backend.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | VfsFile::~VfsFile() = default; | ||||||
|  | 
 | ||||||
|  | std::string VfsFile::GetExtension() const { | ||||||
|  |     return FileUtil::GetExtensionFromFilename(GetName()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VfsDirectory::~VfsDirectory() = default; | ||||||
|  | 
 | ||||||
|  | boost::optional<u8> VfsFile::ReadByte(size_t offset) const { | ||||||
|  |     u8 out{}; | ||||||
|  |     size_t size = Read(&out, 1, offset); | ||||||
|  |     if (size == 1) | ||||||
|  |         return out; | ||||||
|  | 
 | ||||||
|  |     return boost::none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<u8> VfsFile::ReadBytes(size_t size, size_t offset) const { | ||||||
|  |     std::vector<u8> out(size); | ||||||
|  |     size_t read_size = Read(out.data(), size, offset); | ||||||
|  |     out.resize(read_size); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<u8> VfsFile::ReadAllBytes() const { | ||||||
|  |     return ReadBytes(GetSize()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool VfsFile::WriteByte(u8 data, size_t offset) { | ||||||
|  |     return Write(&data, 1, offset) == 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t VfsFile::WriteBytes(std::vector<u8> data, size_t offset) { | ||||||
|  |     return Write(data.data(), data.size(), offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(const std::string& path) const { | ||||||
|  |     auto vec = FileUtil::SplitPathComponents(path); | ||||||
|  |     vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), | ||||||
|  |               vec.end()); | ||||||
|  |     if (vec.empty()) | ||||||
|  |         return nullptr; | ||||||
|  |     if (vec.size() == 1) | ||||||
|  |         return GetFile(vec[0]); | ||||||
|  |     auto dir = GetSubdirectory(vec[0]); | ||||||
|  |     for (size_t component = 1; component < vec.size() - 1; ++component) { | ||||||
|  |         if (dir == nullptr) | ||||||
|  |             return nullptr; | ||||||
|  |         dir = dir->GetSubdirectory(vec[component]); | ||||||
|  |     } | ||||||
|  |     if (dir == nullptr) | ||||||
|  |         return nullptr; | ||||||
|  |     return dir->GetFile(vec.back()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(const std::string& path) const { | ||||||
|  |     if (IsRoot()) | ||||||
|  |         return GetFileRelative(path); | ||||||
|  | 
 | ||||||
|  |     return GetParentDirectory()->GetFileAbsolute(path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(const std::string& path) const { | ||||||
|  |     auto vec = FileUtil::SplitPathComponents(path); | ||||||
|  |     vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), | ||||||
|  |               vec.end()); | ||||||
|  |     if (vec.empty()) | ||||||
|  |         // TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
 | ||||||
|  |         // because of const-ness
 | ||||||
|  |         return nullptr; | ||||||
|  |     auto dir = GetSubdirectory(vec[0]); | ||||||
|  |     for (size_t component = 1; component < vec.size(); ++component) { | ||||||
|  |         if (dir == nullptr) | ||||||
|  |             return nullptr; | ||||||
|  |         dir = dir->GetSubdirectory(vec[component]); | ||||||
|  |     } | ||||||
|  |     return dir; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(const std::string& path) const { | ||||||
|  |     if (IsRoot()) | ||||||
|  |         return GetDirectoryRelative(path); | ||||||
|  | 
 | ||||||
|  |     return GetParentDirectory()->GetDirectoryAbsolute(path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsFile> VfsDirectory::GetFile(const std::string& name) const { | ||||||
|  |     const auto& files = GetFiles(); | ||||||
|  |     const auto iter = std::find_if(files.begin(), files.end(), | ||||||
|  |                                    [&name](const auto& file1) { return name == file1->GetName(); }); | ||||||
|  |     return iter == files.end() ? nullptr : *iter; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(const std::string& name) const { | ||||||
|  |     const auto& subs = GetSubdirectories(); | ||||||
|  |     const auto iter = std::find_if(subs.begin(), subs.end(), | ||||||
|  |                                    [&name](const auto& file1) { return name == file1->GetName(); }); | ||||||
|  |     return iter == subs.end() ? nullptr : *iter; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool VfsDirectory::IsRoot() const { | ||||||
|  |     return GetParentDirectory() == nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t VfsDirectory::GetSize() const { | ||||||
|  |     const auto& files = GetFiles(); | ||||||
|  |     const auto file_total = | ||||||
|  |         std::accumulate(files.begin(), files.end(), 0ull, | ||||||
|  |                         [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); }); | ||||||
|  | 
 | ||||||
|  |     const auto& sub_dir = GetSubdirectories(); | ||||||
|  |     const auto subdir_total = | ||||||
|  |         std::accumulate(sub_dir.begin(), sub_dir.end(), 0ull, | ||||||
|  |                         [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); }); | ||||||
|  | 
 | ||||||
|  |     return file_total + subdir_total; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(const std::string& path) { | ||||||
|  |     auto vec = FileUtil::SplitPathComponents(path); | ||||||
|  |     vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), | ||||||
|  |               vec.end()); | ||||||
|  |     if (vec.empty()) | ||||||
|  |         return nullptr; | ||||||
|  |     if (vec.size() == 1) | ||||||
|  |         return CreateFile(vec[0]); | ||||||
|  |     auto dir = GetSubdirectory(vec[0]); | ||||||
|  |     if (dir == nullptr) { | ||||||
|  |         dir = CreateSubdirectory(vec[0]); | ||||||
|  |         if (dir == nullptr) | ||||||
|  |             return nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return dir->CreateFileRelative(FileUtil::GetPathWithoutTop(path)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(const std::string& path) { | ||||||
|  |     if (IsRoot()) | ||||||
|  |         return CreateFileRelative(path); | ||||||
|  |     return GetParentDirectory()->CreateFileAbsolute(path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(const std::string& path) { | ||||||
|  |     auto vec = FileUtil::SplitPathComponents(path); | ||||||
|  |     vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), | ||||||
|  |               vec.end()); | ||||||
|  |     if (vec.empty()) | ||||||
|  |         return nullptr; | ||||||
|  |     if (vec.size() == 1) | ||||||
|  |         return CreateSubdirectory(vec[0]); | ||||||
|  |     auto dir = GetSubdirectory(vec[0]); | ||||||
|  |     if (dir == nullptr) { | ||||||
|  |         dir = CreateSubdirectory(vec[0]); | ||||||
|  |         if (dir == nullptr) | ||||||
|  |             return nullptr; | ||||||
|  |     } | ||||||
|  |     return dir->CreateDirectoryRelative(FileUtil::GetPathWithoutTop(path)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(const std::string& path) { | ||||||
|  |     if (IsRoot()) | ||||||
|  |         return CreateDirectoryRelative(path); | ||||||
|  |     return GetParentDirectory()->CreateDirectoryAbsolute(path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool VfsDirectory::DeleteSubdirectoryRecursive(const std::string& name) { | ||||||
|  |     auto dir = GetSubdirectory(name); | ||||||
|  |     if (dir == nullptr) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     bool success = true; | ||||||
|  |     for (const auto& file : dir->GetFiles()) { | ||||||
|  |         if (!DeleteFile(file->GetName())) | ||||||
|  |             success = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (const auto& sdir : dir->GetSubdirectories()) { | ||||||
|  |         if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) | ||||||
|  |             success = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool VfsDirectory::Copy(const std::string& src, const std::string& dest) { | ||||||
|  |     const auto f1 = GetFile(src); | ||||||
|  |     auto f2 = CreateFile(dest); | ||||||
|  |     if (f1 == nullptr || f2 == nullptr) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     if (!f2->Resize(f1->GetSize())) { | ||||||
|  |         DeleteFile(dest); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ReadOnlyVfsDirectory::IsWritable() const { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ReadOnlyVfsDirectory::IsReadable() const { | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(const std::string& name) { | ||||||
|  |     return nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(const std::string& name) { | ||||||
|  |     return nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ReadOnlyVfsDirectory::DeleteSubdirectory(const std::string& name) { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ReadOnlyVfsDirectory::DeleteFile(const std::string& name) { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ReadOnlyVfsDirectory::Rename(const std::string& name) { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										237
									
								
								src/core/file_sys/vfs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								src/core/file_sys/vfs.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,237 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <vector> | ||||||
|  | #include "boost/optional.hpp" | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/file_util.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | struct VfsFile; | ||||||
|  | struct VfsDirectory; | ||||||
|  | 
 | ||||||
|  | // Convenience typedefs to use VfsDirectory and VfsFile
 | ||||||
|  | using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>; | ||||||
|  | using VirtualFile = std::shared_ptr<FileSys::VfsFile>; | ||||||
|  | 
 | ||||||
|  | // A class representing a file in an abstract filesystem.
 | ||||||
|  | struct VfsFile : NonCopyable { | ||||||
|  |     virtual ~VfsFile(); | ||||||
|  | 
 | ||||||
|  |     // Retrieves the file name.
 | ||||||
|  |     virtual std::string GetName() const = 0; | ||||||
|  |     // Retrieves the extension of the file name.
 | ||||||
|  |     virtual std::string GetExtension() const; | ||||||
|  |     // Retrieves the size of the file.
 | ||||||
|  |     virtual size_t GetSize() const = 0; | ||||||
|  |     // Resizes the file to new_size. Returns whether or not the operation was successful.
 | ||||||
|  |     virtual bool Resize(size_t new_size) = 0; | ||||||
|  |     // Gets a pointer to the directory containing this file, returning nullptr if there is none.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0; | ||||||
|  | 
 | ||||||
|  |     // Returns whether or not the file can be written to.
 | ||||||
|  |     virtual bool IsWritable() const = 0; | ||||||
|  |     // Returns whether or not the file can be read from.
 | ||||||
|  |     virtual bool IsReadable() const = 0; | ||||||
|  | 
 | ||||||
|  |     // The primary method of reading from the file. Reads length bytes into data starting at offset
 | ||||||
|  |     // into file. Returns number of bytes successfully read.
 | ||||||
|  |     virtual size_t Read(u8* data, size_t length, size_t offset = 0) const = 0; | ||||||
|  |     // The primary method of writing to the file. Writes length bytes from data starting at offset
 | ||||||
|  |     // into file. Returns number of bytes successfully written.
 | ||||||
|  |     virtual size_t Write(const u8* data, size_t length, size_t offset = 0) = 0; | ||||||
|  | 
 | ||||||
|  |     // Reads exactly one byte at the offset provided, returning boost::none on error.
 | ||||||
|  |     virtual boost::optional<u8> ReadByte(size_t offset = 0) const; | ||||||
|  |     // Reads size bytes starting at offset in file into a vector.
 | ||||||
|  |     virtual std::vector<u8> ReadBytes(size_t size, size_t offset = 0) const; | ||||||
|  |     // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
 | ||||||
|  |     // 0)'
 | ||||||
|  |     virtual std::vector<u8> ReadAllBytes() const; | ||||||
|  | 
 | ||||||
|  |     // Reads an array of type T, size number_elements starting at offset.
 | ||||||
|  |     // Returns the number of bytes (sizeof(T)*number_elements) read successfully.
 | ||||||
|  |     template <typename T> | ||||||
|  |     size_t ReadArray(T* data, size_t number_elements, size_t offset = 0) const { | ||||||
|  |         static_assert(std::is_trivially_copyable<T>::value, | ||||||
|  |                       "Data type must be trivially copyable."); | ||||||
|  | 
 | ||||||
|  |         return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Reads size bytes into the memory starting at data starting at offset into the file.
 | ||||||
|  |     // Returns the number of bytes read successfully.
 | ||||||
|  |     template <typename T> | ||||||
|  |     size_t ReadBytes(T* data, size_t size, size_t offset = 0) const { | ||||||
|  |         static_assert(std::is_trivially_copyable<T>::value, | ||||||
|  |                       "Data type must be trivially copyable."); | ||||||
|  |         return Read(reinterpret_cast<u8*>(data), size, offset); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Reads one object of type T starting at offset in file.
 | ||||||
|  |     // Returns the number of bytes read successfully (sizeof(T)).
 | ||||||
|  |     template <typename T> | ||||||
|  |     size_t ReadObject(T* data, size_t offset = 0) const { | ||||||
|  |         static_assert(std::is_trivially_copyable<T>::value, | ||||||
|  |                       "Data type must be trivially copyable."); | ||||||
|  |         return Read(reinterpret_cast<u8*>(data), sizeof(T), offset); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Writes exactly one byte to offset in file and retuns whether or not the byte was written
 | ||||||
|  |     // successfully.
 | ||||||
|  |     virtual bool WriteByte(u8 data, size_t offset = 0); | ||||||
|  |     // Writes a vector of bytes to offset in file and returns the number of bytes successfully
 | ||||||
|  |     // written.
 | ||||||
|  |     virtual size_t WriteBytes(std::vector<u8> data, size_t offset = 0); | ||||||
|  | 
 | ||||||
|  |     // Writes an array of type T, size number_elements to offset in file.
 | ||||||
|  |     // Returns the number of bytes (sizeof(T)*number_elements) written successfully.
 | ||||||
|  |     template <typename T> | ||||||
|  |     size_t WriteArray(T* data, size_t number_elements, size_t offset = 0) { | ||||||
|  |         static_assert(std::is_trivially_copyable<T>::value, | ||||||
|  |                       "Data type must be trivially copyable."); | ||||||
|  | 
 | ||||||
|  |         return Write(data, number_elements * sizeof(T), offset); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Writes size bytes starting at memory location data to offset in file.
 | ||||||
|  |     // Returns the number of bytes written successfully.
 | ||||||
|  |     template <typename T> | ||||||
|  |     size_t WriteBytes(T* data, size_t size, size_t offset = 0) { | ||||||
|  |         static_assert(std::is_trivially_copyable<T>::value, | ||||||
|  |                       "Data type must be trivially copyable."); | ||||||
|  |         return Write(reinterpret_cast<u8*>(data), size, offset); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Writes one object of type T to offset in file.
 | ||||||
|  |     // Returns the number of bytes written successfully (sizeof(T)).
 | ||||||
|  |     template <typename T> | ||||||
|  |     size_t WriteObject(const T& data, size_t offset = 0) { | ||||||
|  |         static_assert(std::is_trivially_copyable<T>::value, | ||||||
|  |                       "Data type must be trivially copyable."); | ||||||
|  |         return Write(&data, sizeof(T), offset); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Renames the file to name. Returns whether or not the operation was successsful.
 | ||||||
|  |     virtual bool Rename(const std::string& name) = 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // A class representing a directory in an abstract filesystem.
 | ||||||
|  | struct VfsDirectory : NonCopyable { | ||||||
|  |     virtual ~VfsDirectory(); | ||||||
|  | 
 | ||||||
|  |     // Retrives the file located at path as if the current directory was root. Returns nullptr if
 | ||||||
|  |     // not found.
 | ||||||
|  |     virtual std::shared_ptr<VfsFile> GetFileRelative(const std::string& path) const; | ||||||
|  |     // Calls GetFileRelative(path) on the root of the current directory.
 | ||||||
|  |     virtual std::shared_ptr<VfsFile> GetFileAbsolute(const std::string& path) const; | ||||||
|  | 
 | ||||||
|  |     // Retrives the directory located at path as if the current directory was root. Returns nullptr
 | ||||||
|  |     // if not found.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(const std::string& path) const; | ||||||
|  |     // Calls GetDirectoryRelative(path) on the root of the current directory.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(const std::string& path) const; | ||||||
|  | 
 | ||||||
|  |     // Returns a vector containing all of the files in this directory.
 | ||||||
|  |     virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0; | ||||||
|  |     // Returns the file with filename matching name. Returns nullptr if directory dosen't have a
 | ||||||
|  |     // file with name.
 | ||||||
|  |     virtual std::shared_ptr<VfsFile> GetFile(const std::string& name) const; | ||||||
|  | 
 | ||||||
|  |     // Returns a vector containing all of the subdirectories in this directory.
 | ||||||
|  |     virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0; | ||||||
|  |     // Returns the directory with name matching name. Returns nullptr if directory dosen't have a
 | ||||||
|  |     // directory with name.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> GetSubdirectory(const std::string& name) const; | ||||||
|  | 
 | ||||||
|  |     // Returns whether or not the directory can be written to.
 | ||||||
|  |     virtual bool IsWritable() const = 0; | ||||||
|  |     // Returns whether of not the directory can be read from.
 | ||||||
|  |     virtual bool IsReadable() const = 0; | ||||||
|  | 
 | ||||||
|  |     // Returns whether or not the directory is the root of the current file tree.
 | ||||||
|  |     virtual bool IsRoot() const; | ||||||
|  | 
 | ||||||
|  |     // Returns the name of the directory.
 | ||||||
|  |     virtual std::string GetName() const = 0; | ||||||
|  |     // Returns the total size of all files and subdirectories in this directory.
 | ||||||
|  |     virtual size_t GetSize() const; | ||||||
|  |     // Returns the parent directory of this directory. Returns nullptr if this directory is root or
 | ||||||
|  |     // has no parent.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0; | ||||||
|  | 
 | ||||||
|  |     // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
 | ||||||
|  |     // if the operation failed.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) = 0; | ||||||
|  |     // Creates a new file with name name. Returns a pointer to the new file or nullptr if the
 | ||||||
|  |     // operation failed.
 | ||||||
|  |     virtual std::shared_ptr<VfsFile> CreateFile(const std::string& name) = 0; | ||||||
|  | 
 | ||||||
|  |     // Creates a new file at the path relative to this directory. Also creates directories if
 | ||||||
|  |     // they do not exist and is supported by this implementation. Returns nullptr on any failure.
 | ||||||
|  |     virtual std::shared_ptr<VfsFile> CreateFileRelative(const std::string& path); | ||||||
|  | 
 | ||||||
|  |     // Creates a new file at the path relative to root of this directory. Also creates directories
 | ||||||
|  |     // if they do not exist and is supported by this implementation. Returns nullptr on any failure.
 | ||||||
|  |     virtual std::shared_ptr<VfsFile> CreateFileAbsolute(const std::string& path); | ||||||
|  | 
 | ||||||
|  |     // Creates a new directory at the path relative to this directory. Also creates directories if
 | ||||||
|  |     // they do not exist and is supported by this implementation. Returns nullptr on any failure.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> CreateDirectoryRelative(const std::string& path); | ||||||
|  | 
 | ||||||
|  |     // Creates a new directory at the path relative to root of this directory. Also creates
 | ||||||
|  |     // directories if they do not exist and is supported by this implementation. Returns nullptr on
 | ||||||
|  |     // any failure.
 | ||||||
|  |     virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(const std::string& path); | ||||||
|  | 
 | ||||||
|  |     // Deletes the subdirectory with name and returns true on success.
 | ||||||
|  |     virtual bool DeleteSubdirectory(const std::string& name) = 0; | ||||||
|  |     // Deletes all subdirectories and files of subdirectory with name recirsively and then deletes
 | ||||||
|  |     // the subdirectory. Returns true on success.
 | ||||||
|  |     virtual bool DeleteSubdirectoryRecursive(const std::string& name); | ||||||
|  |     // Returnes whether or not the file with name name was deleted successfully.
 | ||||||
|  |     virtual bool DeleteFile(const std::string& name) = 0; | ||||||
|  | 
 | ||||||
|  |     // Returns whether or not this directory was renamed to name.
 | ||||||
|  |     virtual bool Rename(const std::string& name) = 0; | ||||||
|  | 
 | ||||||
|  |     // Returns whether or not the file with name src was successfully copied to a new file with name
 | ||||||
|  |     // dest.
 | ||||||
|  |     virtual bool Copy(const std::string& src, const std::string& dest); | ||||||
|  | 
 | ||||||
|  |     // Interprets the file with name file instead as a directory of type directory.
 | ||||||
|  |     // The directory must have a constructor that takes a single argument of type
 | ||||||
|  |     // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
 | ||||||
|  |     // subdirectory in one call.
 | ||||||
|  |     template <typename Directory> | ||||||
|  |     bool InterpretAsDirectory(const std::string& file) { | ||||||
|  |         auto file_p = GetFile(file); | ||||||
|  |         if (file_p == nullptr) | ||||||
|  |             return false; | ||||||
|  |         return ReplaceFileWithSubdirectory(file, std::make_shared<Directory>(file_p)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     // Backend for InterpretAsDirectory.
 | ||||||
|  |     // Removes all references to file and adds a reference to dir in the directory's implementation.
 | ||||||
|  |     virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
 | ||||||
|  | // if writable. This is to avoid redundant empty methods everywhere.
 | ||||||
|  | struct ReadOnlyVfsDirectory : public VfsDirectory { | ||||||
|  |     bool IsWritable() const override; | ||||||
|  |     bool IsReadable() const override; | ||||||
|  |     std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override; | ||||||
|  |     std::shared_ptr<VfsFile> CreateFile(const std::string& name) override; | ||||||
|  |     bool DeleteSubdirectory(const std::string& name) override; | ||||||
|  |     bool DeleteFile(const std::string& name) override; | ||||||
|  |     bool Rename(const std::string& name) override; | ||||||
|  | }; | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										92
									
								
								src/core/file_sys/vfs_offset.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/core/file_sys/vfs_offset.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include "core/file_sys/vfs_offset.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_, | ||||||
|  |                              const std::string& name_) | ||||||
|  |     : file(file_), offset(offset_), size(size_), name(name_) {} | ||||||
|  | 
 | ||||||
|  | std::string OffsetVfsFile::GetName() const { | ||||||
|  |     return name.empty() ? file->GetName() : name; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t OffsetVfsFile::GetSize() const { | ||||||
|  |     return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool OffsetVfsFile::Resize(size_t new_size) { | ||||||
|  |     if (offset + new_size < file->GetSize()) { | ||||||
|  |         size = new_size; | ||||||
|  |     } else { | ||||||
|  |         auto res = file->Resize(offset + new_size); | ||||||
|  |         if (!res) | ||||||
|  |             return false; | ||||||
|  |         size = new_size; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const { | ||||||
|  |     return file->GetContainingDirectory(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool OffsetVfsFile::IsWritable() const { | ||||||
|  |     return file->IsWritable(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool OffsetVfsFile::IsReadable() const { | ||||||
|  |     return file->IsReadable(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t OffsetVfsFile::Read(u8* data, size_t length, size_t r_offset) const { | ||||||
|  |     return file->Read(data, TrimToFit(length, r_offset), offset + r_offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t OffsetVfsFile::Write(const u8* data, size_t length, size_t r_offset) { | ||||||
|  |     return file->Write(data, TrimToFit(length, r_offset), offset + r_offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | boost::optional<u8> OffsetVfsFile::ReadByte(size_t r_offset) const { | ||||||
|  |     if (r_offset < size) | ||||||
|  |         return file->ReadByte(offset + r_offset); | ||||||
|  | 
 | ||||||
|  |     return boost::none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<u8> OffsetVfsFile::ReadBytes(size_t r_size, size_t r_offset) const { | ||||||
|  |     return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<u8> OffsetVfsFile::ReadAllBytes() const { | ||||||
|  |     return file->ReadBytes(size, offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool OffsetVfsFile::WriteByte(u8 data, size_t r_offset) { | ||||||
|  |     if (r_offset < size) | ||||||
|  |         return file->WriteByte(data, offset + r_offset); | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t OffsetVfsFile::WriteBytes(std::vector<u8> data, size_t r_offset) { | ||||||
|  |     return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool OffsetVfsFile::Rename(const std::string& name) { | ||||||
|  |     return file->Rename(name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t OffsetVfsFile::GetOffset() const { | ||||||
|  |     return offset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t OffsetVfsFile::TrimToFit(size_t r_size, size_t r_offset) const { | ||||||
|  |     return std::max<size_t>(std::min<size_t>(size - r_offset, r_size), 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										46
									
								
								src/core/file_sys/vfs_offset.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/core/file_sys/vfs_offset.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | // An implementation of VfsFile that wraps around another VfsFile at a certain offset.
 | ||||||
|  | // Similar to seeking to an offset.
 | ||||||
|  | // If the file is writable, operations that would write past the end of the offset file will expand
 | ||||||
|  | // the size of this wrapper.
 | ||||||
|  | struct OffsetVfsFile : public VfsFile { | ||||||
|  |     OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0, | ||||||
|  |                   const std::string& new_name = ""); | ||||||
|  | 
 | ||||||
|  |     std::string GetName() const override; | ||||||
|  |     size_t GetSize() const override; | ||||||
|  |     bool Resize(size_t new_size) override; | ||||||
|  |     std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; | ||||||
|  |     bool IsWritable() const override; | ||||||
|  |     bool IsReadable() const override; | ||||||
|  |     size_t Read(u8* data, size_t length, size_t offset) const override; | ||||||
|  |     size_t Write(const u8* data, size_t length, size_t offset) override; | ||||||
|  |     boost::optional<u8> ReadByte(size_t offset) const override; | ||||||
|  |     std::vector<u8> ReadBytes(size_t size, size_t offset) const override; | ||||||
|  |     std::vector<u8> ReadAllBytes() const override; | ||||||
|  |     bool WriteByte(u8 data, size_t offset) override; | ||||||
|  |     size_t WriteBytes(std::vector<u8> data, size_t offset) override; | ||||||
|  | 
 | ||||||
|  |     bool Rename(const std::string& name) override; | ||||||
|  | 
 | ||||||
|  |     size_t GetOffset() const; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     size_t TrimToFit(size_t r_size, size_t r_offset) const; | ||||||
|  | 
 | ||||||
|  |     std::shared_ptr<VfsFile> file; | ||||||
|  |     size_t offset; | ||||||
|  |     size_t size; | ||||||
|  |     std::string name; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										177
									
								
								src/core/file_sys/vfs_real.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/core/file_sys/vfs_real.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,177 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include "common/common_paths.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "core/file_sys/vfs_real.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | static std::string PermissionsToCharArray(Mode perms) { | ||||||
|  |     std::string out; | ||||||
|  |     switch (perms) { | ||||||
|  |     case Mode::Read: | ||||||
|  |         out += "r"; | ||||||
|  |         break; | ||||||
|  |     case Mode::Write: | ||||||
|  |         out += "r+"; | ||||||
|  |         break; | ||||||
|  |     case Mode::Append: | ||||||
|  |         out += "a"; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     return out + "b"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | RealVfsFile::RealVfsFile(const std::string& path_, Mode perms_) | ||||||
|  |     : backing(path_, PermissionsToCharArray(perms_).c_str()), path(path_), | ||||||
|  |       parent_path(FileUtil::GetParentPath(path_)), | ||||||
|  |       path_components(FileUtil::SplitPathComponents(path_)), | ||||||
|  |       parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)), | ||||||
|  |       perms(perms_) {} | ||||||
|  | 
 | ||||||
|  | std::string RealVfsFile::GetName() const { | ||||||
|  |     return path_components.back(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t RealVfsFile::GetSize() const { | ||||||
|  |     return backing.GetSize(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsFile::Resize(size_t new_size) { | ||||||
|  |     return backing.Resize(new_size); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const { | ||||||
|  |     return std::make_shared<RealVfsDirectory>(parent_path, perms); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsFile::IsWritable() const { | ||||||
|  |     return perms == Mode::Append || perms == Mode::Write; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsFile::IsReadable() const { | ||||||
|  |     return perms == Mode::Read || perms == Mode::Write; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const { | ||||||
|  |     if (!backing.Seek(offset, SEEK_SET)) | ||||||
|  |         return 0; | ||||||
|  |     return backing.ReadBytes(data, length); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) { | ||||||
|  |     if (!backing.Seek(offset, SEEK_SET)) | ||||||
|  |         return 0; | ||||||
|  |     return backing.WriteBytes(data, length); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsFile::Rename(const std::string& name) { | ||||||
|  |     const auto out = FileUtil::Rename(GetName(), name); | ||||||
|  |     path = parent_path + DIR_SEP + name; | ||||||
|  |     path_components = parent_components; | ||||||
|  |     path_components.push_back(name); | ||||||
|  |     backing = FileUtil::IOFile(path, PermissionsToCharArray(perms).c_str()); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsFile::Close() { | ||||||
|  |     return backing.Close(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | RealVfsDirectory::RealVfsDirectory(const std::string& path_, Mode perms_) | ||||||
|  |     : path(FileUtil::RemoveTrailingSlash(path_)), parent_path(FileUtil::GetParentPath(path)), | ||||||
|  |       path_components(FileUtil::SplitPathComponents(path)), | ||||||
|  |       parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)), | ||||||
|  |       perms(perms_) { | ||||||
|  |     if (!FileUtil::Exists(path) && (perms == Mode::Write || perms == Mode::Append)) | ||||||
|  |         FileUtil::CreateDir(path); | ||||||
|  |     unsigned size; | ||||||
|  |     if (perms == Mode::Append) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     FileUtil::ForeachDirectoryEntry( | ||||||
|  |         &size, path, | ||||||
|  |         [this](unsigned* entries_out, const std::string& directory, const std::string& filename) { | ||||||
|  |             std::string full_path = directory + DIR_SEP + filename; | ||||||
|  |             if (FileUtil::IsDirectory(full_path)) | ||||||
|  |                 subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(full_path, perms)); | ||||||
|  |             else | ||||||
|  |                 files.emplace_back(std::make_shared<RealVfsFile>(full_path, perms)); | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const { | ||||||
|  |     return std::vector<std::shared_ptr<VfsFile>>(files); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const { | ||||||
|  |     return std::vector<std::shared_ptr<VfsDirectory>>(subdirectories); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsDirectory::IsWritable() const { | ||||||
|  |     return perms == Mode::Write || perms == Mode::Append; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsDirectory::IsReadable() const { | ||||||
|  |     return perms == Mode::Read || perms == Mode::Write; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string RealVfsDirectory::GetName() const { | ||||||
|  |     return path_components.back(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const { | ||||||
|  |     if (path_components.size() <= 1) | ||||||
|  |         return nullptr; | ||||||
|  | 
 | ||||||
|  |     return std::make_shared<RealVfsDirectory>(parent_path, perms); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(const std::string& name) { | ||||||
|  |     if (!FileUtil::CreateDir(path + DIR_SEP + name)) | ||||||
|  |         return nullptr; | ||||||
|  |     subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(path + DIR_SEP + name, perms)); | ||||||
|  |     return subdirectories.back(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(const std::string& name) { | ||||||
|  |     if (!FileUtil::CreateEmptyFile(path + DIR_SEP + name)) | ||||||
|  |         return nullptr; | ||||||
|  |     files.emplace_back(std::make_shared<RealVfsFile>(path + DIR_SEP + name, perms)); | ||||||
|  |     return files.back(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsDirectory::DeleteSubdirectory(const std::string& name) { | ||||||
|  |     return FileUtil::DeleteDirRecursively(path + DIR_SEP + name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsDirectory::DeleteFile(const std::string& name) { | ||||||
|  |     auto file = GetFile(name); | ||||||
|  |     if (file == nullptr) | ||||||
|  |         return false; | ||||||
|  |     files.erase(std::find(files.begin(), files.end(), file)); | ||||||
|  |     auto real_file = std::static_pointer_cast<RealVfsFile>(file); | ||||||
|  |     real_file->Close(); | ||||||
|  |     return FileUtil::Delete(path + DIR_SEP + name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsDirectory::Rename(const std::string& name) { | ||||||
|  |     return FileUtil::Rename(path, parent_path + DIR_SEP + name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||||
|  |     auto iter = std::find(files.begin(), files.end(), file); | ||||||
|  |     if (iter == files.end()) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     files[iter - files.begin()] = files.back(); | ||||||
|  |     files.pop_back(); | ||||||
|  | 
 | ||||||
|  |     subdirectories.emplace_back(dir); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										69
									
								
								src/core/file_sys/vfs_real.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/core/file_sys/vfs_real.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "core/file_sys/mode.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | // An implmentation of VfsFile that represents a file on the user's computer.
 | ||||||
|  | struct RealVfsFile : public VfsFile { | ||||||
|  |     friend struct RealVfsDirectory; | ||||||
|  | 
 | ||||||
|  |     RealVfsFile(const std::string& name, Mode perms = Mode::Read); | ||||||
|  | 
 | ||||||
|  |     std::string GetName() const override; | ||||||
|  |     size_t GetSize() const override; | ||||||
|  |     bool Resize(size_t new_size) override; | ||||||
|  |     std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; | ||||||
|  |     bool IsWritable() const override; | ||||||
|  |     bool IsReadable() const override; | ||||||
|  |     size_t Read(u8* data, size_t length, size_t offset) const override; | ||||||
|  |     size_t Write(const u8* data, size_t length, size_t offset) override; | ||||||
|  |     bool Rename(const std::string& name) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     bool Close(); | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile backing; | ||||||
|  |     std::string path; | ||||||
|  |     std::string parent_path; | ||||||
|  |     std::vector<std::string> path_components; | ||||||
|  |     std::vector<std::string> parent_components; | ||||||
|  |     Mode perms; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // An implementation of VfsDirectory that represents a directory on the user's computer.
 | ||||||
|  | struct RealVfsDirectory : public VfsDirectory { | ||||||
|  |     RealVfsDirectory(const std::string& path, Mode perms); | ||||||
|  | 
 | ||||||
|  |     std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; | ||||||
|  |     std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; | ||||||
|  |     bool IsWritable() const override; | ||||||
|  |     bool IsReadable() const override; | ||||||
|  |     std::string GetName() const override; | ||||||
|  |     std::shared_ptr<VfsDirectory> GetParentDirectory() const override; | ||||||
|  |     std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override; | ||||||
|  |     std::shared_ptr<VfsFile> CreateFile(const std::string& name) override; | ||||||
|  |     bool DeleteSubdirectory(const std::string& name) override; | ||||||
|  |     bool DeleteFile(const std::string& name) override; | ||||||
|  |     bool Rename(const std::string& name) override; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::string path; | ||||||
|  |     std::string parent_path; | ||||||
|  |     std::vector<std::string> path_components; | ||||||
|  |     std::vector<std::string> parent_components; | ||||||
|  |     Mode perms; | ||||||
|  |     std::vector<std::shared_ptr<VfsFile>> files; | ||||||
|  |     std::vector<std::shared_ptr<VfsDirectory>> subdirectories; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
| @ -5,7 +5,6 @@ | |||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
| #include <stack> | #include <stack> | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/event.h" | #include "core/hle/kernel/event.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
|  | |||||||
| @ -2,17 +2,204 @@ | |||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <boost/container/flat_map.hpp> | #pragma optimize("", off) | ||||||
|  | 
 | ||||||
|  | #include "common/assert.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/file_sys/errors.h" | #include "core/file_sys/errors.h" | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/file_sys/savedata_factory.h" | #include "core/file_sys/savedata_factory.h" | ||||||
| #include "core/file_sys/sdmc_factory.h" | #include "core/file_sys/sdmc_factory.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | #include "core/file_sys/vfs_offset.h" | ||||||
|  | #include "core/file_sys/vfs_real.h" | ||||||
| #include "core/hle/service/filesystem/filesystem.h" | #include "core/hle/service/filesystem/filesystem.h" | ||||||
| #include "core/hle/service/filesystem/fsp_srv.h" | #include "core/hle/service/filesystem/fsp_srv.h" | ||||||
| 
 | 
 | ||||||
| namespace Service::FileSystem { | namespace Service::FileSystem { | ||||||
| 
 | 
 | ||||||
|  | // Size of emulated sd card free space, reported in bytes.
 | ||||||
|  | // Just using 32GB because thats reasonable
 | ||||||
|  | // TODO(DarkLordZach): Eventually make this configurable in settings.
 | ||||||
|  | constexpr u64 EMULATED_SD_REPORTED_SIZE = 32000000000; | ||||||
|  | 
 | ||||||
|  | static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base, | ||||||
|  |                                                        const std::string& dir_name) { | ||||||
|  |     if (dir_name == "." || dir_name == "" || dir_name == "/" || dir_name == "\\") | ||||||
|  |         return base; | ||||||
|  | 
 | ||||||
|  |     return base->GetDirectoryRelative(dir_name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_) | ||||||
|  |     : backing(backing_) {} | ||||||
|  | 
 | ||||||
|  | std::string VfsDirectoryServiceWrapper::GetName() const { | ||||||
|  |     return backing->GetName(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path, u64 size) const { | ||||||
|  |     auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); | ||||||
|  |     auto file = dir->CreateFile(FileUtil::GetFilename(path)); | ||||||
|  |     if (file == nullptr) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  |     if (!file->Resize(size)) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  |     return RESULT_SUCCESS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path) const { | ||||||
|  |     auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); | ||||||
|  |     if (path == "/" || path == "\\") { | ||||||
|  |         // TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
 | ||||||
|  |         return RESULT_SUCCESS; | ||||||
|  |     } | ||||||
|  |     if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) | ||||||
|  |         return FileSys::ERROR_PATH_NOT_FOUND; | ||||||
|  |     if (!backing->DeleteFile(FileUtil::GetFilename(path))) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  |     return RESULT_SUCCESS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path) const { | ||||||
|  |     auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); | ||||||
|  |     if (dir == nullptr && FileUtil::GetFilename(FileUtil::GetParentPath(path)).empty()) | ||||||
|  |         dir = backing; | ||||||
|  |     auto new_dir = dir->CreateSubdirectory(FileUtil::GetFilename(path)); | ||||||
|  |     if (new_dir == nullptr) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  |     return RESULT_SUCCESS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path) const { | ||||||
|  |     auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); | ||||||
|  |     if (!dir->DeleteSubdirectory(FileUtil::GetFilename(path))) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  |     return RESULT_SUCCESS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path) const { | ||||||
|  |     auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); | ||||||
|  |     if (!dir->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path))) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  |     return RESULT_SUCCESS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path, | ||||||
|  |                                                   const std::string& dest_path) const { | ||||||
|  |     auto src = backing->GetFileRelative(src_path); | ||||||
|  |     if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) { | ||||||
|  |         // Use more-optimized vfs implementation rename.
 | ||||||
|  |         if (src == nullptr) | ||||||
|  |             return FileSys::ERROR_PATH_NOT_FOUND; | ||||||
|  |         if (!src->Rename(FileUtil::GetFilename(dest_path))) { | ||||||
|  |             // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |             return ResultCode(-1); | ||||||
|  |         } | ||||||
|  |         return RESULT_SUCCESS; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Move by hand -- TODO(DarkLordZach): Optimize
 | ||||||
|  |     auto c_res = CreateFile(dest_path, src->GetSize()); | ||||||
|  |     if (c_res != RESULT_SUCCESS) | ||||||
|  |         return c_res; | ||||||
|  | 
 | ||||||
|  |     auto dest = backing->GetFileRelative(dest_path); | ||||||
|  |     ASSERT_MSG(dest != nullptr, "Newly created file with success cannot be found."); | ||||||
|  | 
 | ||||||
|  |     ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(), | ||||||
|  |                "Could not write all of the bytes but everything else has succeded."); | ||||||
|  | 
 | ||||||
|  |     if (!src->GetContainingDirectory()->DeleteFile(FileUtil::GetFilename(src_path))) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return RESULT_SUCCESS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path, | ||||||
|  |                                                        const std::string& dest_path) const { | ||||||
|  |     auto src = GetDirectoryRelativeWrapped(backing, src_path); | ||||||
|  |     if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) { | ||||||
|  |         // Use more-optimized vfs implementation rename.
 | ||||||
|  |         if (src == nullptr) | ||||||
|  |             return FileSys::ERROR_PATH_NOT_FOUND; | ||||||
|  |         if (!src->Rename(FileUtil::GetFilename(dest_path))) { | ||||||
|  |             // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |             return ResultCode(-1); | ||||||
|  |         } | ||||||
|  |         return RESULT_SUCCESS; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO(DarkLordZach): Implement renaming across the tree (move).
 | ||||||
|  |     ASSERT_MSG(false, | ||||||
|  |                "Could not rename directory with path \"{}\" to new path \"{}\" because parent dirs " | ||||||
|  |                "don't match -- UNIMPLEMENTED", | ||||||
|  |                src_path, dest_path); | ||||||
|  | 
 | ||||||
|  |     // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |     return ResultCode(-1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path, | ||||||
|  |                                                                      FileSys::Mode mode) const { | ||||||
|  |     auto npath = path; | ||||||
|  |     while (npath.size() > 0 && (npath[0] == '/' || npath[0] == '\\')) | ||||||
|  |         npath = npath.substr(1); | ||||||
|  |     auto file = backing->GetFileRelative(npath); | ||||||
|  |     if (file == nullptr) | ||||||
|  |         return FileSys::ERROR_PATH_NOT_FOUND; | ||||||
|  | 
 | ||||||
|  |     if (mode == FileSys::Mode::Append) { | ||||||
|  |         return MakeResult<FileSys::VirtualFile>( | ||||||
|  |             std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return MakeResult<FileSys::VirtualFile>(file); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path) { | ||||||
|  |     auto dir = GetDirectoryRelativeWrapped(backing, path); | ||||||
|  |     if (dir == nullptr) { | ||||||
|  |         // TODO(DarkLordZach): Find a better error code for this
 | ||||||
|  |         return ResultCode(-1); | ||||||
|  |     } | ||||||
|  |     return MakeResult(dir); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u64 VfsDirectoryServiceWrapper::GetFreeSpaceSize() const { | ||||||
|  |     if (backing->IsWritable()) | ||||||
|  |         return EMULATED_SD_REPORTED_SIZE; | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( | ||||||
|  |     const std::string& path) const { | ||||||
|  |     auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); | ||||||
|  |     if (dir == nullptr) | ||||||
|  |         return FileSys::ERROR_PATH_NOT_FOUND; | ||||||
|  |     auto filename = FileUtil::GetFilename(path); | ||||||
|  |     if (dir->GetFile(filename) != nullptr) | ||||||
|  |         return MakeResult(FileSys::EntryType::File); | ||||||
|  |     if (dir->GetSubdirectory(filename) != nullptr) | ||||||
|  |         return MakeResult(FileSys::EntryType::Directory); | ||||||
|  |     return FileSys::ERROR_PATH_NOT_FOUND; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Map of registered file systems, identified by type. Once an file system is registered here, it |  * Map of registered file systems, identified by type. Once an file system is registered here, it | ||||||
|  * is never removed until UnregisterFileSystems is called. |  * is never removed until UnregisterFileSystems is called. | ||||||
| @ -42,7 +229,7 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) { | |||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenRomFS(u64 title_id) { | ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) { | ||||||
|     LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id); |     LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id); | ||||||
| 
 | 
 | ||||||
|     if (romfs_factory == nullptr) { |     if (romfs_factory == nullptr) { | ||||||
| @ -53,19 +240,19 @@ ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenRomFS(u64 title_id) { | |||||||
|     return romfs_factory->Open(title_id); |     return romfs_factory->Open(title_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSaveData( | ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, | ||||||
|     FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct) { |                                             FileSys::SaveDataDescriptor save_struct) { | ||||||
|     LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", |     LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", | ||||||
|               static_cast<u8>(space), SaveStructDebugInfo(save_struct)); |               static_cast<u8>(space), save_struct.DebugInfo()); | ||||||
| 
 | 
 | ||||||
|     if (save_data_factory == nullptr) { |     if (save_data_factory == nullptr) { | ||||||
|         return ResultCode(ErrorModule::FS, FileSys::ErrCodes::SaveDataNotFound); |         return ResultCode(ErrorModule::FS, FileSys::ErrCodes::TitleNotFound); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return save_data_factory->Open(space, save_struct); |     return save_data_factory->Open(space, save_struct); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSDMC() { | ResultVal<FileSys::VirtualDir> OpenSDMC() { | ||||||
|     LOG_TRACE(Service_FS, "Opening SDMC"); |     LOG_TRACE(Service_FS, "Opening SDMC"); | ||||||
| 
 | 
 | ||||||
|     if (sdmc_factory == nullptr) { |     if (sdmc_factory == nullptr) { | ||||||
| @ -80,8 +267,10 @@ void RegisterFileSystems() { | |||||||
|     save_data_factory = nullptr; |     save_data_factory = nullptr; | ||||||
|     sdmc_factory = nullptr; |     sdmc_factory = nullptr; | ||||||
| 
 | 
 | ||||||
|     std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); |     auto nand_directory = std::make_shared<FileSys::RealVfsDirectory>( | ||||||
|     std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX); |         FileUtil::GetUserPath(D_NAND_IDX), FileSys::Mode::Write); | ||||||
|  |     auto sd_directory = std::make_shared<FileSys::RealVfsDirectory>( | ||||||
|  |         FileUtil::GetUserPath(D_SDMC_IDX), FileSys::Mode::Write); | ||||||
| 
 | 
 | ||||||
|     auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); |     auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); | ||||||
|     save_data_factory = std::move(savedata); |     save_data_factory = std::move(savedata); | ||||||
|  | |||||||
| @ -6,15 +6,13 @@ | |||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
|  | #include "core/file_sys/directory.h" | ||||||
|  | #include "core/file_sys/mode.h" | ||||||
| #include "core/file_sys/romfs_factory.h" | #include "core/file_sys/romfs_factory.h" | ||||||
| #include "core/file_sys/savedata_factory.h" | #include "core/file_sys/savedata_factory.h" | ||||||
| #include "core/file_sys/sdmc_factory.h" | #include "core/file_sys/sdmc_factory.h" | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
| 
 | 
 | ||||||
| namespace FileSys { |  | ||||||
| class FileSystemBackend; |  | ||||||
| } // namespace FileSys
 |  | ||||||
| 
 |  | ||||||
| namespace Service { | namespace Service { | ||||||
| 
 | 
 | ||||||
| namespace SM { | namespace SM { | ||||||
| @ -29,11 +27,10 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); | |||||||
| 
 | 
 | ||||||
| // TODO(DarkLordZach): BIS Filesystem
 | // TODO(DarkLordZach): BIS Filesystem
 | ||||||
| // ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
 | // ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
 | ||||||
| 
 | ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id); | ||||||
| ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenRomFS(u64 title_id); | ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, | ||||||
| ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSaveData( |                                             FileSys::SaveDataDescriptor save_struct); | ||||||
|     FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct); | ResultVal<FileSys::VirtualDir> OpenSDMC(); | ||||||
| ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSDMC(); |  | ||||||
| 
 | 
 | ||||||
| // TODO(DarkLordZach): BIS Filesystem
 | // TODO(DarkLordZach): BIS Filesystem
 | ||||||
| // ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
 | // ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
 | ||||||
| @ -41,5 +38,100 @@ ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSDMC(); | |||||||
| /// Registers all Filesystem services with the specified service manager.
 | /// Registers all Filesystem services with the specified service manager.
 | ||||||
| void InstallInterfaces(SM::ServiceManager& service_manager); | void InstallInterfaces(SM::ServiceManager& service_manager); | ||||||
| 
 | 
 | ||||||
|  | // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
 | ||||||
|  | // pointers and booleans. This makes using a VfsDirectory with switch services much easier and
 | ||||||
|  | // avoids repetitive code.
 | ||||||
|  | class VfsDirectoryServiceWrapper { | ||||||
|  | public: | ||||||
|  |     explicit VfsDirectoryServiceWrapper(FileSys::VirtualDir backing); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) | ||||||
|  |      */ | ||||||
|  |     std::string GetName() const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Create a file specified by its path | ||||||
|  |      * @param path Path relative to the Archive | ||||||
|  |      * @param size The size of the new file, filled with zeroes | ||||||
|  |      * @return Result of the operation | ||||||
|  |      */ | ||||||
|  |     ResultCode CreateFile(const std::string& path, u64 size) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Delete a file specified by its path | ||||||
|  |      * @param path Path relative to the archive | ||||||
|  |      * @return Result of the operation | ||||||
|  |      */ | ||||||
|  |     ResultCode DeleteFile(const std::string& path) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Create a directory specified by its path | ||||||
|  |      * @param path Path relative to the archive | ||||||
|  |      * @return Result of the operation | ||||||
|  |      */ | ||||||
|  |     ResultCode CreateDirectory(const std::string& path) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Delete a directory specified by its path | ||||||
|  |      * @param path Path relative to the archive | ||||||
|  |      * @return Result of the operation | ||||||
|  |      */ | ||||||
|  |     ResultCode DeleteDirectory(const std::string& path) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Delete a directory specified by its path and anything under it | ||||||
|  |      * @param path Path relative to the archive | ||||||
|  |      * @return Result of the operation | ||||||
|  |      */ | ||||||
|  |     ResultCode DeleteDirectoryRecursively(const std::string& path) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Rename a File specified by its path | ||||||
|  |      * @param src_path Source path relative to the archive | ||||||
|  |      * @param dest_path Destination path relative to the archive | ||||||
|  |      * @return Result of the operation | ||||||
|  |      */ | ||||||
|  |     ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Rename a Directory specified by its path | ||||||
|  |      * @param src_path Source path relative to the archive | ||||||
|  |      * @param dest_path Destination path relative to the archive | ||||||
|  |      * @return Result of the operation | ||||||
|  |      */ | ||||||
|  |     ResultCode RenameDirectory(const std::string& src_path, const std::string& dest_path) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Open a file specified by its path, using the specified mode | ||||||
|  |      * @param path Path relative to the archive | ||||||
|  |      * @param mode Mode to open the file with | ||||||
|  |      * @return Opened file, or error code | ||||||
|  |      */ | ||||||
|  |     ResultVal<FileSys::VirtualFile> OpenFile(const std::string& path, FileSys::Mode mode) const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Open a directory specified by its path | ||||||
|  |      * @param path Path relative to the archive | ||||||
|  |      * @return Opened directory, or error code | ||||||
|  |      */ | ||||||
|  |     ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Get the free space | ||||||
|  |      * @return The number of free bytes in the archive | ||||||
|  |      */ | ||||||
|  |     u64 GetFreeSpaceSize() const; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Get the type of the specified path | ||||||
|  |      * @return The type of the specified path or error code | ||||||
|  |      */ | ||||||
|  |     ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     FileSys::VirtualDir backing; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| } // namespace FileSystem
 | } // namespace FileSystem
 | ||||||
| } // namespace Service
 | } // namespace Service
 | ||||||
|  | |||||||
| @ -8,11 +8,7 @@ | |||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/file_sys/directory.h" | #include "core/file_sys/directory.h" | ||||||
| #include "core/file_sys/errors.h" | #include "core/file_sys/errors.h" | ||||||
| #include "core/file_sys/filesystem.h" |  | ||||||
| #include "core/file_sys/storage.h" |  | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/client_port.h" |  | ||||||
| #include "core/hle/kernel/client_session.h" |  | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/service/filesystem/filesystem.h" | #include "core/hle/service/filesystem/filesystem.h" | ||||||
| #include "core/hle/service/filesystem/fsp_srv.h" | #include "core/hle/service/filesystem/fsp_srv.h" | ||||||
| @ -25,13 +21,13 @@ enum class StorageId : u8 { | |||||||
|     GameCard = 2, |     GameCard = 2, | ||||||
|     NandSystem = 3, |     NandSystem = 3, | ||||||
|     NandUser = 4, |     NandUser = 4, | ||||||
|     SdCard = 5 |     SdCard = 5, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class IStorage final : public ServiceFramework<IStorage> { | class IStorage final : public ServiceFramework<IStorage> { | ||||||
| public: | public: | ||||||
|     IStorage(std::unique_ptr<FileSys::StorageBackend>&& backend) |     IStorage(FileSys::VirtualFile backend_) | ||||||
|         : ServiceFramework("IStorage"), backend(std::move(backend)) { |         : ServiceFramework("IStorage"), backend(std::move(backend_)) { | ||||||
|         static const FunctionInfo functions[] = { |         static const FunctionInfo functions[] = { | ||||||
|             {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"},   {2, nullptr, "Flush"}, |             {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"},   {2, nullptr, "Flush"}, | ||||||
|             {3, nullptr, "SetSize"},      {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"}, |             {3, nullptr, "SetSize"},      {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"}, | ||||||
| @ -40,7 +36,7 @@ public: | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::unique_ptr<FileSys::StorageBackend> backend; |     FileSys::VirtualFile backend; | ||||||
| 
 | 
 | ||||||
|     void Read(Kernel::HLERequestContext& ctx) { |     void Read(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
| @ -62,14 +58,7 @@ private: | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Read the data from the Storage backend
 |         // Read the data from the Storage backend
 | ||||||
|         std::vector<u8> output(length); |         std::vector<u8> output = backend->ReadBytes(length, offset); | ||||||
|         ResultVal<size_t> res = backend->Read(offset, length, output.data()); |  | ||||||
|         if (res.Failed()) { |  | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|             rb.Push(res.Code()); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Write the data to memory
 |         // Write the data to memory
 | ||||||
|         ctx.WriteBuffer(output); |         ctx.WriteBuffer(output); | ||||||
| 
 | 
 | ||||||
| @ -80,8 +69,8 @@ private: | |||||||
| 
 | 
 | ||||||
| class IFile final : public ServiceFramework<IFile> { | class IFile final : public ServiceFramework<IFile> { | ||||||
| public: | public: | ||||||
|     explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend) |     explicit IFile(FileSys::VirtualFile backend_) | ||||||
|         : ServiceFramework("IFile"), backend(std::move(backend)) { |         : ServiceFramework("IFile"), backend(std::move(backend_)) { | ||||||
|         static const FunctionInfo functions[] = { |         static const FunctionInfo functions[] = { | ||||||
|             {0, &IFile::Read, "Read"},       {1, &IFile::Write, "Write"}, |             {0, &IFile::Read, "Read"},       {1, &IFile::Write, "Write"}, | ||||||
|             {2, &IFile::Flush, "Flush"},     {3, &IFile::SetSize, "SetSize"}, |             {2, &IFile::Flush, "Flush"},     {3, &IFile::SetSize, "SetSize"}, | ||||||
| @ -91,7 +80,7 @@ public: | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::unique_ptr<FileSys::StorageBackend> backend; |     FileSys::VirtualFile backend; | ||||||
| 
 | 
 | ||||||
|     void Read(Kernel::HLERequestContext& ctx) { |     void Read(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
| @ -114,20 +103,14 @@ private: | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Read the data from the Storage backend
 |         // Read the data from the Storage backend
 | ||||||
|         std::vector<u8> output(length); |         std::vector<u8> output = backend->ReadBytes(length, offset); | ||||||
|         ResultVal<size_t> res = backend->Read(offset, length, output.data()); |  | ||||||
|         if (res.Failed()) { |  | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|             rb.Push(res.Code()); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         // Write the data to memory
 |         // Write the data to memory
 | ||||||
|         ctx.WriteBuffer(output); |         ctx.WriteBuffer(output); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 4}; |         IPC::ResponseBuilder rb{ctx, 4}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.Push(static_cast<u64>(*res)); |         rb.Push(static_cast<u64>(output.size())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void Write(Kernel::HLERequestContext& ctx) { |     void Write(Kernel::HLERequestContext& ctx) { | ||||||
| @ -150,14 +133,21 @@ private: | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Write the data to the Storage backend
 |  | ||||||
|         std::vector<u8> data = ctx.ReadBuffer(); |         std::vector<u8> data = ctx.ReadBuffer(); | ||||||
|         ResultVal<size_t> res = backend->Write(offset, length, true, data.data()); |         std::vector<u8> actual_data(length); | ||||||
|         if (res.Failed()) { | 
 | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |         ASSERT_MSG( | ||||||
|             rb.Push(res.Code()); |             data.size() <= length, | ||||||
|             return; |             "Attempting to write more data than requested (requested={:016X}, actual={:016X}).", | ||||||
|         } |             length, data.size()); | ||||||
|  | 
 | ||||||
|  |         std::copy(data.begin(), data.end(), actual_data.begin()); | ||||||
|  |         // Write the data to the Storage backend
 | ||||||
|  |         auto written = backend->WriteBytes(data, offset); | ||||||
|  | 
 | ||||||
|  |         ASSERT_MSG(written == length, | ||||||
|  |                    "Could not write all bytes to file (requested={:016X}, actual={:016X}).", length, | ||||||
|  |                    written); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
| @ -165,7 +155,8 @@ private: | |||||||
| 
 | 
 | ||||||
|     void Flush(Kernel::HLERequestContext& ctx) { |     void Flush(Kernel::HLERequestContext& ctx) { | ||||||
|         LOG_DEBUG(Service_FS, "called"); |         LOG_DEBUG(Service_FS, "called"); | ||||||
|         backend->Flush(); | 
 | ||||||
|  |         // Exists for SDK compatibiltity -- No need to flush file.
 | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
| @ -174,7 +165,7 @@ private: | |||||||
|     void SetSize(Kernel::HLERequestContext& ctx) { |     void SetSize(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const u64 size = rp.Pop<u64>(); |         const u64 size = rp.Pop<u64>(); | ||||||
|         backend->SetSize(size); |         backend->Resize(size); | ||||||
|         LOG_DEBUG(Service_FS, "called, size={}", size); |         LOG_DEBUG(Service_FS, "called, size={}", size); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
| @ -191,19 +182,39 @@ private: | |||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | template <typename T> | ||||||
|  | static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vector<T>& new_data, | ||||||
|  |                             FileSys::EntryType type) { | ||||||
|  |     for (const auto& new_entry : new_data) { | ||||||
|  |         FileSys::Entry entry; | ||||||
|  |         entry.filename[0] = '\0'; | ||||||
|  |         std::strncat(entry.filename, new_entry->GetName().c_str(), FileSys::FILENAME_LENGTH - 1); | ||||||
|  |         entry.type = type; | ||||||
|  |         entry.file_size = new_entry->GetSize(); | ||||||
|  |         entries.emplace_back(std::move(entry)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class IDirectory final : public ServiceFramework<IDirectory> { | class IDirectory final : public ServiceFramework<IDirectory> { | ||||||
| public: | public: | ||||||
|     explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend) |     explicit IDirectory(FileSys::VirtualDir backend_) | ||||||
|         : ServiceFramework("IDirectory"), backend(std::move(backend)) { |         : ServiceFramework("IDirectory"), backend(std::move(backend_)) { | ||||||
|         static const FunctionInfo functions[] = { |         static const FunctionInfo functions[] = { | ||||||
|             {0, &IDirectory::Read, "Read"}, |             {0, &IDirectory::Read, "Read"}, | ||||||
|             {1, &IDirectory::GetEntryCount, "GetEntryCount"}, |             {1, &IDirectory::GetEntryCount, "GetEntryCount"}, | ||||||
|         }; |         }; | ||||||
|         RegisterHandlers(functions); |         RegisterHandlers(functions); | ||||||
|  | 
 | ||||||
|  |         // TODO(DarkLordZach): Verify that this is the correct behavior.
 | ||||||
|  |         // Build entry index now to save time later.
 | ||||||
|  |         BuildEntryIndex(entries, backend->GetFiles(), FileSys::File); | ||||||
|  |         BuildEntryIndex(entries, backend->GetSubdirectories(), FileSys::Directory); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::unique_ptr<FileSys::DirectoryBackend> backend; |     FileSys::VirtualDir backend; | ||||||
|  |     std::vector<FileSys::Entry> entries; | ||||||
|  |     u64 next_entry_index = 0; | ||||||
| 
 | 
 | ||||||
|     void Read(Kernel::HLERequestContext& ctx) { |     void Read(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
| @ -214,26 +225,31 @@ private: | |||||||
|         // Calculate how many entries we can fit in the output buffer
 |         // Calculate how many entries we can fit in the output buffer
 | ||||||
|         u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry); |         u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry); | ||||||
| 
 | 
 | ||||||
|  |         // Cap at total number of entries.
 | ||||||
|  |         u64 actual_entries = std::min(count_entries, entries.size() - next_entry_index); | ||||||
|  | 
 | ||||||
|         // Read the data from the Directory backend
 |         // Read the data from the Directory backend
 | ||||||
|         std::vector<FileSys::Entry> entries(count_entries); |         std::vector<FileSys::Entry> entry_data(entries.begin() + next_entry_index, | ||||||
|         u64 read_entries = backend->Read(count_entries, entries.data()); |                                                entries.begin() + next_entry_index + actual_entries); | ||||||
|  | 
 | ||||||
|  |         next_entry_index += actual_entries; | ||||||
| 
 | 
 | ||||||
|         // Convert the data into a byte array
 |         // Convert the data into a byte array
 | ||||||
|         std::vector<u8> output(entries.size() * sizeof(FileSys::Entry)); |         std::vector<u8> output(entry_data.size() * sizeof(FileSys::Entry)); | ||||||
|         std::memcpy(output.data(), entries.data(), output.size()); |         std::memcpy(output.data(), entry_data.data(), output.size()); | ||||||
| 
 | 
 | ||||||
|         // Write the data to memory
 |         // Write the data to memory
 | ||||||
|         ctx.WriteBuffer(output); |         ctx.WriteBuffer(output); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 4}; |         IPC::ResponseBuilder rb{ctx, 4}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.Push(read_entries); |         rb.Push(actual_entries); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void GetEntryCount(Kernel::HLERequestContext& ctx) { |     void GetEntryCount(Kernel::HLERequestContext& ctx) { | ||||||
|         LOG_DEBUG(Service_FS, "called"); |         LOG_DEBUG(Service_FS, "called"); | ||||||
| 
 | 
 | ||||||
|         u64 count = backend->GetEntryCount(); |         u64 count = entries.size() - next_entry_index; | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 4}; |         IPC::ResponseBuilder rb{ctx, 4}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
| @ -243,7 +259,7 @@ private: | |||||||
| 
 | 
 | ||||||
| class IFileSystem final : public ServiceFramework<IFileSystem> { | class IFileSystem final : public ServiceFramework<IFileSystem> { | ||||||
| public: | public: | ||||||
|     explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend) |     explicit IFileSystem(FileSys::VirtualDir backend) | ||||||
|         : ServiceFramework("IFileSystem"), backend(std::move(backend)) { |         : ServiceFramework("IFileSystem"), backend(std::move(backend)) { | ||||||
|         static const FunctionInfo functions[] = { |         static const FunctionInfo functions[] = { | ||||||
|             {0, &IFileSystem::CreateFile, "CreateFile"}, |             {0, &IFileSystem::CreateFile, "CreateFile"}, | ||||||
| @ -278,7 +294,7 @@ public: | |||||||
|         LOG_DEBUG(Service_FS, "called file {} mode 0x{:X} size 0x{:08X}", name, mode, size); |         LOG_DEBUG(Service_FS, "called file {} mode 0x{:X} size 0x{:08X}", name, mode, size); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(backend->CreateFile(name, size)); |         rb.Push(backend.CreateFile(name, size)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void DeleteFile(Kernel::HLERequestContext& ctx) { |     void DeleteFile(Kernel::HLERequestContext& ctx) { | ||||||
| @ -290,7 +306,7 @@ public: | |||||||
|         LOG_DEBUG(Service_FS, "called file {}", name); |         LOG_DEBUG(Service_FS, "called file {}", name); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(backend->DeleteFile(name)); |         rb.Push(backend.DeleteFile(name)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void CreateDirectory(Kernel::HLERequestContext& ctx) { |     void CreateDirectory(Kernel::HLERequestContext& ctx) { | ||||||
| @ -302,7 +318,7 @@ public: | |||||||
|         LOG_DEBUG(Service_FS, "called directory {}", name); |         LOG_DEBUG(Service_FS, "called directory {}", name); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(backend->CreateDirectory(name)); |         rb.Push(backend.CreateDirectory(name)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void RenameFile(Kernel::HLERequestContext& ctx) { |     void RenameFile(Kernel::HLERequestContext& ctx) { | ||||||
| @ -320,7 +336,7 @@ public: | |||||||
|         LOG_DEBUG(Service_FS, "called file '{}' to file '{}'", src_name, dst_name); |         LOG_DEBUG(Service_FS, "called file '{}' to file '{}'", src_name, dst_name); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(backend->RenameFile(src_name, dst_name)); |         rb.Push(backend.RenameFile(src_name, dst_name)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void OpenFile(Kernel::HLERequestContext& ctx) { |     void OpenFile(Kernel::HLERequestContext& ctx) { | ||||||
| @ -333,14 +349,14 @@ public: | |||||||
| 
 | 
 | ||||||
|         LOG_DEBUG(Service_FS, "called file {} mode {}", name, static_cast<u32>(mode)); |         LOG_DEBUG(Service_FS, "called file {} mode {}", name, static_cast<u32>(mode)); | ||||||
| 
 | 
 | ||||||
|         auto result = backend->OpenFile(name, mode); |         auto result = backend.OpenFile(name, mode); | ||||||
|         if (result.Failed()) { |         if (result.Failed()) { | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|             rb.Push(result.Code()); |             rb.Push(result.Code()); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         auto file = std::move(result.Unwrap()); |         IFile file(result.Unwrap()); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |         IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
| @ -358,14 +374,14 @@ public: | |||||||
| 
 | 
 | ||||||
|         LOG_DEBUG(Service_FS, "called directory {} filter {}", name, filter_flags); |         LOG_DEBUG(Service_FS, "called directory {} filter {}", name, filter_flags); | ||||||
| 
 | 
 | ||||||
|         auto result = backend->OpenDirectory(name); |         auto result = backend.OpenDirectory(name); | ||||||
|         if (result.Failed()) { |         if (result.Failed()) { | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|             rb.Push(result.Code()); |             rb.Push(result.Code()); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         auto directory = std::move(result.Unwrap()); |         IDirectory directory(result.Unwrap()); | ||||||
| 
 | 
 | ||||||
|         IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |         IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
| @ -380,7 +396,7 @@ public: | |||||||
| 
 | 
 | ||||||
|         LOG_DEBUG(Service_FS, "called file {}", name); |         LOG_DEBUG(Service_FS, "called file {}", name); | ||||||
| 
 | 
 | ||||||
|         auto result = backend->GetEntryType(name); |         auto result = backend.GetEntryType(name); | ||||||
|         if (result.Failed()) { |         if (result.Failed()) { | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|             rb.Push(result.Code()); |             rb.Push(result.Code()); | ||||||
| @ -400,7 +416,7 @@ public: | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::unique_ptr<FileSys::FileSystemBackend> backend; |     VfsDirectoryServiceWrapper backend; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { | FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { | ||||||
| @ -536,17 +552,19 @@ void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) { | |||||||
|     LOG_INFO(Service_FS, "called with unknown={:08X}", unk); |     LOG_INFO(Service_FS, "called with unknown={:08X}", unk); | ||||||
|     auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>(); |     auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>(); | ||||||
| 
 | 
 | ||||||
|     auto filesystem = OpenSaveData(space_id, save_struct); |     auto dir = OpenSaveData(space_id, save_struct); | ||||||
| 
 | 
 | ||||||
|     if (filesystem.Failed()) { |     if (dir.Failed()) { | ||||||
|         IPC::ResponseBuilder rb{ctx, 2, 0, 0}; |         IPC::ResponseBuilder rb{ctx, 2, 0, 0}; | ||||||
|         rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::SaveDataNotFound)); |         rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::TitleNotFound)); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     IFileSystem filesystem(std::move(dir.Unwrap())); | ||||||
|  | 
 | ||||||
|     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
|     rb.PushIpcInterface<IFileSystem>(std::move(filesystem.Unwrap())); |     rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { | void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { | ||||||
| @ -569,18 +587,11 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto storage = romfs.Unwrap()->OpenFile({}, {}); |     IStorage storage(std::move(romfs.Unwrap())); | ||||||
| 
 |  | ||||||
|     if (storage.Failed()) { |  | ||||||
|         LOG_CRITICAL(Service_FS, "no storage interface available!"); |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|         rb.Push(storage.Code()); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
|     rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap())); |     rb.PushIpcInterface<IStorage>(std::move(storage)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) { | void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) { | ||||||
| @ -591,33 +602,9 @@ void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) { | |||||||
| 
 | 
 | ||||||
|     LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", |     LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", | ||||||
|               static_cast<u8>(storage_id), title_id); |               static_cast<u8>(storage_id), title_id); | ||||||
|     if (title_id != Core::System::GetInstance().CurrentProcess()->program_id) { |  | ||||||
|         LOG_CRITICAL( |  | ||||||
|             Service_FS, |  | ||||||
|             "Attempting to access RomFS of another title id (current={:016X}, requested={:016X}).", |  | ||||||
|             Core::System::GetInstance().CurrentProcess()->program_id, title_id); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     auto romfs = OpenRomFS(title_id); |  | ||||||
|     if (romfs.Failed()) { |  | ||||||
|         LOG_CRITICAL(Service_FS, "no file system interface available!"); |  | ||||||
|     IPC::ResponseBuilder rb{ctx, 2}; |     IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|         rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::RomFSNotFound)); |     rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::TitleNotFound)); | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     auto storage = romfs.Unwrap()->OpenFile({}, {}); |  | ||||||
| 
 |  | ||||||
|     if (storage.Failed()) { |  | ||||||
|         LOG_CRITICAL(Service_FS, "no storage interface available!"); |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|         rb.Push(storage.Code()); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |  | ||||||
|     rb.Push(RESULT_SUCCESS); |  | ||||||
|     rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap())); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace Service::FileSystem
 | } // namespace Service::FileSystem
 | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ private: | |||||||
|     void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); |     void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); | ||||||
|     void OpenRomStorage(Kernel::HLERequestContext& ctx); |     void OpenRomStorage(Kernel::HLERequestContext& ctx); | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<FileSys::FileSystemBackend> romfs; |     FileSys::VirtualFile romfs; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Service::FileSystem
 | } // namespace Service::FileSystem
 | ||||||
|  | |||||||
| @ -4,11 +4,10 @@ | |||||||
| 
 | 
 | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
| #include "common/common_paths.h" |  | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
| #include "core/file_sys/romfs_factory.h" | #include "core/file_sys/content_archive.h" | ||||||
| #include "core/gdbstub/gdbstub.h" | #include "core/gdbstub/gdbstub.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/kernel/resource_limit.h" | #include "core/hle/kernel/resource_limit.h" | ||||||
| @ -47,55 +46,11 @@ static std::string FindRomFS(const std::string& directory) { | |||||||
|     return filepath_romfs; |     return filepath_romfs; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file, | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file) | ||||||
|                                                                          std::string filepath) |     : AppLoader(std::move(file)) {} | ||||||
|     : AppLoader(std::move(file)), filepath(std::move(filepath)) {} |  | ||||||
| 
 | 
 | ||||||
| FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& file, | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) { | ||||||
|                                                            const std::string& filepath) { |     if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) { | ||||||
|     bool is_main_found{}; |  | ||||||
|     bool is_npdm_found{}; |  | ||||||
|     bool is_rtld_found{}; |  | ||||||
|     bool is_sdk_found{}; |  | ||||||
| 
 |  | ||||||
|     const auto callback = [&](unsigned* num_entries_out, const std::string& directory, |  | ||||||
|                               const std::string& virtual_name) -> bool { |  | ||||||
|         // Skip directories
 |  | ||||||
|         std::string physical_name = directory + virtual_name; |  | ||||||
|         if (FileUtil::IsDirectory(physical_name)) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Verify filename
 |  | ||||||
|         if (Common::ToLower(virtual_name) == "main") { |  | ||||||
|             is_main_found = true; |  | ||||||
|         } else if (Common::ToLower(virtual_name) == "main.npdm") { |  | ||||||
|             is_npdm_found = true; |  | ||||||
|             return true; |  | ||||||
|         } else if (Common::ToLower(virtual_name) == "rtld") { |  | ||||||
|             is_rtld_found = true; |  | ||||||
|         } else if (Common::ToLower(virtual_name) == "sdk") { |  | ||||||
|             is_sdk_found = true; |  | ||||||
|         } else { |  | ||||||
|             // Continue searching
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Verify file is an NSO
 |  | ||||||
|         FileUtil::IOFile file(physical_name, "rb"); |  | ||||||
|         if (AppLoader_NSO::IdentifyType(file, physical_name) != FileType::NSO) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // We are done if we've found and verified all required NSOs
 |  | ||||||
|         return !(is_main_found && is_npdm_found && is_rtld_found && is_sdk_found); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     // Search the directory recursively, looking for the required modules
 |  | ||||||
|     const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; |  | ||||||
|     FileUtil::ForeachDirectoryEntry(nullptr, directory, callback); |  | ||||||
| 
 |  | ||||||
|     if (is_main_found && is_npdm_found && is_rtld_found && is_sdk_found) { |  | ||||||
|         return FileType::DeconstructedRomDirectory; |         return FileType::DeconstructedRomDirectory; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -107,14 +62,13 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( | |||||||
|     if (is_loaded) { |     if (is_loaded) { | ||||||
|         return ResultStatus::ErrorAlreadyLoaded; |         return ResultStatus::ErrorAlreadyLoaded; | ||||||
|     } |     } | ||||||
|     if (!file.IsOpen()) { |  | ||||||
|         return ResultStatus::Error; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; |     const FileSys::VirtualDir dir = file->GetContainingDirectory(); | ||||||
|     const std::string npdm_path = directory + DIR_SEP + "main.npdm"; |     const FileSys::VirtualFile npdm = dir->GetFile("main.npdm"); | ||||||
|  |     if (npdm == nullptr) | ||||||
|  |         return ResultStatus::ErrorInvalidFormat; | ||||||
| 
 | 
 | ||||||
|     ResultStatus result = metadata.Load(npdm_path); |     ResultStatus result = metadata.Load(npdm); | ||||||
|     if (result != ResultStatus::Success) { |     if (result != ResultStatus::Success) { | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| @ -129,9 +83,10 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( | |||||||
|     VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; |     VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; | ||||||
|     for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", |     for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", | ||||||
|                                "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { |                                "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { | ||||||
|         const std::string path = directory + DIR_SEP + module; |  | ||||||
|         const VAddr load_addr = next_load_addr; |         const VAddr load_addr = next_load_addr; | ||||||
|         next_load_addr = AppLoader_NSO::LoadModule(path, load_addr); |         const FileSys::VirtualFile module_file = dir->GetFile(module); | ||||||
|  |         if (module_file != nullptr) | ||||||
|  |             next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr); | ||||||
|         if (next_load_addr) { |         if (next_load_addr) { | ||||||
|             LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); |             LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); | ||||||
|             // Register module with GDBStub
 |             // Register module with GDBStub
 | ||||||
| @ -150,10 +105,15 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( | |||||||
|                  metadata.GetMainThreadStackSize()); |                  metadata.GetMainThreadStackSize()); | ||||||
| 
 | 
 | ||||||
|     // Find the RomFS by searching for a ".romfs" file in this directory
 |     // Find the RomFS by searching for a ".romfs" file in this directory
 | ||||||
|     filepath_romfs = FindRomFS(directory); |     const auto& files = dir->GetFiles(); | ||||||
|  |     const auto romfs_iter = | ||||||
|  |         std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) { | ||||||
|  |             return file->GetName().find(".romfs") != std::string::npos; | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|     // Register the RomFS if a ".romfs" file was found
 |     // Register the RomFS if a ".romfs" file was found
 | ||||||
|     if (!filepath_romfs.empty()) { |     if (romfs_iter != files.end() && *romfs_iter != nullptr) { | ||||||
|  |         romfs = *romfs_iter; | ||||||
|         Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); |         Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -161,29 +121,10 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( | |||||||
|     return ResultStatus::Success; |     return ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS( | ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile& dir) { | ||||||
|     std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) { |     if (romfs == nullptr) | ||||||
| 
 |  | ||||||
|     if (filepath_romfs.empty()) { |  | ||||||
|         LOG_DEBUG(Loader, "No RomFS available"); |  | ||||||
|         return ResultStatus::ErrorNotUsed; |         return ResultStatus::ErrorNotUsed; | ||||||
|     } |     dir = romfs; | ||||||
| 
 |  | ||||||
|     // We reopen the file, to allow its position to be independent
 |  | ||||||
|     romfs_file = std::make_shared<FileUtil::IOFile>(filepath_romfs, "rb"); |  | ||||||
|     if (!romfs_file->IsOpen()) { |  | ||||||
|         return ResultStatus::Error; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     offset = 0; |  | ||||||
|     size = romfs_file->GetSize(); |  | ||||||
| 
 |  | ||||||
|     LOG_DEBUG(Loader, "RomFS offset:           0x{:016X}", offset); |  | ||||||
|     LOG_DEBUG(Loader, "RomFS size:             0x{:016X}", size); |  | ||||||
| 
 |  | ||||||
|     // Reset read pointer
 |  | ||||||
|     file.Seek(0, SEEK_SET); |  | ||||||
| 
 |  | ||||||
|     return ResultStatus::Success; |     return ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,29 +20,26 @@ namespace Loader { | |||||||
|  */ |  */ | ||||||
| class AppLoader_DeconstructedRomDirectory final : public AppLoader { | class AppLoader_DeconstructedRomDirectory final : public AppLoader { | ||||||
| public: | public: | ||||||
|     AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file, std::string filepath); |     explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file); | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Returns the type of the file |      * Returns the type of the file | ||||||
|      * @param file FileUtil::IOFile open file |      * @param file std::shared_ptr<VfsFile> open file | ||||||
|      * @param filepath Path of the file that we are opening. |  | ||||||
|      * @return FileType found, or FileType::Error if this loader doesn't know it |      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||||
|      */ |      */ | ||||||
|     static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); |     static FileType IdentifyType(const FileSys::VirtualFile& file); | ||||||
| 
 | 
 | ||||||
|     FileType GetFileType() override { |     FileType GetFileType() override { | ||||||
|         return IdentifyType(file, filepath); |         return IdentifyType(file); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||||||
| 
 | 
 | ||||||
|     ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | ||||||
|                            u64& size) override; |  | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::string filepath_romfs; |  | ||||||
|     std::string filepath; |  | ||||||
|     FileSys::ProgramMetadata metadata; |     FileSys::ProgramMetadata metadata; | ||||||
|  |     FileSys::VirtualFile romfs; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -365,20 +365,17 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const | |||||||
| 
 | 
 | ||||||
| namespace Loader { | namespace Loader { | ||||||
| 
 | 
 | ||||||
| AppLoader_ELF::AppLoader_ELF(FileUtil::IOFile&& file, std::string filename) | AppLoader_ELF::AppLoader_ELF(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} | ||||||
|     : AppLoader(std::move(file)), filename(std::move(filename)) {} |  | ||||||
| 
 | 
 | ||||||
| FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file, const std::string&) { | FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) { | ||||||
|     static constexpr u16 ELF_MACHINE_ARM{0x28}; |     static constexpr u16 ELF_MACHINE_ARM{0x28}; | ||||||
| 
 | 
 | ||||||
|     u32 magic = 0; |     u32 magic = 0; | ||||||
|     file.Seek(0, SEEK_SET); |     if (4 != file->ReadObject(&magic)) | ||||||
|     if (1 != file.ReadArray<u32>(&magic, 1)) |  | ||||||
|         return FileType::Error; |         return FileType::Error; | ||||||
| 
 | 
 | ||||||
|     u16 machine = 0; |     u16 machine = 0; | ||||||
|     file.Seek(18, SEEK_SET); |     if (2 != file->ReadObject(&machine, 18)) | ||||||
|     if (1 != file.ReadArray<u16>(&machine, 1)) |  | ||||||
|         return FileType::Error; |         return FileType::Error; | ||||||
| 
 | 
 | ||||||
|     if (Common::MakeMagic('\x7f', 'E', 'L', 'F') == magic && ELF_MACHINE_ARM == machine) |     if (Common::MakeMagic('\x7f', 'E', 'L', 'F') == magic && ELF_MACHINE_ARM == machine) | ||||||
| @ -391,20 +388,13 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { | |||||||
|     if (is_loaded) |     if (is_loaded) | ||||||
|         return ResultStatus::ErrorAlreadyLoaded; |         return ResultStatus::ErrorAlreadyLoaded; | ||||||
| 
 | 
 | ||||||
|     if (!file.IsOpen()) |     std::vector<u8> buffer = file->ReadAllBytes(); | ||||||
|         return ResultStatus::Error; |     if (buffer.size() != file->GetSize()) | ||||||
| 
 |  | ||||||
|     // Reset read pointer in case this file has been read before.
 |  | ||||||
|     file.Seek(0, SEEK_SET); |  | ||||||
| 
 |  | ||||||
|     size_t size = file.GetSize(); |  | ||||||
|     std::unique_ptr<u8[]> buffer(new u8[size]); |  | ||||||
|     if (file.ReadBytes(&buffer[0], size) != size) |  | ||||||
|         return ResultStatus::Error; |         return ResultStatus::Error; | ||||||
| 
 | 
 | ||||||
|     ElfReader elf_reader(&buffer[0]); |     ElfReader elf_reader(&buffer[0]); | ||||||
|     SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR); |     SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR); | ||||||
|     codeset->name = filename; |     codeset->name = file->GetName(); | ||||||
| 
 | 
 | ||||||
|     process->LoadModule(codeset, codeset->entrypoint); |     process->LoadModule(codeset, codeset->entrypoint); | ||||||
|     process->svc_access_mask.set(); |     process->svc_access_mask.set(); | ||||||
|  | |||||||
| @ -16,24 +16,20 @@ namespace Loader { | |||||||
| /// Loads an ELF/AXF file
 | /// Loads an ELF/AXF file
 | ||||||
| class AppLoader_ELF final : public AppLoader { | class AppLoader_ELF final : public AppLoader { | ||||||
| public: | public: | ||||||
|     AppLoader_ELF(FileUtil::IOFile&& file, std::string filename); |     explicit AppLoader_ELF(FileSys::VirtualFile file); | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Returns the type of the file |      * Returns the type of the file | ||||||
|      * @param file FileUtil::IOFile open file |      * @param file std::shared_ptr<VfsFile> open file | ||||||
|      * @param filepath Path of the file that we are opening. |  | ||||||
|      * @return FileType found, or FileType::Error if this loader doesn't know it |      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||||
|      */ |      */ | ||||||
|     static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); |     static FileType IdentifyType(const FileSys::VirtualFile& file); | ||||||
| 
 | 
 | ||||||
|     FileType GetFileType() override { |     FileType GetFileType() override { | ||||||
|         return IdentifyType(file, filename); |         return IdentifyType(file); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::string filename; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
|  | #include "core/file_sys/vfs_real.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/loader/deconstructed_rom_directory.h" | #include "core/loader/deconstructed_rom_directory.h" | ||||||
| #include "core/loader/elf.h" | #include "core/loader/elf.h" | ||||||
| @ -21,11 +22,11 @@ const std::initializer_list<Kernel::AddressMapping> default_address_mappings = { | |||||||
|     {0x1F000000, 0x600000, false}, // entire VRAM
 |     {0x1F000000, 0x600000, false}, // entire VRAM
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) { | FileType IdentifyFile(FileSys::VirtualFile file) { | ||||||
|     FileType type; |     FileType type; | ||||||
| 
 | 
 | ||||||
| #define CHECK_TYPE(loader)                                                                         \ | #define CHECK_TYPE(loader)                                                                         \ | ||||||
|     type = AppLoader_##loader::IdentifyType(file, filepath);                                       \ |     type = AppLoader_##loader::IdentifyType(file);                                                 \ | ||||||
|     if (FileType::Error != type)                                                                   \ |     if (FileType::Error != type)                                                                   \ | ||||||
|         return type; |         return type; | ||||||
| 
 | 
 | ||||||
| @ -41,25 +42,22 @@ FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FileType IdentifyFile(const std::string& file_name) { | FileType IdentifyFile(const std::string& file_name) { | ||||||
|     FileUtil::IOFile file(file_name, "rb"); |     return IdentifyFile(FileSys::VirtualFile(std::make_shared<FileSys::RealVfsFile>(file_name))); | ||||||
|     if (!file.IsOpen()) { |  | ||||||
|         LOG_ERROR(Loader, "Failed to load file {}", file_name); |  | ||||||
|         return FileType::Unknown; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return IdentifyFile(file, file_name); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FileType GuessFromExtension(const std::string& extension_) { | FileType GuessFromFilename(const std::string& name) { | ||||||
|     std::string extension = Common::ToLower(extension_); |     if (name == "main") | ||||||
|  |         return FileType::DeconstructedRomDirectory; | ||||||
| 
 | 
 | ||||||
|     if (extension == ".elf") |     const std::string extension = Common::ToLower(FileUtil::GetExtensionFromFilename(name)); | ||||||
|  | 
 | ||||||
|  |     if (extension == "elf") | ||||||
|         return FileType::ELF; |         return FileType::ELF; | ||||||
|     else if (extension == ".nro") |     if (extension == "nro") | ||||||
|         return FileType::NRO; |         return FileType::NRO; | ||||||
|     else if (extension == ".nso") |     if (extension == "nso") | ||||||
|         return FileType::NSO; |         return FileType::NSO; | ||||||
|     else if (extension == ".nca") |     if (extension == "nca") | ||||||
|         return FileType::NCA; |         return FileType::NCA; | ||||||
| 
 | 
 | ||||||
|     return FileType::Unknown; |     return FileType::Unknown; | ||||||
| @ -93,58 +91,47 @@ const char* GetFileTypeString(FileType type) { | |||||||
|  * @param filepath the file full path (with name) |  * @param filepath the file full path (with name) | ||||||
|  * @return std::unique_ptr<AppLoader> a pointer to a loader object;  nullptr for unsupported type |  * @return std::unique_ptr<AppLoader> a pointer to a loader object;  nullptr for unsupported type | ||||||
|  */ |  */ | ||||||
| static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileType type, | static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileType type) { | ||||||
|                                                 const std::string& filename, |  | ||||||
|                                                 const std::string& filepath) { |  | ||||||
|     switch (type) { |     switch (type) { | ||||||
| 
 | 
 | ||||||
|     // Standard ELF file format.
 |     // Standard ELF file format.
 | ||||||
|     case FileType::ELF: |     case FileType::ELF: | ||||||
|         return std::make_unique<AppLoader_ELF>(std::move(file), filename); |         return std::make_unique<AppLoader_ELF>(std::move(file)); | ||||||
| 
 | 
 | ||||||
|     // NX NSO file format.
 |     // NX NSO file format.
 | ||||||
|     case FileType::NSO: |     case FileType::NSO: | ||||||
|         return std::make_unique<AppLoader_NSO>(std::move(file), filepath); |         return std::make_unique<AppLoader_NSO>(std::move(file)); | ||||||
| 
 | 
 | ||||||
|     // NX NRO file format.
 |     // NX NRO file format.
 | ||||||
|     case FileType::NRO: |     case FileType::NRO: | ||||||
|         return std::make_unique<AppLoader_NRO>(std::move(file), filepath); |         return std::make_unique<AppLoader_NRO>(std::move(file)); | ||||||
| 
 | 
 | ||||||
|     // NX NCA file format.
 |     // NX NCA file format.
 | ||||||
|     case FileType::NCA: |     case FileType::NCA: | ||||||
|         return std::make_unique<AppLoader_NCA>(std::move(file), filepath); |         return std::make_unique<AppLoader_NCA>(std::move(file)); | ||||||
| 
 | 
 | ||||||
|     // NX deconstructed ROM directory.
 |     // NX deconstructed ROM directory.
 | ||||||
|     case FileType::DeconstructedRomDirectory: |     case FileType::DeconstructedRomDirectory: | ||||||
|         return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath); |         return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); | ||||||
| 
 | 
 | ||||||
|     default: |     default: | ||||||
|         return nullptr; |         return nullptr; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { | std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) { | ||||||
|     FileUtil::IOFile file(filename, "rb"); |     FileType type = IdentifyFile(file); | ||||||
|     if (!file.IsOpen()) { |     FileType filename_type = GuessFromFilename(file->GetName()); | ||||||
|         LOG_ERROR(Loader, "Failed to load file {}", filename); |  | ||||||
|         return nullptr; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::string filename_filename, filename_extension; |  | ||||||
|     Common::SplitPath(filename, nullptr, &filename_filename, &filename_extension); |  | ||||||
| 
 |  | ||||||
|     FileType type = IdentifyFile(file, filename); |  | ||||||
|     FileType filename_type = GuessFromExtension(filename_extension); |  | ||||||
| 
 | 
 | ||||||
|     if (type != filename_type) { |     if (type != filename_type) { | ||||||
|         LOG_WARNING(Loader, "File {} has a different type than its extension.", filename); |         LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName()); | ||||||
|         if (FileType::Unknown == type) |         if (FileType::Unknown == type) | ||||||
|             type = filename_type; |             type = filename_type; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     LOG_DEBUG(Loader, "Loading file {} as {}...", filename, GetFileTypeString(type)); |     LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type)); | ||||||
| 
 | 
 | ||||||
|     return GetFileLoader(std::move(file), type, filename_filename, filename); |     return GetFileLoader(std::move(file), type); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ | |||||||
| #include <boost/optional.hpp> | #include <boost/optional.hpp> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
| #include "core/hle/kernel/kernel.h" | #include "core/hle/kernel/kernel.h" | ||||||
| 
 | 
 | ||||||
| namespace Kernel { | namespace Kernel { | ||||||
| @ -36,10 +37,9 @@ enum class FileType { | |||||||
| /**
 | /**
 | ||||||
|  * Identifies the type of a bootable file based on the magic value in its header. |  * Identifies the type of a bootable file based on the magic value in its header. | ||||||
|  * @param file open file |  * @param file open file | ||||||
|  * @param filepath Path of the file that we are opening. |  | ||||||
|  * @return FileType of file |  * @return FileType of file | ||||||
|  */ |  */ | ||||||
| FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath); | FileType IdentifyFile(FileSys::VirtualFile file); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Identifies the type of a bootable file based on the magic value in its header. |  * Identifies the type of a bootable file based on the magic value in its header. | ||||||
| @ -50,12 +50,12 @@ FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath); | |||||||
| FileType IdentifyFile(const std::string& file_name); | FileType IdentifyFile(const std::string& file_name); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Guess the type of a bootable file from its extension |  * Guess the type of a bootable file from its name | ||||||
|  * @param extension String extension of bootable file |  * @param name String name of bootable file | ||||||
|  * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine |  * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine | ||||||
|  * a filetype, and will never return FileType::Error. |  * a filetype, and will never return FileType::Error. | ||||||
|  */ |  */ | ||||||
| FileType GuessFromExtension(const std::string& extension); | FileType GuessFromFilename(const std::string& name); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Convert a FileType into a string which can be displayed to the user. |  * Convert a FileType into a string which can be displayed to the user. | ||||||
| @ -79,7 +79,7 @@ enum class ResultStatus { | |||||||
| /// Interface for loading an application
 | /// Interface for loading an application
 | ||||||
| class AppLoader : NonCopyable { | class AppLoader : NonCopyable { | ||||||
| public: | public: | ||||||
|     AppLoader(FileUtil::IOFile&& file) : file(std::move(file)) {} |     AppLoader(FileSys::VirtualFile file) : file(std::move(file)) {} | ||||||
|     virtual ~AppLoader() {} |     virtual ~AppLoader() {} | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
| @ -154,26 +154,20 @@ public: | |||||||
|     /**
 |     /**
 | ||||||
|      * Get the RomFS of the application |      * Get the RomFS of the application | ||||||
|      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer |      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer | ||||||
|      * @param romfs_file The file containing the RomFS |      * @param file The file containing the RomFS | ||||||
|      * @param offset The offset the romfs begins on |  | ||||||
|      * @param size The size of the romfs |  | ||||||
|      * @return ResultStatus result of function |      * @return ResultStatus result of function | ||||||
|      */ |      */ | ||||||
|     virtual ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |     virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) { | ||||||
|                                    u64& size) { |  | ||||||
|         return ResultStatus::ErrorNotImplemented; |         return ResultStatus::ErrorNotImplemented; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Get the update RomFS of the application |      * Get the update RomFS of the application | ||||||
|      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer |      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer | ||||||
|      * @param romfs_file The file containing the RomFS |      * @param file The file containing the RomFS | ||||||
|      * @param offset The offset the romfs begins on |  | ||||||
|      * @param size The size of the romfs |  | ||||||
|      * @return ResultStatus result of function |      * @return ResultStatus result of function | ||||||
|      */ |      */ | ||||||
|     virtual ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |     virtual ResultStatus ReadUpdateRomFS(FileSys::VirtualFile& file) { | ||||||
|                                          u64& size) { |  | ||||||
|         return ResultStatus::ErrorNotImplemented; |         return ResultStatus::ErrorNotImplemented; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -187,7 +181,7 @@ public: | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|     FileUtil::IOFile file; |     FileSys::VirtualFile file; | ||||||
|     bool is_loaded = false; |     bool is_loaded = false; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -202,6 +196,6 @@ extern const std::initializer_list<Kernel::AddressMapping> default_address_mappi | |||||||
|  * @param filename String filename of bootable file |  * @param filename String filename of bootable file | ||||||
|  * @return best loader for this file |  * @return best loader for this file | ||||||
|  */ |  */ | ||||||
| std::unique_ptr<AppLoader> GetLoader(const std::string& filename); | std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file); | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -4,14 +4,13 @@ | |||||||
| 
 | 
 | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| #include "common/common_funcs.h" |  | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
| #include "common/swap.h" | #include "common/swap.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
|  | #include "core/file_sys/content_archive.h" | ||||||
| #include "core/file_sys/program_metadata.h" | #include "core/file_sys/program_metadata.h" | ||||||
| #include "core/file_sys/romfs_factory.h" |  | ||||||
| #include "core/gdbstub/gdbstub.h" | #include "core/gdbstub/gdbstub.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/kernel/resource_limit.h" | #include "core/hle/kernel/resource_limit.h" | ||||||
| @ -22,208 +21,15 @@ | |||||||
| 
 | 
 | ||||||
| namespace Loader { | namespace Loader { | ||||||
| 
 | 
 | ||||||
| // Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
 | AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(file) {} | ||||||
| constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; |  | ||||||
| 
 | 
 | ||||||
| constexpr u64 SECTION_HEADER_SIZE = 0x200; | FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { | ||||||
| constexpr u64 SECTION_HEADER_OFFSET = 0x400; |     // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
 | ||||||
| 
 |     FileSys::NCAHeader header{}; | ||||||
| enum class NcaContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 }; |     if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header)) | ||||||
| 
 |  | ||||||
| enum class NcaSectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 }; |  | ||||||
| 
 |  | ||||||
| struct NcaSectionTableEntry { |  | ||||||
|     u32_le media_offset; |  | ||||||
|     u32_le media_end_offset; |  | ||||||
|     INSERT_PADDING_BYTES(0x8); |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(NcaSectionTableEntry) == 0x10, "NcaSectionTableEntry has incorrect size."); |  | ||||||
| 
 |  | ||||||
| struct NcaHeader { |  | ||||||
|     std::array<u8, 0x100> rsa_signature_1; |  | ||||||
|     std::array<u8, 0x100> rsa_signature_2; |  | ||||||
|     u32_le magic; |  | ||||||
|     u8 is_system; |  | ||||||
|     NcaContentType content_type; |  | ||||||
|     u8 crypto_type; |  | ||||||
|     u8 key_index; |  | ||||||
|     u64_le size; |  | ||||||
|     u64_le title_id; |  | ||||||
|     INSERT_PADDING_BYTES(0x4); |  | ||||||
|     u32_le sdk_version; |  | ||||||
|     u8 crypto_type_2; |  | ||||||
|     INSERT_PADDING_BYTES(15); |  | ||||||
|     std::array<u8, 0x10> rights_id; |  | ||||||
|     std::array<NcaSectionTableEntry, 0x4> section_tables; |  | ||||||
|     std::array<std::array<u8, 0x20>, 0x4> hash_tables; |  | ||||||
|     std::array<std::array<u8, 0x10>, 0x4> key_area; |  | ||||||
|     INSERT_PADDING_BYTES(0xC0); |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(NcaHeader) == 0x400, "NcaHeader has incorrect size."); |  | ||||||
| 
 |  | ||||||
| struct NcaSectionHeaderBlock { |  | ||||||
|     INSERT_PADDING_BYTES(3); |  | ||||||
|     NcaSectionFilesystemType filesystem_type; |  | ||||||
|     u8 crypto_type; |  | ||||||
|     INSERT_PADDING_BYTES(3); |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(NcaSectionHeaderBlock) == 0x8, "NcaSectionHeaderBlock has incorrect size."); |  | ||||||
| 
 |  | ||||||
| struct Pfs0Superblock { |  | ||||||
|     NcaSectionHeaderBlock header_block; |  | ||||||
|     std::array<u8, 0x20> hash; |  | ||||||
|     u32_le size; |  | ||||||
|     INSERT_PADDING_BYTES(4); |  | ||||||
|     u64_le hash_table_offset; |  | ||||||
|     u64_le hash_table_size; |  | ||||||
|     u64_le pfs0_header_offset; |  | ||||||
|     u64_le pfs0_size; |  | ||||||
|     INSERT_PADDING_BYTES(432); |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(Pfs0Superblock) == 0x200, "Pfs0Superblock has incorrect size."); |  | ||||||
| 
 |  | ||||||
| static bool IsValidNca(const NcaHeader& header) { |  | ||||||
|     return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || |  | ||||||
|            header.magic == Common::MakeMagic('N', 'C', 'A', '3'); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TODO(DarkLordZach): Add support for encrypted.
 |  | ||||||
| class Nca final { |  | ||||||
|     std::vector<FileSys::PartitionFilesystem> pfs; |  | ||||||
|     std::vector<u64> pfs_offset; |  | ||||||
| 
 |  | ||||||
|     u64 romfs_offset = 0; |  | ||||||
|     u64 romfs_size = 0; |  | ||||||
| 
 |  | ||||||
|     boost::optional<u8> exefs_id = boost::none; |  | ||||||
| 
 |  | ||||||
|     FileUtil::IOFile file; |  | ||||||
|     std::string path; |  | ||||||
| 
 |  | ||||||
|     u64 GetExeFsFileOffset(const std::string& file_name) const; |  | ||||||
|     u64 GetExeFsFileSize(const std::string& file_name) const; |  | ||||||
| 
 |  | ||||||
| public: |  | ||||||
|     ResultStatus Load(FileUtil::IOFile&& file, std::string path); |  | ||||||
| 
 |  | ||||||
|     FileSys::PartitionFilesystem GetPfs(u8 id) const; |  | ||||||
| 
 |  | ||||||
|     u64 GetRomFsOffset() const; |  | ||||||
|     u64 GetRomFsSize() const; |  | ||||||
| 
 |  | ||||||
|     std::vector<u8> GetExeFsFile(const std::string& file_name); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static bool IsPfsExeFs(const FileSys::PartitionFilesystem& pfs) { |  | ||||||
|     // According to switchbrew, an exefs must only contain these two files:
 |  | ||||||
|     return pfs.GetFileSize("main") > 0 && pfs.GetFileSize("main.npdm") > 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultStatus Nca::Load(FileUtil::IOFile&& in_file, std::string in_path) { |  | ||||||
|     file = std::move(in_file); |  | ||||||
|     path = in_path; |  | ||||||
|     file.Seek(0, SEEK_SET); |  | ||||||
|     std::array<u8, sizeof(NcaHeader)> header_array{}; |  | ||||||
|     if (sizeof(NcaHeader) != file.ReadBytes(header_array.data(), sizeof(NcaHeader))) |  | ||||||
|         LOG_CRITICAL(Loader, "File reader errored out during header read."); |  | ||||||
| 
 |  | ||||||
|     NcaHeader header{}; |  | ||||||
|     std::memcpy(&header, header_array.data(), sizeof(NcaHeader)); |  | ||||||
|     if (!IsValidNca(header)) |  | ||||||
|         return ResultStatus::ErrorInvalidFormat; |  | ||||||
| 
 |  | ||||||
|     int number_sections = |  | ||||||
|         std::count_if(std::begin(header.section_tables), std::end(header.section_tables), |  | ||||||
|                       [](NcaSectionTableEntry entry) { return entry.media_offset > 0; }); |  | ||||||
| 
 |  | ||||||
|     for (int i = 0; i < number_sections; ++i) { |  | ||||||
|         // Seek to beginning of this section.
 |  | ||||||
|         file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); |  | ||||||
|         std::array<u8, sizeof(NcaSectionHeaderBlock)> array{}; |  | ||||||
|         if (sizeof(NcaSectionHeaderBlock) != |  | ||||||
|             file.ReadBytes(array.data(), sizeof(NcaSectionHeaderBlock))) |  | ||||||
|             LOG_CRITICAL(Loader, "File reader errored out during header read."); |  | ||||||
| 
 |  | ||||||
|         NcaSectionHeaderBlock block{}; |  | ||||||
|         std::memcpy(&block, array.data(), sizeof(NcaSectionHeaderBlock)); |  | ||||||
| 
 |  | ||||||
|         if (block.filesystem_type == NcaSectionFilesystemType::ROMFS) { |  | ||||||
|             romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; |  | ||||||
|             romfs_size = |  | ||||||
|                 header.section_tables[i].media_end_offset * MEDIA_OFFSET_MULTIPLIER - romfs_offset; |  | ||||||
|         } else if (block.filesystem_type == NcaSectionFilesystemType::PFS0) { |  | ||||||
|             Pfs0Superblock sb{}; |  | ||||||
|             // Seek back to beginning of this section.
 |  | ||||||
|             file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); |  | ||||||
|             if (sizeof(Pfs0Superblock) != file.ReadBytes(&sb, sizeof(Pfs0Superblock))) |  | ||||||
|                 LOG_CRITICAL(Loader, "File reader errored out during header read."); |  | ||||||
| 
 |  | ||||||
|             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * |  | ||||||
|                           MEDIA_OFFSET_MULTIPLIER) + |  | ||||||
|                          sb.pfs0_header_offset; |  | ||||||
|             FileSys::PartitionFilesystem npfs{}; |  | ||||||
|             ResultStatus status = npfs.Load(path, offset); |  | ||||||
| 
 |  | ||||||
|             if (status == ResultStatus::Success) { |  | ||||||
|                 pfs.emplace_back(std::move(npfs)); |  | ||||||
|                 pfs_offset.emplace_back(offset); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for (size_t i = 0; i < pfs.size(); ++i) { |  | ||||||
|         if (IsPfsExeFs(pfs[i])) |  | ||||||
|             exefs_id = i; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ResultStatus::Success; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FileSys::PartitionFilesystem Nca::GetPfs(u8 id) const { |  | ||||||
|     return pfs[id]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Nca::GetExeFsFileOffset(const std::string& file_name) const { |  | ||||||
|     if (exefs_id == boost::none) |  | ||||||
|         return 0; |  | ||||||
|     return pfs[*exefs_id].GetFileOffset(file_name) + pfs_offset[*exefs_id]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Nca::GetExeFsFileSize(const std::string& file_name) const { |  | ||||||
|     if (exefs_id == boost::none) |  | ||||||
|         return 0; |  | ||||||
|     return pfs[*exefs_id].GetFileSize(file_name); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Nca::GetRomFsOffset() const { |  | ||||||
|     return romfs_offset; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 Nca::GetRomFsSize() const { |  | ||||||
|     return romfs_size; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::vector<u8> Nca::GetExeFsFile(const std::string& file_name) { |  | ||||||
|     std::vector<u8> out(GetExeFsFileSize(file_name)); |  | ||||||
|     file.Seek(GetExeFsFileOffset(file_name), SEEK_SET); |  | ||||||
|     file.ReadBytes(out.data(), GetExeFsFileSize(file_name)); |  | ||||||
|     return out; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| AppLoader_NCA::AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath) |  | ||||||
|     : AppLoader(std::move(file)), filepath(std::move(filepath)) {} |  | ||||||
| 
 |  | ||||||
| FileType AppLoader_NCA::IdentifyType(FileUtil::IOFile& file, const std::string&) { |  | ||||||
|     file.Seek(0, SEEK_SET); |  | ||||||
|     std::array<u8, 0x400> header_enc_array{}; |  | ||||||
|     if (0x400 != file.ReadBytes(header_enc_array.data(), 0x400)) |  | ||||||
|         return FileType::Error; |         return FileType::Error; | ||||||
| 
 | 
 | ||||||
|     // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
 |     if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program) | ||||||
|     NcaHeader header{}; |  | ||||||
|     std::memcpy(&header, header_enc_array.data(), sizeof(NcaHeader)); |  | ||||||
| 
 |  | ||||||
|     if (IsValidNca(header) && header.content_type == NcaContentType::Program) |  | ||||||
|         return FileType::NCA; |         return FileType::NCA; | ||||||
| 
 | 
 | ||||||
|     return FileType::Error; |     return FileType::Error; | ||||||
| @ -233,17 +39,22 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { | |||||||
|     if (is_loaded) { |     if (is_loaded) { | ||||||
|         return ResultStatus::ErrorAlreadyLoaded; |         return ResultStatus::ErrorAlreadyLoaded; | ||||||
|     } |     } | ||||||
|     if (!file.IsOpen()) { |  | ||||||
|         return ResultStatus::Error; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     nca = std::make_unique<Nca>(); |     nca = std::make_unique<FileSys::NCA>(file); | ||||||
|     ResultStatus result = nca->Load(std::move(file), filepath); |     ResultStatus result = nca->GetStatus(); | ||||||
|     if (result != ResultStatus::Success) { |     if (result != ResultStatus::Success) { | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     result = metadata.Load(nca->GetExeFsFile("main.npdm")); |     if (nca->GetType() != FileSys::NCAContentType::Program) | ||||||
|  |         return ResultStatus::ErrorInvalidFormat; | ||||||
|  | 
 | ||||||
|  |     auto exefs = nca->GetExeFS(); | ||||||
|  | 
 | ||||||
|  |     if (exefs == nullptr) | ||||||
|  |         return ResultStatus::ErrorInvalidFormat; | ||||||
|  | 
 | ||||||
|  |     result = metadata.Load(exefs->GetFile("main.npdm")); | ||||||
|     if (result != ResultStatus::Success) { |     if (result != ResultStatus::Success) { | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| @ -258,7 +69,8 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { | |||||||
|     for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", |     for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", | ||||||
|                                "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { |                                "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { | ||||||
|         const VAddr load_addr = next_load_addr; |         const VAddr load_addr = next_load_addr; | ||||||
|         next_load_addr = AppLoader_NSO::LoadModule(module, nca->GetExeFsFile(module), load_addr); | 
 | ||||||
|  |         next_load_addr = AppLoader_NSO::LoadModule(exefs->GetFile(module), load_addr); | ||||||
|         if (next_load_addr) { |         if (next_load_addr) { | ||||||
|             LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); |             LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); | ||||||
|             // Register module with GDBStub
 |             // Register module with GDBStub
 | ||||||
| @ -276,28 +88,18 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { | |||||||
|     process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), |     process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), | ||||||
|                  metadata.GetMainThreadStackSize()); |                  metadata.GetMainThreadStackSize()); | ||||||
| 
 | 
 | ||||||
|     if (nca->GetRomFsSize() > 0) |     if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) | ||||||
|         Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); |         Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); | ||||||
| 
 | 
 | ||||||
|     is_loaded = true; |     is_loaded = true; | ||||||
|  | 
 | ||||||
|     return ResultStatus::Success; |     return ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultStatus AppLoader_NCA::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | ||||||
|                                       u64& size) { |     if (nca == nullptr || nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) | ||||||
|     if (nca->GetRomFsSize() == 0) { |  | ||||||
|         LOG_DEBUG(Loader, "No RomFS available"); |  | ||||||
|         return ResultStatus::ErrorNotUsed; |         return ResultStatus::ErrorNotUsed; | ||||||
|     } |     dir = nca->GetRomFS(); | ||||||
| 
 |  | ||||||
|     romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); |  | ||||||
| 
 |  | ||||||
|     offset = nca->GetRomFsOffset(); |  | ||||||
|     size = nca->GetRomFsSize(); |  | ||||||
| 
 |  | ||||||
|     LOG_DEBUG(Loader, "RomFS offset:           0x{:016X}", offset); |  | ||||||
|     LOG_DEBUG(Loader, "RomFS size:             0x{:016X}", size); |  | ||||||
| 
 |  | ||||||
|     return ResultStatus::Success; |     return ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,44 +6,39 @@ | |||||||
| 
 | 
 | ||||||
| #include <string> | #include <string> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/file_sys/partition_filesystem.h" | #include "core/file_sys/content_archive.h" | ||||||
| #include "core/file_sys/program_metadata.h" | #include "core/file_sys/program_metadata.h" | ||||||
| #include "core/hle/kernel/kernel.h" | #include "core/hle/kernel/kernel.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| 
 | 
 | ||||||
| namespace Loader { | namespace Loader { | ||||||
| 
 | 
 | ||||||
| class Nca; |  | ||||||
| 
 |  | ||||||
| /// Loads an NCA file
 | /// Loads an NCA file
 | ||||||
| class AppLoader_NCA final : public AppLoader { | class AppLoader_NCA final : public AppLoader { | ||||||
| public: | public: | ||||||
|     AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath); |     explicit AppLoader_NCA(FileSys::VirtualFile file); | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Returns the type of the file |      * Returns the type of the file | ||||||
|      * @param file FileUtil::IOFile open file |      * @param file std::shared_ptr<VfsFile> open file | ||||||
|      * @param filepath Path of the file that we are opening. |  | ||||||
|      * @return FileType found, or FileType::Error if this loader doesn't know it |      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||||
|      */ |      */ | ||||||
|     static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); |     static FileType IdentifyType(const FileSys::VirtualFile& file); | ||||||
| 
 | 
 | ||||||
|     FileType GetFileType() override { |     FileType GetFileType() override { | ||||||
|         return IdentifyType(file, filepath); |         return IdentifyType(file); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||||||
| 
 | 
 | ||||||
|     ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | ||||||
|                            u64& size) override; |  | ||||||
| 
 | 
 | ||||||
|     ~AppLoader_NCA(); |     ~AppLoader_NCA(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::string filepath; |  | ||||||
|     FileSys::ProgramMetadata metadata; |     FileSys::ProgramMetadata metadata; | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<Nca> nca; |     std::unique_ptr<FileSys::NCA> nca; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -48,14 +48,12 @@ struct ModHeader { | |||||||
| }; | }; | ||||||
| static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); | static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); | ||||||
| 
 | 
 | ||||||
| AppLoader_NRO::AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath) | AppLoader_NRO::AppLoader_NRO(FileSys::VirtualFile file) : AppLoader(file) {} | ||||||
|     : AppLoader(std::move(file)), filepath(std::move(filepath)) {} |  | ||||||
| 
 | 
 | ||||||
| FileType AppLoader_NRO::IdentifyType(FileUtil::IOFile& file, const std::string&) { | FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) { | ||||||
|     // Read NSO header
 |     // Read NSO header
 | ||||||
|     NroHeader nro_header{}; |     NroHeader nro_header{}; | ||||||
|     file.Seek(0, SEEK_SET); |     if (sizeof(NroHeader) != file->ReadObject(&nro_header)) { | ||||||
|     if (sizeof(NroHeader) != file.ReadBytes(&nro_header, sizeof(NroHeader))) { |  | ||||||
|         return FileType::Error; |         return FileType::Error; | ||||||
|     } |     } | ||||||
|     if (nro_header.magic == Common::MakeMagic('N', 'R', 'O', '0')) { |     if (nro_header.magic == Common::MakeMagic('N', 'R', 'O', '0')) { | ||||||
| @ -68,16 +66,10 @@ static constexpr u32 PageAlignSize(u32 size) { | |||||||
|     return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; |     return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { | bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { | ||||||
|     FileUtil::IOFile file(path, "rb"); |  | ||||||
|     if (!file.IsOpen()) { |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Read NSO header
 |     // Read NSO header
 | ||||||
|     NroHeader nro_header{}; |     NroHeader nro_header{}; | ||||||
|     file.Seek(0, SEEK_SET); |     if (sizeof(NroHeader) != file->ReadObject(&nro_header)) { | ||||||
|     if (sizeof(NroHeader) != file.ReadBytes(&nro_header, sizeof(NroHeader))) { |  | ||||||
|         return {}; |         return {}; | ||||||
|     } |     } | ||||||
|     if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) { |     if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) { | ||||||
| @ -86,10 +78,9 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { | |||||||
| 
 | 
 | ||||||
|     // Build program image
 |     // Build program image
 | ||||||
|     Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); |     Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); | ||||||
|     std::vector<u8> program_image; |     std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size)); | ||||||
|     program_image.resize(PageAlignSize(nro_header.file_size)); |     if (program_image.size() != PageAlignSize(nro_header.file_size)) | ||||||
|     file.Seek(0, SEEK_SET); |         return {}; | ||||||
|     file.ReadBytes(program_image.data(), nro_header.file_size); |  | ||||||
| 
 | 
 | ||||||
|     for (int i = 0; i < nro_header.segments.size(); ++i) { |     for (int i = 0; i < nro_header.segments.size(); ++i) { | ||||||
|         codeset->segments[i].addr = nro_header.segments[i].offset; |         codeset->segments[i].addr = nro_header.segments[i].offset; | ||||||
| @ -112,7 +103,7 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { | |||||||
|     program_image.resize(static_cast<u32>(program_image.size()) + bss_size); |     program_image.resize(static_cast<u32>(program_image.size()) + bss_size); | ||||||
| 
 | 
 | ||||||
|     // Load codeset for current process
 |     // Load codeset for current process
 | ||||||
|     codeset->name = path; |     codeset->name = file->GetName(); | ||||||
|     codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); |     codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); | ||||||
|     Core::CurrentProcess()->LoadModule(codeset, load_base); |     Core::CurrentProcess()->LoadModule(codeset, load_base); | ||||||
| 
 | 
 | ||||||
| @ -126,14 +117,11 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { | |||||||
|     if (is_loaded) { |     if (is_loaded) { | ||||||
|         return ResultStatus::ErrorAlreadyLoaded; |         return ResultStatus::ErrorAlreadyLoaded; | ||||||
|     } |     } | ||||||
|     if (!file.IsOpen()) { |  | ||||||
|         return ResultStatus::Error; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // Load NRO
 |     // Load NRO
 | ||||||
|     static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR}; |     static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR}; | ||||||
| 
 | 
 | ||||||
|     if (!LoadNro(filepath, base_addr)) { |     if (!LoadNro(file, base_addr)) { | ||||||
|         return ResultStatus::ErrorInvalidFormat; |         return ResultStatus::ErrorInvalidFormat; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -15,26 +15,23 @@ namespace Loader { | |||||||
| /// Loads an NRO file
 | /// Loads an NRO file
 | ||||||
| class AppLoader_NRO final : public AppLoader, Linker { | class AppLoader_NRO final : public AppLoader, Linker { | ||||||
| public: | public: | ||||||
|     AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath); |     AppLoader_NRO(FileSys::VirtualFile file); | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Returns the type of the file |      * Returns the type of the file | ||||||
|      * @param file FileUtil::IOFile open file |      * @param file std::shared_ptr<VfsFile> open file | ||||||
|      * @param filepath Path of the file that we are opening. |  | ||||||
|      * @return FileType found, or FileType::Error if this loader doesn't know it |      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||||
|      */ |      */ | ||||||
|     static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); |     static FileType IdentifyType(const FileSys::VirtualFile& file); | ||||||
| 
 | 
 | ||||||
|     FileType GetFileType() override { |     FileType GetFileType() override { | ||||||
|         return IdentifyType(file, filepath); |         return IdentifyType(file); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     bool LoadNro(const std::string& path, VAddr load_base); |     bool LoadNro(FileSys::VirtualFile file, VAddr load_base); | ||||||
| 
 |  | ||||||
|     std::string filepath; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -38,6 +38,7 @@ struct NsoHeader { | |||||||
|     std::array<u32_le, 3> segments_compressed_size; |     std::array<u32_le, 3> segments_compressed_size; | ||||||
| }; | }; | ||||||
| static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); | static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); | ||||||
|  | static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable."); | ||||||
| 
 | 
 | ||||||
| struct ModHeader { | struct ModHeader { | ||||||
|     u32_le magic; |     u32_le magic; | ||||||
| @ -50,15 +51,11 @@ struct ModHeader { | |||||||
| }; | }; | ||||||
| static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); | static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); | ||||||
| 
 | 
 | ||||||
| AppLoader_NSO::AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath) | AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} | ||||||
|     : AppLoader(std::move(file)), filepath(std::move(filepath)) {} |  | ||||||
| 
 | 
 | ||||||
| FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&) { | FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { | ||||||
|     u32 magic = 0; |     u32 magic = 0; | ||||||
|     file.Seek(0, SEEK_SET); |     file->ReadObject(&magic); | ||||||
|     if (1 != file.ReadArray<u32>(&magic, 1)) { |  | ||||||
|         return FileType::Error; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (Common::MakeMagic('N', 'S', 'O', '0') == magic) { |     if (Common::MakeMagic('N', 'S', 'O', '0') == magic) { | ||||||
|         return FileType::NSO; |         return FileType::NSO; | ||||||
| @ -99,13 +96,16 @@ static constexpr u32 PageAlignSize(u32 size) { | |||||||
|     return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; |     return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& file_data, | VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) { | ||||||
|                                 VAddr load_base) { |     if (file == nullptr) | ||||||
|     if (file_data.size() < sizeof(NsoHeader)) |  | ||||||
|         return {}; |         return {}; | ||||||
| 
 | 
 | ||||||
|     NsoHeader nso_header; |     if (file->GetSize() < sizeof(NsoHeader)) | ||||||
|     std::memcpy(&nso_header, file_data.data(), sizeof(NsoHeader)); |         return {}; | ||||||
|  | 
 | ||||||
|  |     NsoHeader nso_header{}; | ||||||
|  |     if (sizeof(NsoHeader) != file->ReadObject(&nso_header)) | ||||||
|  |         return {}; | ||||||
| 
 | 
 | ||||||
|     if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) |     if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) | ||||||
|         return {}; |         return {}; | ||||||
| @ -114,9 +114,8 @@ VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& | |||||||
|     Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); |     Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); | ||||||
|     std::vector<u8> program_image; |     std::vector<u8> program_image; | ||||||
|     for (int i = 0; i < nso_header.segments.size(); ++i) { |     for (int i = 0; i < nso_header.segments.size(); ++i) { | ||||||
|         std::vector<u8> compressed_data(nso_header.segments_compressed_size[i]); |         const std::vector<u8> compressed_data = | ||||||
|         for (auto j = 0; j < nso_header.segments_compressed_size[i]; ++j) |             file->ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset); | ||||||
|             compressed_data[j] = file_data[nso_header.segments[i].offset + j]; |  | ||||||
|         std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]); |         std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]); | ||||||
|         program_image.resize(nso_header.segments[i].location); |         program_image.resize(nso_header.segments[i].location); | ||||||
|         program_image.insert(program_image.end(), data.begin(), data.end()); |         program_image.insert(program_image.end(), data.begin(), data.end()); | ||||||
| @ -144,7 +143,7 @@ VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& | |||||||
|     program_image.resize(image_size); |     program_image.resize(image_size); | ||||||
| 
 | 
 | ||||||
|     // Load codeset for current process
 |     // Load codeset for current process
 | ||||||
|     codeset->name = name; |     codeset->name = file->GetName(); | ||||||
|     codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); |     codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); | ||||||
|     Core::CurrentProcess()->LoadModule(codeset, load_base); |     Core::CurrentProcess()->LoadModule(codeset, load_base); | ||||||
| 
 | 
 | ||||||
| @ -154,72 +153,14 @@ VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& | |||||||
|     return load_base + image_size; |     return load_base + image_size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) { |  | ||||||
|     FileUtil::IOFile file(path, "rb"); |  | ||||||
|     if (!file.IsOpen()) { |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Read NSO header
 |  | ||||||
|     NsoHeader nso_header{}; |  | ||||||
|     file.Seek(0, SEEK_SET); |  | ||||||
|     if (sizeof(NsoHeader) != file.ReadBytes(&nso_header, sizeof(NsoHeader))) { |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
|     if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Build program image
 |  | ||||||
|     Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); |  | ||||||
|     std::vector<u8> program_image; |  | ||||||
|     for (int i = 0; i < nso_header.segments.size(); ++i) { |  | ||||||
|         std::vector<u8> data = |  | ||||||
|             ReadSegment(file, nso_header.segments[i], nso_header.segments_compressed_size[i]); |  | ||||||
|         program_image.resize(nso_header.segments[i].location); |  | ||||||
|         program_image.insert(program_image.end(), data.begin(), data.end()); |  | ||||||
|         codeset->segments[i].addr = nso_header.segments[i].location; |  | ||||||
|         codeset->segments[i].offset = nso_header.segments[i].location; |  | ||||||
|         codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // MOD header pointer is at .text offset + 4
 |  | ||||||
|     u32 module_offset; |  | ||||||
|     std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); |  | ||||||
| 
 |  | ||||||
|     // Read MOD header
 |  | ||||||
|     ModHeader mod_header{}; |  | ||||||
|     // Default .bss to size in segment header if MOD0 section doesn't exist
 |  | ||||||
|     u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; |  | ||||||
|     std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); |  | ||||||
|     const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; |  | ||||||
|     if (has_mod_header) { |  | ||||||
|         // Resize program image to include .bss section and page align each section
 |  | ||||||
|         bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); |  | ||||||
|     } |  | ||||||
|     codeset->data.size += bss_size; |  | ||||||
|     const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)}; |  | ||||||
|     program_image.resize(image_size); |  | ||||||
| 
 |  | ||||||
|     // Load codeset for current process
 |  | ||||||
|     codeset->name = path; |  | ||||||
|     codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); |  | ||||||
|     Core::CurrentProcess()->LoadModule(codeset, load_base); |  | ||||||
| 
 |  | ||||||
|     return load_base + image_size; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) { | ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) { | ||||||
|     if (is_loaded) { |     if (is_loaded) { | ||||||
|         return ResultStatus::ErrorAlreadyLoaded; |         return ResultStatus::ErrorAlreadyLoaded; | ||||||
|     } |     } | ||||||
|     if (!file.IsOpen()) { |  | ||||||
|         return ResultStatus::Error; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // Load module
 |     // Load module
 | ||||||
|     LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR); |     LoadModule(file, Memory::PROCESS_IMAGE_VADDR); | ||||||
|     LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", filepath, Memory::PROCESS_IMAGE_VADDR); |     LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), Memory::PROCESS_IMAGE_VADDR); | ||||||
| 
 | 
 | ||||||
|     process->svc_access_mask.set(); |     process->svc_access_mask.set(); | ||||||
|     process->address_mappings = default_address_mappings; |     process->address_mappings = default_address_mappings; | ||||||
|  | |||||||
| @ -15,29 +15,22 @@ namespace Loader { | |||||||
| /// Loads an NSO file
 | /// Loads an NSO file
 | ||||||
| class AppLoader_NSO final : public AppLoader, Linker { | class AppLoader_NSO final : public AppLoader, Linker { | ||||||
| public: | public: | ||||||
|     AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath); |     explicit AppLoader_NSO(FileSys::VirtualFile file); | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Returns the type of the file |      * Returns the type of the file | ||||||
|      * @param file FileUtil::IOFile open file |      * @param file std::shared_ptr<VfsFile> open file | ||||||
|      * @param filepath Path of the file that we are opening. |  | ||||||
|      * @return FileType found, or FileType::Error if this loader doesn't know it |      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||||
|      */ |      */ | ||||||
|     static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); |     static FileType IdentifyType(const FileSys::VirtualFile& file); | ||||||
| 
 | 
 | ||||||
|     FileType GetFileType() override { |     FileType GetFileType() override { | ||||||
|         return IdentifyType(file, filepath); |         return IdentifyType(file); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static VAddr LoadModule(const std::string& name, const std::vector<u8>& file_data, |     static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base); | ||||||
|                             VAddr load_base); |  | ||||||
| 
 |  | ||||||
|     static VAddr LoadModule(const std::string& path, VAddr load_base); |  | ||||||
| 
 | 
 | ||||||
|     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::string filepath; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ add_executable(tests | |||||||
|     core/arm/arm_test_common.cpp |     core/arm/arm_test_common.cpp | ||||||
|     core/arm/arm_test_common.h |     core/arm/arm_test_common.h | ||||||
|     core/core_timing.cpp |     core/core_timing.cpp | ||||||
|     core/file_sys/path_parser.cpp |  | ||||||
|     core/memory/memory.cpp |     core/memory/memory.cpp | ||||||
|     glad.cpp |     glad.cpp | ||||||
|     tests.cpp |     tests.cpp | ||||||
|  | |||||||
| @ -1,38 +0,0 @@ | |||||||
| // Copyright 2016 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <catch.hpp> |  | ||||||
| #include "common/file_util.h" |  | ||||||
| #include "core/file_sys/path_parser.h" |  | ||||||
| 
 |  | ||||||
| namespace FileSys { |  | ||||||
| 
 |  | ||||||
| TEST_CASE("PathParser", "[core][file_sys]") { |  | ||||||
|     REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid()); |  | ||||||
|     REQUIRE(!PathParser(Path("a")).IsValid()); |  | ||||||
|     REQUIRE(!PathParser(Path("/|")).IsValid()); |  | ||||||
|     REQUIRE(PathParser(Path("/a")).IsValid()); |  | ||||||
|     REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid()); |  | ||||||
|     REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid()); |  | ||||||
|     REQUIRE(PathParser(Path("/")).IsRootDirectory()); |  | ||||||
|     REQUIRE(!PathParser(Path("/a")).IsRootDirectory()); |  | ||||||
|     REQUIRE(PathParser(Path("/a/..")).IsRootDirectory()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| TEST_CASE("PathParser - Host file system", "[core][file_sys]") { |  | ||||||
|     std::string test_dir = "./test"; |  | ||||||
|     FileUtil::CreateDir(test_dir); |  | ||||||
|     FileUtil::CreateDir(test_dir + "/z"); |  | ||||||
|     FileUtil::CreateEmptyFile(test_dir + "/a"); |  | ||||||
| 
 |  | ||||||
|     REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound); |  | ||||||
|     REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound); |  | ||||||
|     REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound); |  | ||||||
|     REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath); |  | ||||||
|     REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound); |  | ||||||
| 
 |  | ||||||
|     FileUtil::DeleteDirRecursively(test_dir); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace FileSys
 |  | ||||||
| @ -12,6 +12,7 @@ | |||||||
| #include "common/common_paths.h" | #include "common/common_paths.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
|  | #include "core/file_sys/vfs_real.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| #include "game_list.h" | #include "game_list.h" | ||||||
| #include "game_list_p.h" | #include "game_list_p.h" | ||||||
| @ -403,7 +404,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign | |||||||
|         bool is_dir = FileUtil::IsDirectory(physical_name); |         bool is_dir = FileUtil::IsDirectory(physical_name); | ||||||
|         if (!is_dir && |         if (!is_dir && | ||||||
|             (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { |             (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { | ||||||
|             std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); |             std::unique_ptr<Loader::AppLoader> loader = | ||||||
|  |                 Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(physical_name)); | ||||||
|             if (!loader) |             if (!loader) | ||||||
|                 return true; |                 return true; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Zach Hilman
						Zach Hilman