diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 0d2a00ab..2babc86f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -100,6 +100,8 @@ add_library(skyline SHARED ${source_DIR}/skyline/services/visrv/IManagerDisplayService.cpp ${source_DIR}/skyline/services/visrv/IManagerRootService.cpp ${source_DIR}/skyline/services/visrv/ISystemDisplayService.cpp + ${source_DIR}/skyline/vfs/partition_filesystem.cpp + ${source_DIR}/skyline/vfs/rom_filesystem.cpp ${source_DIR}/skyline/vfs/os_backing.cpp ${source_DIR}/skyline/vfs/nacp.cpp ) diff --git a/app/src/main/cpp/skyline/vfs/partition_filesystem.cpp b/app/src/main/cpp/skyline/vfs/partition_filesystem.cpp new file mode 100644 index 00000000..ee1516b5 --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/partition_filesystem.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "region_backing.h" +#include "partition_filesystem.h" + +namespace skyline::vfs { + PartitionFileSystem::PartitionFileSystem(std::shared_ptr backing) : FileSystem(), backing(backing) { + backing->Read(&header); + + if (header.magic == util::MakeMagic("PFS0")) + hashed = false; + else if (header.magic == util::MakeMagic("HFS0")) + hashed = true; + else + throw exception("Invalid filesystem magic: {}", header.magic); + + size_t entrySize = hashed ? sizeof(HashedFileEntry) : sizeof(PartitionFileEntry); + size_t stringTableOffset = sizeof(FsHeader) + (header.numFiles * entrySize); + fileDataOffset = stringTableOffset + header.stringTableSize; + + std::vector stringTable(header.stringTableSize); + backing->Read(stringTable.data(), stringTableOffset, header.stringTableSize); + + for (u32 i = 0; i < header.numFiles; i++) { + PartitionFileEntry entry{}; + backing->Read(&entry, sizeof(FsHeader) + i * entrySize); + + std::string name(&stringTable[entry.stringTableOffset]); + fileMap.emplace(name, std::move(entry)); + } + } + + std::shared_ptr PartitionFileSystem::OpenFile(std::string path, Backing::Mode mode) { + try { + auto &entry = fileMap.at(path); + return std::make_shared(backing, fileDataOffset + entry.offset, entry.size, mode); + } catch (std::out_of_range &e) { + return nullptr; + } + } + + bool PartitionFileSystem::FileExists(std::string path) { + return fileMap.count(path); + } + + std::shared_ptr PartitionFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) { + // PFS doesn't have directories + if (path != "") + return nullptr; + + std::vector fileList; + for (const auto &file : fileMap) + fileList.emplace_back(Directory::Entry{file.first, Directory::EntryType::File}); + + return std::make_shared(fileList, listMode); + } + + PartitionFileSystemDirectory::PartitionFileSystemDirectory(const std::vector &fileList, ListMode listMode) : Directory(listMode), fileList(fileList) {} + + std::vector PartitionFileSystemDirectory::Read() { + if (listMode.file) + return fileList; + else + return std::vector(); + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/vfs/partition_filesystem.h b/app/src/main/cpp/skyline/vfs/partition_filesystem.h new file mode 100644 index 00000000..86a7bb32 --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/partition_filesystem.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include "filesystem.h" + +namespace skyline::vfs { + /** + * @brief The PartitionFileSystem class abstracts a partition filesystem using the vfs::FileSystem api + */ + class PartitionFileSystem : public FileSystem { + private: + /** + * @brief This holds the header of the filesystem + */ + struct FsHeader { + u32 magic; //!< The filesystem magic: 'PFS0' or 'HFS0' + u32 numFiles; //!< The number of files in the filesystem + u32 stringTableSize; //!< The size of the filesystem's string table + u32 _pad_; + } header{}; + static_assert(sizeof(FsHeader) == 0x10); + + /** + * @brief This holds a file entry in a partition filesystem + */ + struct PartitionFileEntry { + u64 offset; //!< The offset of the file in the backing + u64 size; //!< The size of the file + u32 stringTableOffset; //!< The offset of the file in the string table + u32 _pad_; + }; + static_assert(sizeof(PartitionFileEntry) == 0x18); + + /** + * @brief This holds a file entry in a hashed filesystem + */ + struct HashedFileEntry { + PartitionFileEntry entry; //!< The base file entry + u32 _pad_; + std::array hash; //!< The hash of the file + }; + static_assert(sizeof(HashedFileEntry) == 0x40); + + bool hashed; //!< Whether the filesystem contains hash data + size_t fileDataOffset; //!< The offset from the backing to the base of the file data + std::shared_ptr backing; //!< The backing file of the filesystem + std::unordered_map fileMap; //!< A map that maps file names to their corresponding entry + + public: + PartitionFileSystem(std::shared_ptr backing); + + std::shared_ptr OpenFile(std::string path, Backing::Mode mode = {true, false, false}); + + bool FileExists(std::string path); + + std::shared_ptr OpenDirectory(std::string path, Directory::ListMode listMode); + }; + + /** + * @brief The PartitionFileSystemDirectory provides access to the root directory of a partition filesystem + */ + class PartitionFileSystemDirectory : public Directory { + private: + std::vector fileList; //!< A list of every file in the PFS root directory + + public: + PartitionFileSystemDirectory(const std::vector &fileList, ListMode listMode); + + std::vector Read(); + }; +} diff --git a/app/src/main/cpp/skyline/vfs/rom_filesystem.cpp b/app/src/main/cpp/skyline/vfs/rom_filesystem.cpp new file mode 100644 index 00000000..35b646cd --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/rom_filesystem.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "region_backing.h" +#include "rom_filesystem.h" + +namespace skyline::vfs { + RomFileSystem::RomFileSystem(std::shared_ptr backing) : FileSystem(), backing(backing) { + backing->Read(&header); + + TraverseDirectory(0, ""); + } + + void RomFileSystem::TraverseFiles(u32 offset, std::string path) { + RomFsFileEntry entry{}; + + do { + backing->Read(&entry, header.fileMetaTableOffset + offset); + + if (entry.nameSize) { + std::vector name(entry.nameSize); + backing->Read(name.data(), header.fileMetaTableOffset + offset + sizeof(RomFsFileEntry), entry.nameSize); + + std::string fullPath = path + (path.empty() ? "" : "/") + std::string(name.data(), entry.nameSize); + fileMap.emplace(fullPath, entry); + } + + offset = entry.siblingOffset; + } while (offset != constant::RomFsEmptyEntry); + } + + void RomFileSystem::TraverseDirectory(u32 offset, std::string path) { + RomFsDirectoryEntry entry{}; + backing->Read(&entry, header.dirMetaTableOffset + offset); + + std::string childPath = path; + if (entry.nameSize) { + std::vector name(entry.nameSize); + backing->Read(name.data(), header.dirMetaTableOffset + offset + sizeof(RomFsDirectoryEntry), entry.nameSize); + childPath = path + (path.empty() ? "" : "/") + std::string(name.data(), entry.nameSize); + } + + directoryMap.emplace(childPath, entry); + + if (entry.fileOffset != constant::RomFsEmptyEntry) + TraverseFiles(entry.fileOffset, childPath); + + if (entry.childOffset != constant::RomFsEmptyEntry) + TraverseDirectory(entry.childOffset, childPath); + + if (entry.siblingOffset != constant::RomFsEmptyEntry) + TraverseDirectory(entry.siblingOffset, path); + } + + std::shared_ptr RomFileSystem::OpenFile(std::string path, Backing::Mode mode) { + try { + const auto &entry = fileMap.at(path); + return std::make_shared(backing, header.dataOffset + entry.offset, entry.size, mode); + } catch (std::out_of_range &e) { + return nullptr; + } + } + + bool RomFileSystem::FileExists(std::string path) { + return fileMap.count(path); + } + + std::shared_ptr RomFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) { + try { + auto &entry = directoryMap.at(path); + return std::make_shared(backing, header, entry, listMode); + } catch (std::out_of_range &e) { + return nullptr; + } + } + + RomFileSystemDirectory::RomFileSystemDirectory(const std::shared_ptr &backing, const RomFileSystem::RomFsHeader &header, const RomFileSystem::RomFsDirectoryEntry &ownEntry, ListMode listMode) : Directory(listMode), backing(backing), header(header), ownEntry(ownEntry) {} + + std::vector RomFileSystemDirectory::Read() { + std::vector contents; + + if (listMode.file) { + RomFileSystem::RomFsFileEntry romFsFileEntry; + u32 offset = ownEntry.fileOffset; + + do { + backing->Read(&romFsFileEntry, header.fileMetaTableOffset + offset); + + if (romFsFileEntry.nameSize) { + std::vector name(romFsFileEntry.nameSize); + backing->Read(name.data(), header.fileMetaTableOffset + offset + sizeof(RomFileSystem::RomFsFileEntry), romFsFileEntry.nameSize); + + contents.emplace_back(Entry{std::string(name.data(), romFsFileEntry.nameSize), EntryType::File}); + } + + offset = romFsFileEntry.siblingOffset; + } while (offset != constant::RomFsEmptyEntry); + } + + if (listMode.directory) { + RomFileSystem::RomFsDirectoryEntry romFsDirectoryEntry; + u32 offset = ownEntry.childOffset; + + do { + backing->Read(&romFsDirectoryEntry, header.dirMetaTableOffset + offset); + + if (romFsDirectoryEntry.nameSize) { + std::vector name(romFsDirectoryEntry.nameSize); + backing->Read(name.data(), header.dirMetaTableOffset + offset + sizeof(RomFileSystem::RomFsDirectoryEntry), romFsDirectoryEntry.nameSize); + + contents.emplace_back(Entry{std::string(name.data(), romFsDirectoryEntry.nameSize), EntryType::Directory}); + } + + offset = romFsDirectoryEntry.siblingOffset; + } while (offset != constant::RomFsEmptyEntry); + } + + return contents; + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/vfs/rom_filesystem.h b/app/src/main/cpp/skyline/vfs/rom_filesystem.h new file mode 100644 index 00000000..edfcff97 --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/rom_filesystem.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "filesystem.h" + +namespace skyline { + namespace constant { + constexpr u32 RomFsEmptyEntry = 0xffffffff; //!< The value a RomFS entry has it's offset set to if it is empty + } + + namespace vfs { + /** + * @brief The RomFileSystem class abstracts access to a RomFS image using the vfs::FileSystem api + */ + class RomFileSystem : public FileSystem { + private: + std::shared_ptr backing; //!< The backing file of the filesystem + + /** + * @brief Traverses the sibling files of the given file and adds them to the file map + * @param offset The offset of the file entry to traverses the siblings of + * @param path The path to the parent directory of the supplied file entry + */ + void TraverseFiles(u32 offset, std::string path); + + /** + * @brief Traverses the directories within the given directory, adds them to the directory map and calls TraverseFiles on them + * @param offset The offset of the directory entry to traverses the directories in + * @param path The path to the supplied directory entry + */ + void TraverseDirectory(u32 offset, std::string path); + + public: + /** + * @brief This holds the header of a RomFS image + */ + struct RomFsHeader { + u64 headerSize; //!< The size of the header + u64 dirHashTableOffset; //!< The offset of the directory hash table + u64 dirHashTableSize; //!< The size of the directory hash table + u64 dirMetaTableOffset; //!< The offset of the directory metadata table + u64 dirMetaTableSize; //!< The size of the directory metadata table + u64 fileHashTableOffset; //!< The offset of the file hash table + u64 fileHashTableSize; //!< The size of the file hash table + u64 fileMetaTableOffset; //!< The offset of the file metadata table + u64 fileMetaTableSize; //!< The size of the file metadata table + u64 dataOffset; //!< The offset of the file data + } header{}; + static_assert(sizeof(RomFsHeader) == 0x50); + + /** + * @brief This holds a directory entry in a RomFS image + */ + struct RomFsDirectoryEntry { + u32 parentOffset; //!< The offset from the directory metadata base of the parent directory + u32 siblingOffset; //!< The offset from the directory metadata base of a sibling directory + u32 childOffset; //!< The offset from the directory metadata base of a child directory + u32 fileOffset; //!< The offset from the file metadata base of a child file + u32 hash; //!< The hash of the directory + u32 nameSize; //!< The size of the directory's name in bytes + }; + + /** + * @brief This holds a file entry in a RomFS image + */ + struct RomFsFileEntry { + u32 parentOffset; //!< The offset from the directory metadata base of the parent directory + u32 siblingOffset; //!< The offset from the file metadata base of a sibling file + u64 offset; //!< The offset from the file data base of the file contents + u64 size; //!< The size of the file in bytes + u32 hash; //!< The hash of the file + u32 nameSize; //!< The size of the file's name in bytes + }; + + std::unordered_map fileMap; //!< A map that maps file names to their corresponding entry + std::unordered_map directoryMap; //!< A map that maps directory names to their corresponding entry + + RomFileSystem(std::shared_ptr backing); + + std::shared_ptr OpenFile(std::string path, Backing::Mode mode = {true, false, false}); + + bool FileExists(std::string path); + + std::shared_ptr OpenDirectory(std::string path, Directory::ListMode listMode); + }; + + /** + * @brief The RomFileSystemDirectory provides access to directories within a RomFS + */ + class RomFileSystemDirectory : public Directory { + private: + RomFileSystem::RomFsDirectoryEntry ownEntry; //!< This directory's entry in the RomFS header + RomFileSystem::RomFsHeader header; //!< A header of this files parent RomFS image + std::shared_ptr backing; //!< The backing of the RomFS image + + public: + RomFileSystemDirectory(const std::shared_ptr &backing, const RomFileSystem::RomFsHeader &header, const RomFileSystem::RomFsDirectoryEntry &ownEntry, ListMode listMode); + + std::vector Read(); + }; + } +}