Merge pull request #1005 from DarkLordZach/registered-fmt
file_sys: Add support for registration format
This commit is contained in:
		
						commit
						c594ec3417
					
				@ -38,6 +38,8 @@ add_library(common STATIC
 | 
			
		||||
    file_util.cpp
 | 
			
		||||
    file_util.h
 | 
			
		||||
    hash.h
 | 
			
		||||
    hex_util.cpp
 | 
			
		||||
    hex_util.h
 | 
			
		||||
    logging/backend.cpp
 | 
			
		||||
    logging/backend.h
 | 
			
		||||
    logging/filter.cpp
 | 
			
		||||
 | 
			
		||||
@ -750,6 +750,12 @@ std::string GetHactoolConfigurationPath() {
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string GetNANDRegistrationDir(bool system) {
 | 
			
		||||
    if (system)
 | 
			
		||||
        return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/";
 | 
			
		||||
    return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
 | 
			
		||||
    return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -129,6 +129,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
 | 
			
		||||
 | 
			
		||||
std::string GetHactoolConfigurationPath();
 | 
			
		||||
 | 
			
		||||
std::string GetNANDRegistrationDir(bool system = false);
 | 
			
		||||
 | 
			
		||||
// Returns the path to where the sys file are
 | 
			
		||||
std::string GetSysDirectory();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								src/common/hex_util.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/common/hex_util.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
 | 
			
		||||
u8 ToHexNibble(char c1) {
 | 
			
		||||
    if (c1 >= 65 && c1 <= 70)
 | 
			
		||||
        return c1 - 55;
 | 
			
		||||
    if (c1 >= 97 && c1 <= 102)
 | 
			
		||||
        return c1 - 87;
 | 
			
		||||
    if (c1 >= 48 && c1 <= 57)
 | 
			
		||||
        return c1 - 48;
 | 
			
		||||
    throw std::logic_error("Invalid hex digit");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
 | 
			
		||||
    if (len != 32)
 | 
			
		||||
        throw std::logic_error("Not of correct size.");
 | 
			
		||||
    return HexStringToArray<16>(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
 | 
			
		||||
    if (len != 64)
 | 
			
		||||
        throw std::logic_error("Not of correct size.");
 | 
			
		||||
    return HexStringToArray<32>(str);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/common/hex_util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/common/hex_util.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fmt/format.h>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
u8 ToHexNibble(char c1);
 | 
			
		||||
 | 
			
		||||
template <size_t Size, bool le = false>
 | 
			
		||||
std::array<u8, Size> HexStringToArray(std::string_view str) {
 | 
			
		||||
    std::array<u8, Size> out{};
 | 
			
		||||
    if constexpr (le) {
 | 
			
		||||
        for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
 | 
			
		||||
            out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
 | 
			
		||||
    } else {
 | 
			
		||||
        for (size_t i = 0; i < 2 * Size; i += 2)
 | 
			
		||||
            out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <size_t Size>
 | 
			
		||||
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
 | 
			
		||||
    std::string out;
 | 
			
		||||
    for (u8 c : array)
 | 
			
		||||
        out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
 | 
			
		||||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
 | 
			
		||||
@ -20,6 +20,8 @@ add_library(core STATIC
 | 
			
		||||
    crypto/key_manager.h
 | 
			
		||||
    crypto/ctr_encryption_layer.cpp
 | 
			
		||||
    crypto/ctr_encryption_layer.h
 | 
			
		||||
    file_sys/bis_factory.cpp
 | 
			
		||||
    file_sys/bis_factory.h
 | 
			
		||||
    file_sys/card_image.cpp
 | 
			
		||||
    file_sys/card_image.h
 | 
			
		||||
    file_sys/content_archive.cpp
 | 
			
		||||
@ -29,10 +31,14 @@ add_library(core STATIC
 | 
			
		||||
    file_sys/directory.h
 | 
			
		||||
    file_sys/errors.h
 | 
			
		||||
    file_sys/mode.h
 | 
			
		||||
    file_sys/nca_metadata.cpp
 | 
			
		||||
    file_sys/nca_metadata.h
 | 
			
		||||
    file_sys/partition_filesystem.cpp
 | 
			
		||||
    file_sys/partition_filesystem.h
 | 
			
		||||
    file_sys/program_metadata.cpp
 | 
			
		||||
    file_sys/program_metadata.h
 | 
			
		||||
    file_sys/registered_cache.cpp
 | 
			
		||||
    file_sys/registered_cache.h
 | 
			
		||||
    file_sys/romfs.cpp
 | 
			
		||||
    file_sys/romfs.h
 | 
			
		||||
    file_sys/romfs_factory.cpp
 | 
			
		||||
@ -43,6 +49,8 @@ add_library(core STATIC
 | 
			
		||||
    file_sys/sdmc_factory.h
 | 
			
		||||
    file_sys/vfs.cpp
 | 
			
		||||
    file_sys/vfs.h
 | 
			
		||||
    file_sys/vfs_concat.cpp
 | 
			
		||||
    file_sys/vfs_concat.h
 | 
			
		||||
    file_sys/vfs_offset.cpp
 | 
			
		||||
    file_sys/vfs_offset.h
 | 
			
		||||
    file_sys/vfs_real.cpp
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/string_util.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/gdbstub/gdbstub.h"
 | 
			
		||||
@ -17,6 +18,7 @@
 | 
			
		||||
#include "core/hle/service/sm/sm.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
#include "file_sys/vfs_concat.h"
 | 
			
		||||
#include "file_sys/vfs_real.h"
 | 
			
		||||
#include "video_core/renderer_base.h"
 | 
			
		||||
#include "video_core/video_core.h"
 | 
			
		||||
@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() {
 | 
			
		||||
    return RunLoop(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
 | 
			
		||||
                                                const std::string& path) {
 | 
			
		||||
    // To account for split 00+01+etc files.
 | 
			
		||||
    std::string dir_name;
 | 
			
		||||
    std::string filename;
 | 
			
		||||
    Common::SplitPath(path, &dir_name, &filename, nullptr);
 | 
			
		||||
    if (filename == "00") {
 | 
			
		||||
        const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
 | 
			
		||||
        std::vector<FileSys::VirtualFile> concat;
 | 
			
		||||
        for (u8 i = 0; i < 0x10; ++i) {
 | 
			
		||||
            auto next = dir->GetFile(fmt::format("{:02X}", i));
 | 
			
		||||
            if (next != nullptr)
 | 
			
		||||
                concat.push_back(std::move(next));
 | 
			
		||||
            else {
 | 
			
		||||
                next = dir->GetFile(fmt::format("{:02x}", i));
 | 
			
		||||
                if (next != nullptr)
 | 
			
		||||
                    concat.push_back(std::move(next));
 | 
			
		||||
                else
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (concat.empty())
 | 
			
		||||
            return nullptr;
 | 
			
		||||
 | 
			
		||||
        return FileSys::ConcatenateFiles(concat, dir->GetName());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return vfs->OpenFile(path, FileSys::Mode::Read);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
 | 
			
		||||
    app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read));
 | 
			
		||||
    app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
 | 
			
		||||
 | 
			
		||||
    if (!app_loader) {
 | 
			
		||||
        LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
 | 
			
		||||
 | 
			
		||||
@ -10,44 +10,13 @@
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include "common/common_paths.h"
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/crypto/key_manager.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
 | 
			
		||||
namespace Core::Crypto {
 | 
			
		||||
 | 
			
		||||
static u8 ToHexNibble(char c1) {
 | 
			
		||||
    if (c1 >= 65 && c1 <= 70)
 | 
			
		||||
        return c1 - 55;
 | 
			
		||||
    if (c1 >= 97 && c1 <= 102)
 | 
			
		||||
        return c1 - 87;
 | 
			
		||||
    if (c1 >= 48 && c1 <= 57)
 | 
			
		||||
        return c1 - 48;
 | 
			
		||||
    throw std::logic_error("Invalid hex digit");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <size_t Size>
 | 
			
		||||
static std::array<u8, Size> HexStringToArray(std::string_view str) {
 | 
			
		||||
    std::array<u8, Size> out{};
 | 
			
		||||
    for (size_t i = 0; i < 2 * Size; i += 2) {
 | 
			
		||||
        auto d1 = str[i];
 | 
			
		||||
        auto d2 = str[i + 1];
 | 
			
		||||
        out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
 | 
			
		||||
    if (len != 32)
 | 
			
		||||
        throw std::logic_error("Not of correct size.");
 | 
			
		||||
    return HexStringToArray<16>(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
 | 
			
		||||
    if (len != 64)
 | 
			
		||||
        throw std::logic_error("Not of correct size.");
 | 
			
		||||
    return HexStringToArray<32>(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
KeyManager::KeyManager() {
 | 
			
		||||
    // Initialize keys
 | 
			
		||||
    const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
 | 
			
		||||
 | 
			
		||||
@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> {
 | 
			
		||||
 | 
			
		||||
namespace Core::Crypto {
 | 
			
		||||
 | 
			
		||||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
 | 
			
		||||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
 | 
			
		||||
 | 
			
		||||
class KeyManager {
 | 
			
		||||
public:
 | 
			
		||||
    KeyManager();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								src/core/file_sys/bis_factory.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/core/file_sys/bis_factory.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "core/file_sys/bis_factory.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
 | 
			
		||||
    const auto res = dir->GetDirectoryRelative(path);
 | 
			
		||||
    if (res == nullptr)
 | 
			
		||||
        return dir->CreateDirectoryRelative(path);
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BISFactory::BISFactory(VirtualDir nand_root_)
 | 
			
		||||
    : nand_root(std::move(nand_root_)),
 | 
			
		||||
      sysnand_cache(std::make_shared<RegisteredCache>(
 | 
			
		||||
          GetOrCreateDirectory(nand_root, "/system/Contents/registered"))),
 | 
			
		||||
      usrnand_cache(std::make_shared<RegisteredCache>(
 | 
			
		||||
          GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
 | 
			
		||||
    return sysnand_cache;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
 | 
			
		||||
    return usrnand_cache;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/file_sys/bis_factory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/core/file_sys/bis_factory.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// 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/loader/loader.h"
 | 
			
		||||
#include "registered_cache.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
/// File system interface to the Built-In Storage
 | 
			
		||||
/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
 | 
			
		||||
/// registered caches.
 | 
			
		||||
class BISFactory {
 | 
			
		||||
public:
 | 
			
		||||
    explicit BISFactory(VirtualDir nand_root);
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
 | 
			
		||||
    std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    VirtualDir nand_root;
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<RegisteredCache> sysnand_cache;
 | 
			
		||||
    std::shared_ptr<RegisteredCache> usrnand_cache;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -96,6 +96,10 @@ VirtualDir XCI::GetLogoPartition() const {
 | 
			
		||||
    return GetPartition(XCIPartition::Logo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
 | 
			
		||||
    return ncas;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
 | 
			
		||||
    const auto iter =
 | 
			
		||||
        std::find_if(ncas.begin(), ncas.end(),
 | 
			
		||||
 | 
			
		||||
@ -68,6 +68,7 @@ public:
 | 
			
		||||
    VirtualDir GetUpdatePartition() const;
 | 
			
		||||
    VirtualDir GetLogoPartition() const;
 | 
			
		||||
 | 
			
		||||
    const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
 | 
			
		||||
    std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
 | 
			
		||||
    VirtualFile GetNCAFileByType(NCAContentType type) const;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const {
 | 
			
		||||
    return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) {
 | 
			
		||||
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
 | 
			
		||||
    file->ReadObject(raw.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -81,7 +81,6 @@ public:
 | 
			
		||||
    std::string GetVersionString() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    VirtualFile file;
 | 
			
		||||
    std::unique_ptr<RawNACP> raw;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										131
									
								
								src/core/file_sys/nca_metadata.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/core/file_sys/nca_metadata.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 <cstring>
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
#include "content_archive.h"
 | 
			
		||||
#include "core/file_sys/nca_metadata.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
bool operator>=(TitleType lhs, TitleType rhs) {
 | 
			
		||||
    return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool operator<=(TitleType lhs, TitleType rhs) {
 | 
			
		||||
    return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CNMT::CNMT(VirtualFile file) {
 | 
			
		||||
    if (file->ReadObject(&header) != sizeof(CNMTHeader))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // If type is {Application, Update, AOC} has opt-header.
 | 
			
		||||
    if (header.type >= TitleType::Application && header.type <= TitleType::AOC) {
 | 
			
		||||
        if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) {
 | 
			
		||||
            LOG_WARNING(Loader, "Failed to read optional header.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (u16 i = 0; i < header.number_content_entries; ++i) {
 | 
			
		||||
        auto& next = content_records.emplace_back(ContentRecord{});
 | 
			
		||||
        if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) +
 | 
			
		||||
                                        header.table_offset) != sizeof(ContentRecord)) {
 | 
			
		||||
            content_records.erase(content_records.end() - 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (u16 i = 0; i < header.number_meta_entries; ++i) {
 | 
			
		||||
        auto& next = meta_records.emplace_back(MetaRecord{});
 | 
			
		||||
        if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) +
 | 
			
		||||
                                        header.table_offset) != sizeof(MetaRecord)) {
 | 
			
		||||
            meta_records.erase(meta_records.end() - 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
 | 
			
		||||
           std::vector<MetaRecord> meta_records)
 | 
			
		||||
    : header(std::move(header)), opt_header(std::move(opt_header)),
 | 
			
		||||
      content_records(std::move(content_records)), meta_records(std::move(meta_records)) {}
 | 
			
		||||
 | 
			
		||||
u64 CNMT::GetTitleID() const {
 | 
			
		||||
    return header.title_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 CNMT::GetTitleVersion() const {
 | 
			
		||||
    return header.title_version;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TitleType CNMT::GetType() const {
 | 
			
		||||
    return header.type;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::vector<ContentRecord>& CNMT::GetContentRecords() const {
 | 
			
		||||
    return content_records;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::vector<MetaRecord>& CNMT::GetMetaRecords() const {
 | 
			
		||||
    return meta_records;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CNMT::UnionRecords(const CNMT& other) {
 | 
			
		||||
    bool change = false;
 | 
			
		||||
    for (const auto& rec : other.content_records) {
 | 
			
		||||
        const auto iter = std::find_if(content_records.begin(), content_records.end(),
 | 
			
		||||
                                       [&rec](const ContentRecord& r) {
 | 
			
		||||
                                           return r.nca_id == rec.nca_id && r.type == rec.type;
 | 
			
		||||
                                       });
 | 
			
		||||
        if (iter == content_records.end()) {
 | 
			
		||||
            content_records.emplace_back(rec);
 | 
			
		||||
            ++header.number_content_entries;
 | 
			
		||||
            change = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    for (const auto& rec : other.meta_records) {
 | 
			
		||||
        const auto iter =
 | 
			
		||||
            std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) {
 | 
			
		||||
                return r.title_id == rec.title_id && r.title_version == rec.title_version &&
 | 
			
		||||
                       r.type == rec.type;
 | 
			
		||||
            });
 | 
			
		||||
        if (iter == meta_records.end()) {
 | 
			
		||||
            meta_records.emplace_back(rec);
 | 
			
		||||
            ++header.number_meta_entries;
 | 
			
		||||
            change = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return change;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<u8> CNMT::Serialize() const {
 | 
			
		||||
    const bool has_opt_header =
 | 
			
		||||
        header.type >= TitleType::Application && header.type <= TitleType::AOC;
 | 
			
		||||
    const auto dead_zone = header.table_offset + sizeof(CNMTHeader);
 | 
			
		||||
    std::vector<u8> out(
 | 
			
		||||
        std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) +
 | 
			
		||||
        content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord));
 | 
			
		||||
    memcpy(out.data(), &header, sizeof(CNMTHeader));
 | 
			
		||||
 | 
			
		||||
    // Optional Header
 | 
			
		||||
    if (has_opt_header) {
 | 
			
		||||
        memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto offset = header.table_offset;
 | 
			
		||||
 | 
			
		||||
    for (const auto& rec : content_records) {
 | 
			
		||||
        memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
 | 
			
		||||
        offset += sizeof(ContentRecord);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const auto& rec : meta_records) {
 | 
			
		||||
        memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord));
 | 
			
		||||
        offset += sizeof(MetaRecord);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										111
									
								
								src/core/file_sys/nca_metadata.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/core/file_sys/nca_metadata.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
class CNMT;
 | 
			
		||||
 | 
			
		||||
struct CNMTHeader;
 | 
			
		||||
struct OptionalHeader;
 | 
			
		||||
 | 
			
		||||
enum class TitleType : u8 {
 | 
			
		||||
    SystemProgram = 0x01,
 | 
			
		||||
    SystemDataArchive = 0x02,
 | 
			
		||||
    SystemUpdate = 0x03,
 | 
			
		||||
    FirmwarePackageA = 0x04,
 | 
			
		||||
    FirmwarePackageB = 0x05,
 | 
			
		||||
    Application = 0x80,
 | 
			
		||||
    Update = 0x81,
 | 
			
		||||
    AOC = 0x82,
 | 
			
		||||
    DeltaTitle = 0x83,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool operator>=(TitleType lhs, TitleType rhs);
 | 
			
		||||
bool operator<=(TitleType lhs, TitleType rhs);
 | 
			
		||||
 | 
			
		||||
enum class ContentRecordType : u8 {
 | 
			
		||||
    Meta = 0,
 | 
			
		||||
    Program = 1,
 | 
			
		||||
    Data = 2,
 | 
			
		||||
    Control = 3,
 | 
			
		||||
    Manual = 4,
 | 
			
		||||
    Legal = 5,
 | 
			
		||||
    Patch = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ContentRecord {
 | 
			
		||||
    std::array<u8, 0x20> hash;
 | 
			
		||||
    std::array<u8, 0x10> nca_id;
 | 
			
		||||
    std::array<u8, 0x6> size;
 | 
			
		||||
    ContentRecordType type;
 | 
			
		||||
    INSERT_PADDING_BYTES(1);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
 | 
			
		||||
 | 
			
		||||
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
 | 
			
		||||
 | 
			
		||||
struct MetaRecord {
 | 
			
		||||
    u64_le title_id;
 | 
			
		||||
    u32_le title_version;
 | 
			
		||||
    TitleType type;
 | 
			
		||||
    u8 install_byte;
 | 
			
		||||
    INSERT_PADDING_BYTES(2);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size.");
 | 
			
		||||
 | 
			
		||||
struct OptionalHeader {
 | 
			
		||||
    u64_le title_id;
 | 
			
		||||
    u64_le minimum_version;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size.");
 | 
			
		||||
 | 
			
		||||
struct CNMTHeader {
 | 
			
		||||
    u64_le title_id;
 | 
			
		||||
    u32_le title_version;
 | 
			
		||||
    TitleType type;
 | 
			
		||||
    INSERT_PADDING_BYTES(1);
 | 
			
		||||
    u16_le table_offset;
 | 
			
		||||
    u16_le number_content_entries;
 | 
			
		||||
    u16_le number_meta_entries;
 | 
			
		||||
    INSERT_PADDING_BYTES(12);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
 | 
			
		||||
 | 
			
		||||
// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or
 | 
			
		||||
// meta0.ncd. These describe which NCA's belong with which titles in the registered cache.
 | 
			
		||||
class CNMT {
 | 
			
		||||
public:
 | 
			
		||||
    explicit CNMT(VirtualFile file);
 | 
			
		||||
    CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
 | 
			
		||||
         std::vector<MetaRecord> meta_records);
 | 
			
		||||
 | 
			
		||||
    u64 GetTitleID() const;
 | 
			
		||||
    u32 GetTitleVersion() const;
 | 
			
		||||
    TitleType GetType() const;
 | 
			
		||||
 | 
			
		||||
    const std::vector<ContentRecord>& GetContentRecords() const;
 | 
			
		||||
    const std::vector<MetaRecord>& GetMetaRecords() const;
 | 
			
		||||
 | 
			
		||||
    bool UnionRecords(const CNMT& other);
 | 
			
		||||
    std::vector<u8> Serialize() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    CNMTHeader header;
 | 
			
		||||
    OptionalHeader opt_header;
 | 
			
		||||
    std::vector<ContentRecord> content_records;
 | 
			
		||||
    std::vector<MetaRecord> meta_records;
 | 
			
		||||
 | 
			
		||||
    // TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data
 | 
			
		||||
    // after the table. This is not documented, unfortunately.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										476
									
								
								src/core/file_sys/registered_cache.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										476
									
								
								src/core/file_sys/registered_cache.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,476 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <regex>
 | 
			
		||||
#include <mbedtls/sha256.h>
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/crypto/encryption_layer.h"
 | 
			
		||||
#include "core/file_sys/card_image.h"
 | 
			
		||||
#include "core/file_sys/nca_metadata.h"
 | 
			
		||||
#include "core/file_sys/registered_cache.h"
 | 
			
		||||
#include "core/file_sys/vfs_concat.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
std::string RegisteredCacheEntry::DebugInfo() const {
 | 
			
		||||
    return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
 | 
			
		||||
    return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool FollowsTwoDigitDirFormat(std::string_view name) {
 | 
			
		||||
    static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
 | 
			
		||||
                                                                     std::regex_constants::icase);
 | 
			
		||||
    return std::regex_match(name.begin(), name.end(), two_digit_regex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool FollowsNcaIdFormat(std::string_view name) {
 | 
			
		||||
    static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
 | 
			
		||||
                                                                   std::regex_constants::icase);
 | 
			
		||||
    return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
 | 
			
		||||
                                            bool within_two_digit) {
 | 
			
		||||
    if (!within_two_digit)
 | 
			
		||||
        return fmt::format("/{}.nca", HexArrayToString(nca_id, second_hex_upper));
 | 
			
		||||
 | 
			
		||||
    Core::Crypto::SHA256Hash hash{};
 | 
			
		||||
    mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
 | 
			
		||||
    return fmt::format("/000000{:02X}/{}.nca", hash[0], HexArrayToString(nca_id, second_hex_upper));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string GetCNMTName(TitleType type, u64 title_id) {
 | 
			
		||||
    constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{
 | 
			
		||||
        "SystemProgram",
 | 
			
		||||
        "SystemData",
 | 
			
		||||
        "SystemUpdate",
 | 
			
		||||
        "BootImagePackage",
 | 
			
		||||
        "BootImagePackageSafe",
 | 
			
		||||
        "Application",
 | 
			
		||||
        "Patch",
 | 
			
		||||
        "AddOnContent",
 | 
			
		||||
        "" ///< Currently unknown 'DeltaTitle'
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto index = static_cast<size_t>(type);
 | 
			
		||||
    // If the index is after the jump in TitleType, subtract it out.
 | 
			
		||||
    if (index >= static_cast<size_t>(TitleType::Application)) {
 | 
			
		||||
        index -= static_cast<size_t>(TitleType::Application) -
 | 
			
		||||
                 static_cast<size_t>(TitleType::FirmwarePackageB);
 | 
			
		||||
    }
 | 
			
		||||
    return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
    case NCAContentType::Program:
 | 
			
		||||
        // TODO(DarkLordZach): Differentiate between Program and Patch
 | 
			
		||||
        return ContentRecordType::Program;
 | 
			
		||||
    case NCAContentType::Meta:
 | 
			
		||||
        return ContentRecordType::Meta;
 | 
			
		||||
    case NCAContentType::Control:
 | 
			
		||||
        return ContentRecordType::Control;
 | 
			
		||||
    case NCAContentType::Data:
 | 
			
		||||
        return ContentRecordType::Data;
 | 
			
		||||
    case NCAContentType::Manual:
 | 
			
		||||
        // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
 | 
			
		||||
        return ContentRecordType::Manual;
 | 
			
		||||
    default:
 | 
			
		||||
        UNREACHABLE();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
 | 
			
		||||
                                                       std::string_view path) const {
 | 
			
		||||
    if (dir->GetFileRelative(path) != nullptr)
 | 
			
		||||
        return dir->GetFileRelative(path);
 | 
			
		||||
    if (dir->GetDirectoryRelative(path) != nullptr) {
 | 
			
		||||
        const auto nca_dir = dir->GetDirectoryRelative(path);
 | 
			
		||||
        VirtualFile file = nullptr;
 | 
			
		||||
 | 
			
		||||
        const auto files = nca_dir->GetFiles();
 | 
			
		||||
        if (files.size() == 1 && files[0]->GetName() == "00") {
 | 
			
		||||
            file = files[0];
 | 
			
		||||
        } else {
 | 
			
		||||
            std::vector<VirtualFile> concat;
 | 
			
		||||
            // Since the files are a two-digit hex number, max is FF.
 | 
			
		||||
            for (size_t i = 0; i < 0x100; ++i) {
 | 
			
		||||
                auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
 | 
			
		||||
                if (next != nullptr) {
 | 
			
		||||
                    concat.push_back(std::move(next));
 | 
			
		||||
                } else {
 | 
			
		||||
                    next = nca_dir->GetFile(fmt::format("{:02x}", i));
 | 
			
		||||
                    if (next != nullptr)
 | 
			
		||||
                        concat.push_back(std::move(next));
 | 
			
		||||
                    else
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (concat.empty())
 | 
			
		||||
                return nullptr;
 | 
			
		||||
 | 
			
		||||
            file = FileSys::ConcatenateFiles(concat);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return file;
 | 
			
		||||
    }
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
 | 
			
		||||
    VirtualFile file;
 | 
			
		||||
    // Try all four modes of file storage:
 | 
			
		||||
    // (bit 1 = uppercase/lower, bit 0 = within a two-digit dir)
 | 
			
		||||
    // 00: /000000**/{:032X}.nca
 | 
			
		||||
    // 01: /{:032X}.nca
 | 
			
		||||
    // 10: /000000**/{:032x}.nca
 | 
			
		||||
    // 11: /{:032x}.nca
 | 
			
		||||
    for (u8 i = 0; i < 4; ++i) {
 | 
			
		||||
        const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0);
 | 
			
		||||
        file = OpenFileOrDirectoryConcat(dir, path);
 | 
			
		||||
        if (file != nullptr)
 | 
			
		||||
            return file;
 | 
			
		||||
    }
 | 
			
		||||
    return file;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static boost::optional<NcaID> CheckMapForContentRecord(
 | 
			
		||||
    const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
 | 
			
		||||
    if (map.find(title_id) == map.end())
 | 
			
		||||
        return boost::none;
 | 
			
		||||
 | 
			
		||||
    const auto& cnmt = map.at(title_id);
 | 
			
		||||
 | 
			
		||||
    const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
 | 
			
		||||
                                   [type](const ContentRecord& rec) { return rec.type == type; });
 | 
			
		||||
    if (iter == cnmt.GetContentRecords().end())
 | 
			
		||||
        return boost::none;
 | 
			
		||||
 | 
			
		||||
    return boost::make_optional(iter->nca_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
 | 
			
		||||
                                                             ContentRecordType type) const {
 | 
			
		||||
    if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
 | 
			
		||||
        return meta_id.at(title_id);
 | 
			
		||||
 | 
			
		||||
    const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
 | 
			
		||||
    if (res1 != boost::none)
 | 
			
		||||
        return res1;
 | 
			
		||||
    return CheckMapForContentRecord(meta, title_id, type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
 | 
			
		||||
    std::vector<NcaID> ids;
 | 
			
		||||
    for (const auto& d2_dir : dir->GetSubdirectories()) {
 | 
			
		||||
        if (FollowsNcaIdFormat(d2_dir->GetName())) {
 | 
			
		||||
            ids.push_back(HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20)));
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!FollowsTwoDigitDirFormat(d2_dir->GetName()))
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        for (const auto& nca_dir : d2_dir->GetSubdirectories()) {
 | 
			
		||||
            if (!FollowsNcaIdFormat(nca_dir->GetName()))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            ids.push_back(HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const auto& nca_file : d2_dir->GetFiles()) {
 | 
			
		||||
            if (!FollowsNcaIdFormat(nca_file->GetName()))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            ids.push_back(HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const auto& d2_file : dir->GetFiles()) {
 | 
			
		||||
        if (FollowsNcaIdFormat(d2_file->GetName()))
 | 
			
		||||
            ids.push_back(HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20)));
 | 
			
		||||
    }
 | 
			
		||||
    return ids;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
 | 
			
		||||
    for (const auto& id : ids) {
 | 
			
		||||
        const auto file = GetFileAtID(id);
 | 
			
		||||
 | 
			
		||||
        if (file == nullptr)
 | 
			
		||||
            continue;
 | 
			
		||||
        const auto nca = std::make_shared<NCA>(parser(file, id));
 | 
			
		||||
        if (nca->GetStatus() != Loader::ResultStatus::Success ||
 | 
			
		||||
            nca->GetType() != NCAContentType::Meta) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auto section0 = nca->GetSubdirectories()[0];
 | 
			
		||||
 | 
			
		||||
        for (const auto& file : section0->GetFiles()) {
 | 
			
		||||
            if (file->GetExtension() != "cnmt")
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            meta.insert_or_assign(nca->GetTitleId(), CNMT(file));
 | 
			
		||||
            meta_id.insert_or_assign(nca->GetTitleId(), id);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegisteredCache::AccumulateYuzuMeta() {
 | 
			
		||||
    const auto dir = this->dir->GetSubdirectory("yuzu_meta");
 | 
			
		||||
    if (dir == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    for (const auto& file : dir->GetFiles()) {
 | 
			
		||||
        if (file->GetExtension() != "cnmt")
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        CNMT cnmt(file);
 | 
			
		||||
        yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegisteredCache::Refresh() {
 | 
			
		||||
    if (dir == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
    const auto ids = AccumulateFiles();
 | 
			
		||||
    ProcessFiles(ids);
 | 
			
		||||
    AccumulateYuzuMeta();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
 | 
			
		||||
    : dir(std::move(dir_)), parser(std::move(parsing_function)) {
 | 
			
		||||
    Refresh();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
 | 
			
		||||
    return GetEntryRaw(title_id, type) != nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
 | 
			
		||||
    return GetEntryRaw(entry) != nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
 | 
			
		||||
    const auto id = GetNcaIDFromMetadata(title_id, type);
 | 
			
		||||
    if (id == boost::none)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    return parser(GetFileAtID(id.get()), id.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
 | 
			
		||||
    return GetEntryRaw(entry.title_id, entry.type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
 | 
			
		||||
    const auto raw = GetEntryRaw(title_id, type);
 | 
			
		||||
    if (raw == nullptr)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    return std::make_shared<NCA>(raw);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
 | 
			
		||||
    return GetEntry(entry.title_id, entry.type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
void RegisteredCache::IterateAllMetadata(
 | 
			
		||||
    std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
 | 
			
		||||
    std::function<bool(const CNMT&, const ContentRecord&)> filter) const {
 | 
			
		||||
    for (const auto& kv : meta) {
 | 
			
		||||
        const auto& cnmt = kv.second;
 | 
			
		||||
        if (filter(cnmt, EMPTY_META_CONTENT_RECORD))
 | 
			
		||||
            out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD));
 | 
			
		||||
        for (const auto& rec : cnmt.GetContentRecords()) {
 | 
			
		||||
            if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
 | 
			
		||||
                out.push_back(proc(cnmt, rec));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    for (const auto& kv : yuzu_meta) {
 | 
			
		||||
        const auto& cnmt = kv.second;
 | 
			
		||||
        for (const auto& rec : cnmt.GetContentRecords()) {
 | 
			
		||||
            if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
 | 
			
		||||
                out.push_back(proc(cnmt, rec));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
 | 
			
		||||
    std::vector<RegisteredCacheEntry> out;
 | 
			
		||||
    IterateAllMetadata<RegisteredCacheEntry>(
 | 
			
		||||
        out,
 | 
			
		||||
        [](const CNMT& c, const ContentRecord& r) {
 | 
			
		||||
            return RegisteredCacheEntry{c.GetTitleID(), r.type};
 | 
			
		||||
        },
 | 
			
		||||
        [](const CNMT& c, const ContentRecord& r) { return true; });
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
 | 
			
		||||
    boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
 | 
			
		||||
    boost::optional<u64> title_id) const {
 | 
			
		||||
    std::vector<RegisteredCacheEntry> out;
 | 
			
		||||
    IterateAllMetadata<RegisteredCacheEntry>(
 | 
			
		||||
        out,
 | 
			
		||||
        [](const CNMT& c, const ContentRecord& r) {
 | 
			
		||||
            return RegisteredCacheEntry{c.GetTitleID(), r.type};
 | 
			
		||||
        },
 | 
			
		||||
        [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
 | 
			
		||||
            if (title_type != boost::none && title_type.get() != c.GetType())
 | 
			
		||||
                return false;
 | 
			
		||||
            if (record_type != boost::none && record_type.get() != r.type)
 | 
			
		||||
                return false;
 | 
			
		||||
            if (title_id != boost::none && title_id.get() != c.GetTitleID())
 | 
			
		||||
                return false;
 | 
			
		||||
            return true;
 | 
			
		||||
        });
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
 | 
			
		||||
    const auto filename = fmt::format("{}.nca", HexArrayToString(id, false));
 | 
			
		||||
    const auto iter =
 | 
			
		||||
        std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(),
 | 
			
		||||
                     [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; });
 | 
			
		||||
    return iter == xci->GetNCAs().end() ? nullptr : *iter;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
 | 
			
		||||
                                            const VfsCopyFunction& copy) {
 | 
			
		||||
    const auto& ncas = xci->GetNCAs();
 | 
			
		||||
    const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
 | 
			
		||||
        return nca->GetType() == NCAContentType::Meta;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (meta_iter == ncas.end()) {
 | 
			
		||||
        LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
 | 
			
		||||
                          "is therefore malformed. Double check your encryption keys.");
 | 
			
		||||
        return InstallResult::ErrorMetaFailed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Install Metadata File
 | 
			
		||||
    const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
 | 
			
		||||
    const auto meta_id = HexStringToArray<16>(meta_id_raw);
 | 
			
		||||
 | 
			
		||||
    const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id);
 | 
			
		||||
    if (res != InstallResult::Success)
 | 
			
		||||
        return res;
 | 
			
		||||
 | 
			
		||||
    // Install all the other NCAs
 | 
			
		||||
    const auto section0 = (*meta_iter)->GetSubdirectories()[0];
 | 
			
		||||
    const auto cnmt_file = section0->GetFiles()[0];
 | 
			
		||||
    const CNMT cnmt(cnmt_file);
 | 
			
		||||
    for (const auto& record : cnmt.GetContentRecords()) {
 | 
			
		||||
        const auto nca = GetNCAFromXCIForID(xci, record.nca_id);
 | 
			
		||||
        if (nca == nullptr)
 | 
			
		||||
            return InstallResult::ErrorCopyFailed;
 | 
			
		||||
        const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
 | 
			
		||||
        if (res2 != InstallResult::Success)
 | 
			
		||||
            return res2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Refresh();
 | 
			
		||||
    return InstallResult::Success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
 | 
			
		||||
                                            bool overwrite_if_exists, const VfsCopyFunction& copy) {
 | 
			
		||||
    CNMTHeader header{
 | 
			
		||||
        nca->GetTitleId(), ///< Title ID
 | 
			
		||||
        0,                 ///< Ignore/Default title version
 | 
			
		||||
        type,              ///< Type
 | 
			
		||||
        {},                ///< Padding
 | 
			
		||||
        0x10,              ///< Default table offset
 | 
			
		||||
        1,                 ///< 1 Content Entry
 | 
			
		||||
        0,                 ///< No Meta Entries
 | 
			
		||||
        {},                ///< Padding
 | 
			
		||||
    };
 | 
			
		||||
    OptionalHeader opt_header{0, 0};
 | 
			
		||||
    ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}};
 | 
			
		||||
    const auto& data = nca->GetBaseFile()->ReadBytes(0x100000);
 | 
			
		||||
    mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
 | 
			
		||||
    memcpy(&c_rec.nca_id, &c_rec.hash, 16);
 | 
			
		||||
    const CNMT new_cnmt(header, opt_header, {c_rec}, {});
 | 
			
		||||
    if (!RawInstallYuzuMeta(new_cnmt))
 | 
			
		||||
        return InstallResult::ErrorMetaFailed;
 | 
			
		||||
    return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
 | 
			
		||||
                                             bool overwrite_if_exists,
 | 
			
		||||
                                             boost::optional<NcaID> override_id) {
 | 
			
		||||
    const auto in = nca->GetBaseFile();
 | 
			
		||||
    Core::Crypto::SHA256Hash hash{};
 | 
			
		||||
 | 
			
		||||
    // Calculate NcaID
 | 
			
		||||
    // NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the
 | 
			
		||||
    // game is massive), we're going to cheat and only hash the first MB of the NCA.
 | 
			
		||||
    // Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
 | 
			
		||||
    NcaID id{};
 | 
			
		||||
    if (override_id == boost::none) {
 | 
			
		||||
        const auto& data = in->ReadBytes(0x100000);
 | 
			
		||||
        mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
 | 
			
		||||
        memcpy(id.data(), hash.data(), 16);
 | 
			
		||||
    } else {
 | 
			
		||||
        id = override_id.get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string path = GetRelativePathFromNcaID(id, false, true);
 | 
			
		||||
 | 
			
		||||
    if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
 | 
			
		||||
        LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
 | 
			
		||||
        return InstallResult::ErrorAlreadyExists;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (GetFileAtID(id) != nullptr) {
 | 
			
		||||
        LOG_WARNING(Loader, "Overwriting existing NCA...");
 | 
			
		||||
        VirtualDir c_dir;
 | 
			
		||||
        { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
 | 
			
		||||
        c_dir->DeleteFile(FileUtil::GetFilename(path));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto out = dir->CreateFileRelative(path);
 | 
			
		||||
    if (out == nullptr)
 | 
			
		||||
        return InstallResult::ErrorCopyFailed;
 | 
			
		||||
    return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
 | 
			
		||||
    // Reasoning behind this method can be found in the comment for InstallEntry, NCA overload.
 | 
			
		||||
    const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta");
 | 
			
		||||
    const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID());
 | 
			
		||||
    if (dir->GetFile(filename) == nullptr) {
 | 
			
		||||
        auto out = dir->CreateFile(filename);
 | 
			
		||||
        const auto buffer = cnmt.Serialize();
 | 
			
		||||
        out->Resize(buffer.size());
 | 
			
		||||
        out->WriteBytes(buffer);
 | 
			
		||||
    } else {
 | 
			
		||||
        auto out = dir->GetFile(filename);
 | 
			
		||||
        CNMT old_cnmt(out);
 | 
			
		||||
        // Returns true on change
 | 
			
		||||
        if (old_cnmt.UnionRecords(cnmt)) {
 | 
			
		||||
            out->Resize(0);
 | 
			
		||||
            const auto buffer = old_cnmt.Serialize();
 | 
			
		||||
            out->Resize(buffer.size());
 | 
			
		||||
            out->WriteBytes(buffer);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    Refresh();
 | 
			
		||||
    return std::find_if(yuzu_meta.begin(), yuzu_meta.end(),
 | 
			
		||||
                        [&cnmt](const std::pair<u64, CNMT>& kv) {
 | 
			
		||||
                            return kv.second.GetType() == cnmt.GetType() &&
 | 
			
		||||
                                   kv.second.GetTitleID() == cnmt.GetTitleID();
 | 
			
		||||
                        }) != yuzu_meta.end();
 | 
			
		||||
}
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										124
									
								
								src/core/file_sys/registered_cache.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/core/file_sys/registered_cache.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <boost/container/flat_map.hpp>
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "content_archive.h"
 | 
			
		||||
#include "core/file_sys/nca_metadata.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
class XCI;
 | 
			
		||||
class CNMT;
 | 
			
		||||
 | 
			
		||||
using NcaID = std::array<u8, 0x10>;
 | 
			
		||||
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
 | 
			
		||||
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
 | 
			
		||||
 | 
			
		||||
enum class InstallResult {
 | 
			
		||||
    Success,
 | 
			
		||||
    ErrorAlreadyExists,
 | 
			
		||||
    ErrorCopyFailed,
 | 
			
		||||
    ErrorMetaFailed,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct RegisteredCacheEntry {
 | 
			
		||||
    u64 title_id;
 | 
			
		||||
    ContentRecordType type;
 | 
			
		||||
 | 
			
		||||
    std::string DebugInfo() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// boost flat_map requires operator< for O(log(n)) lookups.
 | 
			
		||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * A class that catalogues NCAs in the registered directory structure.
 | 
			
		||||
 * Nintendo's registered format follows this structure:
 | 
			
		||||
 *
 | 
			
		||||
 * Root
 | 
			
		||||
 *   | 000000XX <- XX is the ____ two digits of the NcaID
 | 
			
		||||
 *       | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder)
 | 
			
		||||
 *         | 00
 | 
			
		||||
 *         | 01 <- Actual content split along 4GB boundaries. (optional)
 | 
			
		||||
 *
 | 
			
		||||
 * (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
 | 
			
		||||
 * 4GB splitting can be ignored.)
 | 
			
		||||
 */
 | 
			
		||||
class RegisteredCache {
 | 
			
		||||
public:
 | 
			
		||||
    // Parsing function defines the conversion from raw file to NCA. If there are other steps
 | 
			
		||||
    // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
 | 
			
		||||
    // parsing function.
 | 
			
		||||
    explicit RegisteredCache(VirtualDir dir,
 | 
			
		||||
                             RegisteredCacheParsingFunction parsing_function =
 | 
			
		||||
                                 [](const VirtualFile& file, const NcaID& id) { return file; });
 | 
			
		||||
 | 
			
		||||
    void Refresh();
 | 
			
		||||
 | 
			
		||||
    bool HasEntry(u64 title_id, ContentRecordType type) const;
 | 
			
		||||
    bool HasEntry(RegisteredCacheEntry entry) const;
 | 
			
		||||
 | 
			
		||||
    VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
 | 
			
		||||
    VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
 | 
			
		||||
    std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
 | 
			
		||||
 | 
			
		||||
    std::vector<RegisteredCacheEntry> ListEntries() const;
 | 
			
		||||
    // If a parameter is not boost::none, it will be filtered for from all entries.
 | 
			
		||||
    std::vector<RegisteredCacheEntry> ListEntriesFilter(
 | 
			
		||||
        boost::optional<TitleType> title_type = boost::none,
 | 
			
		||||
        boost::optional<ContentRecordType> record_type = boost::none,
 | 
			
		||||
        boost::optional<u64> title_id = boost::none) const;
 | 
			
		||||
 | 
			
		||||
    // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
 | 
			
		||||
    // is a meta NCA and all of them are accessible.
 | 
			
		||||
    InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
 | 
			
		||||
                               const VfsCopyFunction& copy = &VfsRawCopy);
 | 
			
		||||
 | 
			
		||||
    // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
 | 
			
		||||
    // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
 | 
			
		||||
    // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
 | 
			
		||||
    // TODO(DarkLordZach): Author real meta-type NCAs and install those.
 | 
			
		||||
    InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
 | 
			
		||||
                               bool overwrite_if_exists = false,
 | 
			
		||||
                               const VfsCopyFunction& copy = &VfsRawCopy);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    void IterateAllMetadata(std::vector<T>& out,
 | 
			
		||||
                            std::function<T(const CNMT&, const ContentRecord&)> proc,
 | 
			
		||||
                            std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
 | 
			
		||||
    std::vector<NcaID> AccumulateFiles() const;
 | 
			
		||||
    void ProcessFiles(const std::vector<NcaID>& ids);
 | 
			
		||||
    void AccumulateYuzuMeta();
 | 
			
		||||
    boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
 | 
			
		||||
    VirtualFile GetFileAtID(NcaID id) const;
 | 
			
		||||
    VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
 | 
			
		||||
    InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
 | 
			
		||||
                                bool overwrite_if_exists,
 | 
			
		||||
                                boost::optional<NcaID> override_id = boost::none);
 | 
			
		||||
    bool RawInstallYuzuMeta(const CNMT& cnmt);
 | 
			
		||||
 | 
			
		||||
    VirtualDir dir;
 | 
			
		||||
    RegisteredCacheParsingFunction parser;
 | 
			
		||||
    // maps tid -> NcaID of meta
 | 
			
		||||
    boost::container::flat_map<u64, NcaID> meta_id;
 | 
			
		||||
    // maps tid -> meta
 | 
			
		||||
    boost::container::flat_map<u64, CNMT> meta;
 | 
			
		||||
    // maps tid -> meta for CNMT in yuzu_meta
 | 
			
		||||
    boost::container::flat_map<u64, CNMT> yuzu_meta;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t
 | 
			
		||||
        auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
 | 
			
		||||
 | 
			
		||||
        parent->AddFile(std::make_shared<OffsetVfsFile>(
 | 
			
		||||
            file, entry.first.size, entry.first.offset + data_offset, entry.second, parent));
 | 
			
		||||
            file, entry.first.size, entry.first.offset + data_offset, entry.second));
 | 
			
		||||
 | 
			
		||||
        if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
 | 
			
		||||
            break;
 | 
			
		||||
@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s
 | 
			
		||||
    while (true) {
 | 
			
		||||
        auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
 | 
			
		||||
        auto current = std::make_shared<VectorVfsDirectory>(
 | 
			
		||||
            std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second);
 | 
			
		||||
            std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
 | 
			
		||||
 | 
			
		||||
        if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
 | 
			
		||||
            ProcessFile(file, file_offset, data_offset, entry.first.child_file, current);
 | 
			
		||||
@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) {
 | 
			
		||||
    const u64 file_offset = header.file_meta.offset;
 | 
			
		||||
    const u64 dir_offset = header.directory_meta.offset + 4;
 | 
			
		||||
 | 
			
		||||
    const auto root =
 | 
			
		||||
    auto root =
 | 
			
		||||
        std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
 | 
			
		||||
                                             file->GetContainingDirectory(), file->GetName());
 | 
			
		||||
                                             file->GetName(), file->GetContainingDirectory());
 | 
			
		||||
 | 
			
		||||
    ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										94
									
								
								src/core/file_sys/vfs_concat.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/core/file_sys/vfs_concat.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
// 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_concat.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
 | 
			
		||||
    if (files.empty())
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    if (files.size() == 1)
 | 
			
		||||
        return files[0];
 | 
			
		||||
 | 
			
		||||
    return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
 | 
			
		||||
    : name(std::move(name)) {
 | 
			
		||||
    size_t next_offset = 0;
 | 
			
		||||
    for (const auto& file : files_) {
 | 
			
		||||
        files[next_offset] = file;
 | 
			
		||||
        next_offset += file->GetSize();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ConcatenatedVfsFile::GetName() const {
 | 
			
		||||
    if (files.empty())
 | 
			
		||||
        return "";
 | 
			
		||||
    if (!name.empty())
 | 
			
		||||
        return name;
 | 
			
		||||
    return files.begin()->second->GetName();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t ConcatenatedVfsFile::GetSize() const {
 | 
			
		||||
    if (files.empty())
 | 
			
		||||
        return 0;
 | 
			
		||||
    return files.rbegin()->first + files.rbegin()->second->GetSize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ConcatenatedVfsFile::Resize(size_t new_size) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
 | 
			
		||||
    if (files.empty())
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    return files.begin()->second->GetContainingDirectory();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ConcatenatedVfsFile::IsWritable() const {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ConcatenatedVfsFile::IsReadable() const {
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const {
 | 
			
		||||
    auto entry = files.end();
 | 
			
		||||
    for (auto iter = files.begin(); iter != files.end(); ++iter) {
 | 
			
		||||
        if (iter->first > offset) {
 | 
			
		||||
            entry = --iter;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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())
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return entry->second->Read(data, length, offset - entry->first);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) {
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ConcatenatedVfsFile::Rename(std::string_view name) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										41
									
								
								src/core/file_sys/vfs_concat.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/core/file_sys/vfs_concat.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <boost/container/flat_map.hpp>
 | 
			
		||||
#include "core/file_sys/vfs.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);
 | 
			
		||||
 | 
			
		||||
    ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    // Maps starting offset to file -- more efficient.
 | 
			
		||||
    boost::container::flat_map<u64, VirtualFile> files;
 | 
			
		||||
    std::string name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
 | 
			
		||||
 | 
			
		||||
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
 | 
			
		||||
    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
 | 
			
		||||
    if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path))
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
 | 
			
		||||
    if (!FileUtil::Exists(path)) {
 | 
			
		||||
        FileUtil::CreateFullPath(path_fwd);
 | 
			
		||||
        if (!FileUtil::CreateEmptyFile(path))
 | 
			
		||||
            return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
    return OpenFile(path, perms);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
 | 
			
		||||
 | 
			
		||||
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
 | 
			
		||||
    const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
 | 
			
		||||
    if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path))
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
 | 
			
		||||
    if (!FileUtil::Exists(path)) {
 | 
			
		||||
        FileUtil::CreateFullPath(path_fwd);
 | 
			
		||||
        if (!FileUtil::CreateDir(path))
 | 
			
		||||
            return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
    // Cannot use make_shared as RealVfsDirectory constructor is private
 | 
			
		||||
    return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
 | 
			
		||||
}
 | 
			
		||||
@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
 | 
			
		||||
    const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
 | 
			
		||||
    if (!FileUtil::Exists(full_path))
 | 
			
		||||
    if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    return base.OpenFile(full_path, perms);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
 | 
			
		||||
    const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
 | 
			
		||||
    if (!FileUtil::Exists(full_path))
 | 
			
		||||
    if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    return base.OpenDirectory(full_path, perms);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string_view>
 | 
			
		||||
 | 
			
		||||
#include <boost/container/flat_map.hpp>
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "core/file_sys/mode.h"
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
 | 
			
		||||
                                       std::vector<VirtualDir> dirs_, VirtualDir parent_,
 | 
			
		||||
                                       std::string name_)
 | 
			
		||||
                                       std::vector<VirtualDir> dirs_, std::string name_,
 | 
			
		||||
                                       VirtualDir parent_)
 | 
			
		||||
    : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
 | 
			
		||||
      name(std::move(name_)) {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,8 @@ namespace FileSys {
 | 
			
		||||
class VectorVfsDirectory : public VfsDirectory {
 | 
			
		||||
public:
 | 
			
		||||
    explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
 | 
			
		||||
                                std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr,
 | 
			
		||||
                                std::string name = "");
 | 
			
		||||
                                std::vector<VirtualDir> dirs = {}, std::string name = "",
 | 
			
		||||
                                VirtualDir parent = nullptr);
 | 
			
		||||
 | 
			
		||||
    std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
 | 
			
		||||
    std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
 | 
			
		||||
 | 
			
		||||
@ -226,6 +226,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
 | 
			
		||||
static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
 | 
			
		||||
static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
 | 
			
		||||
static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
 | 
			
		||||
static std::unique_ptr<FileSys::BISFactory> bis_factory;
 | 
			
		||||
 | 
			
		||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
 | 
			
		||||
    ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
 | 
			
		||||
@ -248,6 +249,13 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
 | 
			
		||||
    ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
 | 
			
		||||
    bis_factory = std::move(factory);
 | 
			
		||||
    LOG_DEBUG(Service_FS, "Registred BIS");
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
 | 
			
		||||
    LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
 | 
			
		||||
 | 
			
		||||
@ -281,6 +289,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
 | 
			
		||||
    return sdmc_factory->Open();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
 | 
			
		||||
    return bis_factory->GetSystemNANDContents();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
 | 
			
		||||
    return bis_factory->GetUserNANDContents();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
 | 
			
		||||
    romfs_factory = nullptr;
 | 
			
		||||
    save_data_factory = nullptr;
 | 
			
		||||
@ -291,6 +307,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
 | 
			
		||||
    auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
 | 
			
		||||
                                           FileSys::Mode::ReadWrite);
 | 
			
		||||
 | 
			
		||||
    if (bis_factory == nullptr)
 | 
			
		||||
        bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
 | 
			
		||||
 | 
			
		||||
    auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
 | 
			
		||||
    save_data_factory = std::move(savedata);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "core/file_sys/bis_factory.h"
 | 
			
		||||
#include "core/file_sys/directory.h"
 | 
			
		||||
#include "core/file_sys/mode.h"
 | 
			
		||||
#include "core/file_sys/romfs_factory.h"
 | 
			
		||||
@ -24,16 +25,15 @@ namespace FileSystem {
 | 
			
		||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
 | 
			
		||||
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
 | 
			
		||||
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
 | 
			
		||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
 | 
			
		||||
 | 
			
		||||
// TODO(DarkLordZach): BIS Filesystem
 | 
			
		||||
// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
 | 
			
		||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);
 | 
			
		||||
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
 | 
			
		||||
                                            FileSys::SaveDataDescriptor save_struct);
 | 
			
		||||
ResultVal<FileSys::VirtualDir> OpenSDMC();
 | 
			
		||||
 | 
			
		||||
// TODO(DarkLordZach): BIS Filesystem
 | 
			
		||||
// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
 | 
			
		||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
 | 
			
		||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
 | 
			
		||||
 | 
			
		||||
/// Registers all Filesystem services with the specified service manager.
 | 
			
		||||
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
 | 
			
		||||
FileType GuessFromFilename(const std::string& name) {
 | 
			
		||||
    if (name == "main")
 | 
			
		||||
        return FileType::DeconstructedRomDirectory;
 | 
			
		||||
    if (name == "00")
 | 
			
		||||
        return FileType::NCA;
 | 
			
		||||
 | 
			
		||||
    const std::string extension =
 | 
			
		||||
        Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name)));
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <regex>
 | 
			
		||||
#include <QApplication>
 | 
			
		||||
#include <QDir>
 | 
			
		||||
#include <QFileInfo>
 | 
			
		||||
@ -402,12 +403,72 @@ void GameList::RefreshGameDirectory() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
 | 
			
		||||
    boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
 | 
			
		||||
static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
 | 
			
		||||
                                      std::vector<u8>& icon, std::string& name) {
 | 
			
		||||
    const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
 | 
			
		||||
    if (control_dir == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const auto nca_control_callback =
 | 
			
		||||
        [this, &nca_control_map](u64* num_entries_out, const std::string& directory,
 | 
			
		||||
                                 const std::string& virtual_name) -> bool {
 | 
			
		||||
    const auto nacp_file = control_dir->GetFile("control.nacp");
 | 
			
		||||
    if (nacp_file == nullptr)
 | 
			
		||||
        return;
 | 
			
		||||
    FileSys::NACP nacp(nacp_file);
 | 
			
		||||
    name = nacp.GetApplicationName();
 | 
			
		||||
 | 
			
		||||
    FileSys::VirtualFile icon_file = nullptr;
 | 
			
		||||
    for (const auto& language : FileSys::LANGUAGE_NAMES) {
 | 
			
		||||
        icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
 | 
			
		||||
        if (icon_file != nullptr) {
 | 
			
		||||
            icon = icon_file->ReadAllBytes();
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GameListWorker::AddInstalledTitlesToGameList() {
 | 
			
		||||
    const auto usernand = Service::FileSystem::GetUserNANDContents();
 | 
			
		||||
    const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
 | 
			
		||||
                                                             FileSys::ContentRecordType::Program);
 | 
			
		||||
 | 
			
		||||
    for (const auto& game : installed_games) {
 | 
			
		||||
        const auto& file = usernand->GetEntryRaw(game);
 | 
			
		||||
        std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
 | 
			
		||||
        if (!loader)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        std::vector<u8> icon;
 | 
			
		||||
        std::string name;
 | 
			
		||||
        u64 program_id;
 | 
			
		||||
        loader->ReadProgramId(program_id);
 | 
			
		||||
 | 
			
		||||
        const auto& control =
 | 
			
		||||
            usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
 | 
			
		||||
        if (control != nullptr)
 | 
			
		||||
            GetMetadataFromControlNCA(control, icon, name);
 | 
			
		||||
        emit EntryReady({
 | 
			
		||||
            new GameListItemPath(
 | 
			
		||||
                FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
 | 
			
		||||
                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
 | 
			
		||||
                program_id),
 | 
			
		||||
            new GameListItem(
 | 
			
		||||
                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
 | 
			
		||||
            new GameListItemSize(file->GetSize()),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application,
 | 
			
		||||
                                                          FileSys::ContentRecordType::Control);
 | 
			
		||||
 | 
			
		||||
    for (const auto& entry : control_data) {
 | 
			
		||||
        const auto nca = usernand->GetEntry(entry);
 | 
			
		||||
        if (nca != nullptr)
 | 
			
		||||
            nca_control_map.insert_or_assign(entry.title_id, nca);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
 | 
			
		||||
    const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
 | 
			
		||||
                                             const std::string& virtual_name) -> bool {
 | 
			
		||||
        std::string physical_name = directory + DIR_SEP + virtual_name;
 | 
			
		||||
 | 
			
		||||
        if (stop_processing)
 | 
			
		||||
@ -425,10 +486,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    const auto callback = [this, recursion,
 | 
			
		||||
                           &nca_control_map](u64* num_entries_out, const std::string& directory,
 | 
			
		||||
                                             const std::string& virtual_name) -> bool {
 | 
			
		||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
 | 
			
		||||
    const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
 | 
			
		||||
                                            const std::string& virtual_name) -> bool {
 | 
			
		||||
        std::string physical_name = directory + DIR_SEP + virtual_name;
 | 
			
		||||
 | 
			
		||||
        if (stop_processing)
 | 
			
		||||
@ -458,20 +520,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
 | 
			
		||||
                // Use from metadata pool.
 | 
			
		||||
                if (nca_control_map.find(program_id) != nca_control_map.end()) {
 | 
			
		||||
                    const auto nca = nca_control_map[program_id];
 | 
			
		||||
                    const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
 | 
			
		||||
 | 
			
		||||
                    const auto nacp_file = control_dir->GetFile("control.nacp");
 | 
			
		||||
                    FileSys::NACP nacp(nacp_file);
 | 
			
		||||
                    name = nacp.GetApplicationName();
 | 
			
		||||
 | 
			
		||||
                    FileSys::VirtualFile icon_file = nullptr;
 | 
			
		||||
                    for (const auto& language : FileSys::LANGUAGE_NAMES) {
 | 
			
		||||
                        icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
 | 
			
		||||
                        if (icon_file != nullptr) {
 | 
			
		||||
                            icon = icon_file->ReadAllBytes();
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    GetMetadataFromControlNCA(nca, icon, name);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -498,7 +547,10 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
 | 
			
		||||
void GameListWorker::run() {
 | 
			
		||||
    stop_processing = false;
 | 
			
		||||
    watch_list.append(dir_path);
 | 
			
		||||
    FillControlMap(dir_path.toStdString());
 | 
			
		||||
    AddInstalledTitlesToGameList();
 | 
			
		||||
    AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
 | 
			
		||||
    nca_control_map.clear();
 | 
			
		||||
    emit Finished(watch_list);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -163,10 +163,13 @@ signals:
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    FileSys::VirtualFilesystem vfs;
 | 
			
		||||
    std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
 | 
			
		||||
    QStringList watch_list;
 | 
			
		||||
    QString dir_path;
 | 
			
		||||
    bool deep_scan;
 | 
			
		||||
    std::atomic_bool stop_processing;
 | 
			
		||||
 | 
			
		||||
    void AddInstalledTitlesToGameList();
 | 
			
		||||
    void FillControlMap(const std::string& dir_path);
 | 
			
		||||
    void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@
 | 
			
		||||
#include "common/string_util.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/crypto/key_manager.h"
 | 
			
		||||
#include "core/file_sys/card_image.h"
 | 
			
		||||
#include "core/file_sys/vfs_real.h"
 | 
			
		||||
#include "core/gdbstub/gdbstub.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
@ -117,6 +118,9 @@ GMainWindow::GMainWindow()
 | 
			
		||||
                       .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
 | 
			
		||||
    show();
 | 
			
		||||
 | 
			
		||||
    // Necessary to load titles from nand in gamelist.
 | 
			
		||||
    Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory(
 | 
			
		||||
        FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
 | 
			
		||||
    game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
 | 
			
		||||
 | 
			
		||||
    // Show one-time "callout" messages to the user
 | 
			
		||||
@ -312,6 +316,8 @@ void GMainWindow::ConnectMenuEvents() {
 | 
			
		||||
    // File
 | 
			
		||||
    connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
 | 
			
		||||
    connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
 | 
			
		||||
    connect(ui.action_Install_File_NAND, &QAction::triggered, this,
 | 
			
		||||
            &GMainWindow::OnMenuInstallToNAND);
 | 
			
		||||
    connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
 | 
			
		||||
            &GMainWindow::OnMenuSelectGameListRoot);
 | 
			
		||||
    connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
 | 
			
		||||
@ -615,6 +621,143 @@ void GMainWindow::OnMenuLoadFolder() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GMainWindow::OnMenuInstallToNAND() {
 | 
			
		||||
    const QString file_filter =
 | 
			
		||||
        tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
 | 
			
		||||
           "Image (*.xci)");
 | 
			
		||||
    QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
 | 
			
		||||
                                                    UISettings::values.roms_path, file_filter);
 | 
			
		||||
 | 
			
		||||
    const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
 | 
			
		||||
        if (src == nullptr || dest == nullptr)
 | 
			
		||||
            return false;
 | 
			
		||||
        if (!dest->Resize(src->GetSize()))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        QProgressDialog progress(fmt::format("Installing file \"{}\"...", src->GetName()).c_str(),
 | 
			
		||||
                                 "Cancel", 0, src->GetSize() / 0x1000, this);
 | 
			
		||||
        progress.setWindowModality(Qt::WindowModal);
 | 
			
		||||
 | 
			
		||||
        std::array<u8, 0x1000> buffer{};
 | 
			
		||||
        for (size_t i = 0; i < src->GetSize(); i += 0x1000) {
 | 
			
		||||
            if (progress.wasCanceled()) {
 | 
			
		||||
                dest->Resize(0);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            progress.setValue(i / 0x1000);
 | 
			
		||||
            const auto read = src->Read(buffer.data(), buffer.size(), i);
 | 
			
		||||
            dest->Write(buffer.data(), read, i);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const auto success = [this]() {
 | 
			
		||||
        QMessageBox::information(this, tr("Successfully Installed"),
 | 
			
		||||
                                 tr("The file was successfully installed."));
 | 
			
		||||
        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const auto failed = [this]() {
 | 
			
		||||
        QMessageBox::warning(
 | 
			
		||||
            this, tr("Failed to Install"),
 | 
			
		||||
            tr("There was an error while attempting to install the provided file. It "
 | 
			
		||||
               "could have an incorrect format or be missing metadata. Please "
 | 
			
		||||
               "double-check your file and try again."));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const auto overwrite = [this]() {
 | 
			
		||||
        return QMessageBox::question(this, "Failed to Install",
 | 
			
		||||
                                     "The file you are attempting to install already exists "
 | 
			
		||||
                                     "in the cache. Would you like to overwrite it?") ==
 | 
			
		||||
               QMessageBox::Yes;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (!filename.isEmpty()) {
 | 
			
		||||
        if (filename.endsWith("xci", Qt::CaseInsensitive)) {
 | 
			
		||||
            const auto xci = std::make_shared<FileSys::XCI>(
 | 
			
		||||
                vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
 | 
			
		||||
            if (xci->GetStatus() != Loader::ResultStatus::Success) {
 | 
			
		||||
                failed();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            const auto res =
 | 
			
		||||
                Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy);
 | 
			
		||||
            if (res == FileSys::InstallResult::Success) {
 | 
			
		||||
                success();
 | 
			
		||||
            } else {
 | 
			
		||||
                if (res == FileSys::InstallResult::ErrorAlreadyExists) {
 | 
			
		||||
                    if (overwrite()) {
 | 
			
		||||
                        const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
 | 
			
		||||
                            xci, true, qt_raw_copy);
 | 
			
		||||
                        if (res2 == FileSys::InstallResult::Success) {
 | 
			
		||||
                            success();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            failed();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    failed();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const auto nca = std::make_shared<FileSys::NCA>(
 | 
			
		||||
                vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
 | 
			
		||||
            if (nca->GetStatus() != Loader::ResultStatus::Success) {
 | 
			
		||||
                failed();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static const QStringList tt_options{"System Application",
 | 
			
		||||
                                                "System Archive",
 | 
			
		||||
                                                "System Application Update",
 | 
			
		||||
                                                "Firmware Package (Type A)",
 | 
			
		||||
                                                "Firmware Package (Type B)",
 | 
			
		||||
                                                "Game",
 | 
			
		||||
                                                "Game Update",
 | 
			
		||||
                                                "Game DLC",
 | 
			
		||||
                                                "Delta Title"};
 | 
			
		||||
            bool ok;
 | 
			
		||||
            const auto item = QInputDialog::getItem(
 | 
			
		||||
                this, tr("Select NCA Install Type..."),
 | 
			
		||||
                tr("Please select the type of title you would like to install this NCA as:\n(In "
 | 
			
		||||
                   "most instances, the default 'Game' is fine.)"),
 | 
			
		||||
                tt_options, 5, false, &ok);
 | 
			
		||||
 | 
			
		||||
            auto index = tt_options.indexOf(item);
 | 
			
		||||
            if (!ok || index == -1) {
 | 
			
		||||
                QMessageBox::warning(this, tr("Failed to Install"),
 | 
			
		||||
                                     tr("The title type you selected for the NCA is invalid."));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (index >= 5)
 | 
			
		||||
                index += 0x7B;
 | 
			
		||||
 | 
			
		||||
            const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry(
 | 
			
		||||
                nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
 | 
			
		||||
            if (res == FileSys::InstallResult::Success) {
 | 
			
		||||
                success();
 | 
			
		||||
            } else {
 | 
			
		||||
                if (res == FileSys::InstallResult::ErrorAlreadyExists) {
 | 
			
		||||
                    if (overwrite()) {
 | 
			
		||||
                        const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
 | 
			
		||||
                            nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
 | 
			
		||||
                        if (res2 == FileSys::InstallResult::Success) {
 | 
			
		||||
                            success();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            failed();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    failed();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GMainWindow::OnMenuSelectGameListRoot() {
 | 
			
		||||
    QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
 | 
			
		||||
    if (!dir_path.isEmpty()) {
 | 
			
		||||
 | 
			
		||||
@ -125,6 +125,7 @@ private slots:
 | 
			
		||||
    void OnGameListOpenSaveFolder(u64 program_id);
 | 
			
		||||
    void OnMenuLoadFile();
 | 
			
		||||
    void OnMenuLoadFolder();
 | 
			
		||||
    void OnMenuInstallToNAND();
 | 
			
		||||
    /// Called whenever a user selects the "File->Select Game List Root" menu item
 | 
			
		||||
    void OnMenuSelectGameListRoot();
 | 
			
		||||
    void OnMenuRecentFile();
 | 
			
		||||
 | 
			
		||||
@ -57,6 +57,8 @@
 | 
			
		||||
      <string>Recent Files</string>
 | 
			
		||||
     </property>
 | 
			
		||||
    </widget>
 | 
			
		||||
     <addaction name="action_Install_File_NAND" />
 | 
			
		||||
     <addaction name="separator"/>
 | 
			
		||||
    <addaction name="action_Load_File"/>
 | 
			
		||||
    <addaction name="action_Load_Folder"/>
 | 
			
		||||
    <addaction name="separator"/>
 | 
			
		||||
@ -102,6 +104,11 @@
 | 
			
		||||
   <addaction name="menu_View"/>
 | 
			
		||||
   <addaction name="menu_Help"/>
 | 
			
		||||
  </widget>
 | 
			
		||||
   <action name="action_Install_File_NAND">
 | 
			
		||||
     <property name="text">
 | 
			
		||||
       <string>Install File to NAND...</string>
 | 
			
		||||
     </property>
 | 
			
		||||
   </action>
 | 
			
		||||
  <action name="action_Load_File">
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Load File...</string>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user