mirror of
				https://git.h3cjp.net/H3cJP/citra.git
				synced 2025-11-04 09:05:08 +00:00 
			
		
		
		
	Merge pull request #1365 from DarkLordZach/lfs
file_sys: Add support for LayeredFS mods
This commit is contained in:
		
						commit
						7b81e1e525
					
				| 
						 | 
				
			
			@ -33,6 +33,8 @@
 | 
			
		|||
#define NAND_DIR "nand"
 | 
			
		||||
#define SYSDATA_DIR "sysdata"
 | 
			
		||||
#define KEYS_DIR "keys"
 | 
			
		||||
#define LOAD_DIR "load"
 | 
			
		||||
#define DUMP_DIR "dump"
 | 
			
		||||
#define LOG_DIR "log"
 | 
			
		||||
 | 
			
		||||
// Filenames
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -705,6 +705,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
 | 
			
		|||
#endif
 | 
			
		||||
        paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
 | 
			
		||||
        paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
 | 
			
		||||
        paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
 | 
			
		||||
        paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
 | 
			
		||||
        paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
 | 
			
		||||
        paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
 | 
			
		||||
        // TODO: Put the logs in a better location for each OS
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,8 @@ enum class UserPath {
 | 
			
		|||
    NANDDir,
 | 
			
		||||
    RootDir,
 | 
			
		||||
    SDMCDir,
 | 
			
		||||
    LoadDir,
 | 
			
		||||
    DumpDir,
 | 
			
		||||
    SysDataDir,
 | 
			
		||||
    UserDir,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,8 @@ add_library(core STATIC
 | 
			
		|||
    file_sys/control_metadata.h
 | 
			
		||||
    file_sys/directory.h
 | 
			
		||||
    file_sys/errors.h
 | 
			
		||||
    file_sys/fsmitm_romfsbuild.cpp
 | 
			
		||||
    file_sys/fsmitm_romfsbuild.h
 | 
			
		||||
    file_sys/mode.h
 | 
			
		||||
    file_sys/nca_metadata.cpp
 | 
			
		||||
    file_sys/nca_metadata.h
 | 
			
		||||
| 
						 | 
				
			
			@ -59,10 +61,13 @@ add_library(core STATIC
 | 
			
		|||
    file_sys/vfs.h
 | 
			
		||||
    file_sys/vfs_concat.cpp
 | 
			
		||||
    file_sys/vfs_concat.h
 | 
			
		||||
    file_sys/vfs_layered.cpp
 | 
			
		||||
    file_sys/vfs_layered.h
 | 
			
		||||
    file_sys/vfs_offset.cpp
 | 
			
		||||
    file_sys/vfs_offset.h
 | 
			
		||||
    file_sys/vfs_real.cpp
 | 
			
		||||
    file_sys/vfs_real.h
 | 
			
		||||
    file_sys/vfs_static.h
 | 
			
		||||
    file_sys/vfs_vector.cpp
 | 
			
		||||
    file_sys/vfs_vector.h
 | 
			
		||||
    file_sys/xts_archive.cpp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,13 +2,14 @@
 | 
			
		|||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <fmt/format.h>
 | 
			
		||||
#include "core/file_sys/bis_factory.h"
 | 
			
		||||
#include "core/file_sys/registered_cache.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
BISFactory::BISFactory(VirtualDir nand_root_)
 | 
			
		||||
    : nand_root(std::move(nand_root_)),
 | 
			
		||||
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
 | 
			
		||||
    : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
 | 
			
		||||
      sysnand_cache(std::make_shared<RegisteredCache>(
 | 
			
		||||
          GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
 | 
			
		||||
      usrnand_cache(std::make_shared<RegisteredCache>(
 | 
			
		||||
| 
						 | 
				
			
			@ -24,4 +25,11 @@ std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
 | 
			
		|||
    return usrnand_cache;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
 | 
			
		||||
    // LayeredFS doesn't work on updates and title id-less homebrew
 | 
			
		||||
    if (title_id == 0 || (title_id & 0x800) > 0)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,14 +17,17 @@ class RegisteredCache;
 | 
			
		|||
/// registered caches.
 | 
			
		||||
class BISFactory {
 | 
			
		||||
public:
 | 
			
		||||
    explicit BISFactory(VirtualDir nand_root);
 | 
			
		||||
    explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
 | 
			
		||||
    ~BISFactory();
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
 | 
			
		||||
    std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
 | 
			
		||||
 | 
			
		||||
    VirtualDir GetModificationLoadRoot(u64 title_id) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    VirtualDir nand_root;
 | 
			
		||||
    VirtualDir load_root;
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<RegisteredCache> sysnand_cache;
 | 
			
		||||
    std::shared_ptr<RegisteredCache> usrnand_cache;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										372
									
								
								src/core/file_sys/fsmitm_romfsbuild.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								src/core/file_sys/fsmitm_romfsbuild.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,372 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018 Atmosphère-NX
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify it
 | 
			
		||||
 * under the terms and conditions of the GNU General Public License,
 | 
			
		||||
 * version 2, as published by the Free Software Foundation.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope it will be useful, but WITHOUT
 | 
			
		||||
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | 
			
		||||
 * more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Adapted by DarkLordZach for use/interaction with yuzu
 | 
			
		||||
 *
 | 
			
		||||
 * Modifications Copyright 2018 yuzu emulator team
 | 
			
		||||
 * Licensed under GPLv2 or any later version
 | 
			
		||||
 * Refer to the license.txt file included.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "core/file_sys/fsmitm_romfsbuild.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
#include "core/file_sys/vfs_vector.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
constexpr u64 FS_MAX_PATH = 0x301;
 | 
			
		||||
 | 
			
		||||
constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
 | 
			
		||||
constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
 | 
			
		||||
 | 
			
		||||
// Types for building a RomFS.
 | 
			
		||||
struct RomFSHeader {
 | 
			
		||||
    u64 header_size;
 | 
			
		||||
    u64 dir_hash_table_ofs;
 | 
			
		||||
    u64 dir_hash_table_size;
 | 
			
		||||
    u64 dir_table_ofs;
 | 
			
		||||
    u64 dir_table_size;
 | 
			
		||||
    u64 file_hash_table_ofs;
 | 
			
		||||
    u64 file_hash_table_size;
 | 
			
		||||
    u64 file_table_ofs;
 | 
			
		||||
    u64 file_table_size;
 | 
			
		||||
    u64 file_partition_ofs;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
 | 
			
		||||
 | 
			
		||||
struct RomFSDirectoryEntry {
 | 
			
		||||
    u32 parent;
 | 
			
		||||
    u32 sibling;
 | 
			
		||||
    u32 child;
 | 
			
		||||
    u32 file;
 | 
			
		||||
    u32 hash;
 | 
			
		||||
    u32 name_size;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size.");
 | 
			
		||||
 | 
			
		||||
struct RomFSFileEntry {
 | 
			
		||||
    u32 parent;
 | 
			
		||||
    u32 sibling;
 | 
			
		||||
    u64 offset;
 | 
			
		||||
    u64 size;
 | 
			
		||||
    u32 hash;
 | 
			
		||||
    u32 name_size;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size.");
 | 
			
		||||
 | 
			
		||||
struct RomFSBuildFileContext;
 | 
			
		||||
 | 
			
		||||
struct RomFSBuildDirectoryContext {
 | 
			
		||||
    std::string path = "";
 | 
			
		||||
    u32 cur_path_ofs = 0;
 | 
			
		||||
    u32 path_len = 0;
 | 
			
		||||
    u32 entry_offset = 0;
 | 
			
		||||
    std::shared_ptr<RomFSBuildDirectoryContext> parent;
 | 
			
		||||
    std::shared_ptr<RomFSBuildDirectoryContext> child;
 | 
			
		||||
    std::shared_ptr<RomFSBuildDirectoryContext> sibling;
 | 
			
		||||
    std::shared_ptr<RomFSBuildFileContext> file;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct RomFSBuildFileContext {
 | 
			
		||||
    std::string path = "";
 | 
			
		||||
    u32 cur_path_ofs = 0;
 | 
			
		||||
    u32 path_len = 0;
 | 
			
		||||
    u32 entry_offset = 0;
 | 
			
		||||
    u64 offset = 0;
 | 
			
		||||
    u64 size = 0;
 | 
			
		||||
    std::shared_ptr<RomFSBuildDirectoryContext> parent;
 | 
			
		||||
    std::shared_ptr<RomFSBuildFileContext> sibling;
 | 
			
		||||
    VirtualFile source = nullptr;
 | 
			
		||||
 | 
			
		||||
    RomFSBuildFileContext() : path(""), cur_path_ofs(0), path_len(0) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, size_t path_len) {
 | 
			
		||||
    u32 hash = parent ^ 123456789;
 | 
			
		||||
    for (u32 i = 0; i < path_len; i++) {
 | 
			
		||||
        hash = (hash >> 5) | (hash << 27);
 | 
			
		||||
        hash ^= path[start + i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return hash;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static u32 romfs_get_hash_table_count(u32 num_entries) {
 | 
			
		||||
    if (num_entries < 3) {
 | 
			
		||||
        return 3;
 | 
			
		||||
    } else if (num_entries < 19) {
 | 
			
		||||
        return num_entries | 1;
 | 
			
		||||
    }
 | 
			
		||||
    u32 count = num_entries;
 | 
			
		||||
    while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
 | 
			
		||||
           count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
 | 
			
		||||
        count++;
 | 
			
		||||
    }
 | 
			
		||||
    return count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
 | 
			
		||||
                                       std::shared_ptr<RomFSBuildDirectoryContext> parent) {
 | 
			
		||||
    std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
 | 
			
		||||
 | 
			
		||||
    VirtualDir dir;
 | 
			
		||||
 | 
			
		||||
    if (parent->path_len == 0)
 | 
			
		||||
        dir = root_romfs;
 | 
			
		||||
    else
 | 
			
		||||
        dir = root_romfs->GetDirectoryRelative(parent->path);
 | 
			
		||||
 | 
			
		||||
    const auto entries = dir->GetEntries();
 | 
			
		||||
 | 
			
		||||
    for (const auto& kv : entries) {
 | 
			
		||||
        if (kv.second == VfsEntryType::Directory) {
 | 
			
		||||
            const auto child = std::make_shared<RomFSBuildDirectoryContext>();
 | 
			
		||||
            // Set child's path.
 | 
			
		||||
            child->cur_path_ofs = parent->path_len + 1;
 | 
			
		||||
            child->path_len = child->cur_path_ofs + kv.first.size();
 | 
			
		||||
            child->path = parent->path + "/" + kv.first;
 | 
			
		||||
 | 
			
		||||
            // Sanity check on path_len
 | 
			
		||||
            ASSERT(child->path_len < FS_MAX_PATH);
 | 
			
		||||
 | 
			
		||||
            if (AddDirectory(parent, child)) {
 | 
			
		||||
                child_dirs.push_back(child);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const auto child = std::make_shared<RomFSBuildFileContext>();
 | 
			
		||||
            // Set child's path.
 | 
			
		||||
            child->cur_path_ofs = parent->path_len + 1;
 | 
			
		||||
            child->path_len = child->cur_path_ofs + kv.first.size();
 | 
			
		||||
            child->path = parent->path + "/" + kv.first;
 | 
			
		||||
 | 
			
		||||
            // Sanity check on path_len
 | 
			
		||||
            ASSERT(child->path_len < FS_MAX_PATH);
 | 
			
		||||
 | 
			
		||||
            child->source = root_romfs->GetFileRelative(child->path);
 | 
			
		||||
 | 
			
		||||
            child->size = child->source->GetSize();
 | 
			
		||||
 | 
			
		||||
            AddFile(parent, child);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto& child : child_dirs) {
 | 
			
		||||
        this->VisitDirectory(root_romfs, child);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
 | 
			
		||||
                                     std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
 | 
			
		||||
    // Check whether it's already in the known directories.
 | 
			
		||||
    const auto existing = directories.find(dir_ctx->path);
 | 
			
		||||
    if (existing != directories.end())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    // Add a new directory.
 | 
			
		||||
    num_dirs++;
 | 
			
		||||
    dir_table_size +=
 | 
			
		||||
        sizeof(RomFSDirectoryEntry) + ((dir_ctx->path_len - dir_ctx->cur_path_ofs + 3) & ~3);
 | 
			
		||||
    dir_ctx->parent = parent_dir_ctx;
 | 
			
		||||
    directories.emplace(dir_ctx->path, dir_ctx);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
 | 
			
		||||
                                std::shared_ptr<RomFSBuildFileContext> file_ctx) {
 | 
			
		||||
    // Check whether it's already in the known files.
 | 
			
		||||
    const auto existing = files.find(file_ctx->path);
 | 
			
		||||
    if (existing != files.end()) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add a new file.
 | 
			
		||||
    num_files++;
 | 
			
		||||
    file_table_size +=
 | 
			
		||||
        sizeof(RomFSFileEntry) + ((file_ctx->path_len - file_ctx->cur_path_ofs + 3) & ~3);
 | 
			
		||||
    file_ctx->parent = parent_dir_ctx;
 | 
			
		||||
    files.emplace(file_ctx->path, file_ctx);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
 | 
			
		||||
    root = std::make_shared<RomFSBuildDirectoryContext>();
 | 
			
		||||
    root->path = "\0";
 | 
			
		||||
    directories.emplace(root->path, root);
 | 
			
		||||
    num_dirs = 1;
 | 
			
		||||
    dir_table_size = 0x18;
 | 
			
		||||
 | 
			
		||||
    VisitDirectory(base, root);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RomFSBuildContext::~RomFSBuildContext() = default;
 | 
			
		||||
 | 
			
		||||
std::map<u64, VirtualFile> RomFSBuildContext::Build() {
 | 
			
		||||
    const auto dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
 | 
			
		||||
    const auto file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
 | 
			
		||||
    dir_hash_table_size = 4 * dir_hash_table_entry_count;
 | 
			
		||||
    file_hash_table_size = 4 * file_hash_table_entry_count;
 | 
			
		||||
 | 
			
		||||
    // Assign metadata pointers
 | 
			
		||||
    RomFSHeader header{};
 | 
			
		||||
 | 
			
		||||
    std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
 | 
			
		||||
    std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> dir_table(dir_table_size);
 | 
			
		||||
    std::vector<u8> file_table(file_table_size);
 | 
			
		||||
 | 
			
		||||
    // Clear out hash tables.
 | 
			
		||||
    for (u32 i = 0; i < dir_hash_table_entry_count; i++)
 | 
			
		||||
        dir_hash_table[i] = ROMFS_ENTRY_EMPTY;
 | 
			
		||||
    for (u32 i = 0; i < file_hash_table_entry_count; i++)
 | 
			
		||||
        file_hash_table[i] = ROMFS_ENTRY_EMPTY;
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<RomFSBuildFileContext> cur_file;
 | 
			
		||||
 | 
			
		||||
    // Determine file offsets.
 | 
			
		||||
    u32 entry_offset = 0;
 | 
			
		||||
    std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
 | 
			
		||||
    for (const auto& it : files) {
 | 
			
		||||
        cur_file = it.second;
 | 
			
		||||
        file_partition_size = (file_partition_size + 0xFULL) & ~0xFULL;
 | 
			
		||||
        cur_file->offset = file_partition_size;
 | 
			
		||||
        file_partition_size += cur_file->size;
 | 
			
		||||
        cur_file->entry_offset = entry_offset;
 | 
			
		||||
        entry_offset +=
 | 
			
		||||
            sizeof(RomFSFileEntry) + ((cur_file->path_len - cur_file->cur_path_ofs + 3) & ~3);
 | 
			
		||||
        prev_file = cur_file;
 | 
			
		||||
    }
 | 
			
		||||
    // Assign deferred parent/sibling ownership.
 | 
			
		||||
    for (auto it = files.rbegin(); it != files.rend(); ++it) {
 | 
			
		||||
        cur_file = it->second;
 | 
			
		||||
        cur_file->sibling = cur_file->parent->file;
 | 
			
		||||
        cur_file->parent->file = cur_file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<RomFSBuildDirectoryContext> cur_dir;
 | 
			
		||||
 | 
			
		||||
    // Determine directory offsets.
 | 
			
		||||
    entry_offset = 0;
 | 
			
		||||
    for (const auto& it : directories) {
 | 
			
		||||
        cur_dir = it.second;
 | 
			
		||||
        cur_dir->entry_offset = entry_offset;
 | 
			
		||||
        entry_offset +=
 | 
			
		||||
            sizeof(RomFSDirectoryEntry) + ((cur_dir->path_len - cur_dir->cur_path_ofs + 3) & ~3);
 | 
			
		||||
    }
 | 
			
		||||
    // Assign deferred parent/sibling ownership.
 | 
			
		||||
    for (auto it = directories.rbegin(); it->second != root; ++it) {
 | 
			
		||||
        cur_dir = it->second;
 | 
			
		||||
        cur_dir->sibling = cur_dir->parent->child;
 | 
			
		||||
        cur_dir->parent->child = cur_dir;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::map<u64, VirtualFile> out;
 | 
			
		||||
 | 
			
		||||
    // Populate file tables.
 | 
			
		||||
    for (const auto& it : files) {
 | 
			
		||||
        cur_file = it.second;
 | 
			
		||||
        RomFSFileEntry cur_entry{};
 | 
			
		||||
 | 
			
		||||
        cur_entry.parent = cur_file->parent->entry_offset;
 | 
			
		||||
        cur_entry.sibling =
 | 
			
		||||
            cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
 | 
			
		||||
        cur_entry.offset = cur_file->offset;
 | 
			
		||||
        cur_entry.size = cur_file->size;
 | 
			
		||||
 | 
			
		||||
        const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
 | 
			
		||||
        const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
 | 
			
		||||
                                               cur_file->cur_path_ofs, name_size);
 | 
			
		||||
        cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
 | 
			
		||||
        file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
 | 
			
		||||
 | 
			
		||||
        cur_entry.name_size = name_size;
 | 
			
		||||
 | 
			
		||||
        out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
 | 
			
		||||
        std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
 | 
			
		||||
        std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
 | 
			
		||||
                    (cur_entry.name_size + 3) & ~3);
 | 
			
		||||
        std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
 | 
			
		||||
                    cur_file->path.data() + cur_file->cur_path_ofs, name_size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Populate dir tables.
 | 
			
		||||
    for (const auto& it : directories) {
 | 
			
		||||
        cur_dir = it.second;
 | 
			
		||||
        RomFSDirectoryEntry cur_entry{};
 | 
			
		||||
 | 
			
		||||
        cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
 | 
			
		||||
        cur_entry.sibling =
 | 
			
		||||
            cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
 | 
			
		||||
        cur_entry.child =
 | 
			
		||||
            cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
 | 
			
		||||
        cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
 | 
			
		||||
 | 
			
		||||
        const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
 | 
			
		||||
        const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
 | 
			
		||||
                                               cur_dir->path, cur_dir->cur_path_ofs, name_size);
 | 
			
		||||
        cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
 | 
			
		||||
        dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
 | 
			
		||||
 | 
			
		||||
        cur_entry.name_size = name_size;
 | 
			
		||||
 | 
			
		||||
        std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
 | 
			
		||||
                    sizeof(RomFSDirectoryEntry));
 | 
			
		||||
        std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
 | 
			
		||||
                    sizeof(RomFSDirectoryEntry));
 | 
			
		||||
        std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
 | 
			
		||||
                    (cur_entry.name_size + 3) & ~3);
 | 
			
		||||
        std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
 | 
			
		||||
                    cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set header fields.
 | 
			
		||||
    header.header_size = sizeof(RomFSHeader);
 | 
			
		||||
    header.file_hash_table_size = file_hash_table_size;
 | 
			
		||||
    header.file_table_size = file_table_size;
 | 
			
		||||
    header.dir_hash_table_size = dir_hash_table_size;
 | 
			
		||||
    header.dir_table_size = dir_table_size;
 | 
			
		||||
    header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
 | 
			
		||||
    header.dir_hash_table_ofs = (header.file_partition_ofs + file_partition_size + 3ULL) & ~3ULL;
 | 
			
		||||
    header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
 | 
			
		||||
    header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
 | 
			
		||||
    header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> header_data(sizeof(RomFSHeader));
 | 
			
		||||
    std::memcpy(header_data.data(), &header, header_data.size());
 | 
			
		||||
    out.emplace(0, std::make_shared<VectorVfsFile>(header_data));
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
 | 
			
		||||
                             dir_table_size);
 | 
			
		||||
    auto index = 0;
 | 
			
		||||
    std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32));
 | 
			
		||||
    index += dir_hash_table.size() * sizeof(u32);
 | 
			
		||||
    std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size());
 | 
			
		||||
    index += dir_table.size();
 | 
			
		||||
    std::memcpy(metadata.data() + index, file_hash_table.data(),
 | 
			
		||||
                file_hash_table.size() * sizeof(u32));
 | 
			
		||||
    index += file_hash_table.size() * sizeof(u32);
 | 
			
		||||
    std::memcpy(metadata.data() + index, file_table.data(), file_table.size());
 | 
			
		||||
    out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(metadata));
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										70
									
								
								src/core/file_sys/fsmitm_romfsbuild.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/core/file_sys/fsmitm_romfsbuild.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright (c) 2018 Atmosphère-NX
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify it
 | 
			
		||||
 * under the terms and conditions of the GNU General Public License,
 | 
			
		||||
 * version 2, as published by the Free Software Foundation.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope it will be useful, but WITHOUT
 | 
			
		||||
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | 
			
		||||
 * more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Adapted by DarkLordZach for use/interaction with yuzu
 | 
			
		||||
 *
 | 
			
		||||
 * Modifications Copyright 2018 yuzu emulator team
 | 
			
		||||
 * Licensed under GPLv2 or any later version
 | 
			
		||||
 * Refer to the license.txt file included.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <boost/detail/container_fwd.hpp>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
struct RomFSBuildDirectoryContext;
 | 
			
		||||
struct RomFSBuildFileContext;
 | 
			
		||||
struct RomFSDirectoryEntry;
 | 
			
		||||
struct RomFSFileEntry;
 | 
			
		||||
 | 
			
		||||
class RomFSBuildContext {
 | 
			
		||||
public:
 | 
			
		||||
    explicit RomFSBuildContext(VirtualDir base);
 | 
			
		||||
    ~RomFSBuildContext();
 | 
			
		||||
 | 
			
		||||
    // This finalizes the context.
 | 
			
		||||
    std::map<u64, VirtualFile> Build();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    VirtualDir base;
 | 
			
		||||
    std::shared_ptr<RomFSBuildDirectoryContext> root;
 | 
			
		||||
    std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
 | 
			
		||||
    std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
 | 
			
		||||
    u64 num_dirs = 0;
 | 
			
		||||
    u64 num_files = 0;
 | 
			
		||||
    u64 dir_table_size = 0;
 | 
			
		||||
    u64 file_table_size = 0;
 | 
			
		||||
    u64 dir_hash_table_size = 0;
 | 
			
		||||
    u64 file_hash_table_size = 0;
 | 
			
		||||
    u64 file_partition_size = 0;
 | 
			
		||||
 | 
			
		||||
    void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
 | 
			
		||||
 | 
			
		||||
    bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
 | 
			
		||||
                      std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
 | 
			
		||||
    bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
 | 
			
		||||
                 std::shared_ptr<RomFSBuildFileContext> file_ctx);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +11,7 @@
 | 
			
		|||
#include "core/file_sys/patch_manager.h"
 | 
			
		||||
#include "core/file_sys/registered_cache.h"
 | 
			
		||||
#include "core/file_sys/romfs.h"
 | 
			
		||||
#include "core/file_sys/vfs_layered.h"
 | 
			
		||||
#include "core/hle/service/filesystem/filesystem.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,8 +32,9 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
 | 
			
		|||
    return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{
 | 
			
		||||
constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{
 | 
			
		||||
    "Update",
 | 
			
		||||
    "LayeredFS",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string FormatPatchTypeName(PatchType type) {
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +68,42 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
 | 
			
		|||
    return exefs;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
 | 
			
		||||
    const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
 | 
			
		||||
    if (type == ContentRecordType::Program && load_dir != nullptr && load_dir->GetSize() > 0) {
 | 
			
		||||
        auto extracted = ExtractRomFS(romfs);
 | 
			
		||||
 | 
			
		||||
        if (extracted != nullptr) {
 | 
			
		||||
            auto patch_dirs = load_dir->GetSubdirectories();
 | 
			
		||||
            std::sort(patch_dirs.begin(), patch_dirs.end(),
 | 
			
		||||
                      [](const VirtualDir& l, const VirtualDir& r) {
 | 
			
		||||
                          return l->GetName() < r->GetName();
 | 
			
		||||
                      });
 | 
			
		||||
 | 
			
		||||
            std::vector<VirtualDir> layers;
 | 
			
		||||
            layers.reserve(patch_dirs.size() + 1);
 | 
			
		||||
            for (const auto& subdir : patch_dirs) {
 | 
			
		||||
                auto romfs_dir = subdir->GetSubdirectory("romfs");
 | 
			
		||||
                if (romfs_dir != nullptr)
 | 
			
		||||
                    layers.push_back(std::move(romfs_dir));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            layers.push_back(std::move(extracted));
 | 
			
		||||
 | 
			
		||||
            const auto layered = LayerDirectories(layers);
 | 
			
		||||
 | 
			
		||||
            if (layered != nullptr) {
 | 
			
		||||
                auto packed = CreateRomFS(layered);
 | 
			
		||||
 | 
			
		||||
                if (packed != nullptr) {
 | 
			
		||||
                    LOG_INFO(Loader, "    RomFS: LayeredFS patches applied successfully");
 | 
			
		||||
                    romfs = std::move(packed);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
 | 
			
		||||
                                     ContentRecordType type) const {
 | 
			
		||||
    LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +127,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // LayeredFS
 | 
			
		||||
    ApplyLayeredFS(romfs, title_id, type);
 | 
			
		||||
 | 
			
		||||
    return romfs;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -114,6 +155,10 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
 | 
			
		||||
    if (lfs_dir != nullptr && lfs_dir->GetSize() > 0)
 | 
			
		||||
        out.insert_or_assign(PatchType::LayeredFS, "");
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@ std::string FormatTitleVersion(u32 version,
 | 
			
		|||
 | 
			
		||||
enum class PatchType {
 | 
			
		||||
    Update,
 | 
			
		||||
    LayeredFS,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string FormatPatchTypeName(PatchType type);
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +43,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    // Currently tracked RomFS patches:
 | 
			
		||||
    // - Game Updates
 | 
			
		||||
    // - LayeredFS
 | 
			
		||||
    VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
 | 
			
		||||
                           ContentRecordType type = ContentRecordType::Program) const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,10 @@
 | 
			
		|||
#include "core/loader/loader.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
// The size of blocks to use when vfs raw copying into nand.
 | 
			
		||||
constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
 | 
			
		||||
 | 
			
		||||
std::string RegisteredCacheEntry::DebugInfo() const {
 | 
			
		||||
    return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +125,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
 | 
			
		|||
            if (concat.empty())
 | 
			
		||||
                return nullptr;
 | 
			
		||||
 | 
			
		||||
            file = FileSys::ConcatenateFiles(concat);
 | 
			
		||||
            file = FileSys::ConcatenateFiles(concat, concat.front()->GetName());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return file;
 | 
			
		||||
| 
						 | 
				
			
			@ -480,7 +484,8 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
 | 
			
		|||
    auto out = dir->CreateFileRelative(path);
 | 
			
		||||
    if (out == nullptr)
 | 
			
		||||
        return InstallResult::ErrorCopyFailed;
 | 
			
		||||
    return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
 | 
			
		||||
    return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success
 | 
			
		||||
                                                  : InstallResult::ErrorCopyFailed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ struct ContentRecord;
 | 
			
		|||
 | 
			
		||||
using NcaID = std::array<u8, 0x10>;
 | 
			
		||||
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
 | 
			
		||||
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
 | 
			
		||||
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
 | 
			
		||||
 | 
			
		||||
enum class InstallResult {
 | 
			
		||||
    Success,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,8 +4,10 @@
 | 
			
		|||
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
#include "core/file_sys/fsmitm_romfsbuild.h"
 | 
			
		||||
#include "core/file_sys/romfs.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
#include "core/file_sys/vfs_concat.h"
 | 
			
		||||
#include "core/file_sys/vfs_offset.h"
 | 
			
		||||
#include "core/file_sys/vfs_vector.h"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +100,7 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualDir ExtractRomFS(VirtualFile file) {
 | 
			
		||||
VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
 | 
			
		||||
    RomFSHeader header{};
 | 
			
		||||
    if (file->ReadObject(&header) != sizeof(RomFSHeader))
 | 
			
		||||
        return nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -117,9 +119,22 @@ VirtualDir ExtractRomFS(VirtualFile file) {
 | 
			
		|||
 | 
			
		||||
    VirtualDir out = std::move(root);
 | 
			
		||||
 | 
			
		||||
    while (out->GetSubdirectory("") != nullptr)
 | 
			
		||||
        out = out->GetSubdirectory("");
 | 
			
		||||
    while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
 | 
			
		||||
        if (out->GetSubdirectories().front()->GetName() == "data" &&
 | 
			
		||||
            type == RomFSExtractionType::Truncated)
 | 
			
		||||
            break;
 | 
			
		||||
        out = out->GetSubdirectories().front();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualFile CreateRomFS(VirtualDir dir) {
 | 
			
		||||
    if (dir == nullptr)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    RomFSBuildContext ctx{dir};
 | 
			
		||||
    return ConcatenateFiles<0>(ctx.Build(), dir->GetName());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +13,8 @@
 | 
			
		|||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
struct RomFSHeader;
 | 
			
		||||
 | 
			
		||||
struct IVFCLevel {
 | 
			
		||||
    u64_le offset;
 | 
			
		||||
    u64_le size;
 | 
			
		||||
| 
						 | 
				
			
			@ -29,8 +32,18 @@ struct IVFCHeader {
 | 
			
		|||
};
 | 
			
		||||
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
 | 
			
		||||
 | 
			
		||||
enum class RomFSExtractionType {
 | 
			
		||||
    Full,      // Includes data directory
 | 
			
		||||
    Truncated, // Traverses into data directory
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Converts a RomFS binary blob to VFS Filesystem
 | 
			
		||||
// Returns nullptr on failure
 | 
			
		||||
VirtualDir ExtractRomFS(VirtualFile file);
 | 
			
		||||
VirtualDir ExtractRomFS(VirtualFile file,
 | 
			
		||||
                        RomFSExtractionType type = RomFSExtractionType::Truncated);
 | 
			
		||||
 | 
			
		||||
// Converts a VFS filesystem into a RomFS binary
 | 
			
		||||
// Returns nullptr on failure
 | 
			
		||||
VirtualFile CreateRomFS(VirtualDir dir);
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -399,6 +399,15 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
 | 
			
		|||
    return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
 | 
			
		||||
    std::map<std::string, VfsEntryType, std::less<>> out;
 | 
			
		||||
    for (const auto& dir : GetSubdirectories())
 | 
			
		||||
        out.emplace(dir->GetName(), VfsEntryType::Directory);
 | 
			
		||||
    for (const auto& file : GetFiles())
 | 
			
		||||
        out.emplace(file->GetName(), VfsEntryType::File);
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string VfsDirectory::GetFullPath() const {
 | 
			
		||||
    if (IsRoot())
 | 
			
		||||
        return GetName();
 | 
			
		||||
| 
						 | 
				
			
			@ -454,13 +463,41 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
 | 
			
		||||
    if (src == nullptr || dest == nullptr)
 | 
			
		||||
bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, size_t block_size) {
 | 
			
		||||
    if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
 | 
			
		||||
        return false;
 | 
			
		||||
    if (!dest->Resize(src->GetSize()))
 | 
			
		||||
        return false;
 | 
			
		||||
    std::vector<u8> data = src->ReadAllBytes();
 | 
			
		||||
    return dest->WriteBytes(data, 0) == data.size();
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> temp(std::min(block_size, src->GetSize()));
 | 
			
		||||
    for (size_t i = 0; i < src->GetSize(); i += block_size) {
 | 
			
		||||
        const auto read = std::min(block_size, src->GetSize() - i);
 | 
			
		||||
        const auto block = src->Read(temp.data(), read, i);
 | 
			
		||||
 | 
			
		||||
        if (dest->Write(temp.data(), read, i) != read)
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, size_t block_size) {
 | 
			
		||||
    if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    for (const auto& file : src->GetFiles()) {
 | 
			
		||||
        const auto out = dest->CreateFile(file->GetName());
 | 
			
		||||
        if (!VfsRawCopy(file, out, block_size))
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const auto& dir : src->GetSubdirectories()) {
 | 
			
		||||
        const auto out = dest->CreateSubdirectory(dir->GetName());
 | 
			
		||||
        if (!VfsRawCopyD(dir, out, block_size))
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
| 
						 | 
				
			
			@ -265,6 +266,10 @@ public:
 | 
			
		|||
    // dest.
 | 
			
		||||
    virtual bool Copy(std::string_view src, std::string_view dest);
 | 
			
		||||
 | 
			
		||||
    // Gets all of the entries directly in the directory (files and dirs), returning a map between
 | 
			
		||||
    // item name -> type.
 | 
			
		||||
    virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
 | 
			
		||||
 | 
			
		||||
    // 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
 | 
			
		||||
| 
						 | 
				
			
			@ -311,12 +316,17 @@ public:
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
// Compare the two files, byte-for-byte, in increments specificed by block_size
 | 
			
		||||
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200);
 | 
			
		||||
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x1000);
 | 
			
		||||
 | 
			
		||||
// A method that copies the raw data between two different implementations of VirtualFile. If you
 | 
			
		||||
// are using the same implementation, it is probably better to use the Copy method in the parent
 | 
			
		||||
// directory of src/dest.
 | 
			
		||||
bool VfsRawCopy(VirtualFile src, VirtualFile dest);
 | 
			
		||||
bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, size_t block_size = 0x1000);
 | 
			
		||||
 | 
			
		||||
// A method that performs a similar function to VfsRawCopy above, but instead copies entire
 | 
			
		||||
// directories. It suffers the same performance penalties as above and an implementation-specific
 | 
			
		||||
// Copy should always be preferred.
 | 
			
		||||
bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, size_t block_size = 0x1000);
 | 
			
		||||
 | 
			
		||||
// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
 | 
			
		||||
// it attempts to create it and returns the new dir or nullptr on failure.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,10 +5,23 @@
 | 
			
		|||
#include <algorithm>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "core/file_sys/vfs_concat.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) {
 | 
			
		||||
    const auto last_valid = --map.end();
 | 
			
		||||
    for (auto iter = map.begin(); iter != last_valid;) {
 | 
			
		||||
        const auto old = iter++;
 | 
			
		||||
        if (old->first + old->second->GetSize() != iter->first) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return map.begin()->first == 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
 | 
			
		||||
    if (files.empty())
 | 
			
		||||
        return nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +40,11 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name)
 | 
			
		||||
    : files(std::move(files_)), name(std::move(name)) {
 | 
			
		||||
    ASSERT(VerifyConcatenationMapContinuity(files));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
 | 
			
		||||
 | 
			
		||||
std::string ConcatenatedVfsFile::GetName() const {
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +80,7 @@ bool ConcatenatedVfsFile::IsReadable() const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
 | 
			
		||||
    auto entry = files.end();
 | 
			
		||||
    auto entry = --files.end();
 | 
			
		||||
    for (auto iter = files.begin(); iter != files.end(); ++iter) {
 | 
			
		||||
        if (iter->first > offset) {
 | 
			
		||||
            entry = --iter;
 | 
			
		||||
| 
						 | 
				
			
			@ -70,20 +88,17 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check if the entry should be the last one. The loop above will make it end().
 | 
			
		||||
    if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
 | 
			
		||||
        --entry;
 | 
			
		||||
 | 
			
		||||
    if (entry == files.end())
 | 
			
		||||
    if (entry->first + entry->second->GetSize() <= offset)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    const auto remaining = entry->second->GetSize() + offset - entry->first;
 | 
			
		||||
    if (length > remaining) {
 | 
			
		||||
        return entry->second->Read(data, remaining, offset - entry->first) +
 | 
			
		||||
               Read(data + remaining, length - remaining, offset + remaining);
 | 
			
		||||
    const auto read_in =
 | 
			
		||||
        std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize());
 | 
			
		||||
    if (length > read_in) {
 | 
			
		||||
        return entry->second->Read(data, read_in, offset - entry->first) +
 | 
			
		||||
               Read(data + read_in, length - read_in, offset + read_in);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return entry->second->Read(data, length, offset - entry->first);
 | 
			
		||||
    return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
 | 
			
		||||
| 
						 | 
				
			
			@ -93,4 +108,5 @@ std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::
 | 
			
		|||
bool ConcatenatedVfsFile::Rename(std::string_view name) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,22 +4,25 @@
 | 
			
		|||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <boost/container/flat_map.hpp>
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
#include "core/file_sys/vfs_static.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
 | 
			
		||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
 | 
			
		||||
 | 
			
		||||
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
 | 
			
		||||
// read-only.
 | 
			
		||||
class ConcatenatedVfsFile : public VfsFile {
 | 
			
		||||
    friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
 | 
			
		||||
 | 
			
		||||
    template <u8 filler_byte>
 | 
			
		||||
    friend VirtualFile ConcatenateFiles(std::map<u64, VirtualFile> files, std::string name);
 | 
			
		||||
 | 
			
		||||
    ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
 | 
			
		||||
    ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    ~ConcatenatedVfsFile() override;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,8 +39,37 @@ public:
 | 
			
		|||
 | 
			
		||||
private:
 | 
			
		||||
    // Maps starting offset to file -- more efficient.
 | 
			
		||||
    boost::container::flat_map<u64, VirtualFile> files;
 | 
			
		||||
    std::map<u64, VirtualFile> files;
 | 
			
		||||
    std::string name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
 | 
			
		||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
 | 
			
		||||
 | 
			
		||||
// Convenience function that turns a map of offsets to files into a concatenated file, filling gaps
 | 
			
		||||
// with template parameter.
 | 
			
		||||
template <u8 filler_byte>
 | 
			
		||||
VirtualFile ConcatenateFiles(std::map<u64, VirtualFile> files, std::string name) {
 | 
			
		||||
    if (files.empty())
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    if (files.size() == 1)
 | 
			
		||||
        return files.begin()->second;
 | 
			
		||||
 | 
			
		||||
    const auto last_valid = --files.end();
 | 
			
		||||
    for (auto iter = files.begin(); iter != last_valid;) {
 | 
			
		||||
        const auto old = iter++;
 | 
			
		||||
        if (old->first + old->second->GetSize() != iter->first) {
 | 
			
		||||
            files.emplace(old->first + old->second->GetSize(),
 | 
			
		||||
                          std::make_shared<StaticVfsFile<filler_byte>>(iter->first - old->first -
 | 
			
		||||
                                                                       old->second->GetSize()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Ensure the map starts at offset 0 (start of file), otherwise pad to fill.
 | 
			
		||||
    if (files.begin()->first != 0)
 | 
			
		||||
        files.emplace(0, std::make_shared<StaticVfsFile<filler_byte>>(files.begin()->first));
 | 
			
		||||
 | 
			
		||||
    return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										131
									
								
								src/core/file_sys/vfs_layered.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/core/file_sys/vfs_layered.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include "core/file_sys/vfs_layered.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name) {
 | 
			
		||||
    if (dirs.empty())
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    if (dirs.size() == 1)
 | 
			
		||||
        return dirs[0];
 | 
			
		||||
 | 
			
		||||
    return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name)
 | 
			
		||||
    : dirs(std::move(dirs)), name(std::move(name)) {}
 | 
			
		||||
 | 
			
		||||
LayeredVfsDirectory::~LayeredVfsDirectory() = default;
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
 | 
			
		||||
    for (const auto& layer : dirs) {
 | 
			
		||||
        const auto file = layer->GetFileRelative(path);
 | 
			
		||||
        if (file != nullptr)
 | 
			
		||||
            return file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
 | 
			
		||||
    std::string_view path) const {
 | 
			
		||||
    std::vector<VirtualDir> out;
 | 
			
		||||
    for (const auto& layer : dirs) {
 | 
			
		||||
        auto dir = layer->GetDirectoryRelative(path);
 | 
			
		||||
        if (dir != nullptr)
 | 
			
		||||
            out.push_back(std::move(dir));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return LayerDirectories(std::move(out));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
 | 
			
		||||
    return GetFileRelative(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
 | 
			
		||||
    return GetDirectoryRelative(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string LayeredVfsDirectory::GetFullPath() const {
 | 
			
		||||
    return dirs[0]->GetFullPath();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
 | 
			
		||||
    std::vector<VirtualFile> out;
 | 
			
		||||
    for (const auto& layer : dirs) {
 | 
			
		||||
        for (const auto& file : layer->GetFiles()) {
 | 
			
		||||
            if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) {
 | 
			
		||||
                    return comp->GetName() == file->GetName();
 | 
			
		||||
                }) == out.end()) {
 | 
			
		||||
                out.push_back(file);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
 | 
			
		||||
    std::vector<std::string> names;
 | 
			
		||||
    for (const auto& layer : dirs) {
 | 
			
		||||
        for (const auto& sd : layer->GetSubdirectories()) {
 | 
			
		||||
            if (std::find(names.begin(), names.end(), sd->GetName()) == names.end())
 | 
			
		||||
                names.push_back(sd->GetName());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<VirtualDir> out;
 | 
			
		||||
    out.reserve(names.size());
 | 
			
		||||
    for (const auto& subdir : names)
 | 
			
		||||
        out.push_back(GetSubdirectory(subdir));
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LayeredVfsDirectory::IsWritable() const {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LayeredVfsDirectory::IsReadable() const {
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string LayeredVfsDirectory::GetName() const {
 | 
			
		||||
    return name.empty() ? dirs[0]->GetName() : name;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
 | 
			
		||||
    return dirs[0]->GetParentDirectory();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LayeredVfsDirectory::DeleteFile(std::string_view name) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LayeredVfsDirectory::Rename(std::string_view name_) {
 | 
			
		||||
    name = name_;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										52
									
								
								src/core/file_sys/vfs_layered.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/core/file_sys/vfs_layered.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
 | 
			
		||||
VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name = "");
 | 
			
		||||
 | 
			
		||||
// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
 | 
			
		||||
// one and falling back to the one after. The highest priority directory (overwrites all others)
 | 
			
		||||
// should be element 0 in the dirs vector.
 | 
			
		||||
class LayeredVfsDirectory : public VfsDirectory {
 | 
			
		||||
    friend VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name);
 | 
			
		||||
 | 
			
		||||
    LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    ~LayeredVfsDirectory() override;
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
 | 
			
		||||
    std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
 | 
			
		||||
    std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
 | 
			
		||||
    std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
 | 
			
		||||
    std::string GetFullPath() const override;
 | 
			
		||||
 | 
			
		||||
    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(std::string_view name) override;
 | 
			
		||||
    std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
 | 
			
		||||
    bool DeleteSubdirectory(std::string_view name) override;
 | 
			
		||||
    bool DeleteFile(std::string_view name) override;
 | 
			
		||||
    bool Rename(std::string_view name) override;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::vector<VirtualDir> dirs;
 | 
			
		||||
    std::string name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			@ -413,6 +413,23 @@ std::string RealVfsDirectory::GetFullPath() const {
 | 
			
		|||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
 | 
			
		||||
    if (perms == Mode::Append)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    std::map<std::string, VfsEntryType, std::less<>> out;
 | 
			
		||||
    FileUtil::ForeachDirectoryEntry(
 | 
			
		||||
        nullptr, path,
 | 
			
		||||
        [&out](u64* entries_out, const std::string& directory, const std::string& filename) {
 | 
			
		||||
            const std::string full_path = directory + DIR_SEP + filename;
 | 
			
		||||
            out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory
 | 
			
		||||
                                                                   : VfsEntryType::File);
 | 
			
		||||
            return true;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,6 +98,7 @@ public:
 | 
			
		|||
    bool DeleteFile(std::string_view name) override;
 | 
			
		||||
    bool Rename(std::string_view name) override;
 | 
			
		||||
    std::string GetFullPath() const override;
 | 
			
		||||
    std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										78
									
								
								src/core/file_sys/vfs_static.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/core/file_sys/vfs_static.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
template <u8 value>
 | 
			
		||||
class StaticVfsFile : public VfsFile {
 | 
			
		||||
public:
 | 
			
		||||
    explicit StaticVfsFile(size_t size = 0, std::string name = "", VirtualDir parent = nullptr)
 | 
			
		||||
        : size(size), name(std::move(name)), parent(std::move(parent)) {}
 | 
			
		||||
 | 
			
		||||
    std::string GetName() const override {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t GetSize() const override {
 | 
			
		||||
        return size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool Resize(size_t new_size) override {
 | 
			
		||||
        size = new_size;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
 | 
			
		||||
        return parent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool IsWritable() const override {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool IsReadable() const override {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t Read(u8* data, size_t length, size_t offset) const override {
 | 
			
		||||
        const auto read = std::min(length, size - offset);
 | 
			
		||||
        std::fill(data, data + read, value);
 | 
			
		||||
        return read;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t Write(const u8* data, size_t length, size_t offset) override {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boost::optional<u8> ReadByte(size_t offset) const override {
 | 
			
		||||
        if (offset < size)
 | 
			
		||||
            return value;
 | 
			
		||||
        return boost::none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> ReadBytes(size_t length, size_t offset) const override {
 | 
			
		||||
        const auto read = std::min(length, size - offset);
 | 
			
		||||
        return std::vector<u8>(read, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool Rename(std::string_view new_name) override {
 | 
			
		||||
        name = new_name;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    size_t size;
 | 
			
		||||
    std::string name;
 | 
			
		||||
    VirtualDir parent;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
| 
						 | 
				
			
			@ -3,10 +3,64 @@
 | 
			
		|||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include "core/file_sys/vfs_vector.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent)
 | 
			
		||||
    : data(std::move(initial_data)), name(std::move(name)), parent(std::move(parent)) {}
 | 
			
		||||
 | 
			
		||||
VectorVfsFile::~VectorVfsFile() = default;
 | 
			
		||||
 | 
			
		||||
std::string VectorVfsFile::GetName() const {
 | 
			
		||||
    return name;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t VectorVfsFile::GetSize() const {
 | 
			
		||||
    return data.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VectorVfsFile::Resize(size_t new_size) {
 | 
			
		||||
    data.resize(new_size);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
 | 
			
		||||
    return parent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VectorVfsFile::IsWritable() const {
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VectorVfsFile::IsReadable() const {
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t VectorVfsFile::Read(u8* data_, size_t length, size_t offset) const {
 | 
			
		||||
    const auto read = std::min(length, data.size() - offset);
 | 
			
		||||
    std::memcpy(data_, data.data() + offset, read);
 | 
			
		||||
    return read;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t VectorVfsFile::Write(const u8* data_, size_t length, size_t offset) {
 | 
			
		||||
    if (offset + length > data.size())
 | 
			
		||||
        data.resize(offset + length);
 | 
			
		||||
    const auto write = std::min(length, data.size() - offset);
 | 
			
		||||
    std::memcpy(data.data(), data_, write);
 | 
			
		||||
    return write;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VectorVfsFile::Rename(std::string_view name_) {
 | 
			
		||||
    name = name_;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VectorVfsFile::Assign(std::vector<u8> new_data) {
 | 
			
		||||
    data = std::move(new_data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
 | 
			
		||||
                                       std::vector<VirtualDir> dirs_, std::string name_,
 | 
			
		||||
                                       VirtualDir parent_)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,31 @@
 | 
			
		|||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
 | 
			
		||||
class VectorVfsFile : public VfsFile {
 | 
			
		||||
public:
 | 
			
		||||
    explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "",
 | 
			
		||||
                           VirtualDir parent = nullptr);
 | 
			
		||||
    ~VectorVfsFile() override;
 | 
			
		||||
 | 
			
		||||
    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(std::string_view name) override;
 | 
			
		||||
 | 
			
		||||
    virtual void Assign(std::vector<u8> new_data);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::vector<u8> data;
 | 
			
		||||
    VirtualDir parent;
 | 
			
		||||
    std::string name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
 | 
			
		||||
// Vector data is supplied upon construction.
 | 
			
		||||
class VectorVfsDirectory : public VfsDirectory {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -343,6 +343,15 @@ std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
 | 
			
		|||
    return sdmc_factory->GetSDMCContents();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
 | 
			
		||||
    LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id);
 | 
			
		||||
 | 
			
		||||
    if (bis_factory == nullptr)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    return bis_factory->GetModificationLoadRoot(title_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
 | 
			
		||||
    if (overwrite) {
 | 
			
		||||
        bis_factory = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -354,9 +363,11 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
 | 
			
		|||
                                             FileSys::Mode::ReadWrite);
 | 
			
		||||
    auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
 | 
			
		||||
                                           FileSys::Mode::ReadWrite);
 | 
			
		||||
    auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
 | 
			
		||||
                                             FileSys::Mode::ReadWrite);
 | 
			
		||||
 | 
			
		||||
    if (bis_factory == nullptr)
 | 
			
		||||
        bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
 | 
			
		||||
        bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory);
 | 
			
		||||
    if (save_data_factory == nullptr)
 | 
			
		||||
        save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
 | 
			
		||||
    if (sdmc_factory == nullptr)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,6 +52,8 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
 | 
			
		|||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
 | 
			
		||||
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
 | 
			
		||||
 | 
			
		||||
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
 | 
			
		||||
 | 
			
		||||
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
 | 
			
		||||
// above is called.
 | 
			
		||||
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
 | 
			
		|||
    int row = item_model->itemFromIndex(item)->row();
 | 
			
		||||
    QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
 | 
			
		||||
    u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
 | 
			
		||||
    std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString();
 | 
			
		||||
 | 
			
		||||
    QMenu context_menu;
 | 
			
		||||
    QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
 | 
			
		||||
    QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
 | 
			
		||||
    context_menu.addSeparator();
 | 
			
		||||
    QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
 | 
			
		||||
    QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
 | 
			
		||||
    QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
 | 
			
		||||
 | 
			
		||||
    open_save_location->setEnabled(program_id != 0);
 | 
			
		||||
| 
						 | 
				
			
			@ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
 | 
			
		|||
 | 
			
		||||
    connect(open_save_location, &QAction::triggered,
 | 
			
		||||
            [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
 | 
			
		||||
    connect(open_lfs_location, &QAction::triggered,
 | 
			
		||||
            [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); });
 | 
			
		||||
    connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });
 | 
			
		||||
    connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
 | 
			
		||||
    connect(navigate_to_gamedb_entry, &QAction::triggered,
 | 
			
		||||
            [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,10 @@ namespace FileSys {
 | 
			
		|||
class VfsFilesystem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum class GameListOpenTarget { SaveData };
 | 
			
		||||
enum class GameListOpenTarget {
 | 
			
		||||
    SaveData,
 | 
			
		||||
    ModData,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class GameList : public QWidget {
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +92,8 @@ signals:
 | 
			
		|||
    void GameChosen(QString game_path);
 | 
			
		||||
    void ShouldCancelWorker();
 | 
			
		||||
    void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
 | 
			
		||||
    void DumpRomFSRequested(u64 program_id, const std::string& game_path);
 | 
			
		||||
    void CopyTIDRequested(u64 program_id);
 | 
			
		||||
    void NavigateToGamedbEntryRequested(u64 program_id,
 | 
			
		||||
                                        const CompatibilityList& compatibility_list);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,22 @@
 | 
			
		|||
#include <memory>
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
#include "core/file_sys/vfs_real.h"
 | 
			
		||||
 | 
			
		||||
// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
 | 
			
		||||
// defines.
 | 
			
		||||
static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(
 | 
			
		||||
    const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) {
 | 
			
		||||
    return vfs->CreateDirectory(path, mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir,
 | 
			
		||||
                                                          const std::string& path) {
 | 
			
		||||
    return dir->CreateFile(path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#include <fmt/ostream.h>
 | 
			
		||||
#include <glad/glad.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,16 +46,18 @@
 | 
			
		|||
#include "common/telemetry.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/crypto/key_manager.h"
 | 
			
		||||
#include "core/file_sys/bis_factory.h"
 | 
			
		||||
#include "core/file_sys/card_image.h"
 | 
			
		||||
#include "core/file_sys/content_archive.h"
 | 
			
		||||
#include "core/file_sys/control_metadata.h"
 | 
			
		||||
#include "core/file_sys/patch_manager.h"
 | 
			
		||||
#include "core/file_sys/registered_cache.h"
 | 
			
		||||
#include "core/file_sys/romfs.h"
 | 
			
		||||
#include "core/file_sys/savedata_factory.h"
 | 
			
		||||
#include "core/file_sys/submission_package.h"
 | 
			
		||||
#include "core/file_sys/vfs_real.h"
 | 
			
		||||
#include "core/hle/kernel/process.h"
 | 
			
		||||
#include "core/hle/service/filesystem/filesystem.h"
 | 
			
		||||
#include "core/hle/service/filesystem/fsp_ldr.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/perf_stats.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() {
 | 
			
		|||
void GMainWindow::ConnectWidgetEvents() {
 | 
			
		||||
    connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
 | 
			
		||||
    connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
 | 
			
		||||
    connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
 | 
			
		||||
    connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
 | 
			
		||||
    connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
 | 
			
		||||
            &GMainWindow::OnGameListNavigateToGamedbEntry);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
 | 
			
		|||
                                                                program_id, user_id, 0);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case GameListOpenTarget::ModData: {
 | 
			
		||||
        open_target = "Mod Data";
 | 
			
		||||
        const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
 | 
			
		||||
        path = fmt::format("{}{:016X}", load_dir, program_id);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        UNIMPLEMENTED();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
 | 
			
		|||
    QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
 | 
			
		||||
    const auto path = fmt::format("{}{:016X}/romfs",
 | 
			
		||||
                                  FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
 | 
			
		||||
 | 
			
		||||
    auto failed = [this, &path]() {
 | 
			
		||||
        QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
 | 
			
		||||
                             tr("There was an error copying the RomFS files or the user "
 | 
			
		||||
                                "cancelled the operation."));
 | 
			
		||||
        vfs->DeleteDirectory(path);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
 | 
			
		||||
    if (loader == nullptr) {
 | 
			
		||||
        failed();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FileSys::VirtualFile file;
 | 
			
		||||
    if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) {
 | 
			
		||||
        failed();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto romfs =
 | 
			
		||||
        loader->IsRomFSUpdatable()
 | 
			
		||||
            ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
 | 
			
		||||
            : file;
 | 
			
		||||
 | 
			
		||||
    const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
 | 
			
		||||
    if (extracted == nullptr) {
 | 
			
		||||
        failed();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
 | 
			
		||||
 | 
			
		||||
    if (out == nullptr) {
 | 
			
		||||
        failed();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool ok;
 | 
			
		||||
    const auto res = QInputDialog::getItem(
 | 
			
		||||
        this, tr("Select RomFS Dump Mode"),
 | 
			
		||||
        tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "
 | 
			
		||||
           "files into the new directory while <br>skeleton will only create the directory "
 | 
			
		||||
           "structure."),
 | 
			
		||||
        {"Full", "Skeleton"}, 0, false, &ok);
 | 
			
		||||
    if (!ok)
 | 
			
		||||
        failed();
 | 
			
		||||
 | 
			
		||||
    const auto full = res == "Full";
 | 
			
		||||
 | 
			
		||||
    const static std::function<size_t(const FileSys::VirtualDir&, bool)> calculate_entry_size =
 | 
			
		||||
        [](const FileSys::VirtualDir& dir, bool full) {
 | 
			
		||||
            size_t out = 0;
 | 
			
		||||
            for (const auto& subdir : dir->GetSubdirectories())
 | 
			
		||||
                out += 1 + calculate_entry_size(subdir, full);
 | 
			
		||||
            return out + full ? dir->GetFiles().size() : 0;
 | 
			
		||||
        };
 | 
			
		||||
    const auto entry_size = calculate_entry_size(extracted, full);
 | 
			
		||||
 | 
			
		||||
    QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this);
 | 
			
		||||
    progress.setWindowModality(Qt::WindowModal);
 | 
			
		||||
    progress.setMinimumDuration(100);
 | 
			
		||||
 | 
			
		||||
    const static std::function<bool(QProgressDialog&, const FileSys::VirtualDir&,
 | 
			
		||||
                                    const FileSys::VirtualDir&, size_t, bool)>
 | 
			
		||||
        qt_raw_copy = [](QProgressDialog& dialog, const FileSys::VirtualDir& src,
 | 
			
		||||
                         const FileSys::VirtualDir& dest, size_t block_size, bool full) {
 | 
			
		||||
            if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
 | 
			
		||||
                return false;
 | 
			
		||||
            if (dialog.wasCanceled())
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (full) {
 | 
			
		||||
                for (const auto& file : src->GetFiles()) {
 | 
			
		||||
                    const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
 | 
			
		||||
                    if (!FileSys::VfsRawCopy(file, out, block_size))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    dialog.setValue(dialog.value() + 1);
 | 
			
		||||
                    if (dialog.wasCanceled())
 | 
			
		||||
                        return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (const auto& dir : src->GetSubdirectories()) {
 | 
			
		||||
                const auto out = dest->CreateSubdirectory(dir->GetName());
 | 
			
		||||
                if (!qt_raw_copy(dialog, dir, out, block_size, full))
 | 
			
		||||
                    return false;
 | 
			
		||||
                dialog.setValue(dialog.value() + 1);
 | 
			
		||||
                if (dialog.wasCanceled())
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    if (qt_raw_copy(progress, extracted, out, 0x400000, full)) {
 | 
			
		||||
        progress.close();
 | 
			
		||||
        QMessageBox::information(this, tr("RomFS Extraction Succeeded!"),
 | 
			
		||||
                                 tr("The operation completed successfully."));
 | 
			
		||||
        QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path)));
 | 
			
		||||
    } else {
 | 
			
		||||
        progress.close();
 | 
			
		||||
        failed();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GMainWindow::OnGameListCopyTID(u64 program_id) {
 | 
			
		||||
    QClipboard* clipboard = QGuiApplication::clipboard();
 | 
			
		||||
    clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
 | 
			
		||||
                                                  const CompatibilityList& compatibility_list) {
 | 
			
		||||
    const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
 | 
			
		||||
| 
						 | 
				
			
			@ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() {
 | 
			
		|||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
 | 
			
		||||
    const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
 | 
			
		||||
                                    const FileSys::VirtualFile& dest, size_t block_size) {
 | 
			
		||||
        if (src == nullptr || dest == nullptr)
 | 
			
		||||
            return false;
 | 
			
		||||
        if (!dest->Resize(src->GetSize()))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -138,6 +138,8 @@ private slots:
 | 
			
		|||
    /// Called whenever a user selects a game in the game list widget.
 | 
			
		||||
    void OnGameListLoadFile(QString game_path);
 | 
			
		||||
    void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
 | 
			
		||||
    void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
 | 
			
		||||
    void OnGameListCopyTID(u64 program_id);
 | 
			
		||||
    void OnGameListNavigateToGamedbEntry(u64 program_id,
 | 
			
		||||
                                         const CompatibilityList& compatibility_list);
 | 
			
		||||
    void OnMenuLoadFile();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue