mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
Removed unused files and directories
Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
parent
1986c7bd5c
commit
3cd11ab4ab
145 changed files with 0 additions and 94197 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -4,10 +4,6 @@
|
|||
[submodule "3rd_Party/toml11"]
|
||||
path = 3rd_Party/toml11
|
||||
url = https://github.com/ToruNiina/toml11.git
|
||||
[submodule "3rd_Party/rem"]
|
||||
path = 3rd_Party/rem
|
||||
url = https://github.com/pound-emu/rem.git
|
||||
ignore = dirty
|
||||
[submodule "3rd_Party/SDL3"]
|
||||
path = 3rd_Party/SDL3
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
|
|
|
|||
5
3rd_Party/CMakeLists.txt
vendored
5
3rd_Party/CMakeLists.txt
vendored
|
|
@ -12,11 +12,6 @@ if (NOT TARGET fmt::fmt)
|
|||
add_subdirectory(fmt)
|
||||
endif()
|
||||
|
||||
# rem
|
||||
if (NOT TARGET rem)
|
||||
add_subdirectory(rem)
|
||||
endif()
|
||||
|
||||
# SDL3
|
||||
if (NOT TARGET SDL3::SDL3)
|
||||
set(SDL_DISKAUDIO OFF)
|
||||
|
|
|
|||
1
3rd_Party/rem
vendored
1
3rd_Party/rem
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a9bd11d777f2f45d8f64e658a5bc6eb161ac2e05
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Modified by Pound emulator for SW2 compatibility
|
||||
|
||||
#include <fmt/format.cc>
|
||||
#include "core/common/fs/path_util.h"
|
||||
#include "core/fs/bis_factory.h"
|
||||
#include "core/fs/registered_cache.h"
|
||||
#include "core/fs/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 NAND_USER_SIZE = 0x680000000; // 26624 MiB
|
||||
constexpr u64 NAND_SYSTEM_SIZE = 0xA0000000; // 2560 MiB
|
||||
constexpr u64 NAND_TOTAL_SIZE = 0x747C00000; // 29820 MiB
|
||||
|
||||
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir dump_root_)
|
||||
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
|
||||
dump_root(std::move(dump_root_)),
|
||||
sysnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))),
|
||||
sysnand_placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/placehld"))),
|
||||
usrnand_placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/placehld"))) {}
|
||||
|
||||
BISFactory::~BISFactory() = default;
|
||||
|
||||
VirtualDir BISFactory::GetSystemNANDContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/system/Contents");
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetUserNANDContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user/Contents");
|
||||
}
|
||||
|
||||
RegisteredCache* BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache.get();
|
||||
}
|
||||
|
||||
RegisteredCache* BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* BISFactory::GetSystemNANDPlaceholder() const {
|
||||
return sysnand_placeholder.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* BISFactory::GetUserNANDPlaceholder() const {
|
||||
return usrnand_placeholder.get();
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
|
||||
// LayeredFS doesn't work on updates and title id-less homebrew
|
||||
if (title_id == 0 || (title_id & 0xFFF) == 0x800)
|
||||
return nullptr;
|
||||
return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const {
|
||||
if (title_id == 0)
|
||||
return nullptr;
|
||||
return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
|
||||
switch (id) {
|
||||
case BisPartitionId::CalibrationFile:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/prodinfof");
|
||||
case BisPartitionId::SafeMode:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/safe");
|
||||
case BisPartitionId::System:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/system");
|
||||
case BisPartitionId::User:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user");
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id,
|
||||
VirtualFilesystem file_system) const {
|
||||
auto& keys = Core::Crypto::KeyManager::Instance();
|
||||
Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory(
|
||||
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), OpenMode::Read)};
|
||||
keys.PopulateFromPartitionData(pdm);
|
||||
|
||||
switch (id) {
|
||||
case BisPartitionId::CalibrationBinary:
|
||||
return pdm.GetDecryptedProdInfo();
|
||||
case BisPartitionId::BootConfigAndPackage2Part1:
|
||||
case BisPartitionId::BootConfigAndPackage2Part2:
|
||||
case BisPartitionId::BootConfigAndPackage2Part3:
|
||||
case BisPartitionId::BootConfigAndPackage2Part4:
|
||||
case BisPartitionId::BootConfigAndPackage2Part5:
|
||||
case BisPartitionId::BootConfigAndPackage2Part6: {
|
||||
const auto new_id = static_cast<u8>(id) -
|
||||
static_cast<u8>(BisPartitionId::BootConfigAndPackage2Part1) +
|
||||
static_cast<u8>(Core::Crypto::Package2Type::NormalMain);
|
||||
return pdm.GetPackage2Raw(static_cast<Core::Crypto::Package2Type>(new_id));
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetImageDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user/Album");
|
||||
}
|
||||
|
||||
u64 BISFactory::GetSystemNANDFreeSpace() const {
|
||||
const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system");
|
||||
if (sys_dir == nullptr) {
|
||||
return GetSystemNANDTotalSpace();
|
||||
}
|
||||
|
||||
return GetSystemNANDTotalSpace() - sys_dir->GetSize();
|
||||
}
|
||||
|
||||
u64 BISFactory::GetSystemNANDTotalSpace() const {
|
||||
return NAND_SYSTEM_SIZE;
|
||||
}
|
||||
|
||||
u64 BISFactory::GetUserNANDFreeSpace() const {
|
||||
// For some reason games such as BioShock 1 checks whether this is exactly 0x680000000 bytes.
|
||||
// Set the free space to be 1 MiB less than the total as a workaround to this issue.
|
||||
return GetUserNANDTotalSpace() - 0x100000;
|
||||
}
|
||||
|
||||
u64 BISFactory::GetUserNANDTotalSpace() const {
|
||||
return NAND_USER_SIZE;
|
||||
}
|
||||
|
||||
u64 BISFactory::GetFullNANDTotalSpace() const {
|
||||
return NAND_TOTAL_SIZE;
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const {
|
||||
return GetOrCreateDirectoryRelative(nand_root,
|
||||
fmt::format("/system/save/bcat/{:016X}", title_id));
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Modified by Pound emulator for SW2 compatibility
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "import/common/common_types.h"
|
||||
#include "core/fs/vfs/vfs_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class BisPartitionId : u32 {
|
||||
UserDataRoot = 20,
|
||||
CalibrationBinary = 27,
|
||||
CalibrationFile = 28,
|
||||
BootConfigAndPackage2Part1 = 21,
|
||||
BootConfigAndPackage2Part2 = 22,
|
||||
BootConfigAndPackage2Part3 = 23,
|
||||
BootConfigAndPackage2Part4 = 24,
|
||||
BootConfigAndPackage2Part5 = 25,
|
||||
BootConfigAndPackage2Part6 = 26,
|
||||
SafeMode = 29,
|
||||
System = 31,
|
||||
SystemProperEncryption = 32,
|
||||
SystemProperPartition = 33,
|
||||
User = 30,
|
||||
};
|
||||
|
||||
class RegisteredCache;
|
||||
class PlaceholderCache;
|
||||
|
||||
/// 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, VirtualDir load_root, VirtualDir dump_root);
|
||||
~BISFactory();
|
||||
|
||||
VirtualDir GetSystemNANDContentDirectory() const;
|
||||
VirtualDir GetUserNANDContentDirectory() const;
|
||||
|
||||
RegisteredCache* GetSystemNANDContents() const;
|
||||
RegisteredCache* GetUserNANDContents() const;
|
||||
|
||||
PlaceholderCache* GetSystemNANDPlaceholder() const;
|
||||
PlaceholderCache* GetUserNANDPlaceholder() const;
|
||||
|
||||
VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
VirtualDir GetModificationDumpRoot(u64 title_id) const;
|
||||
|
||||
VirtualDir OpenPartition(BisPartitionId id) const;
|
||||
VirtualFile OpenPartitionStorage(BisPartitionId id, VirtualFilesystem file_system) const;
|
||||
|
||||
VirtualDir GetImageDirectory() const;
|
||||
|
||||
u64 GetSystemNANDFreeSpace() const;
|
||||
u64 GetSystemNANDTotalSpace() const;
|
||||
u64 GetUserNANDFreeSpace() const;
|
||||
u64 GetUserNANDTotalSpace() const;
|
||||
u64 GetFullNANDTotalSpace() const;
|
||||
|
||||
VirtualDir GetBCATDirectory(u64 title_id) const;
|
||||
|
||||
private:
|
||||
VirtualDir nand_root;
|
||||
VirtualDir load_root;
|
||||
VirtualDir dump_root;
|
||||
|
||||
std::unique_ptr<RegisteredCache> sysnand_cache;
|
||||
std::unique_ptr<RegisteredCache> usrnand_cache;
|
||||
|
||||
std::unique_ptr<PlaceholderCache> sysnand_placeholder;
|
||||
std::unique_ptr<PlaceholderCache> usrnand_placeholder;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,358 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Modified by Pound emulator for SW2 compatibility
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include <fmt/ostream.h> //can't find that yet (ownedbywuigi)
|
||||
|
||||
#include "import/common/logging/log.h"
|
||||
#include "import/core/crypto/key_manager.h"
|
||||
#include "core/fs/card_image.h"
|
||||
#include "core/fs/content_archive.h"
|
||||
#include "core/fs/nca_metadata.h"
|
||||
#include "core/fs/partition_filesystem.h"
|
||||
#include "core/fs/submission_package.h"
|
||||
#include "core/fs/vfs/vfs_offset.h"
|
||||
#include "core/fs/vfs/vfs_vector.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000;
|
||||
constexpr std::array partition_names{
|
||||
"update",
|
||||
"normal",
|
||||
"secure",
|
||||
"logo",
|
||||
};
|
||||
|
||||
XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index)
|
||||
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
|
||||
partitions(partition_names.size()),
|
||||
partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
|
||||
const auto header_status = TryReadHeader();
|
||||
if (header_status != Loader::ResultStatus::Success) {
|
||||
status = header_status;
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionFilesystem main_hfs(std::make_shared<OffsetVfsFile>(
|
||||
file, file->GetSize() - header.hfs_offset, header.hfs_offset));
|
||||
|
||||
update_normal_partition_end = main_hfs.GetFileOffsets()["secure"];
|
||||
|
||||
if (main_hfs.GetStatus() != Loader::ResultStatus::Success) {
|
||||
status = main_hfs.GetStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
for (XCIPartition partition :
|
||||
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
|
||||
const auto partition_idx = static_cast<std::size_t>(partition);
|
||||
auto raw = main_hfs.GetFile(partition_names[partition_idx]);
|
||||
|
||||
partitions_raw[static_cast<std::size_t>(partition)] = std::move(raw);
|
||||
}
|
||||
|
||||
secure_partition = std::make_shared<NSP>(
|
||||
main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]),
|
||||
program_id, program_index);
|
||||
|
||||
ncas = secure_partition->GetNCAsCollapsed();
|
||||
program =
|
||||
secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
|
||||
program_nca_status = secure_partition->GetProgramStatus();
|
||||
if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) {
|
||||
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
}
|
||||
|
||||
auto result = AddNCAFromPartition(XCIPartition::Normal);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetFormatVersion() >= 0x2) {
|
||||
result = AddNCAFromPartition(XCIPartition::Logo);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
XCI::~XCI() = default;
|
||||
|
||||
Loader::ResultStatus XCI::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::GetProgramNCAStatus() const {
|
||||
return program_nca_status;
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetPartition(XCIPartition partition) {
|
||||
const auto id = static_cast<std::size_t>(partition);
|
||||
if (partitions[id] == nullptr && partitions_raw[id] != nullptr) {
|
||||
partitions[id] = std::make_shared<PartitionFilesystem>(partitions_raw[id]);
|
||||
}
|
||||
|
||||
return partitions[static_cast<std::size_t>(partition)];
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> XCI::GetPartitions() {
|
||||
std::vector<VirtualDir> out;
|
||||
for (const auto& id :
|
||||
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
|
||||
const auto part = GetPartition(id);
|
||||
if (part != nullptr) {
|
||||
out.push_back(part);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
|
||||
return secure_partition;
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetSecurePartition() {
|
||||
return GetPartition(XCIPartition::Secure);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetNormalPartition() {
|
||||
return GetPartition(XCIPartition::Normal);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetUpdatePartition() {
|
||||
return GetPartition(XCIPartition::Update);
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetLogoPartition() {
|
||||
return GetPartition(XCIPartition::Logo);
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetPartitionRaw(XCIPartition partition) const {
|
||||
return partitions_raw[static_cast<std::size_t>(partition)];
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetSecurePartitionRaw() const {
|
||||
return GetPartitionRaw(XCIPartition::Secure);
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetStoragePartition0() const {
|
||||
return std::make_shared<OffsetVfsFile>(file, update_normal_partition_end, 0, "partition0");
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetStoragePartition1() const {
|
||||
return std::make_shared<OffsetVfsFile>(file, file->GetSize() - update_normal_partition_end,
|
||||
update_normal_partition_end, "partition1");
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetNormalPartitionRaw() const {
|
||||
return GetPartitionRaw(XCIPartition::Normal);
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetUpdatePartitionRaw() const {
|
||||
return GetPartitionRaw(XCIPartition::Update);
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetLogoPartitionRaw() const {
|
||||
return GetPartitionRaw(XCIPartition::Logo);
|
||||
}
|
||||
|
||||
u64 XCI::GetProgramTitleID() const {
|
||||
return secure_partition->GetProgramTitleID();
|
||||
}
|
||||
|
||||
std::vector<u64> XCI::GetProgramTitleIDs() const {
|
||||
return secure_partition->GetProgramTitleIDs();
|
||||
}
|
||||
|
||||
u32 XCI::GetSystemUpdateVersion() {
|
||||
const auto update = GetPartition(XCIPartition::Update);
|
||||
if (update == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& update_file : update->GetFiles()) {
|
||||
NCA nca{update_file};
|
||||
|
||||
if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.GetType() == NCAContentType::Meta && nca.GetTitleId() == 0x0100000000000816) {
|
||||
const auto dir = nca.GetSubdirectories()[0];
|
||||
const auto cnmt = dir->GetFile("SystemUpdate_0100000000000816.cnmt");
|
||||
if (cnmt == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CNMT cnmt_data{cnmt};
|
||||
|
||||
const auto metas = cnmt_data.GetMetaRecords();
|
||||
if (metas.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return metas[0].title_version;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 XCI::GetSystemUpdateTitleID() const {
|
||||
return 0x0100000000000816;
|
||||
}
|
||||
|
||||
bool XCI::HasProgramNCA() const {
|
||||
return program != nullptr;
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetProgramNCAFile() const {
|
||||
if (!HasProgramNCA()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return program->GetBaseFile();
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
|
||||
return ncas;
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
|
||||
const auto program_id = secure_partition->GetProgramTitleID();
|
||||
const auto iter =
|
||||
std::find_if(ncas.begin(), ncas.end(), [type, program_id](const std::shared_ptr<NCA>& nca) {
|
||||
return nca->GetType() == type && nca->GetTitleId() == program_id;
|
||||
});
|
||||
return iter == ncas.end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
|
||||
auto nca = GetNCAByType(type);
|
||||
if (nca != nullptr) {
|
||||
return nca->GetBaseFile();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<VirtualFile> XCI::GetFiles() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> XCI::GetSubdirectories() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string XCI::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
VirtualDir XCI::ConcatenatedPseudoDirectory() {
|
||||
const auto out = std::make_shared<VectorVfsDirectory>();
|
||||
for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) {
|
||||
const auto& part = GetPartition(part_id);
|
||||
if (part == nullptr)
|
||||
continue;
|
||||
|
||||
for (const auto& part_file : part->GetFiles())
|
||||
out->AddFile(part_file);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 0x200> XCI::GetCertificate() const {
|
||||
std::array<u8, 0x200> out;
|
||||
file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET);
|
||||
return out;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
const auto partition_index = static_cast<std::size_t>(part);
|
||||
const auto partition = GetPartition(part);
|
||||
|
||||
if (partition == nullptr) {
|
||||
return Loader::ResultStatus::ErrorXCIMissingPartition;
|
||||
}
|
||||
|
||||
for (const VirtualFile& partition_file : partition->GetFiles()) {
|
||||
if (partition_file->GetExtension() != "nca") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nca = std::make_shared<NCA>(partition_file);
|
||||
if (nca->IsUpdate()) {
|
||||
continue;
|
||||
}
|
||||
if (nca->GetType() == NCAContentType::Program) {
|
||||
program_nca_status = nca->GetStatus();
|
||||
}
|
||||
if (nca->GetStatus() == Loader::ResultStatus::Success) {
|
||||
ncas.push_back(std::move(nca));
|
||||
} else {
|
||||
const u16 error_id = static_cast<u16>(nca->GetStatus());
|
||||
LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
|
||||
partition_names[partition_index], nca->GetName(), error_id,
|
||||
nca->GetStatus());
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::TryReadHeader() {
|
||||
constexpr size_t CardInitialDataRegionSize = 0x1000;
|
||||
|
||||
// Define the function we'll use to determine if we read a valid header.
|
||||
const auto ReadCardHeader = [&]() {
|
||||
// Ensure we can read the entire header. If we can't, we can't read the card image.
|
||||
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
|
||||
return Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
}
|
||||
|
||||
// Ensure the header magic matches. If it doesn't, this isn't a card image header.
|
||||
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
|
||||
return Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
}
|
||||
|
||||
// We read a card image header.
|
||||
return Loader::ResultStatus::Success;
|
||||
};
|
||||
|
||||
// Try to read the header directly.
|
||||
if (ReadCardHeader() == Loader::ResultStatus::Success) {
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
// Get the size of the file.
|
||||
const size_t card_image_size = file->GetSize();
|
||||
|
||||
// If we are large enough to have a key area, offset past the key area and retry.
|
||||
if (card_image_size >= CardInitialDataRegionSize) {
|
||||
file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize,
|
||||
CardInitialDataRegionSize);
|
||||
return ReadCardHeader();
|
||||
}
|
||||
|
||||
// We had no header and aren't large enough to have a key area, so this can't be parsed.
|
||||
return Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
}
|
||||
|
||||
u8 XCI::GetFormatVersion() {
|
||||
return GetLogoPartition() == nullptr ? 0x1 : 0x2;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Modified by Pound emulator for SW2 compatibility
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "import/common/common_types.h"
|
||||
#include "import/common/swap.h"
|
||||
#include "core/fs/vfs/vfs.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
class KeyManager;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
enum class NCAContentType : u8;
|
||||
class NSP;
|
||||
|
||||
enum class GamecardSize : u8 {
|
||||
S_1GB = 0xFA,
|
||||
S_2GB = 0xF8,
|
||||
S_4GB = 0xF0,
|
||||
S_8GB = 0xE0,
|
||||
S_16GB = 0xE1,
|
||||
S_32GB = 0xE2,
|
||||
};
|
||||
|
||||
struct GamecardInfo {
|
||||
u64_le firmware_version;
|
||||
u32_le access_control_flags;
|
||||
u32_le read_wait_time1;
|
||||
u32_le read_wait_time2;
|
||||
u32_le write_wait_time1;
|
||||
u32_le write_wait_time2;
|
||||
u32_le firmware_mode;
|
||||
u32_le cup_version;
|
||||
std::array<u8, 4> reserved1;
|
||||
u64_le update_partition_hash;
|
||||
u64_le cup_id;
|
||||
std::array<u8, 0x38> reserved2;
|
||||
};
|
||||
static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");
|
||||
|
||||
struct GamecardHeader {
|
||||
std::array<u8, 0x100> signature;
|
||||
u32_le magic;
|
||||
u32_le secure_area_start;
|
||||
u32_le backup_area_start;
|
||||
u8 kek_index;
|
||||
GamecardSize size;
|
||||
u8 header_version;
|
||||
u8 flags;
|
||||
u64_le package_id;
|
||||
u64_le valid_data_end;
|
||||
u128 info_iv;
|
||||
u64_le hfs_offset;
|
||||
u64_le hfs_size;
|
||||
std::array<u8, 0x20> hfs_header_hash;
|
||||
std::array<u8, 0x20> initial_data_hash;
|
||||
u32_le secure_mode_flag;
|
||||
u32_le title_key_flag;
|
||||
u32_le key_flag;
|
||||
u32_le normal_area_end;
|
||||
GamecardInfo info;
|
||||
};
|
||||
static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size.");
|
||||
|
||||
enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
|
||||
|
||||
class XCI : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit XCI(VirtualFile file, u64 program_id = 0, size_t program_index = 0);
|
||||
~XCI() override;
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
Loader::ResultStatus GetProgramNCAStatus() const;
|
||||
|
||||
u8 GetFormatVersion();
|
||||
|
||||
VirtualDir GetPartition(XCIPartition partition);
|
||||
std::vector<VirtualDir> GetPartitions();
|
||||
|
||||
std::shared_ptr<NSP> GetSecurePartitionNSP() const;
|
||||
VirtualDir GetSecurePartition();
|
||||
VirtualDir GetNormalPartition();
|
||||
VirtualDir GetUpdatePartition();
|
||||
VirtualDir GetLogoPartition();
|
||||
|
||||
VirtualFile GetPartitionRaw(XCIPartition partition) const;
|
||||
VirtualFile GetSecurePartitionRaw() const;
|
||||
VirtualFile GetStoragePartition0() const;
|
||||
VirtualFile GetStoragePartition1() const;
|
||||
VirtualFile GetNormalPartitionRaw() const;
|
||||
VirtualFile GetUpdatePartitionRaw() const;
|
||||
VirtualFile GetLogoPartitionRaw() const;
|
||||
|
||||
u64 GetProgramTitleID() const;
|
||||
std::vector<u64> GetProgramTitleIDs() const;
|
||||
u32 GetSystemUpdateVersion();
|
||||
u64 GetSystemUpdateTitleID() const;
|
||||
|
||||
bool HasProgramNCA() const;
|
||||
VirtualFile GetProgramNCAFile() const;
|
||||
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
|
||||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
|
||||
VirtualFile GetNCAFileByType(NCAContentType type) const;
|
||||
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
// Creates a directory that contains all the NCAs in the gamecard
|
||||
VirtualDir ConcatenatedPseudoDirectory();
|
||||
|
||||
std::array<u8, 0x200> GetCertificate() const;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
|
||||
Loader::ResultStatus TryReadHeader();
|
||||
|
||||
VirtualFile file;
|
||||
GamecardHeader header{};
|
||||
|
||||
Loader::ResultStatus status;
|
||||
Loader::ResultStatus program_nca_status;
|
||||
|
||||
std::vector<VirtualDir> partitions;
|
||||
std::vector<VirtualFile> partitions_raw;
|
||||
std::shared_ptr<NSP> secure_partition;
|
||||
std::shared_ptr<NCA> program;
|
||||
std::vector<std::shared_ptr<NCA>> ncas;
|
||||
|
||||
u64 update_normal_partition_end;
|
||||
|
||||
Core::Crypto::KeyManager& keys;
|
||||
};
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 AOC_TITLE_ID_MASK = 0x7FF;
|
||||
constexpr u64 AOC_TITLE_ID_OFFSET = 0x1000;
|
||||
constexpr u64 BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
|
||||
/**
|
||||
* Gets the base title ID from a given title ID.
|
||||
*
|
||||
* @param title_id The title ID.
|
||||
* @returns The base title ID.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetBaseTitleID(u64 title_id) {
|
||||
return title_id & BASE_TITLE_ID_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base title ID with a program index offset from a given title ID.
|
||||
*
|
||||
* @param title_id The title ID.
|
||||
* @param program_index The program index.
|
||||
* @returns The base title ID with a program index offset.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetBaseTitleIDWithProgramIndex(u64 title_id, u64 program_index) {
|
||||
return GetBaseTitleID(title_id) + program_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AOC (Add-On Content) base title ID from a given title ID.
|
||||
*
|
||||
* @param title_id The title ID.
|
||||
* @returns The AOC base title ID.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetAOCBaseTitleID(u64 title_id) {
|
||||
return GetBaseTitleID(title_id) + AOC_TITLE_ID_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AOC (Add-On Content) ID from a given AOC title ID.
|
||||
*
|
||||
* @param aoc_title_id The AOC title ID.
|
||||
* @returns The AOC ID.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetAOCID(u64 aoc_title_id) {
|
||||
return aoc_title_id & AOC_TITLE_ID_MASK;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/polyfill_ranges.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
|
||||
#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
|
||||
#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
|
||||
return std::max<u8>(key_generation, 1) - 1;
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_, const NCA* base_nca)
|
||||
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
|
||||
reader = std::make_shared<NcaReader>();
|
||||
if (Result rc =
|
||||
reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration());
|
||||
R_FAILED(rc)) {
|
||||
if (rc != ResultInvalidNcaSignature) {
|
||||
LOG_ERROR(Loader, "File reader errored out during header read: {:#x}",
|
||||
rc.GetInnerValue());
|
||||
}
|
||||
status = Loader::ResultStatus::ErrorBadNCAHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we have the proper key area keys to continue.
|
||||
const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration());
|
||||
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
|
||||
RightsId rights_id{};
|
||||
reader->GetRightsId(rights_id.data(), rights_id.size());
|
||||
if (rights_id != RightsId{}) {
|
||||
// External decryption key required; provide it here.
|
||||
u128 rights_id_u128;
|
||||
std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
|
||||
|
||||
auto titlekey =
|
||||
keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]);
|
||||
if (titlekey == Core::Crypto::Key128{}) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekek;
|
||||
return;
|
||||
}
|
||||
|
||||
auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB);
|
||||
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(),
|
||||
Core::Crypto::Op::Decrypt);
|
||||
|
||||
reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size());
|
||||
}
|
||||
|
||||
const s32 fs_count = reader->GetFsCount();
|
||||
NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader);
|
||||
std::vector<VirtualFile> filesystems(fs_count);
|
||||
for (s32 i = 0; i < fs_count; i++) {
|
||||
NcaFsHeaderReader header_reader;
|
||||
const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i);
|
||||
if (R_FAILED(rc)) {
|
||||
LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i,
|
||||
rc.GetInnerValue());
|
||||
status = Loader::ResultStatus::ErrorBadNCAHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) {
|
||||
files.push_back(filesystems[i]);
|
||||
romfs = files.back();
|
||||
}
|
||||
|
||||
if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) {
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]);
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.push_back(npfs);
|
||||
if (IsDirectoryExeFS(npfs)) {
|
||||
exefs = dirs.back();
|
||||
} else if (IsDirectoryLogoPartition(npfs)) {
|
||||
logo = dirs.back();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) {
|
||||
is_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_update && base_nca == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
|
||||
} else {
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
NCA::~NCA() = default;
|
||||
|
||||
Loader::ResultStatus NCA::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::vector<VirtualFile> NCA::GetFiles() const {
|
||||
if (status != Loader::ResultStatus::Success) {
|
||||
return {};
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> NCA::GetSubdirectories() const {
|
||||
if (status != Loader::ResultStatus::Success) {
|
||||
return {};
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
std::string NCA::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
VirtualDir NCA::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
NCAContentType NCA::GetType() const {
|
||||
return static_cast<NCAContentType>(reader->GetContentType());
|
||||
}
|
||||
|
||||
u64 NCA::GetTitleId() const {
|
||||
if (is_update) {
|
||||
return reader->GetProgramId() | 0x800;
|
||||
}
|
||||
return reader->GetProgramId();
|
||||
}
|
||||
|
||||
RightsId NCA::GetRightsId() const {
|
||||
RightsId result;
|
||||
reader->GetRightsId(result.data(), result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 NCA::GetSDKVersion() const {
|
||||
return reader->GetSdkAddonVersion();
|
||||
}
|
||||
|
||||
u8 NCA::GetKeyGeneration() const {
|
||||
return reader->GetKeyGeneration();
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetRomFS() const {
|
||||
return romfs;
|
||||
}
|
||||
|
||||
VirtualDir NCA::GetExeFS() const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetBaseFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
VirtualDir NCA::GetLogoPartition() const {
|
||||
return logo;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NcaReader;
|
||||
|
||||
/// Describes the type of content within an NCA archive.
|
||||
enum class NCAContentType : u8 {
|
||||
/// Executable-related data
|
||||
Program = 0,
|
||||
|
||||
/// Metadata.
|
||||
Meta = 1,
|
||||
|
||||
/// Access control data.
|
||||
Control = 2,
|
||||
|
||||
/// Information related to the game manual
|
||||
/// e.g. Legal information, etc.
|
||||
Manual = 3,
|
||||
|
||||
/// System data.
|
||||
Data = 4,
|
||||
|
||||
/// Data that can be accessed by applications.
|
||||
PublicData = 5,
|
||||
};
|
||||
|
||||
using RightsId = std::array<u8, 0x10>;
|
||||
|
||||
inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
|
||||
// According to switchbrew, an exefs must only contain these two files:
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
|
||||
inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
|
||||
// NintendoLogo is the static image in the top left corner while StartupMovie is the animation
|
||||
// in the bottom right corner.
|
||||
return pfs->GetFile("NintendoLogo.png") != nullptr &&
|
||||
pfs->GetFile("StartupMovie.gif") != nullptr;
|
||||
}
|
||||
|
||||
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) container.
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NCA(VirtualFile file, const NCA* base_nca = nullptr);
|
||||
~NCA() override;
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
std::string GetName() const override;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
RightsId GetRightsId() const;
|
||||
u32 GetSDKVersion() const;
|
||||
u8 GetKeyGeneration() const;
|
||||
bool IsUpdate() const;
|
||||
|
||||
VirtualFile GetRomFS() const;
|
||||
VirtualDir GetExeFS() const;
|
||||
|
||||
VirtualFile GetBaseFile() const;
|
||||
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
private:
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::vector<VirtualFile> files;
|
||||
|
||||
VirtualFile romfs = nullptr;
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualDir logo = nullptr;
|
||||
VirtualFile file;
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
bool encrypted = false;
|
||||
bool is_update = false;
|
||||
|
||||
Core::Crypto::KeyManager& keys;
|
||||
std::shared_ptr<NcaReader> reader;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
const std::array<const char*, 16> LANGUAGE_NAMES{{
|
||||
"AmericanEnglish",
|
||||
"BritishEnglish",
|
||||
"Japanese",
|
||||
"French",
|
||||
"German",
|
||||
"LatinAmericanSpanish",
|
||||
"Spanish",
|
||||
"Italian",
|
||||
"Dutch",
|
||||
"CanadianFrench",
|
||||
"Portuguese",
|
||||
"Russian",
|
||||
"Korean",
|
||||
"TraditionalChinese",
|
||||
"SimplifiedChinese",
|
||||
"BrazilianPortuguese",
|
||||
}};
|
||||
|
||||
std::string LanguageEntry::GetApplicationName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(),
|
||||
application_name.size());
|
||||
}
|
||||
|
||||
std::string LanguageEntry::GetDeveloperName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(),
|
||||
developer_name.size());
|
||||
}
|
||||
|
||||
constexpr std::array<Language, 18> language_to_codes = {{
|
||||
Language::Japanese,
|
||||
Language::AmericanEnglish,
|
||||
Language::French,
|
||||
Language::German,
|
||||
Language::Italian,
|
||||
Language::Spanish,
|
||||
Language::SimplifiedChinese,
|
||||
Language::Korean,
|
||||
Language::Dutch,
|
||||
Language::Portuguese,
|
||||
Language::Russian,
|
||||
Language::TraditionalChinese,
|
||||
Language::BritishEnglish,
|
||||
Language::CanadianFrench,
|
||||
Language::LatinAmericanSpanish,
|
||||
Language::SimplifiedChinese,
|
||||
Language::TraditionalChinese,
|
||||
Language::BrazilianPortuguese,
|
||||
}};
|
||||
|
||||
NACP::NACP() = default;
|
||||
|
||||
NACP::NACP(VirtualFile file) {
|
||||
file->ReadObject(&raw);
|
||||
}
|
||||
|
||||
NACP::~NACP() = default;
|
||||
|
||||
const LanguageEntry& NACP::GetLanguageEntry() const {
|
||||
Language language =
|
||||
language_to_codes[static_cast<s32>(Settings::values.language_index.GetValue())];
|
||||
|
||||
{
|
||||
const auto& language_entry = raw.language_entries.at(static_cast<u8>(language));
|
||||
if (!language_entry.GetApplicationName().empty())
|
||||
return language_entry;
|
||||
}
|
||||
|
||||
for (const auto& language_entry : raw.language_entries) {
|
||||
if (!language_entry.GetApplicationName().empty())
|
||||
return language_entry;
|
||||
}
|
||||
|
||||
return raw.language_entries.at(static_cast<u8>(Language::AmericanEnglish));
|
||||
}
|
||||
|
||||
std::string NACP::GetApplicationName() const {
|
||||
return GetLanguageEntry().GetApplicationName();
|
||||
}
|
||||
|
||||
std::string NACP::GetDeveloperName() const {
|
||||
return GetLanguageEntry().GetDeveloperName();
|
||||
}
|
||||
|
||||
u64 NACP::GetTitleId() const {
|
||||
return raw.save_data_owner_id;
|
||||
}
|
||||
|
||||
u64 NACP::GetDLCBaseTitleId() const {
|
||||
return raw.dlc_base_title_id;
|
||||
}
|
||||
|
||||
std::string NACP::GetVersionString() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(raw.version_string.data(),
|
||||
raw.version_string.size());
|
||||
}
|
||||
|
||||
u64 NACP::GetDefaultNormalSaveSize() const {
|
||||
return raw.user_account_save_data_size;
|
||||
}
|
||||
|
||||
u64 NACP::GetDefaultJournalSaveSize() const {
|
||||
return raw.user_account_save_data_journal_size;
|
||||
}
|
||||
|
||||
bool NACP::GetUserAccountSwitchLock() const {
|
||||
return raw.user_account_switch_lock != 0;
|
||||
}
|
||||
|
||||
u32 NACP::GetSupportedLanguages() const {
|
||||
return raw.supported_languages;
|
||||
}
|
||||
|
||||
u64 NACP::GetDeviceSaveDataSize() const {
|
||||
return raw.device_save_data_size;
|
||||
}
|
||||
|
||||
u32 NACP::GetParentalControlFlag() const {
|
||||
return raw.parental_control;
|
||||
}
|
||||
|
||||
const std::array<u8, 0x20>& NACP::GetRatingAge() const {
|
||||
return raw.rating_age;
|
||||
}
|
||||
|
||||
std::vector<u8> NACP::GetRawBytes() const {
|
||||
std::vector<u8> out(sizeof(RawNACP));
|
||||
std::memcpy(out.data(), &raw, sizeof(RawNACP));
|
||||
return out;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// A localized entry containing strings within the NACP.
|
||||
// One for each language of type Language.
|
||||
struct LanguageEntry {
|
||||
std::array<char, 0x200> application_name;
|
||||
std::array<char, 0x100> developer_name;
|
||||
|
||||
std::string GetApplicationName() const;
|
||||
std::string GetDeveloperName() const;
|
||||
};
|
||||
static_assert(sizeof(LanguageEntry) == 0x300, "LanguageEntry has incorrect size.");
|
||||
|
||||
// The raw file format of a NACP file.
|
||||
struct RawNACP {
|
||||
std::array<LanguageEntry, 16> language_entries;
|
||||
std::array<u8, 0x25> isbn;
|
||||
u8 startup_user_account;
|
||||
u8 user_account_switch_lock;
|
||||
u8 addon_content_registration_type;
|
||||
u32_le application_attribute;
|
||||
u32_le supported_languages;
|
||||
u32_le parental_control;
|
||||
bool screenshot_enabled;
|
||||
u8 video_capture_mode;
|
||||
bool data_loss_confirmation;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u64_le presence_group_id;
|
||||
std::array<u8, 0x20> rating_age;
|
||||
std::array<char, 0x10> version_string;
|
||||
u64_le dlc_base_title_id;
|
||||
u64_le save_data_owner_id;
|
||||
u64_le user_account_save_data_size;
|
||||
u64_le user_account_save_data_journal_size;
|
||||
u64_le device_save_data_size;
|
||||
u64_le device_save_data_journal_size;
|
||||
u64_le bcat_delivery_cache_storage_size;
|
||||
char application_error_code_category[8];
|
||||
std::array<u64_le, 0x8> local_communication;
|
||||
u8 logo_type;
|
||||
u8 logo_handling;
|
||||
bool runtime_add_on_content_install;
|
||||
INSERT_PADDING_BYTES(5);
|
||||
u64_le seed_for_pseudo_device_id;
|
||||
std::array<u8, 0x41> bcat_passphrase;
|
||||
INSERT_PADDING_BYTES(7);
|
||||
u64_le user_account_save_data_max_size;
|
||||
u64_le user_account_save_data_max_journal_size;
|
||||
u64_le device_save_data_max_size;
|
||||
u64_le device_save_data_max_journal_size;
|
||||
u64_le temporary_storage_size;
|
||||
u64_le cache_storage_size;
|
||||
u64_le cache_storage_journal_size;
|
||||
u64_le cache_storage_data_and_journal_max_size;
|
||||
u16_le cache_storage_max_index;
|
||||
INSERT_PADDING_BYTES(0xE76);
|
||||
};
|
||||
static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size.");
|
||||
|
||||
// A language on the NX. These are for names and icons.
|
||||
enum class Language : u8 {
|
||||
AmericanEnglish = 0,
|
||||
BritishEnglish = 1,
|
||||
Japanese = 2,
|
||||
French = 3,
|
||||
German = 4,
|
||||
LatinAmericanSpanish = 5,
|
||||
Spanish = 6,
|
||||
Italian = 7,
|
||||
Dutch = 8,
|
||||
CanadianFrench = 9,
|
||||
Portuguese = 10,
|
||||
Russian = 11,
|
||||
Korean = 12,
|
||||
TraditionalChinese = 13,
|
||||
SimplifiedChinese = 14,
|
||||
BrazilianPortuguese = 15,
|
||||
|
||||
Default = 255,
|
||||
};
|
||||
|
||||
extern const std::array<const char*, 16> LANGUAGE_NAMES;
|
||||
|
||||
// A class representing the format used by NX metadata files, typically named Control.nacp.
|
||||
// These store application name, dev name, title id, and other miscellaneous data.
|
||||
class NACP {
|
||||
public:
|
||||
explicit NACP();
|
||||
explicit NACP(VirtualFile file);
|
||||
~NACP();
|
||||
|
||||
const LanguageEntry& GetLanguageEntry() const;
|
||||
std::string GetApplicationName() const;
|
||||
std::string GetDeveloperName() const;
|
||||
u64 GetTitleId() const;
|
||||
u64 GetDLCBaseTitleId() const;
|
||||
std::string GetVersionString() const;
|
||||
u64 GetDefaultNormalSaveSize() const;
|
||||
u64 GetDefaultJournalSaveSize() const;
|
||||
u32 GetSupportedLanguages() const;
|
||||
std::vector<u8> GetRawBytes() const;
|
||||
bool GetUserAccountSwitchLock() const;
|
||||
u64 GetDeviceSaveDataSize() const;
|
||||
u32 GetParentalControlFlag() const;
|
||||
const std::array<u8, 0x20>& GetRatingAge() const;
|
||||
|
||||
private:
|
||||
RawNACP raw{};
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr Result ResultPathNotFound{ErrorModule::FS, 1};
|
||||
constexpr Result ResultPathAlreadyExists{ErrorModule::FS, 2};
|
||||
constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
|
||||
constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
|
||||
constexpr Result ResultTargetNotFound{ErrorModule::FS, 1002};
|
||||
constexpr Result ResultPortSdCardNoDevice{ErrorModule::FS, 2001};
|
||||
constexpr Result ResultNotImplemented{ErrorModule::FS, 3001};
|
||||
constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
|
||||
constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
|
||||
constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
|
||||
constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341};
|
||||
constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363};
|
||||
constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399};
|
||||
constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412};
|
||||
constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422};
|
||||
constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423};
|
||||
constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012};
|
||||
constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021};
|
||||
constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022};
|
||||
constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023};
|
||||
constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024};
|
||||
constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032};
|
||||
constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033};
|
||||
constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034};
|
||||
constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035};
|
||||
constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036};
|
||||
constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037};
|
||||
constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038};
|
||||
constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039};
|
||||
constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084};
|
||||
constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085};
|
||||
constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086};
|
||||
constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087};
|
||||
constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088};
|
||||
constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089};
|
||||
constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090};
|
||||
constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091};
|
||||
constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091};
|
||||
constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509};
|
||||
constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510};
|
||||
constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511};
|
||||
constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517};
|
||||
constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520};
|
||||
constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521};
|
||||
constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522};
|
||||
constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523};
|
||||
constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524};
|
||||
constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525};
|
||||
constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526};
|
||||
constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528};
|
||||
constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529};
|
||||
constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530};
|
||||
constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532};
|
||||
constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533};
|
||||
constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534};
|
||||
constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535};
|
||||
constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541};
|
||||
constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542};
|
||||
constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543};
|
||||
constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547};
|
||||
constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548};
|
||||
constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549};
|
||||
constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
|
||||
constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
|
||||
constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
|
||||
constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
|
||||
constexpr Result ResultUnexpectedInPathA{ErrorModule::FS, 5328};
|
||||
constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
|
||||
constexpr Result ResultInvalidPath{ErrorModule::FS, 6002};
|
||||
constexpr Result ResultTooLongPath{ErrorModule::FS, 6003};
|
||||
constexpr Result ResultInvalidCharacter{ErrorModule::FS, 6004};
|
||||
constexpr Result ResultInvalidPathFormat{ErrorModule::FS, 6005};
|
||||
constexpr Result ResultDirectoryUnobtainable{ErrorModule::FS, 6006};
|
||||
constexpr Result ResultNotNormalized{ErrorModule::FS, 6007};
|
||||
constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
|
||||
constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
|
||||
constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
|
||||
constexpr Result ResultInvalidOpenMode{ErrorModule::FS, 6072};
|
||||
constexpr Result ResultFileExtensionWithoutOpenModeAllowAppend{ErrorModule::FS, 6201};
|
||||
constexpr Result ResultReadNotPermitted{ErrorModule::FS, 6202};
|
||||
constexpr Result ResultWriteNotPermitted{ErrorModule::FS, 6203};
|
||||
constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
|
||||
constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
|
||||
constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
|
||||
constexpr Result ResultPermissionDenied{ErrorModule::FS, 6400};
|
||||
constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr inline size_t EntryNameLengthMax = 0x300;
|
||||
|
||||
struct DirectoryEntry {
|
||||
DirectoryEntry(std::string_view view, s8 entry_type, u64 entry_size)
|
||||
: type{entry_type}, file_size{static_cast<s64>(entry_size)} {
|
||||
const std::size_t copy_size = view.copy(name, std::size(name) - 1);
|
||||
name[copy_size] = '\0';
|
||||
}
|
||||
|
||||
char name[EntryNameLengthMax + 1];
|
||||
INSERT_PADDING_BYTES(3);
|
||||
s8 type;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
s64 file_size;
|
||||
};
|
||||
|
||||
static_assert(sizeof(DirectoryEntry) == 0x310,
|
||||
"Directory Entry struct isn't exactly 0x310 bytes long!");
|
||||
static_assert(offsetof(DirectoryEntry, type) == 0x304, "Wrong offset for type in Entry.");
|
||||
static_assert(offsetof(DirectoryEntry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
|
||||
|
||||
struct DirectoryHandle {
|
||||
void* handle;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct ReadOption {
|
||||
u32 value;
|
||||
|
||||
static const ReadOption None;
|
||||
};
|
||||
|
||||
enum ReadOptionFlag : u32 {
|
||||
ReadOptionFlag_None = (0 << 0),
|
||||
};
|
||||
|
||||
inline constexpr const ReadOption ReadOption::None = {ReadOptionFlag_None};
|
||||
|
||||
inline constexpr bool operator==(const ReadOption& lhs, const ReadOption& rhs) {
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
|
||||
inline constexpr bool operator!=(const ReadOption& lhs, const ReadOption& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
static_assert(sizeof(ReadOption) == sizeof(u32));
|
||||
|
||||
enum WriteOptionFlag : u32 {
|
||||
WriteOptionFlag_None = (0 << 0),
|
||||
WriteOptionFlag_Flush = (1 << 0),
|
||||
};
|
||||
|
||||
struct WriteOption {
|
||||
u32 value;
|
||||
|
||||
constexpr inline bool HasFlushFlag() const {
|
||||
return value & WriteOptionFlag_Flush;
|
||||
}
|
||||
|
||||
static const WriteOption None;
|
||||
static const WriteOption Flush;
|
||||
};
|
||||
|
||||
inline constexpr const WriteOption WriteOption::None = {WriteOptionFlag_None};
|
||||
inline constexpr const WriteOption WriteOption::Flush = {WriteOptionFlag_Flush};
|
||||
|
||||
inline constexpr bool operator==(const WriteOption& lhs, const WriteOption& rhs) {
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
|
||||
inline constexpr bool operator!=(const WriteOption& lhs, const WriteOption& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
static_assert(sizeof(WriteOption) == sizeof(u32));
|
||||
|
||||
struct FileHandle {
|
||||
void* handle;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/fs/common_funcs.h"
|
||||
#include "import/common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class OpenMode : u32 {
|
||||
Read = (1 << 0),
|
||||
Write = (1 << 1),
|
||||
AllowAppend = (1 << 2),
|
||||
|
||||
ReadWrite = (Read | Write),
|
||||
All = (ReadWrite | AllowAppend),
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(OpenMode)
|
||||
|
||||
enum class OpenDirectoryMode : u64 {
|
||||
Directory = (1 << 0),
|
||||
File = (1 << 1),
|
||||
|
||||
All = (Directory | File),
|
||||
|
||||
NotRequireFileSize = (1ULL << 31),
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode)
|
||||
|
||||
enum class DirectoryEntryType : u8 {
|
||||
Directory = 0,
|
||||
File = 1,
|
||||
};
|
||||
|
||||
enum class CreateOption : u8 {
|
||||
None = (0 << 0),
|
||||
BigFile = (1 << 0),
|
||||
};
|
||||
|
||||
struct FileSystemAttribute {
|
||||
u8 dir_entry_name_length_max_defined;
|
||||
u8 file_entry_name_length_max_defined;
|
||||
u8 dir_path_name_length_max_defined;
|
||||
u8 file_path_name_length_max_defined;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x5);
|
||||
u8 utf16_dir_entry_name_length_max_defined;
|
||||
u8 utf16_file_entry_name_length_max_defined;
|
||||
u8 utf16_dir_path_name_length_max_defined;
|
||||
u8 utf16_file_path_name_length_max_defined;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x18);
|
||||
s32 dir_entry_name_length_max;
|
||||
s32 file_entry_name_length_max;
|
||||
s32 dir_path_name_length_max;
|
||||
s32 file_path_name_length_max;
|
||||
INSERT_PADDING_WORDS_NOINIT(0x5);
|
||||
s32 utf16_dir_entry_name_length_max;
|
||||
s32 utf16_file_entry_name_length_max;
|
||||
s32 utf16_dir_path_name_length_max;
|
||||
s32 utf16_file_path_name_length_max;
|
||||
INSERT_PADDING_WORDS_NOINIT(0x18);
|
||||
INSERT_PADDING_WORDS_NOINIT(0x1);
|
||||
};
|
||||
static_assert(sizeof(FileSystemAttribute) == 0xC0, "FileSystemAttribute has incorrect size");
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/alignment.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr size_t RequiredAlignment = alignof(u64);
|
||||
|
||||
inline void* AllocateUnsafe(size_t size) {
|
||||
// Allocate
|
||||
void* const ptr = ::operator new(size, std::align_val_t{RequiredAlignment});
|
||||
|
||||
// Check alignment
|
||||
ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(ptr), RequiredAlignment));
|
||||
|
||||
// Return allocated pointer
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline void DeallocateUnsafe(void* ptr, size_t size) {
|
||||
// Deallocate the pointer
|
||||
::operator delete(ptr, std::align_val_t{RequiredAlignment});
|
||||
}
|
||||
|
||||
inline void* Allocate(size_t size) {
|
||||
return AllocateUnsafe(size);
|
||||
}
|
||||
|
||||
inline void Deallocate(void* ptr, size_t size) {
|
||||
// If the pointer is non-null, deallocate it
|
||||
if (ptr != nullptr) {
|
||||
DeallocateUnsafe(ptr, size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class OperationId : s64 {
|
||||
FillZero = 0,
|
||||
DestroySignature = 1,
|
||||
Invalidate = 2,
|
||||
QueryRange = 3,
|
||||
QueryUnpreparedRange = 4,
|
||||
QueryLazyLoadCompletionRate = 5,
|
||||
SetLazyLoadPriority = 6,
|
||||
|
||||
ReadLazyLoadFileForciblyForDebug = 10001,
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
566
WIP/fs/fs_path.h
566
WIP/fs/fs_path.h
|
|
@ -1,566 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_memory_management.h"
|
||||
#include "core/file_sys/fs_path_utility.h"
|
||||
#include "core/file_sys/fs_string_util.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
class DirectoryPathParser;
|
||||
|
||||
class Path {
|
||||
YUZU_NON_COPYABLE(Path);
|
||||
YUZU_NON_MOVEABLE(Path);
|
||||
|
||||
private:
|
||||
static constexpr const char* EmptyPath = "";
|
||||
static constexpr size_t WriteBufferAlignmentLength = 8;
|
||||
|
||||
private:
|
||||
friend class DirectoryPathParser;
|
||||
|
||||
public:
|
||||
class WriteBuffer {
|
||||
YUZU_NON_COPYABLE(WriteBuffer);
|
||||
|
||||
private:
|
||||
char* m_buffer;
|
||||
size_t m_length_and_is_normalized;
|
||||
|
||||
public:
|
||||
constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) {}
|
||||
|
||||
constexpr ~WriteBuffer() {
|
||||
if (m_buffer != nullptr) {
|
||||
Deallocate(m_buffer, this->GetLength());
|
||||
this->ResetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr WriteBuffer(WriteBuffer&& rhs)
|
||||
: m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) {
|
||||
rhs.ResetBuffer();
|
||||
}
|
||||
|
||||
constexpr WriteBuffer& operator=(WriteBuffer&& rhs) {
|
||||
if (m_buffer != nullptr) {
|
||||
Deallocate(m_buffer, this->GetLength());
|
||||
}
|
||||
|
||||
m_buffer = rhs.m_buffer;
|
||||
m_length_and_is_normalized = rhs.m_length_and_is_normalized;
|
||||
|
||||
rhs.ResetBuffer();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr void ResetBuffer() {
|
||||
m_buffer = nullptr;
|
||||
this->SetLength(0);
|
||||
}
|
||||
|
||||
constexpr char* Get() const {
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
constexpr size_t GetLength() const {
|
||||
return m_length_and_is_normalized >> 1;
|
||||
}
|
||||
|
||||
constexpr bool IsNormalized() const {
|
||||
return static_cast<bool>(m_length_and_is_normalized & 1);
|
||||
}
|
||||
|
||||
constexpr void SetNormalized() {
|
||||
m_length_and_is_normalized |= static_cast<size_t>(1);
|
||||
}
|
||||
|
||||
constexpr void SetNotNormalized() {
|
||||
m_length_and_is_normalized &= ~static_cast<size_t>(1);
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr WriteBuffer(char* buffer, size_t length)
|
||||
: m_buffer(buffer), m_length_and_is_normalized(0) {
|
||||
this->SetLength(length);
|
||||
}
|
||||
|
||||
public:
|
||||
static WriteBuffer Make(size_t length) {
|
||||
if (void* alloc = Allocate(length); alloc != nullptr) {
|
||||
return WriteBuffer(static_cast<char*>(alloc), length);
|
||||
} else {
|
||||
return WriteBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr void SetLength(size_t size) {
|
||||
m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
const char* m_str;
|
||||
WriteBuffer m_write_buffer;
|
||||
|
||||
public:
|
||||
constexpr Path() : m_str(EmptyPath), m_write_buffer() {}
|
||||
|
||||
constexpr Path(const char* s) : m_str(s), m_write_buffer() {
|
||||
m_write_buffer.SetNormalized();
|
||||
}
|
||||
|
||||
constexpr ~Path() = default;
|
||||
|
||||
constexpr Result SetShallowBuffer(const char* buffer) {
|
||||
// Check pre-conditions
|
||||
ASSERT(m_write_buffer.GetLength() == 0);
|
||||
|
||||
// Check the buffer is valid
|
||||
R_UNLESS(buffer != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Set buffer
|
||||
this->SetReadOnlyBuffer(buffer);
|
||||
|
||||
// Note that we're normalized
|
||||
this->SetNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
constexpr const char* GetString() const {
|
||||
// Check pre-conditions
|
||||
ASSERT(this->IsNormalized());
|
||||
|
||||
return m_str;
|
||||
}
|
||||
|
||||
constexpr size_t GetLength() const {
|
||||
if (std::is_constant_evaluated()) {
|
||||
return Strlen(this->GetString());
|
||||
} else {
|
||||
return std::strlen(this->GetString());
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool IsEmpty() const {
|
||||
return *m_str == '\x00';
|
||||
}
|
||||
|
||||
constexpr bool IsMatchHead(const char* p, size_t len) const {
|
||||
return Strncmp(this->GetString(), p, len) == 0;
|
||||
}
|
||||
|
||||
Result Initialize(const Path& rhs) {
|
||||
// Check the other path is normalized
|
||||
const bool normalized = rhs.IsNormalized();
|
||||
R_UNLESS(normalized, ResultNotNormalized);
|
||||
|
||||
// Allocate buffer for our path
|
||||
const auto len = rhs.GetLength();
|
||||
R_TRY(this->Preallocate(len + 1));
|
||||
|
||||
// Copy the path
|
||||
const size_t copied = Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1);
|
||||
R_UNLESS(copied == len, ResultUnexpectedInPathA);
|
||||
|
||||
// Set normalized
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Initialize(const char* path, size_t len) {
|
||||
// Check the path is valid
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Initialize
|
||||
R_TRY(this->InitializeImpl(path, len));
|
||||
|
||||
// Set not normalized
|
||||
this->SetNotNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Initialize(const char* path) {
|
||||
// Check the path is valid
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
R_RETURN(this->Initialize(path, std::strlen(path)));
|
||||
}
|
||||
|
||||
Result InitializeWithReplaceBackslash(const char* path) {
|
||||
// Check the path is valid
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Initialize
|
||||
R_TRY(this->InitializeImpl(path, std::strlen(path)));
|
||||
|
||||
// Replace slashes as desired
|
||||
if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) {
|
||||
Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/');
|
||||
}
|
||||
|
||||
// Set not normalized
|
||||
this->SetNotNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeWithReplaceForwardSlashes(const char* path) {
|
||||
// Check the path is valid
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Initialize
|
||||
R_TRY(this->InitializeImpl(path, std::strlen(path)));
|
||||
|
||||
// Replace slashes as desired
|
||||
if (m_write_buffer.GetLength() > 1) {
|
||||
if (auto* p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') {
|
||||
p[0] = '\\';
|
||||
p[1] = '\\';
|
||||
}
|
||||
}
|
||||
|
||||
// Set not normalized
|
||||
this->SetNotNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeWithNormalization(const char* path, size_t size) {
|
||||
// Check the path is valid
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Initialize
|
||||
R_TRY(this->InitializeImpl(path, size));
|
||||
|
||||
// Set not normalized
|
||||
this->SetNotNormalized();
|
||||
|
||||
// Perform normalization
|
||||
PathFlags path_flags;
|
||||
if (IsPathRelative(m_str)) {
|
||||
path_flags.AllowRelativePath();
|
||||
} else if (IsWindowsPath(m_str, true)) {
|
||||
path_flags.AllowWindowsPath();
|
||||
} else {
|
||||
/* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then
|
||||
* returns success. */
|
||||
/* This seems like a bug. */
|
||||
size_t dummy;
|
||||
bool normalized;
|
||||
R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy),
|
||||
m_str));
|
||||
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// Normalize
|
||||
R_TRY(this->Normalize(path_flags));
|
||||
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeWithNormalization(const char* path) {
|
||||
// Check the path is valid
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
R_RETURN(this->InitializeWithNormalization(path, std::strlen(path)));
|
||||
}
|
||||
|
||||
Result InitializeAsEmpty() {
|
||||
// Clear our buffer
|
||||
this->ClearBuffer();
|
||||
|
||||
// Set normalized
|
||||
this->SetNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AppendChild(const char* child) {
|
||||
// Check the path is valid
|
||||
R_UNLESS(child != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Basic checks. If we have a path and the child is empty, we have nothing to do
|
||||
const char* c = child;
|
||||
if (m_str[0]) {
|
||||
// Skip an early separator
|
||||
if (*c == '/') {
|
||||
++c;
|
||||
}
|
||||
|
||||
R_SUCCEED_IF(*c == '\x00');
|
||||
}
|
||||
|
||||
// If we don't have a string, we can just initialize
|
||||
auto cur_len = std::strlen(m_str);
|
||||
if (cur_len == 0) {
|
||||
R_RETURN(this->Initialize(child));
|
||||
}
|
||||
|
||||
// Remove a trailing separator
|
||||
if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') {
|
||||
--cur_len;
|
||||
}
|
||||
|
||||
// Get the child path's length
|
||||
auto child_len = std::strlen(c);
|
||||
|
||||
// Reset our write buffer
|
||||
WriteBuffer old_write_buffer;
|
||||
if (m_write_buffer.Get() != nullptr) {
|
||||
old_write_buffer = std::move(m_write_buffer);
|
||||
this->ClearBuffer();
|
||||
}
|
||||
|
||||
// Pre-allocate the new buffer
|
||||
R_TRY(this->Preallocate(cur_len + 1 + child_len + 1));
|
||||
|
||||
// Get our write buffer
|
||||
auto* dst = m_write_buffer.Get();
|
||||
if (old_write_buffer.Get() != nullptr && cur_len > 0) {
|
||||
Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1);
|
||||
}
|
||||
|
||||
// Add separator
|
||||
dst[cur_len] = '/';
|
||||
|
||||
// Copy the child path
|
||||
const size_t copied = Strlcpy<char>(dst + cur_len + 1, c, child_len + 1);
|
||||
R_UNLESS(copied == child_len, ResultUnexpectedInPathA);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AppendChild(const Path& rhs) {
|
||||
R_RETURN(this->AppendChild(rhs.GetString()));
|
||||
}
|
||||
|
||||
Result Combine(const Path& parent, const Path& child) {
|
||||
// Get the lengths
|
||||
const auto p_len = parent.GetLength();
|
||||
const auto c_len = child.GetLength();
|
||||
|
||||
// Allocate our buffer
|
||||
R_TRY(this->Preallocate(p_len + c_len + 1));
|
||||
|
||||
// Initialize as parent
|
||||
R_TRY(this->Initialize(parent));
|
||||
|
||||
// If we're empty, we can just initialize as child
|
||||
if (this->IsEmpty()) {
|
||||
R_TRY(this->Initialize(child));
|
||||
} else {
|
||||
// Otherwise, we should append the child
|
||||
R_TRY(this->AppendChild(child));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result RemoveChild() {
|
||||
// If we don't have a write-buffer, ensure that we have one
|
||||
if (m_write_buffer.Get() == nullptr) {
|
||||
if (const auto len = std::strlen(m_str); len > 0) {
|
||||
R_TRY(this->Preallocate(len));
|
||||
Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that it's possible for us to remove a child
|
||||
auto* p = m_write_buffer.Get();
|
||||
s32 len = static_cast<s32>(std::strlen(p));
|
||||
R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), ResultNotImplemented);
|
||||
|
||||
// Handle a trailing separator
|
||||
if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) {
|
||||
--len;
|
||||
}
|
||||
|
||||
// Remove the child path segment
|
||||
while ((--len) >= 0 && p[len]) {
|
||||
if (p[len] == '/' || p[len] == '\\') {
|
||||
if (len > 0) {
|
||||
p[len] = 0;
|
||||
} else {
|
||||
p[1] = 0;
|
||||
len = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that length remains > 0
|
||||
R_UNLESS(len > 0, ResultNotImplemented);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Normalize(const PathFlags& flags) {
|
||||
// If we're already normalized, nothing to do
|
||||
R_SUCCEED_IF(this->IsNormalized());
|
||||
|
||||
// Check if we're normalized
|
||||
bool normalized;
|
||||
size_t dummy;
|
||||
R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str,
|
||||
flags));
|
||||
|
||||
// If we're not normalized, normalize
|
||||
if (!normalized) {
|
||||
// Determine necessary buffer length
|
||||
auto len = m_write_buffer.GetLength();
|
||||
if (flags.IsRelativePathAllowed() && IsPathRelative(m_str)) {
|
||||
len += 2;
|
||||
}
|
||||
if (flags.IsWindowsPathAllowed() && IsWindowsPath(m_str, true)) {
|
||||
len += 1;
|
||||
}
|
||||
|
||||
// Allocate a new buffer
|
||||
const size_t size = Common::AlignUp(len, WriteBufferAlignmentLength);
|
||||
auto buf = WriteBuffer::Make(size);
|
||||
R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
|
||||
|
||||
// Normalize into it
|
||||
R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(),
|
||||
m_write_buffer.GetLength(), flags));
|
||||
|
||||
// Set the normalized buffer as our buffer
|
||||
this->SetModifiableBuffer(std::move(buf));
|
||||
}
|
||||
|
||||
// Set normalized
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
private:
|
||||
void ClearBuffer() {
|
||||
m_write_buffer.ResetBuffer();
|
||||
m_str = EmptyPath;
|
||||
}
|
||||
|
||||
void SetModifiableBuffer(WriteBuffer&& buffer) {
|
||||
// Check pre-conditions
|
||||
ASSERT(buffer.Get() != nullptr);
|
||||
ASSERT(buffer.GetLength() > 0);
|
||||
ASSERT(Common::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength));
|
||||
|
||||
// Get whether we're normalized
|
||||
if (m_write_buffer.IsNormalized()) {
|
||||
buffer.SetNormalized();
|
||||
} else {
|
||||
buffer.SetNotNormalized();
|
||||
}
|
||||
|
||||
// Set write buffer
|
||||
m_write_buffer = std::move(buffer);
|
||||
m_str = m_write_buffer.Get();
|
||||
}
|
||||
|
||||
constexpr void SetReadOnlyBuffer(const char* buffer) {
|
||||
m_str = buffer;
|
||||
m_write_buffer.ResetBuffer();
|
||||
}
|
||||
|
||||
Result Preallocate(size_t length) {
|
||||
// Allocate additional space, if needed
|
||||
if (length > m_write_buffer.GetLength()) {
|
||||
// Allocate buffer
|
||||
const size_t size = Common::AlignUp(length, WriteBufferAlignmentLength);
|
||||
auto buf = WriteBuffer::Make(size);
|
||||
R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
|
||||
|
||||
// Set write buffer
|
||||
this->SetModifiableBuffer(std::move(buf));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeImpl(const char* path, size_t size) {
|
||||
if (size > 0 && path[0]) {
|
||||
// Pre allocate a buffer for the path
|
||||
R_TRY(this->Preallocate(size + 1));
|
||||
|
||||
// Copy the path
|
||||
const size_t copied = Strlcpy<char>(m_write_buffer.Get(), path, size + 1);
|
||||
R_UNLESS(copied >= size, ResultUnexpectedInPathA);
|
||||
} else {
|
||||
// We can just clear the buffer
|
||||
this->ClearBuffer();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
constexpr char* GetWriteBuffer() {
|
||||
ASSERT(m_write_buffer.Get() != nullptr);
|
||||
return m_write_buffer.Get();
|
||||
}
|
||||
|
||||
constexpr size_t GetWriteBufferLength() const {
|
||||
return m_write_buffer.GetLength();
|
||||
}
|
||||
|
||||
constexpr bool IsNormalized() const {
|
||||
return m_write_buffer.IsNormalized();
|
||||
}
|
||||
|
||||
constexpr void SetNormalized() {
|
||||
m_write_buffer.SetNormalized();
|
||||
}
|
||||
|
||||
constexpr void SetNotNormalized() {
|
||||
m_write_buffer.SetNotNormalized();
|
||||
}
|
||||
|
||||
public:
|
||||
bool operator==(const FileSys::Path& rhs) const {
|
||||
return std::strcmp(this->GetString(), rhs.GetString()) == 0;
|
||||
}
|
||||
bool operator!=(const FileSys::Path& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
bool operator==(const char* p) const {
|
||||
return std::strcmp(this->GetString(), p) == 0;
|
||||
}
|
||||
bool operator!=(const char* p) const {
|
||||
return !(*this == p);
|
||||
}
|
||||
};
|
||||
|
||||
inline Result SetUpFixedPath(FileSys::Path* out, const char* s) {
|
||||
// Verify the path is normalized
|
||||
bool normalized;
|
||||
size_t dummy;
|
||||
R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s));
|
||||
|
||||
R_UNLESS(normalized, ResultInvalidPathFormat);
|
||||
|
||||
// Set the fixed path
|
||||
R_RETURN(out->SetShallowBuffer(s));
|
||||
}
|
||||
|
||||
constexpr inline bool IsWindowsDriveRootPath(const FileSys::Path& path) {
|
||||
const char* const str = path.GetString();
|
||||
return IsWindowsDrive(str) &&
|
||||
(str[2] == StringTraits::DirectorySeparator ||
|
||||
str[2] == StringTraits::AlternateDirectorySeparator) &&
|
||||
str[3] == StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,188 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
using SaveDataId = u64;
|
||||
using SystemSaveDataId = u64;
|
||||
using SystemBcatSaveDataId = SystemSaveDataId;
|
||||
using ProgramId = u64;
|
||||
|
||||
enum class SaveDataSpaceId : u8 {
|
||||
System = 0,
|
||||
User = 1,
|
||||
SdSystem = 2,
|
||||
Temporary = 3,
|
||||
SdUser = 4,
|
||||
|
||||
ProperSystem = 100,
|
||||
SafeMode = 101,
|
||||
};
|
||||
|
||||
enum class SaveDataType : u8 {
|
||||
System = 0,
|
||||
Account = 1,
|
||||
Bcat = 2,
|
||||
Device = 3,
|
||||
Temporary = 4,
|
||||
Cache = 5,
|
||||
SystemBcat = 6,
|
||||
};
|
||||
|
||||
enum class SaveDataRank : u8 {
|
||||
Primary = 0,
|
||||
Secondary = 1,
|
||||
};
|
||||
|
||||
struct SaveDataSize {
|
||||
u64 normal;
|
||||
u64 journal;
|
||||
};
|
||||
static_assert(sizeof(SaveDataSize) == 0x10, "SaveDataSize has invalid size.");
|
||||
|
||||
using UserId = u128;
|
||||
static_assert(std::is_trivially_copyable_v<UserId>, "Data type must be trivially copyable.");
|
||||
static_assert(sizeof(UserId) == 0x10, "UserId has invalid size.");
|
||||
|
||||
constexpr inline SystemSaveDataId InvalidSystemSaveDataId = 0;
|
||||
constexpr inline UserId InvalidUserId = {};
|
||||
|
||||
enum class SaveDataFlags : u32 {
|
||||
None = (0 << 0),
|
||||
KeepAfterResettingSystemSaveData = (1 << 0),
|
||||
KeepAfterRefurbishment = (1 << 1),
|
||||
KeepAfterResettingSystemSaveDataWithoutUserSaveData = (1 << 2),
|
||||
NeedsSecureDelete = (1 << 3),
|
||||
};
|
||||
|
||||
enum class SaveDataMetaType : u8 {
|
||||
None = 0,
|
||||
Thumbnail = 1,
|
||||
ExtensionContext = 2,
|
||||
};
|
||||
|
||||
struct SaveDataMetaInfo {
|
||||
u32 size;
|
||||
SaveDataMetaType type;
|
||||
INSERT_PADDING_BYTES(0xB);
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<SaveDataMetaInfo>,
|
||||
"Data type must be trivially copyable.");
|
||||
static_assert(sizeof(SaveDataMetaInfo) == 0x10, "SaveDataMetaInfo has invalid size.");
|
||||
|
||||
struct SaveDataCreationInfo {
|
||||
s64 size;
|
||||
s64 journal_size;
|
||||
s64 block_size;
|
||||
u64 owner_id;
|
||||
u32 flags;
|
||||
SaveDataSpaceId space_id;
|
||||
bool pseudo;
|
||||
INSERT_PADDING_BYTES(0x1A);
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<SaveDataCreationInfo>,
|
||||
"Data type must be trivially copyable.");
|
||||
static_assert(sizeof(SaveDataCreationInfo) == 0x40, "SaveDataCreationInfo has invalid size.");
|
||||
|
||||
struct SaveDataAttribute {
|
||||
ProgramId program_id;
|
||||
UserId user_id;
|
||||
SystemSaveDataId system_save_data_id;
|
||||
SaveDataType type;
|
||||
SaveDataRank rank;
|
||||
u16 index;
|
||||
INSERT_PADDING_BYTES(0x1C);
|
||||
|
||||
static constexpr SaveDataAttribute Make(ProgramId program_id, SaveDataType type, UserId user_id,
|
||||
SystemSaveDataId system_save_data_id, u16 index,
|
||||
SaveDataRank rank) {
|
||||
return {
|
||||
.program_id = program_id,
|
||||
.user_id = user_id,
|
||||
.system_save_data_id = system_save_data_id,
|
||||
.type = type,
|
||||
.rank = rank,
|
||||
.index = index,
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr SaveDataAttribute Make(ProgramId program_id, SaveDataType type, UserId user_id,
|
||||
SystemSaveDataId system_save_data_id, u16 index) {
|
||||
return Make(program_id, type, user_id, system_save_data_id, index, SaveDataRank::Primary);
|
||||
}
|
||||
|
||||
static constexpr SaveDataAttribute Make(ProgramId program_id, SaveDataType type, UserId user_id,
|
||||
SystemSaveDataId system_save_data_id) {
|
||||
return Make(program_id, type, user_id, system_save_data_id, 0, SaveDataRank::Primary);
|
||||
}
|
||||
|
||||
std::string DebugInfo() const {
|
||||
return fmt::format(
|
||||
"[title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, type={:02X}, "
|
||||
"rank={}, index={}]",
|
||||
program_id, user_id[1], user_id[0], system_save_data_id, static_cast<u8>(type),
|
||||
static_cast<u8>(rank), index);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(SaveDataAttribute) == 0x40);
|
||||
static_assert(std::is_trivially_destructible<SaveDataAttribute>::value);
|
||||
|
||||
constexpr inline bool operator<(const SaveDataAttribute& lhs, const SaveDataAttribute& rhs) {
|
||||
return std::tie(lhs.program_id, lhs.user_id, lhs.system_save_data_id, lhs.index, lhs.rank) <
|
||||
std::tie(rhs.program_id, rhs.user_id, rhs.system_save_data_id, rhs.index, rhs.rank);
|
||||
}
|
||||
|
||||
constexpr inline bool operator==(const SaveDataAttribute& lhs, const SaveDataAttribute& rhs) {
|
||||
return std::tie(lhs.program_id, lhs.user_id, lhs.system_save_data_id, lhs.type, lhs.rank,
|
||||
lhs.index) == std::tie(rhs.program_id, rhs.user_id, rhs.system_save_data_id,
|
||||
rhs.type, rhs.rank, rhs.index);
|
||||
}
|
||||
|
||||
constexpr inline bool operator!=(const SaveDataAttribute& lhs, const SaveDataAttribute& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
struct SaveDataExtraData {
|
||||
SaveDataAttribute attr;
|
||||
u64 owner_id;
|
||||
s64 timestamp;
|
||||
u32 flags;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
s64 available_size;
|
||||
s64 journal_size;
|
||||
s64 commit_id;
|
||||
INSERT_PADDING_BYTES(0x190);
|
||||
};
|
||||
static_assert(sizeof(SaveDataExtraData) == 0x200, "SaveDataExtraData has invalid size.");
|
||||
static_assert(std::is_trivially_copyable_v<SaveDataExtraData>,
|
||||
"Data type must be trivially copyable.");
|
||||
|
||||
struct SaveDataFilter {
|
||||
bool use_program_id;
|
||||
bool use_save_data_type;
|
||||
bool use_user_id;
|
||||
bool use_save_data_id;
|
||||
bool use_index;
|
||||
SaveDataRank rank;
|
||||
SaveDataAttribute attribute;
|
||||
};
|
||||
static_assert(sizeof(SaveDataFilter) == 0x48, "SaveDataFilter has invalid size.");
|
||||
static_assert(std::is_trivially_copyable_v<SaveDataFilter>,
|
||||
"Data type must be trivially copyable.");
|
||||
|
||||
struct HashSalt {
|
||||
static constexpr size_t Size = 32;
|
||||
|
||||
std::array<u8, Size> value;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<HashSalt>, "Data type must be trivially copyable.");
|
||||
static_assert(sizeof(HashSalt) == HashSalt::Size);
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,241 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strlen(const T* str) {
|
||||
ASSERT(str != nullptr);
|
||||
|
||||
int length = 0;
|
||||
while (*str++) {
|
||||
++length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strnlen(const T* str, std::size_t count) {
|
||||
return Strnlen(str, static_cast<int>(count));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strnlen(const T* str, int count) {
|
||||
ASSERT(str != nullptr);
|
||||
ASSERT(count >= 0);
|
||||
|
||||
int length = 0;
|
||||
while (count-- && *str++) {
|
||||
++length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strncmp(const T* lhs, const T* rhs, std::size_t count) {
|
||||
return Strncmp(lhs, rhs, static_cast<int>(count));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strncmp(const T* lhs, const T* rhs, int count) {
|
||||
ASSERT(lhs != nullptr);
|
||||
ASSERT(rhs != nullptr);
|
||||
ASSERT(count >= 0);
|
||||
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
T l, r;
|
||||
do {
|
||||
l = *(lhs++);
|
||||
r = *(rhs++);
|
||||
} while (l && (l == r) && (--count));
|
||||
|
||||
return l - r;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr int Strlcpy(T* dst, const T* src, std::size_t count) {
|
||||
return Strlcpy<T>(dst, src, static_cast<int>(count));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr int Strlcpy(T* dst, const T* src, int count) {
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(src != nullptr);
|
||||
|
||||
const T* cur = src;
|
||||
if (count > 0) {
|
||||
while ((--count) && *cur) {
|
||||
*(dst++) = *(cur++);
|
||||
}
|
||||
*dst = 0;
|
||||
}
|
||||
|
||||
while (*cur) {
|
||||
cur++;
|
||||
}
|
||||
|
||||
return static_cast<int>(cur - src);
|
||||
}
|
||||
|
||||
enum CharacterEncodingResult {
|
||||
CharacterEncodingResult_Success = 0,
|
||||
CharacterEncodingResult_InsufficientLength = 1,
|
||||
CharacterEncodingResult_InvalidFormat = 2,
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
class CharacterEncodingHelper {
|
||||
public:
|
||||
static constexpr int8_t Utf8NBytesInnerTable[0x100 + 1] = {
|
||||
-1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8,
|
||||
};
|
||||
|
||||
static constexpr char GetUtf8NBytes(size_t i) {
|
||||
return static_cast<char>(Utf8NBytesInnerTable[1 + i]);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
constexpr inline CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32* dst, const char* src) {
|
||||
// Check pre-conditions
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(src != nullptr);
|
||||
|
||||
// Perform the conversion
|
||||
const auto* p = src;
|
||||
switch (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[0]))) {
|
||||
case 1:
|
||||
*dst = static_cast<u32>(p[0]);
|
||||
return CharacterEncodingResult_Success;
|
||||
case 2:
|
||||
if ((static_cast<u32>(p[0]) & 0x1E) != 0) {
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
|
||||
0) {
|
||||
*dst = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
|
||||
const u32 c = (static_cast<u32>(p[0] & 0xF) << 12) |
|
||||
(static_cast<u32>(p[1] & 0x3F) << 6) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 0);
|
||||
if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
|
||||
*dst = c;
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 4:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
|
||||
const u32 c =
|
||||
(static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
|
||||
if (c >= 0x10000 && c < 0x110000) {
|
||||
*dst = c;
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// We failed to convert
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
}
|
||||
|
||||
constexpr inline CharacterEncodingResult PickOutCharacterFromUtf8String(char* dst,
|
||||
const char** str) {
|
||||
// Check pre-conditions
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(str != nullptr);
|
||||
ASSERT(*str != nullptr);
|
||||
|
||||
// Clear the output
|
||||
dst[0] = 0;
|
||||
dst[1] = 0;
|
||||
dst[2] = 0;
|
||||
dst[3] = 0;
|
||||
|
||||
// Perform the conversion
|
||||
const auto* p = *str;
|
||||
u32 c = static_cast<u32>(*p);
|
||||
switch (impl::CharacterEncodingHelper::GetUtf8NBytes(c)) {
|
||||
case 1:
|
||||
dst[0] = (*str)[0];
|
||||
++(*str);
|
||||
break;
|
||||
case 2:
|
||||
if ((p[0] & 0x1E) != 0) {
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
|
||||
0) {
|
||||
c = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
(*str) += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 3:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
|
||||
c = (static_cast<u32>(p[0] & 0xF) << 12) | (static_cast<u32>(p[1] & 0x3F) << 6) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 0);
|
||||
if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
dst[2] = (*str)[2];
|
||||
(*str) += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 4:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
|
||||
c = (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
|
||||
if (c >= 0x10000 && c < 0x110000) {
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
dst[2] = (*str)[2];
|
||||
dst[3] = (*str)[3];
|
||||
(*str) += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
default:
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
}
|
||||
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
#include "core/file_sys/fs_file.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys::Fsa {
|
||||
|
||||
class IDirectory {
|
||||
public:
|
||||
explicit IDirectory(VirtualDir backend_, OpenDirectoryMode mode)
|
||||
: backend(std::move(backend_)) {
|
||||
// TODO(DarkLordZach): Verify that this is the correct behavior.
|
||||
// Build entry index now to save time later.
|
||||
if (True(mode & OpenDirectoryMode::Directory)) {
|
||||
BuildEntryIndex(backend->GetSubdirectories(), DirectoryEntryType::Directory);
|
||||
}
|
||||
if (True(mode & OpenDirectoryMode::File)) {
|
||||
BuildEntryIndex(backend->GetFiles(), DirectoryEntryType::File);
|
||||
}
|
||||
}
|
||||
virtual ~IDirectory() {}
|
||||
|
||||
Result Read(s64* out_count, DirectoryEntry* out_entries, s64 max_entries) {
|
||||
R_UNLESS(out_count != nullptr, ResultNullptrArgument);
|
||||
if (max_entries == 0) {
|
||||
*out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
R_UNLESS(out_entries != nullptr, ResultNullptrArgument);
|
||||
R_UNLESS(max_entries > 0, ResultInvalidArgument);
|
||||
R_RETURN(this->DoRead(out_count, out_entries, max_entries));
|
||||
}
|
||||
|
||||
Result GetEntryCount(s64* out) {
|
||||
R_UNLESS(out != nullptr, ResultNullptrArgument);
|
||||
R_RETURN(this->DoGetEntryCount(out));
|
||||
}
|
||||
|
||||
private:
|
||||
Result DoRead(s64* out_count, DirectoryEntry* out_entries, s64 max_entries) {
|
||||
const u64 actual_entries =
|
||||
std::min(static_cast<u64>(max_entries), entries.size() - next_entry_index);
|
||||
const auto* begin = reinterpret_cast<u8*>(entries.data() + next_entry_index);
|
||||
const auto* end = reinterpret_cast<u8*>(entries.data() + next_entry_index + actual_entries);
|
||||
const auto range_size = static_cast<std::size_t>(std::distance(begin, end));
|
||||
|
||||
next_entry_index += actual_entries;
|
||||
*out_count = actual_entries;
|
||||
|
||||
std::memcpy(out_entries, begin, range_size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DoGetEntryCount(s64* out) {
|
||||
*out = entries.size() - next_entry_index;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// TODO: Remove this when VFS is gone
|
||||
template <typename T>
|
||||
void BuildEntryIndex(const std::vector<T>& new_data, DirectoryEntryType type) {
|
||||
entries.reserve(entries.size() + new_data.size());
|
||||
|
||||
for (const auto& new_entry : new_data) {
|
||||
auto name = new_entry->GetName();
|
||||
|
||||
if (type == DirectoryEntryType::File && name == GetSaveDataSizeFileName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.emplace_back(name, static_cast<s8>(type),
|
||||
type == DirectoryEntryType::Directory ? 0 : new_entry->GetSize());
|
||||
}
|
||||
}
|
||||
|
||||
VirtualDir backend;
|
||||
std::vector<DirectoryEntry> entries;
|
||||
u64 next_entry_index = 0;
|
||||
};
|
||||
|
||||
} // namespace FileSys::Fsa
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/overflow.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_file.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/fs_operate_range.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys::Fsa {
|
||||
|
||||
class IFile {
|
||||
public:
|
||||
explicit IFile(VirtualFile backend_) : backend(std::move(backend_)) {}
|
||||
virtual ~IFile() {}
|
||||
|
||||
Result Read(size_t* out, s64 offset, void* buffer, size_t size, const ReadOption& option) {
|
||||
// Check that we have an output pointer
|
||||
R_UNLESS(out != nullptr, ResultNullptrArgument);
|
||||
|
||||
// If we have nothing to read, just succeed
|
||||
if (size == 0) {
|
||||
*out = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// Check that the read is valid
|
||||
R_UNLESS(buffer != nullptr, ResultNullptrArgument);
|
||||
R_UNLESS(offset >= 0, ResultOutOfRange);
|
||||
R_UNLESS(Common::CanAddWithoutOverflow<s64>(offset, size), ResultOutOfRange);
|
||||
|
||||
// Do the read
|
||||
R_RETURN(this->DoRead(out, offset, buffer, size, option));
|
||||
}
|
||||
|
||||
Result Read(size_t* out, s64 offset, void* buffer, size_t size) {
|
||||
R_RETURN(this->Read(out, offset, buffer, size, ReadOption::None));
|
||||
}
|
||||
|
||||
Result GetSize(s64* out) {
|
||||
R_UNLESS(out != nullptr, ResultNullptrArgument);
|
||||
R_RETURN(this->DoGetSize(out));
|
||||
}
|
||||
|
||||
Result Flush() {
|
||||
R_RETURN(this->DoFlush());
|
||||
}
|
||||
|
||||
Result Write(s64 offset, const void* buffer, size_t size, const WriteOption& option) {
|
||||
// Handle the zero-size case
|
||||
if (size == 0) {
|
||||
if (option.HasFlushFlag()) {
|
||||
R_TRY(this->Flush());
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// Check the write is valid
|
||||
R_UNLESS(buffer != nullptr, ResultNullptrArgument);
|
||||
R_UNLESS(offset >= 0, ResultOutOfRange);
|
||||
R_UNLESS(Common::CanAddWithoutOverflow<s64>(offset, size), ResultOutOfRange);
|
||||
|
||||
R_RETURN(this->DoWrite(offset, buffer, size, option));
|
||||
}
|
||||
|
||||
Result SetSize(s64 size) {
|
||||
R_UNLESS(size >= 0, ResultOutOfRange);
|
||||
R_RETURN(this->DoSetSize(size));
|
||||
}
|
||||
|
||||
Result OperateRange(void* dst, size_t dst_size, OperationId op_id, s64 offset, s64 size,
|
||||
const void* src, size_t src_size) {
|
||||
R_RETURN(this->DoOperateRange(dst, dst_size, op_id, offset, size, src, src_size));
|
||||
}
|
||||
|
||||
Result OperateRange(OperationId op_id, s64 offset, s64 size) {
|
||||
R_RETURN(this->DoOperateRange(nullptr, 0, op_id, offset, size, nullptr, 0));
|
||||
}
|
||||
|
||||
protected:
|
||||
Result DryRead(size_t* out, s64 offset, size_t size, const ReadOption& option,
|
||||
OpenMode open_mode) {
|
||||
// Check that we can read
|
||||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::Read) != 0, ResultReadNotPermitted);
|
||||
|
||||
// Get the file size, and validate our offset
|
||||
s64 file_size = 0;
|
||||
R_TRY(this->DoGetSize(std::addressof(file_size)));
|
||||
R_UNLESS(offset <= file_size, ResultOutOfRange);
|
||||
|
||||
*out = static_cast<size_t>(std::min(file_size - offset, static_cast<s64>(size)));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DrySetSize(s64 size, OpenMode open_mode) {
|
||||
// Check that we can write
|
||||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::Write) != 0, ResultWriteNotPermitted);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DryWrite(bool* out_append, s64 offset, size_t size, const WriteOption& option,
|
||||
OpenMode open_mode) {
|
||||
// Check that we can write
|
||||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::Write) != 0, ResultWriteNotPermitted);
|
||||
|
||||
// Get the file size
|
||||
s64 file_size = 0;
|
||||
R_TRY(this->DoGetSize(&file_size));
|
||||
|
||||
// Determine if we need to append
|
||||
*out_append = false;
|
||||
if (file_size < offset + static_cast<s64>(size)) {
|
||||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::AllowAppend) != 0,
|
||||
ResultFileExtensionWithoutOpenModeAllowAppend);
|
||||
*out_append = true;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
private:
|
||||
Result DoRead(size_t* out, s64 offset, void* buffer, size_t size, const ReadOption& option) {
|
||||
const auto read_size = backend->Read(static_cast<u8*>(buffer), size, offset);
|
||||
*out = read_size;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DoGetSize(s64* out) {
|
||||
*out = backend->GetSize();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DoFlush() {
|
||||
// Exists for SDK compatibiltity -- No need to flush file.
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DoWrite(s64 offset, const void* buffer, size_t size, const WriteOption& option) {
|
||||
const std::size_t written = backend->Write(static_cast<const u8*>(buffer), size, offset);
|
||||
|
||||
ASSERT_MSG(written == size,
|
||||
"Could not write all bytes to file (requested={:016X}, actual={:016X}).", size,
|
||||
written);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DoSetSize(s64 size) {
|
||||
backend->Resize(size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DoOperateRange(void* dst, size_t dst_size, OperationId op_id, s64 offset, s64 size,
|
||||
const void* src, size_t src_size) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
VirtualFile backend;
|
||||
};
|
||||
|
||||
} // namespace FileSys::Fsa
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/fs_path.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
||||
namespace FileSys::Fsa {
|
||||
|
||||
class IFile;
|
||||
class IDirectory;
|
||||
|
||||
enum class QueryId : u32 {
|
||||
SetConcatenationFileAttribute = 0,
|
||||
UpdateMac = 1,
|
||||
IsSignedSystemPartitionOnSdCardValid = 2,
|
||||
QueryUnpreparedFileInformation = 3,
|
||||
};
|
||||
|
||||
class IFileSystem {
|
||||
public:
|
||||
explicit IFileSystem(VirtualDir backend_) : backend{std::move(backend_)} {}
|
||||
virtual ~IFileSystem() {}
|
||||
|
||||
Result CreateFile(const Path& path, s64 size, CreateOption option) {
|
||||
R_UNLESS(size >= 0, ResultOutOfRange);
|
||||
R_RETURN(this->DoCreateFile(path, size, static_cast<int>(option)));
|
||||
}
|
||||
|
||||
Result CreateFile(const Path& path, s64 size) {
|
||||
R_RETURN(this->CreateFile(path, size, CreateOption::None));
|
||||
}
|
||||
|
||||
Result DeleteFile(const Path& path) {
|
||||
R_RETURN(this->DoDeleteFile(path));
|
||||
}
|
||||
|
||||
Result CreateDirectory(const Path& path) {
|
||||
R_RETURN(this->DoCreateDirectory(path));
|
||||
}
|
||||
|
||||
Result DeleteDirectory(const Path& path) {
|
||||
R_RETURN(this->DoDeleteDirectory(path));
|
||||
}
|
||||
|
||||
Result DeleteDirectoryRecursively(const Path& path) {
|
||||
R_RETURN(this->DoDeleteDirectoryRecursively(path));
|
||||
}
|
||||
|
||||
Result RenameFile(const Path& old_path, const Path& new_path) {
|
||||
R_RETURN(this->DoRenameFile(old_path, new_path));
|
||||
}
|
||||
|
||||
Result RenameDirectory(const Path& old_path, const Path& new_path) {
|
||||
R_RETURN(this->DoRenameDirectory(old_path, new_path));
|
||||
}
|
||||
|
||||
Result GetEntryType(DirectoryEntryType* out, const Path& path) {
|
||||
R_RETURN(this->DoGetEntryType(out, path));
|
||||
}
|
||||
|
||||
Result OpenFile(VirtualFile* out_file, const Path& path, OpenMode mode) {
|
||||
R_UNLESS(out_file != nullptr, ResultNullptrArgument);
|
||||
R_UNLESS(static_cast<u32>(mode & OpenMode::ReadWrite) != 0, ResultInvalidOpenMode);
|
||||
R_UNLESS(static_cast<u32>(mode & ~OpenMode::All) == 0, ResultInvalidOpenMode);
|
||||
R_RETURN(this->DoOpenFile(out_file, path, mode));
|
||||
}
|
||||
|
||||
Result OpenDirectory(VirtualDir* out_dir, const Path& path, OpenDirectoryMode mode) {
|
||||
R_UNLESS(out_dir != nullptr, ResultNullptrArgument);
|
||||
R_UNLESS(static_cast<u64>(mode & OpenDirectoryMode::All) != 0, ResultInvalidOpenMode);
|
||||
R_UNLESS(static_cast<u64>(
|
||||
mode & ~(OpenDirectoryMode::All | OpenDirectoryMode::NotRequireFileSize)) == 0,
|
||||
ResultInvalidOpenMode);
|
||||
R_RETURN(this->DoOpenDirectory(out_dir, path, mode));
|
||||
}
|
||||
|
||||
Result Commit() {
|
||||
R_RETURN(this->DoCommit());
|
||||
}
|
||||
|
||||
Result GetFreeSpaceSize(s64* out, const Path& path) {
|
||||
R_UNLESS(out != nullptr, ResultNullptrArgument);
|
||||
R_RETURN(this->DoGetFreeSpaceSize(out, path));
|
||||
}
|
||||
|
||||
Result GetTotalSpaceSize(s64* out, const Path& path) {
|
||||
R_UNLESS(out != nullptr, ResultNullptrArgument);
|
||||
R_RETURN(this->DoGetTotalSpaceSize(out, path));
|
||||
}
|
||||
|
||||
Result CleanDirectoryRecursively(const Path& path) {
|
||||
R_RETURN(this->DoCleanDirectoryRecursively(path));
|
||||
}
|
||||
|
||||
Result GetFileTimeStampRaw(FileTimeStampRaw* out, const Path& path) {
|
||||
R_UNLESS(out != nullptr, ResultNullptrArgument);
|
||||
R_RETURN(this->DoGetFileTimeStampRaw(out, path));
|
||||
}
|
||||
|
||||
Result QueryEntry(char* dst, size_t dst_size, const char* src, size_t src_size, QueryId query,
|
||||
const Path& path) {
|
||||
R_RETURN(this->DoQueryEntry(dst, dst_size, src, src_size, query, path));
|
||||
}
|
||||
|
||||
// These aren't accessible as commands
|
||||
Result CommitProvisionally(s64 counter) {
|
||||
R_RETURN(this->DoCommitProvisionally(counter));
|
||||
}
|
||||
|
||||
Result Rollback() {
|
||||
R_RETURN(this->DoRollback());
|
||||
}
|
||||
|
||||
Result Flush() {
|
||||
R_RETURN(this->DoFlush());
|
||||
}
|
||||
|
||||
private:
|
||||
Result DoCreateFile(const Path& path, s64 size, int flags) {
|
||||
R_RETURN(backend.CreateFile(path.GetString(), size));
|
||||
}
|
||||
|
||||
Result DoDeleteFile(const Path& path) {
|
||||
R_RETURN(backend.DeleteFile(path.GetString()));
|
||||
}
|
||||
|
||||
Result DoCreateDirectory(const Path& path) {
|
||||
R_RETURN(backend.CreateDirectory(path.GetString()));
|
||||
}
|
||||
|
||||
Result DoDeleteDirectory(const Path& path) {
|
||||
R_RETURN(backend.DeleteDirectory(path.GetString()));
|
||||
}
|
||||
|
||||
Result DoDeleteDirectoryRecursively(const Path& path) {
|
||||
R_RETURN(backend.DeleteDirectoryRecursively(path.GetString()));
|
||||
}
|
||||
|
||||
Result DoRenameFile(const Path& old_path, const Path& new_path) {
|
||||
R_RETURN(backend.RenameFile(old_path.GetString(), new_path.GetString()));
|
||||
}
|
||||
|
||||
Result DoRenameDirectory(const Path& old_path, const Path& new_path) {
|
||||
R_RETURN(backend.RenameDirectory(old_path.GetString(), new_path.GetString()));
|
||||
}
|
||||
|
||||
Result DoGetEntryType(DirectoryEntryType* out, const Path& path) {
|
||||
R_RETURN(backend.GetEntryType(out, path.GetString()));
|
||||
}
|
||||
|
||||
Result DoOpenFile(VirtualFile* out_file, const Path& path, OpenMode mode) {
|
||||
R_RETURN(backend.OpenFile(out_file, path.GetString(), mode));
|
||||
}
|
||||
|
||||
Result DoOpenDirectory(VirtualDir* out_directory, const Path& path, OpenDirectoryMode mode) {
|
||||
R_RETURN(backend.OpenDirectory(out_directory, path.GetString()));
|
||||
}
|
||||
|
||||
Result DoCommit() {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
Result DoGetFreeSpaceSize(s64* out, const Path& path) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
Result DoGetTotalSpaceSize(s64* out, const Path& path) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
Result DoCleanDirectoryRecursively(const Path& path) {
|
||||
R_RETURN(backend.CleanDirectoryRecursively(path.GetString()));
|
||||
}
|
||||
|
||||
Result DoGetFileTimeStampRaw(FileTimeStampRaw* out, const Path& path) {
|
||||
R_RETURN(backend.GetFileTimeStampRaw(out, path.GetString()));
|
||||
}
|
||||
|
||||
Result DoQueryEntry(char* dst, size_t dst_size, const char* src, size_t src_size, QueryId query,
|
||||
const Path& path) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
// These aren't accessible as commands
|
||||
Result DoCommitProvisionally(s64 counter) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
Result DoRollback() {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
Result DoFlush() {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
Service::FileSystem::VfsDirectoryServiceWrapper backend;
|
||||
};
|
||||
|
||||
} // namespace FileSys::Fsa
|
||||
|
|
@ -1,354 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/fsmitm_romfsbuild.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 FS_MAX_PATH = 0x301;
|
||||
|
||||
constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
|
||||
constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
|
||||
|
||||
// Types for building a RomFS.
|
||||
struct RomFSHeader {
|
||||
u64 header_size;
|
||||
u64 dir_hash_table_ofs;
|
||||
u64 dir_hash_table_size;
|
||||
u64 dir_table_ofs;
|
||||
u64 dir_table_size;
|
||||
u64 file_hash_table_ofs;
|
||||
u64 file_hash_table_size;
|
||||
u64 file_table_ofs;
|
||||
u64 file_table_size;
|
||||
u64 file_partition_ofs;
|
||||
};
|
||||
static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
|
||||
|
||||
struct RomFSDirectoryEntry {
|
||||
u32 parent;
|
||||
u32 sibling;
|
||||
u32 child;
|
||||
u32 file;
|
||||
u32 hash;
|
||||
u32 name_size;
|
||||
};
|
||||
static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size.");
|
||||
|
||||
struct RomFSFileEntry {
|
||||
u32 parent;
|
||||
u32 sibling;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u32 hash;
|
||||
u32 name_size;
|
||||
};
|
||||
static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size.");
|
||||
|
||||
struct RomFSBuildFileContext;
|
||||
|
||||
struct RomFSBuildDirectoryContext {
|
||||
std::string path;
|
||||
u32 cur_path_ofs = 0;
|
||||
u32 path_len = 0;
|
||||
u32 entry_offset = 0;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> child;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> sibling;
|
||||
std::shared_ptr<RomFSBuildFileContext> file;
|
||||
};
|
||||
|
||||
struct RomFSBuildFileContext {
|
||||
std::string path;
|
||||
u32 cur_path_ofs = 0;
|
||||
u32 path_len = 0;
|
||||
u32 entry_offset = 0;
|
||||
u64 offset = 0;
|
||||
u64 size = 0;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent;
|
||||
std::shared_ptr<RomFSBuildFileContext> sibling;
|
||||
VirtualFile source;
|
||||
};
|
||||
|
||||
static u32 romfs_calc_path_hash(u32 parent, std::string_view path, u32 start,
|
||||
std::size_t path_len) {
|
||||
u32 hash = parent ^ 123456789;
|
||||
for (u32 i = 0; i < path_len; i++) {
|
||||
hash = (hash >> 5) | (hash << 27);
|
||||
hash ^= path[start + i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static u64 romfs_get_hash_table_count(u64 num_entries) {
|
||||
if (num_entries < 3) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (num_entries < 19) {
|
||||
return num_entries | 1;
|
||||
}
|
||||
|
||||
u64 count = num_entries;
|
||||
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
|
||||
count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void RomFSBuildContext::VisitDirectory(VirtualDir romfs_dir, VirtualDir ext_dir,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
|
||||
for (auto& child_romfs_file : romfs_dir->GetFiles()) {
|
||||
const auto name = child_romfs_file->GetName();
|
||||
const auto child = std::make_shared<RomFSBuildFileContext>();
|
||||
// Set child's path.
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
child->path_len = child->cur_path_ofs + static_cast<u32>(name.size());
|
||||
child->path = parent->path + "/" + name;
|
||||
|
||||
if (ext_dir != nullptr && ext_dir->GetFile(name + ".stub") != nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanity check on path_len
|
||||
ASSERT(child->path_len < FS_MAX_PATH);
|
||||
|
||||
child->source = std::move(child_romfs_file);
|
||||
|
||||
if (ext_dir != nullptr) {
|
||||
if (const auto ips = ext_dir->GetFile(name + ".ips")) {
|
||||
if (auto patched = PatchIPS(child->source, ips)) {
|
||||
child->source = std::move(patched);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
child->size = child->source->GetSize();
|
||||
|
||||
AddFile(parent, std::move(child));
|
||||
}
|
||||
|
||||
for (auto& child_romfs_dir : romfs_dir->GetSubdirectories()) {
|
||||
const auto name = child_romfs_dir->GetName();
|
||||
const auto child = std::make_shared<RomFSBuildDirectoryContext>();
|
||||
// Set child's path.
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
child->path_len = child->cur_path_ofs + static_cast<u32>(name.size());
|
||||
child->path = parent->path + "/" + name;
|
||||
|
||||
if (ext_dir != nullptr && ext_dir->GetFile(name + ".stub") != nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanity check on path_len
|
||||
ASSERT(child->path_len < FS_MAX_PATH);
|
||||
|
||||
if (!AddDirectory(parent, child)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto child_ext_dir = ext_dir != nullptr ? ext_dir->GetSubdirectory(name) : nullptr;
|
||||
this->VisitDirectory(child_romfs_dir, child_ext_dir, child);
|
||||
}
|
||||
}
|
||||
|
||||
bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
|
||||
// Add a new directory.
|
||||
num_dirs++;
|
||||
dir_table_size +=
|
||||
sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4);
|
||||
dir_ctx->parent = std::move(parent_dir_ctx);
|
||||
directories.emplace_back(std::move(dir_ctx));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildFileContext> file_ctx) {
|
||||
// Add a new file.
|
||||
num_files++;
|
||||
file_table_size +=
|
||||
sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4);
|
||||
file_ctx->parent = std::move(parent_dir_ctx);
|
||||
files.emplace_back(std::move(file_ctx));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
|
||||
: base(std::move(base_)), ext(std::move(ext_)) {
|
||||
root = std::make_shared<RomFSBuildDirectoryContext>();
|
||||
root->path = "\0";
|
||||
directories.emplace_back(root);
|
||||
num_dirs = 1;
|
||||
dir_table_size = 0x18;
|
||||
|
||||
VisitDirectory(base, ext, root);
|
||||
}
|
||||
|
||||
RomFSBuildContext::~RomFSBuildContext() = default;
|
||||
|
||||
std::vector<std::pair<u64, VirtualFile>> RomFSBuildContext::Build() {
|
||||
const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
|
||||
const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
|
||||
dir_hash_table_size = 4 * dir_hash_table_entry_count;
|
||||
file_hash_table_size = 4 * file_hash_table_entry_count;
|
||||
|
||||
// Assign metadata pointers.
|
||||
RomFSHeader header{};
|
||||
|
||||
std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
|
||||
dir_table_size);
|
||||
u32* const dir_hash_table_pointer = reinterpret_cast<u32*>(metadata.data());
|
||||
u8* const dir_table_pointer = metadata.data() + dir_hash_table_size;
|
||||
u32* const file_hash_table_pointer =
|
||||
reinterpret_cast<u32*>(metadata.data() + dir_hash_table_size + dir_table_size);
|
||||
u8* const file_table_pointer =
|
||||
metadata.data() + dir_hash_table_size + dir_table_size + file_hash_table_size;
|
||||
|
||||
std::span<u32> dir_hash_table(dir_hash_table_pointer, dir_hash_table_entry_count);
|
||||
std::span<u32> file_hash_table(file_hash_table_pointer, file_hash_table_entry_count);
|
||||
std::span<u8> dir_table(dir_table_pointer, dir_table_size);
|
||||
std::span<u8> file_table(file_table_pointer, file_table_size);
|
||||
|
||||
// Initialize hash tables.
|
||||
std::memset(dir_hash_table.data(), 0xFF, dir_hash_table.size_bytes());
|
||||
std::memset(file_hash_table.data(), 0xFF, file_hash_table.size_bytes());
|
||||
|
||||
// Sort tables by name.
|
||||
std::sort(files.begin(), files.end(),
|
||||
[](const auto& a, const auto& b) { return a->path < b->path; });
|
||||
std::sort(directories.begin(), directories.end(),
|
||||
[](const auto& a, const auto& b) { return a->path < b->path; });
|
||||
|
||||
// Determine file offsets.
|
||||
u32 entry_offset = 0;
|
||||
std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
|
||||
for (const auto& cur_file : files) {
|
||||
file_partition_size = Common::AlignUp(file_partition_size, 16);
|
||||
cur_file->offset = file_partition_size;
|
||||
file_partition_size += cur_file->size;
|
||||
cur_file->entry_offset = entry_offset;
|
||||
entry_offset +=
|
||||
static_cast<u32>(sizeof(RomFSFileEntry) +
|
||||
Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4));
|
||||
prev_file = cur_file;
|
||||
}
|
||||
// Assign deferred parent/sibling ownership.
|
||||
for (auto it = files.rbegin(); it != files.rend(); ++it) {
|
||||
auto& cur_file = *it;
|
||||
cur_file->sibling = cur_file->parent->file;
|
||||
cur_file->parent->file = cur_file;
|
||||
}
|
||||
|
||||
// Determine directory offsets.
|
||||
entry_offset = 0;
|
||||
for (const auto& cur_dir : directories) {
|
||||
cur_dir->entry_offset = entry_offset;
|
||||
entry_offset +=
|
||||
static_cast<u32>(sizeof(RomFSDirectoryEntry) +
|
||||
Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4));
|
||||
}
|
||||
// Assign deferred parent/sibling ownership.
|
||||
for (auto it = directories.rbegin(); (*it) != root; ++it) {
|
||||
auto& cur_dir = *it;
|
||||
cur_dir->sibling = cur_dir->parent->child;
|
||||
cur_dir->parent->child = cur_dir;
|
||||
}
|
||||
|
||||
// Create output map.
|
||||
std::vector<std::pair<u64, VirtualFile>> out;
|
||||
out.reserve(num_files + 2);
|
||||
|
||||
// Set header fields.
|
||||
header.header_size = sizeof(RomFSHeader);
|
||||
header.file_hash_table_size = file_hash_table_size;
|
||||
header.file_table_size = file_table_size;
|
||||
header.dir_hash_table_size = dir_hash_table_size;
|
||||
header.dir_table_size = dir_table_size;
|
||||
header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
|
||||
header.dir_hash_table_ofs = Common::AlignUp(header.file_partition_ofs + file_partition_size, 4);
|
||||
header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
|
||||
header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
|
||||
header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
|
||||
|
||||
std::vector<u8> header_data(sizeof(RomFSHeader));
|
||||
std::memcpy(header_data.data(), &header, header_data.size());
|
||||
out.emplace_back(0, std::make_shared<VectorVfsFile>(std::move(header_data)));
|
||||
|
||||
// Populate file tables.
|
||||
for (const auto& cur_file : files) {
|
||||
RomFSFileEntry cur_entry{};
|
||||
|
||||
cur_entry.parent = cur_file->parent->entry_offset;
|
||||
cur_entry.sibling =
|
||||
cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
|
||||
cur_entry.offset = cur_file->offset;
|
||||
cur_entry.size = cur_file->size;
|
||||
|
||||
const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
|
||||
const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
|
||||
cur_file->cur_path_ofs, name_size);
|
||||
cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
|
||||
file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
|
||||
|
||||
cur_entry.name_size = name_size;
|
||||
|
||||
out.emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, std::move(cur_file->source));
|
||||
std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
|
||||
std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
|
||||
Common::AlignUp(cur_entry.name_size, 4));
|
||||
std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
|
||||
cur_file->path.data() + cur_file->cur_path_ofs, name_size);
|
||||
}
|
||||
|
||||
// Populate dir tables.
|
||||
for (const auto& cur_dir : directories) {
|
||||
RomFSDirectoryEntry cur_entry{};
|
||||
|
||||
cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
|
||||
cur_entry.sibling =
|
||||
cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
|
||||
cur_entry.child =
|
||||
cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
|
||||
cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
|
||||
|
||||
const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
|
||||
const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
|
||||
cur_dir->path, cur_dir->cur_path_ofs, name_size);
|
||||
cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
|
||||
dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
|
||||
|
||||
cur_entry.name_size = name_size;
|
||||
|
||||
std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
|
||||
sizeof(RomFSDirectoryEntry));
|
||||
std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
|
||||
Common::AlignUp(cur_entry.name_size, 4));
|
||||
std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
|
||||
cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
|
||||
}
|
||||
|
||||
// Write metadata.
|
||||
out.emplace_back(header.dir_hash_table_ofs,
|
||||
std::make_shared<VectorVfsFile>(std::move(metadata)));
|
||||
|
||||
// Sort the output.
|
||||
std::sort(out.begin(), out.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/fs/vfs/vfs.h"
|
||||
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct RomFSBuildDirectoryContext;
|
||||
struct RomFSBuildFileContext;
|
||||
struct RomFSDirectoryEntry;
|
||||
struct RomFSFileEntry;
|
||||
|
||||
class RomFSBuildContext {
|
||||
public:
|
||||
explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
|
||||
~RomFSBuildContext();
|
||||
|
||||
// This finalizes the context.
|
||||
std::vector<std::pair<u64, VirtualFile>> Build();
|
||||
|
||||
private:
|
||||
VirtualDir base;
|
||||
VirtualDir ext;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> root;
|
||||
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> directories;
|
||||
std::vector<std::shared_ptr<RomFSBuildFileContext>> files;
|
||||
u64 num_dirs = 0;
|
||||
u64 num_files = 0;
|
||||
u64 dir_table_size = 0;
|
||||
u64 file_table_size = 0;
|
||||
u64 dir_hash_table_size = 0;
|
||||
u64 file_hash_table_size = 0;
|
||||
u64 file_partition_size = 0;
|
||||
|
||||
void VisitDirectory(VirtualDir filesys, VirtualDir ext_dir,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent);
|
||||
|
||||
bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
|
||||
bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildFileContext> file_ctx);
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
|
||||
namespace FileSys::Sf {
|
||||
|
||||
struct Path {
|
||||
char str[EntryNameLengthMax + 1];
|
||||
|
||||
static constexpr Path Encode(const char* p) {
|
||||
Path path = {};
|
||||
for (size_t i = 0; i < sizeof(path) - 1; i++) {
|
||||
path.str[i] = p[i];
|
||||
if (p[i] == '\x00') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static constexpr size_t GetPathLength(const Path& path) {
|
||||
size_t len = 0;
|
||||
for (size_t i = 0; i < sizeof(path) - 1 && path.str[i] != '\x00'; i++) {
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<Path>, "Path must be trivially copyable.");
|
||||
|
||||
using FspPath = Path;
|
||||
|
||||
} // namespace FileSys::Sf
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/overflow.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class IStorage : public VfsFile {
|
||||
public:
|
||||
virtual std::string GetName() const override {
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual VirtualDir GetContainingDirectory() const override {
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual bool IsWritable() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool IsReadable() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool Resize(size_t size) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool Rename(std::string_view name) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) {
|
||||
R_UNLESS(offset >= 0, ResultInvalidOffset);
|
||||
R_UNLESS(size >= 0, ResultInvalidSize);
|
||||
R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange);
|
||||
R_UNLESS(offset + size <= total_size, ResultOutOfRange);
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
class IReadOnlyStorage : public IStorage {
|
||||
public:
|
||||
virtual bool IsWritable() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct Int64 {
|
||||
u32 low;
|
||||
u32 high;
|
||||
|
||||
constexpr void Set(s64 v) {
|
||||
this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0);
|
||||
this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32);
|
||||
}
|
||||
|
||||
constexpr s64 Get() const {
|
||||
return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low));
|
||||
}
|
||||
|
||||
constexpr Int64& operator=(s64 v) {
|
||||
this->Set(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr operator s64() const {
|
||||
return this->Get();
|
||||
}
|
||||
};
|
||||
|
||||
struct HashSalt {
|
||||
static constexpr size_t Size = 32;
|
||||
|
||||
std::array<u8, Size> value;
|
||||
};
|
||||
static_assert(std::is_trivial_v<HashSalt>);
|
||||
static_assert(sizeof(HashSalt) == HashSalt::Size);
|
||||
|
||||
constexpr inline size_t IntegrityMinLayerCount = 2;
|
||||
constexpr inline size_t IntegrityMaxLayerCount = 7;
|
||||
constexpr inline size_t IntegrityLayerCountSave = 5;
|
||||
constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4;
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_nca_header.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
|
||||
public:
|
||||
virtual void Decrypt(
|
||||
u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
|
||||
const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) {
|
||||
std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>();
|
||||
R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA);
|
||||
*out = std::move(decryptor);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
|
||||
VirtualFile data_storage,
|
||||
VirtualFile table_storage) {
|
||||
// Read and verify the bucket tree header.
|
||||
BucketTree::Header header;
|
||||
table_storage->ReadObject(std::addressof(header), 0);
|
||||
R_TRY(header.Verify());
|
||||
|
||||
// Determine extents.
|
||||
const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
|
||||
const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
|
||||
const auto node_storage_offset = QueryHeaderStorageSize();
|
||||
const auto entry_storage_offset = node_storage_offset + node_storage_size;
|
||||
|
||||
// Create a software decryptor.
|
||||
std::unique_ptr<IDecryptor> sw_decryptor;
|
||||
R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
|
||||
|
||||
// Initialize.
|
||||
R_RETURN(this->Initialize(
|
||||
key, key_size, secure_value, 0, data_storage,
|
||||
std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
|
||||
std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
|
||||
header.entry_count, std::move(sw_decryptor)));
|
||||
}
|
||||
|
||||
Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
|
||||
s64 counter_offset, VirtualFile data_storage,
|
||||
VirtualFile node_storage, VirtualFile entry_storage,
|
||||
s32 entry_count,
|
||||
std::unique_ptr<IDecryptor>&& decryptor) {
|
||||
// Validate preconditions.
|
||||
ASSERT(key != nullptr);
|
||||
ASSERT(key_size == KeySize);
|
||||
ASSERT(counter_offset >= 0);
|
||||
ASSERT(decryptor != nullptr);
|
||||
|
||||
// Initialize the bucket tree table.
|
||||
if (entry_count > 0) {
|
||||
R_TRY(
|
||||
m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
|
||||
} else {
|
||||
m_table.Initialize(NodeSize, 0);
|
||||
}
|
||||
|
||||
// Set members.
|
||||
m_data_storage = data_storage;
|
||||
std::memcpy(m_key.data(), key, key_size);
|
||||
m_secure_value = secure_value;
|
||||
m_counter_offset = counter_offset;
|
||||
m_decryptor = std::move(decryptor);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void AesCtrCounterExtendedStorage::Finalize() {
|
||||
if (this->IsInitialized()) {
|
||||
m_table.Finalize();
|
||||
m_data_storage = VirtualFile();
|
||||
}
|
||||
}
|
||||
|
||||
Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count,
|
||||
s32 entry_count, s64 offset, s64 size) {
|
||||
// Validate pre-conditions.
|
||||
ASSERT(offset >= 0);
|
||||
ASSERT(size >= 0);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
// Clear the out count.
|
||||
R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
|
||||
*out_entry_count = 0;
|
||||
|
||||
// Succeed if there's no range.
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// If we have an output array, we need it to be non-null.
|
||||
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
|
||||
|
||||
// Check that our range is valid.
|
||||
BucketTree::Offsets table_offsets;
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
|
||||
|
||||
// Find the offset in our tree.
|
||||
BucketTree::Visitor visitor;
|
||||
R_TRY(m_table.Find(std::addressof(visitor), offset));
|
||||
{
|
||||
const auto entry_offset = visitor.Get<Entry>()->GetOffset();
|
||||
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
|
||||
ResultInvalidAesCtrCounterExtendedEntryOffset);
|
||||
}
|
||||
|
||||
// Prepare to loop over entries.
|
||||
const auto end_offset = offset + static_cast<s64>(size);
|
||||
s32 count = 0;
|
||||
|
||||
auto cur_entry = *visitor.Get<Entry>();
|
||||
while (cur_entry.GetOffset() < end_offset) {
|
||||
// Try to write the entry to the out list.
|
||||
if (entry_count != 0) {
|
||||
if (count >= entry_count) {
|
||||
break;
|
||||
}
|
||||
std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
// Advance.
|
||||
if (visitor.CanMoveNext()) {
|
||||
R_TRY(visitor.MoveNext());
|
||||
cur_entry = *visitor.Get<Entry>();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the output count.
|
||||
*out_entry_count = count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Validate preconditions.
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
// Allow zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
ASSERT(Common::IsAligned(offset, BlockSize));
|
||||
ASSERT(Common::IsAligned(size, BlockSize));
|
||||
|
||||
BucketTree::Offsets table_offsets;
|
||||
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
|
||||
|
||||
ASSERT(table_offsets.IsInclude(offset, size));
|
||||
|
||||
// Read the data.
|
||||
m_data_storage->Read(buffer, size, offset);
|
||||
|
||||
// Find the offset in our tree.
|
||||
BucketTree::Visitor visitor;
|
||||
ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset)));
|
||||
{
|
||||
const auto entry_offset = visitor.Get<Entry>()->GetOffset();
|
||||
ASSERT(Common::IsAligned(entry_offset, BlockSize));
|
||||
ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset));
|
||||
}
|
||||
|
||||
// Prepare to read in chunks.
|
||||
u8* cur_data = static_cast<u8*>(buffer);
|
||||
auto cur_offset = offset;
|
||||
const auto end_offset = offset + static_cast<s64>(size);
|
||||
|
||||
while (cur_offset < end_offset) {
|
||||
// Get the current entry.
|
||||
const auto cur_entry = *visitor.Get<Entry>();
|
||||
|
||||
// Get and validate the entry's offset.
|
||||
const auto cur_entry_offset = cur_entry.GetOffset();
|
||||
ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset);
|
||||
|
||||
// Get and validate the next entry offset.
|
||||
s64 next_entry_offset;
|
||||
if (visitor.CanMoveNext()) {
|
||||
ASSERT(R_SUCCEEDED(visitor.MoveNext()));
|
||||
next_entry_offset = visitor.Get<Entry>()->GetOffset();
|
||||
ASSERT(table_offsets.IsInclude(next_entry_offset));
|
||||
} else {
|
||||
next_entry_offset = table_offsets.end_offset;
|
||||
}
|
||||
ASSERT(Common::IsAligned(next_entry_offset, BlockSize));
|
||||
ASSERT(cur_offset < static_cast<size_t>(next_entry_offset));
|
||||
|
||||
// Get the offset of the entry in the data we read.
|
||||
const auto data_offset = cur_offset - cur_entry_offset;
|
||||
const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset;
|
||||
ASSERT(data_size > 0);
|
||||
|
||||
// Determine how much is left.
|
||||
const auto remaining_size = end_offset - cur_offset;
|
||||
const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size));
|
||||
ASSERT(cur_size <= size);
|
||||
|
||||
// If necessary, perform decryption.
|
||||
if (cur_entry.encryption_value == Entry::Encryption::Encrypted) {
|
||||
// Make the CTR for the data we're decrypting.
|
||||
const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset;
|
||||
NcaAesCtrUpperIv upper_iv = {
|
||||
.part = {.generation = static_cast<u32>(cur_entry.generation),
|
||||
.secure_value = m_secure_value}};
|
||||
|
||||
std::array<u8, IvSize> iv;
|
||||
AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset);
|
||||
|
||||
// Decrypt.
|
||||
m_decryptor->Decrypt(cur_data, cur_size, m_key, iv);
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_data += cur_size;
|
||||
cur_offset += cur_size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size,
|
||||
const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
|
||||
const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) {
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher(
|
||||
key, Core::Crypto::Mode::CTR);
|
||||
cipher.SetIV(iv);
|
||||
cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "common/literals.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
class AesCtrCounterExtendedStorage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage);
|
||||
YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage);
|
||||
|
||||
public:
|
||||
static constexpr size_t BlockSize = 0x10;
|
||||
static constexpr size_t KeySize = 0x10;
|
||||
static constexpr size_t IvSize = 0x10;
|
||||
static constexpr size_t NodeSize = 16_KiB;
|
||||
|
||||
class IDecryptor {
|
||||
public:
|
||||
virtual ~IDecryptor() {}
|
||||
virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key,
|
||||
const std::array<u8, IvSize>& iv) = 0;
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
enum class Encryption : u8 {
|
||||
Encrypted = 0,
|
||||
NotEncrypted = 1,
|
||||
};
|
||||
|
||||
std::array<u8, sizeof(s64)> offset;
|
||||
Encryption encryption_value;
|
||||
std::array<u8, 3> reserved;
|
||||
s32 generation;
|
||||
|
||||
void SetOffset(s64 value) {
|
||||
std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64));
|
||||
}
|
||||
|
||||
s64 GetOffset() const {
|
||||
s64 value;
|
||||
std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64));
|
||||
return value;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Entry) == 0x10);
|
||||
static_assert(alignof(Entry) == 4);
|
||||
static_assert(std::is_trivial_v<Entry>);
|
||||
|
||||
public:
|
||||
static constexpr s64 QueryHeaderStorageSize() {
|
||||
return BucketTree::QueryHeaderStorageSize();
|
||||
}
|
||||
|
||||
static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
|
||||
return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
|
||||
}
|
||||
|
||||
static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
|
||||
return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
|
||||
}
|
||||
|
||||
static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out);
|
||||
|
||||
public:
|
||||
AesCtrCounterExtendedStorage()
|
||||
: m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {}
|
||||
virtual ~AesCtrCounterExtendedStorage() {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset,
|
||||
VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
|
||||
s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor);
|
||||
void Finalize();
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_table.IsInitialized();
|
||||
}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
BucketTree::Offsets offsets;
|
||||
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
|
||||
|
||||
return offsets.end_offset;
|
||||
}
|
||||
|
||||
Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
|
||||
s64 size);
|
||||
|
||||
private:
|
||||
Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage,
|
||||
VirtualFile table_storage);
|
||||
|
||||
private:
|
||||
mutable BucketTree m_table;
|
||||
VirtualFile m_data_storage;
|
||||
std::array<u8, KeySize> m_key;
|
||||
u32 m_secure_value;
|
||||
s64 m_counter_offset;
|
||||
std::unique_ptr<IDecryptor> m_decryptor;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
|
||||
#include "core/file_sys/fssystem/fssystem_utility.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) {
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(dst_size == IvSize);
|
||||
ASSERT(offset >= 0);
|
||||
|
||||
const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
|
||||
|
||||
*reinterpret_cast<u64_be*>(out_addr + 0) = upper;
|
||||
*reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize);
|
||||
}
|
||||
|
||||
AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
|
||||
size_t iv_size)
|
||||
: m_base_storage(std::move(base)) {
|
||||
ASSERT(m_base_storage != nullptr);
|
||||
ASSERT(key != nullptr);
|
||||
ASSERT(iv != nullptr);
|
||||
ASSERT(key_size == KeySize);
|
||||
ASSERT(iv_size == IvSize);
|
||||
|
||||
std::memcpy(m_key.data(), key, KeySize);
|
||||
std::memcpy(m_iv.data(), iv, IvSize);
|
||||
|
||||
m_cipher.emplace(m_key, Core::Crypto::Mode::CTR);
|
||||
}
|
||||
|
||||
size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Allow zero-size reads.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Ensure buffer is valid.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// We can only read at block aligned offsets.
|
||||
ASSERT(Common::IsAligned(offset, BlockSize));
|
||||
ASSERT(Common::IsAligned(size, BlockSize));
|
||||
|
||||
// Read the data.
|
||||
m_base_storage->Read(buffer, size, offset);
|
||||
|
||||
// Setup the counter.
|
||||
std::array<u8, IvSize> ctr;
|
||||
std::memcpy(ctr.data(), m_iv.data(), IvSize);
|
||||
AddCounter(ctr.data(), IvSize, offset / BlockSize);
|
||||
|
||||
// Decrypt.
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) {
|
||||
// Allow zero-size writes.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Ensure buffer is valid.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// We can only write at block aligned offsets.
|
||||
ASSERT(Common::IsAligned(offset, BlockSize));
|
||||
ASSERT(Common::IsAligned(size, BlockSize));
|
||||
|
||||
// Get a pooled buffer.
|
||||
PooledBuffer pooled_buffer;
|
||||
const bool use_work_buffer = true;
|
||||
if (use_work_buffer) {
|
||||
pooled_buffer.Allocate(size, BlockSize);
|
||||
}
|
||||
|
||||
// Setup the counter.
|
||||
std::array<u8, IvSize> ctr;
|
||||
std::memcpy(ctr.data(), m_iv.data(), IvSize);
|
||||
AddCounter(ctr.data(), IvSize, offset / BlockSize);
|
||||
|
||||
// Loop until all data is written.
|
||||
size_t remaining = size;
|
||||
s64 cur_offset = 0;
|
||||
while (remaining > 0) {
|
||||
// Determine data we're writing and where.
|
||||
const size_t write_size =
|
||||
use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
|
||||
|
||||
void* write_buf;
|
||||
if (use_work_buffer) {
|
||||
write_buf = pooled_buffer.GetBuffer();
|
||||
} else {
|
||||
write_buf = const_cast<u8*>(buffer);
|
||||
}
|
||||
|
||||
// Encrypt the data.
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf),
|
||||
Core::Crypto::Op::Encrypt);
|
||||
|
||||
// Write the encrypted data.
|
||||
m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset);
|
||||
|
||||
// Advance.
|
||||
cur_offset += write_size;
|
||||
remaining -= write_size;
|
||||
if (remaining > 0) {
|
||||
AddCounter(ctr.data(), IvSize, write_size / BlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t AesCtrStorage::GetSize() const {
|
||||
return m_base_storage->GetSize();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class AesCtrStorage : public IStorage {
|
||||
YUZU_NON_COPYABLE(AesCtrStorage);
|
||||
YUZU_NON_MOVEABLE(AesCtrStorage);
|
||||
|
||||
public:
|
||||
static constexpr size_t BlockSize = 0x10;
|
||||
static constexpr size_t KeySize = 0x10;
|
||||
static constexpr size_t IvSize = 0x10;
|
||||
|
||||
public:
|
||||
static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset);
|
||||
|
||||
public:
|
||||
AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
|
||||
size_t iv_size);
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override;
|
||||
virtual size_t GetSize() const override;
|
||||
|
||||
private:
|
||||
VirtualFile m_base_storage;
|
||||
std::array<u8, KeySize> m_key;
|
||||
std::array<u8, IvSize> m_iv;
|
||||
mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
|
||||
#include "core/file_sys/fssystem/fssystem_utility.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) {
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(dst_size == IvSize);
|
||||
ASSERT(offset >= 0);
|
||||
|
||||
const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
|
||||
|
||||
*reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size;
|
||||
}
|
||||
|
||||
AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
|
||||
const void* iv, size_t iv_size, size_t block_size)
|
||||
: m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() {
|
||||
ASSERT(m_base_storage != nullptr);
|
||||
ASSERT(key1 != nullptr);
|
||||
ASSERT(key2 != nullptr);
|
||||
ASSERT(iv != nullptr);
|
||||
ASSERT(key_size == KeySize);
|
||||
ASSERT(iv_size == IvSize);
|
||||
ASSERT(Common::IsAligned(m_block_size, AesBlockSize));
|
||||
|
||||
std::memcpy(m_key.data() + 0, key1, KeySize / 2);
|
||||
std::memcpy(m_key.data() + 0x10, key2, KeySize / 2);
|
||||
std::memcpy(m_iv.data(), iv, IvSize);
|
||||
|
||||
m_cipher.emplace(m_key, Core::Crypto::Mode::XTS);
|
||||
}
|
||||
|
||||
size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Allow zero-size reads.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Ensure buffer is valid.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// We can only read at block aligned offsets.
|
||||
ASSERT(Common::IsAligned(offset, AesBlockSize));
|
||||
ASSERT(Common::IsAligned(size, AesBlockSize));
|
||||
|
||||
// Read the data.
|
||||
m_base_storage->Read(buffer, size, offset);
|
||||
|
||||
// Setup the counter.
|
||||
std::array<u8, IvSize> ctr;
|
||||
std::memcpy(ctr.data(), m_iv.data(), IvSize);
|
||||
AddCounter(ctr.data(), IvSize, offset / m_block_size);
|
||||
|
||||
// Handle any unaligned data before the start.
|
||||
size_t processed_size = 0;
|
||||
if ((offset % m_block_size) != 0) {
|
||||
// Determine the size of the pre-data read.
|
||||
const size_t skip_size =
|
||||
static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size));
|
||||
const size_t data_size = std::min(size, m_block_size - skip_size);
|
||||
|
||||
// Decrypt into a pooled buffer.
|
||||
{
|
||||
PooledBuffer tmp_buf(m_block_size, m_block_size);
|
||||
ASSERT(tmp_buf.GetSize() >= m_block_size);
|
||||
|
||||
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
|
||||
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
|
||||
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(),
|
||||
Core::Crypto::Op::Decrypt);
|
||||
|
||||
std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
|
||||
}
|
||||
|
||||
AddCounter(ctr.data(), IvSize, 1);
|
||||
processed_size += data_size;
|
||||
ASSERT(processed_size == std::min(size, m_block_size - skip_size));
|
||||
}
|
||||
|
||||
// Decrypt aligned chunks.
|
||||
char* cur = reinterpret_cast<char*>(buffer) + processed_size;
|
||||
size_t remaining = size - processed_size;
|
||||
while (remaining > 0) {
|
||||
const size_t cur_size = std::min(m_block_size, remaining);
|
||||
|
||||
m_cipher->SetIV(ctr);
|
||||
m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt);
|
||||
|
||||
remaining -= cur_size;
|
||||
cur += cur_size;
|
||||
|
||||
AddCounter(ctr.data(), IvSize, 1);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t AesXtsStorage::GetSize() const {
|
||||
return m_base_storage->GetSize();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class AesXtsStorage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(AesXtsStorage);
|
||||
YUZU_NON_MOVEABLE(AesXtsStorage);
|
||||
|
||||
public:
|
||||
static constexpr size_t AesBlockSize = 0x10;
|
||||
static constexpr size_t KeySize = 0x20;
|
||||
static constexpr size_t IvSize = 0x10;
|
||||
|
||||
public:
|
||||
static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size);
|
||||
|
||||
public:
|
||||
AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
|
||||
const void* iv, size_t iv_size, size_t block_size);
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
virtual size_t GetSize() const override;
|
||||
|
||||
private:
|
||||
VirtualFile m_base_storage;
|
||||
std::array<u8, KeySize> m_key;
|
||||
std::array<u8, IvSize> m_iv;
|
||||
const size_t m_block_size;
|
||||
std::mutex m_mutex;
|
||||
mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
|
||||
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
template <size_t DataAlign_, size_t BufferAlign_>
|
||||
class AlignmentMatchingStorage : public IStorage {
|
||||
YUZU_NON_COPYABLE(AlignmentMatchingStorage);
|
||||
YUZU_NON_MOVEABLE(AlignmentMatchingStorage);
|
||||
|
||||
public:
|
||||
static constexpr size_t DataAlign = DataAlign_;
|
||||
static constexpr size_t BufferAlign = BufferAlign_;
|
||||
|
||||
static constexpr size_t DataAlignMax = 0x200;
|
||||
static_assert(DataAlign <= DataAlignMax);
|
||||
static_assert(Common::IsPowerOfTwo(DataAlign));
|
||||
static_assert(Common::IsPowerOfTwo(BufferAlign));
|
||||
|
||||
private:
|
||||
VirtualFile m_base_storage;
|
||||
s64 m_base_storage_size;
|
||||
|
||||
public:
|
||||
explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
|
||||
// Allocate a work buffer on stack.
|
||||
alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
|
||||
|
||||
// Succeed if zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
s64 bs_size = this->GetSize();
|
||||
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
|
||||
|
||||
return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(),
|
||||
DataAlign, BufferAlign, offset, buffer, size);
|
||||
}
|
||||
|
||||
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
|
||||
// Allocate a work buffer on stack.
|
||||
alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
|
||||
|
||||
// Succeed if zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
s64 bs_size = this->GetSize();
|
||||
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
|
||||
|
||||
return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(),
|
||||
DataAlign, BufferAlign, offset, buffer, size);
|
||||
}
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
return m_base_storage->GetSize();
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t BufferAlign_>
|
||||
class AlignmentMatchingStoragePooledBuffer : public IStorage {
|
||||
YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer);
|
||||
YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer);
|
||||
|
||||
public:
|
||||
static constexpr size_t BufferAlign = BufferAlign_;
|
||||
|
||||
static_assert(Common::IsPowerOfTwo(BufferAlign));
|
||||
|
||||
private:
|
||||
VirtualFile m_base_storage;
|
||||
s64 m_base_storage_size;
|
||||
size_t m_data_align;
|
||||
|
||||
public:
|
||||
explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da)
|
||||
: m_base_storage(std::move(bs)), m_data_align(da) {
|
||||
ASSERT(Common::IsPowerOfTwo(da));
|
||||
}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
|
||||
// Succeed if zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
s64 bs_size = this->GetSize();
|
||||
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
|
||||
|
||||
// Allocate a pooled buffer.
|
||||
PooledBuffer pooled_buffer;
|
||||
pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
|
||||
|
||||
return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(),
|
||||
pooled_buffer.GetSize(), m_data_align,
|
||||
BufferAlign, offset, buffer, size);
|
||||
}
|
||||
|
||||
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
|
||||
// Succeed if zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
s64 bs_size = this->GetSize();
|
||||
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
|
||||
|
||||
// Allocate a pooled buffer.
|
||||
PooledBuffer pooled_buffer;
|
||||
pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
|
||||
|
||||
return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(),
|
||||
pooled_buffer.GetSize(), m_data_align,
|
||||
BufferAlign, offset, buffer, size);
|
||||
}
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
return m_base_storage->GetSize();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
constexpr size_t GetRoundDownDifference(T x, size_t align) {
|
||||
return static_cast<size_t>(x - Common::AlignDown(x, align));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr size_t GetRoundUpDifference(T x, size_t align) {
|
||||
return static_cast<size_t>(Common::AlignUp(x, align) - x);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t GetRoundUpDifference(T* x, size_t align) {
|
||||
return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf,
|
||||
size_t work_buf_size, size_t data_alignment,
|
||||
size_t buffer_alignment, s64 offset, u8* buffer,
|
||||
size_t size) {
|
||||
// Check preconditions.
|
||||
ASSERT(work_buf_size >= data_alignment);
|
||||
|
||||
// Succeed if zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// Determine extents.
|
||||
u8* aligned_core_buffer;
|
||||
s64 core_offset;
|
||||
size_t core_size;
|
||||
size_t buffer_gap;
|
||||
size_t offset_gap;
|
||||
s64 covered_offset;
|
||||
|
||||
const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
|
||||
if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
|
||||
buffer_alignment)) {
|
||||
aligned_core_buffer = buffer + offset_round_up_difference;
|
||||
|
||||
core_offset = Common::AlignUp(offset, data_alignment);
|
||||
core_size = (size < offset_round_up_difference)
|
||||
? 0
|
||||
: Common::AlignDown(size - offset_round_up_difference, data_alignment);
|
||||
buffer_gap = 0;
|
||||
offset_gap = 0;
|
||||
|
||||
covered_offset = core_size > 0 ? core_offset : offset;
|
||||
} else {
|
||||
const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment);
|
||||
|
||||
aligned_core_buffer = buffer + buffer_round_up_difference;
|
||||
|
||||
core_offset = Common::AlignDown(offset, data_alignment);
|
||||
core_size = (size < buffer_round_up_difference)
|
||||
? 0
|
||||
: Common::AlignDown(size - buffer_round_up_difference, data_alignment);
|
||||
buffer_gap = buffer_round_up_difference;
|
||||
offset_gap = GetRoundDownDifference(offset, data_alignment);
|
||||
|
||||
covered_offset = offset;
|
||||
}
|
||||
|
||||
// Read the core portion.
|
||||
if (core_size > 0) {
|
||||
base_storage->Read(aligned_core_buffer, core_size, core_offset);
|
||||
|
||||
if (offset_gap != 0 || buffer_gap != 0) {
|
||||
std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap,
|
||||
core_size - offset_gap);
|
||||
core_size -= offset_gap;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the head portion.
|
||||
if (offset < covered_offset) {
|
||||
const s64 head_offset = Common::AlignDown(offset, data_alignment);
|
||||
const size_t head_size = static_cast<size_t>(covered_offset - offset);
|
||||
|
||||
ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size);
|
||||
|
||||
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
|
||||
std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size);
|
||||
}
|
||||
|
||||
// Handle the tail portion.
|
||||
s64 tail_offset = covered_offset + core_size;
|
||||
size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
|
||||
while (remaining_tail_size > 0) {
|
||||
const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
|
||||
const auto cur_size =
|
||||
std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
|
||||
remaining_tail_size);
|
||||
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
|
||||
|
||||
ASSERT((tail_offset - offset) + cur_size <= size);
|
||||
ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment);
|
||||
std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset),
|
||||
work_buf + (tail_offset - aligned_tail_offset), cur_size);
|
||||
|
||||
remaining_tail_size -= cur_size;
|
||||
tail_offset += cur_size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf,
|
||||
size_t work_buf_size, size_t data_alignment,
|
||||
size_t buffer_alignment, s64 offset, const u8* buffer,
|
||||
size_t size) {
|
||||
// Check preconditions.
|
||||
ASSERT(work_buf_size >= data_alignment);
|
||||
|
||||
// Succeed if zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// Determine extents.
|
||||
const u8* aligned_core_buffer;
|
||||
s64 core_offset;
|
||||
size_t core_size;
|
||||
s64 covered_offset;
|
||||
|
||||
const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
|
||||
if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
|
||||
buffer_alignment)) {
|
||||
aligned_core_buffer = buffer + offset_round_up_difference;
|
||||
|
||||
core_offset = Common::AlignUp(offset, data_alignment);
|
||||
core_size = (size < offset_round_up_difference)
|
||||
? 0
|
||||
: Common::AlignDown(size - offset_round_up_difference, data_alignment);
|
||||
|
||||
covered_offset = core_size > 0 ? core_offset : offset;
|
||||
} else {
|
||||
aligned_core_buffer = nullptr;
|
||||
|
||||
core_offset = Common::AlignDown(offset, data_alignment);
|
||||
core_size = 0;
|
||||
|
||||
covered_offset = offset;
|
||||
}
|
||||
|
||||
// Write the core portion.
|
||||
if (core_size > 0) {
|
||||
base_storage->Write(aligned_core_buffer, core_size, core_offset);
|
||||
}
|
||||
|
||||
// Handle the head portion.
|
||||
if (offset < covered_offset) {
|
||||
const s64 head_offset = Common::AlignDown(offset, data_alignment);
|
||||
const size_t head_size = static_cast<size_t>(covered_offset - offset);
|
||||
|
||||
ASSERT((offset - head_offset) + head_size <= data_alignment);
|
||||
|
||||
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
|
||||
std::memcpy(work_buf + (offset - head_offset), buffer, head_size);
|
||||
base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
|
||||
}
|
||||
|
||||
// Handle the tail portion.
|
||||
s64 tail_offset = covered_offset + core_size;
|
||||
size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
|
||||
while (remaining_tail_size > 0) {
|
||||
ASSERT(static_cast<size_t>(tail_offset - offset) < size);
|
||||
|
||||
const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
|
||||
const auto cur_size =
|
||||
std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
|
||||
remaining_tail_size);
|
||||
|
||||
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
|
||||
std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment),
|
||||
buffer + (tail_offset - offset), cur_size);
|
||||
base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
|
||||
|
||||
remaining_tail_size -= cur_size;
|
||||
tail_offset += cur_size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class AlignmentMatchingStorageImpl {
|
||||
public:
|
||||
static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
|
||||
size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer,
|
||||
size_t size);
|
||||
static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
|
||||
size_t data_alignment, size_t buffer_alignment, s64 offset,
|
||||
const u8* buffer, size_t size);
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,598 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
|
||||
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
using Node = impl::BucketTreeNode<const s64*>;
|
||||
static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
|
||||
static_assert(std::is_trivial_v<Node>);
|
||||
|
||||
constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
|
||||
|
||||
class StorageNode {
|
||||
private:
|
||||
class Offset {
|
||||
public:
|
||||
using difference_type = s64;
|
||||
|
||||
private:
|
||||
s64 m_offset;
|
||||
s32 m_stride;
|
||||
|
||||
public:
|
||||
constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {}
|
||||
|
||||
constexpr Offset& operator++() {
|
||||
m_offset += m_stride;
|
||||
return *this;
|
||||
}
|
||||
constexpr Offset operator++(int) {
|
||||
Offset ret(*this);
|
||||
m_offset += m_stride;
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr Offset& operator--() {
|
||||
m_offset -= m_stride;
|
||||
return *this;
|
||||
}
|
||||
constexpr Offset operator--(int) {
|
||||
Offset ret(*this);
|
||||
m_offset -= m_stride;
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr difference_type operator-(const Offset& rhs) const {
|
||||
return (m_offset - rhs.m_offset) / m_stride;
|
||||
}
|
||||
|
||||
constexpr Offset operator+(difference_type ofs) const {
|
||||
return Offset(m_offset + ofs * m_stride, m_stride);
|
||||
}
|
||||
constexpr Offset operator-(difference_type ofs) const {
|
||||
return Offset(m_offset - ofs * m_stride, m_stride);
|
||||
}
|
||||
|
||||
constexpr Offset& operator+=(difference_type ofs) {
|
||||
m_offset += ofs * m_stride;
|
||||
return *this;
|
||||
}
|
||||
constexpr Offset& operator-=(difference_type ofs) {
|
||||
m_offset -= ofs * m_stride;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const Offset& rhs) const {
|
||||
return m_offset == rhs.m_offset;
|
||||
}
|
||||
constexpr bool operator!=(const Offset& rhs) const {
|
||||
return m_offset != rhs.m_offset;
|
||||
}
|
||||
|
||||
constexpr s64 Get() const {
|
||||
return m_offset;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
const Offset m_start;
|
||||
const s32 m_count;
|
||||
s32 m_index;
|
||||
|
||||
public:
|
||||
StorageNode(size_t size, s32 count)
|
||||
: m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {}
|
||||
StorageNode(s64 ofs, size_t size, s32 count)
|
||||
: m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {}
|
||||
|
||||
s32 GetIndex() const {
|
||||
return m_index;
|
||||
}
|
||||
|
||||
void Find(const char* buffer, s64 virtual_address) {
|
||||
s32 end = m_count;
|
||||
auto pos = m_start;
|
||||
|
||||
while (end > 0) {
|
||||
auto half = end / 2;
|
||||
auto mid = pos + half;
|
||||
|
||||
s64 offset = 0;
|
||||
std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64));
|
||||
|
||||
if (offset <= virtual_address) {
|
||||
pos = mid + 1;
|
||||
end -= half + 1;
|
||||
} else {
|
||||
end = half;
|
||||
}
|
||||
}
|
||||
|
||||
m_index = static_cast<s32>(pos - m_start) - 1;
|
||||
}
|
||||
|
||||
Result Find(VirtualFile storage, s64 virtual_address) {
|
||||
s32 end = m_count;
|
||||
auto pos = m_start;
|
||||
|
||||
while (end > 0) {
|
||||
auto half = end / 2;
|
||||
auto mid = pos + half;
|
||||
|
||||
s64 offset = 0;
|
||||
storage->ReadObject(std::addressof(offset), mid.Get());
|
||||
|
||||
if (offset <= virtual_address) {
|
||||
pos = mid + 1;
|
||||
end -= half + 1;
|
||||
} else {
|
||||
end = half;
|
||||
}
|
||||
}
|
||||
|
||||
m_index = static_cast<s32>(pos - m_start) - 1;
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void BucketTree::Header::Format(s32 entry_count_) {
|
||||
ASSERT(entry_count_ >= 0);
|
||||
|
||||
this->magic = Magic;
|
||||
this->version = Version;
|
||||
this->entry_count = entry_count_;
|
||||
this->reserved = 0;
|
||||
}
|
||||
|
||||
Result BucketTree::Header::Verify() const {
|
||||
R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature);
|
||||
R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount);
|
||||
R_UNLESS(this->version <= Version, ResultUnsupportedVersion);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const {
|
||||
R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex);
|
||||
R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize);
|
||||
|
||||
const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size;
|
||||
R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count,
|
||||
ResultInvalidBucketTreeNodeEntryCount);
|
||||
R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
|
||||
size_t entry_size, s32 entry_count) {
|
||||
// Validate preconditions.
|
||||
ASSERT(entry_size >= sizeof(s64));
|
||||
ASSERT(node_size >= entry_size + sizeof(NodeHeader));
|
||||
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
|
||||
ASSERT(Common::IsPowerOfTwo(node_size));
|
||||
ASSERT(!this->IsInitialized());
|
||||
|
||||
// Ensure valid entry count.
|
||||
R_UNLESS(entry_count > 0, ResultInvalidArgument);
|
||||
|
||||
// Allocate node.
|
||||
R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed);
|
||||
ON_RESULT_FAILURE {
|
||||
m_node_l1.Free(node_size);
|
||||
};
|
||||
|
||||
// Read node.
|
||||
node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size);
|
||||
|
||||
// Verify node.
|
||||
R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64)));
|
||||
|
||||
// Validate offsets.
|
||||
const auto offset_count = GetOffsetCount(node_size);
|
||||
const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
|
||||
const auto* const node = m_node_l1.Get<Node>();
|
||||
|
||||
s64 start_offset;
|
||||
if (offset_count < entry_set_count && node->GetCount() < offset_count) {
|
||||
start_offset = *node->GetEnd();
|
||||
} else {
|
||||
start_offset = *node->GetBegin();
|
||||
}
|
||||
const auto end_offset = node->GetEndOffset();
|
||||
|
||||
R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
|
||||
ResultInvalidBucketTreeEntryOffset);
|
||||
R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
|
||||
|
||||
// Set member variables.
|
||||
m_node_storage = node_storage;
|
||||
m_entry_storage = entry_storage;
|
||||
m_node_size = node_size;
|
||||
m_entry_size = entry_size;
|
||||
m_entry_count = entry_count;
|
||||
m_offset_count = offset_count;
|
||||
m_entry_set_count = entry_set_count;
|
||||
|
||||
m_offset_cache.offsets.start_offset = start_offset;
|
||||
m_offset_cache.offsets.end_offset = end_offset;
|
||||
m_offset_cache.is_initialized = true;
|
||||
|
||||
// We succeeded.
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void BucketTree::Initialize(size_t node_size, s64 end_offset) {
|
||||
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
|
||||
ASSERT(Common::IsPowerOfTwo(node_size));
|
||||
ASSERT(end_offset > 0);
|
||||
ASSERT(!this->IsInitialized());
|
||||
|
||||
m_node_size = node_size;
|
||||
|
||||
m_offset_cache.offsets.start_offset = 0;
|
||||
m_offset_cache.offsets.end_offset = end_offset;
|
||||
m_offset_cache.is_initialized = true;
|
||||
}
|
||||
|
||||
void BucketTree::Finalize() {
|
||||
if (this->IsInitialized()) {
|
||||
m_node_storage = VirtualFile();
|
||||
m_entry_storage = VirtualFile();
|
||||
m_node_l1.Free(m_node_size);
|
||||
m_node_size = 0;
|
||||
m_entry_size = 0;
|
||||
m_entry_count = 0;
|
||||
m_offset_count = 0;
|
||||
m_entry_set_count = 0;
|
||||
|
||||
m_offset_cache.offsets.start_offset = 0;
|
||||
m_offset_cache.offsets.end_offset = 0;
|
||||
m_offset_cache.is_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
Result BucketTree::Find(Visitor* visitor, s64 virtual_address) {
|
||||
ASSERT(visitor != nullptr);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
R_UNLESS(virtual_address >= 0, ResultInvalidOffset);
|
||||
R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
|
||||
|
||||
BucketTree::Offsets offsets;
|
||||
R_TRY(this->GetOffsets(std::addressof(offsets)));
|
||||
|
||||
R_TRY(visitor->Initialize(this, offsets));
|
||||
|
||||
R_RETURN(visitor->Find(virtual_address));
|
||||
}
|
||||
|
||||
Result BucketTree::InvalidateCache() {
|
||||
// Reset our offsets.
|
||||
m_offset_cache.is_initialized = false;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::EnsureOffsetCache() {
|
||||
// If we already have an offset cache, we're good.
|
||||
R_SUCCEED_IF(m_offset_cache.is_initialized);
|
||||
|
||||
// Acquire exclusive right to edit the offset cache.
|
||||
std::scoped_lock lk(m_offset_cache.mutex);
|
||||
|
||||
// Check again, to be sure.
|
||||
R_SUCCEED_IF(m_offset_cache.is_initialized);
|
||||
|
||||
// Read/verify L1.
|
||||
m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size);
|
||||
R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64)));
|
||||
|
||||
// Get the node.
|
||||
auto* const node = m_node_l1.Get<Node>();
|
||||
|
||||
s64 start_offset;
|
||||
if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) {
|
||||
start_offset = *node->GetEnd();
|
||||
} else {
|
||||
start_offset = *node->GetBegin();
|
||||
}
|
||||
const auto end_offset = node->GetEndOffset();
|
||||
|
||||
R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
|
||||
ResultInvalidBucketTreeEntryOffset);
|
||||
R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
|
||||
|
||||
m_offset_cache.offsets.start_offset = start_offset;
|
||||
m_offset_cache.offsets.end_offset = end_offset;
|
||||
m_offset_cache.is_initialized = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) {
|
||||
ASSERT(tree != nullptr);
|
||||
ASSERT(m_tree == nullptr || m_tree == tree);
|
||||
|
||||
if (m_entry == nullptr) {
|
||||
m_entry = ::operator new(tree->m_entry_size);
|
||||
R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed);
|
||||
|
||||
m_tree = tree;
|
||||
m_offsets = offsets;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::MoveNext() {
|
||||
R_UNLESS(this->IsValid(), ResultOutOfRange);
|
||||
|
||||
// Invalidate our index, and read the header for the next index.
|
||||
auto entry_index = m_entry_index + 1;
|
||||
if (entry_index == m_entry_set.info.count) {
|
||||
const auto entry_set_index = m_entry_set.info.index + 1;
|
||||
R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange);
|
||||
|
||||
m_entry_index = -1;
|
||||
|
||||
const auto end = m_entry_set.info.end;
|
||||
|
||||
const auto entry_set_size = m_tree->m_node_size;
|
||||
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
|
||||
|
||||
m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
|
||||
R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
|
||||
|
||||
R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end,
|
||||
ResultInvalidBucketTreeEntrySetOffset);
|
||||
|
||||
entry_index = 0;
|
||||
} else {
|
||||
m_entry_index = -1;
|
||||
}
|
||||
|
||||
// Read the new entry.
|
||||
const auto entry_size = m_tree->m_entry_size;
|
||||
const auto entry_offset = impl::GetBucketTreeEntryOffset(
|
||||
m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
|
||||
m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
|
||||
|
||||
// Note that we changed index.
|
||||
m_entry_index = entry_index;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::MovePrevious() {
|
||||
R_UNLESS(this->IsValid(), ResultOutOfRange);
|
||||
|
||||
// Invalidate our index, and read the header for the previous index.
|
||||
auto entry_index = m_entry_index;
|
||||
if (entry_index == 0) {
|
||||
R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange);
|
||||
|
||||
m_entry_index = -1;
|
||||
|
||||
const auto start = m_entry_set.info.start;
|
||||
|
||||
const auto entry_set_size = m_tree->m_node_size;
|
||||
const auto entry_set_index = m_entry_set.info.index - 1;
|
||||
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
|
||||
|
||||
m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
|
||||
R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
|
||||
|
||||
R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end,
|
||||
ResultInvalidBucketTreeEntrySetOffset);
|
||||
|
||||
entry_index = m_entry_set.info.count;
|
||||
} else {
|
||||
m_entry_index = -1;
|
||||
}
|
||||
|
||||
--entry_index;
|
||||
|
||||
// Read the new entry.
|
||||
const auto entry_size = m_tree->m_entry_size;
|
||||
const auto entry_offset = impl::GetBucketTreeEntryOffset(
|
||||
m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
|
||||
m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
|
||||
|
||||
// Note that we changed index.
|
||||
m_entry_index = entry_index;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::Find(s64 virtual_address) {
|
||||
ASSERT(m_tree != nullptr);
|
||||
|
||||
// Get the node.
|
||||
const auto* const node = m_tree->m_node_l1.Get<Node>();
|
||||
R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange);
|
||||
|
||||
// Get the entry set index.
|
||||
s32 entry_set_index = -1;
|
||||
if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) {
|
||||
const auto start = node->GetEnd();
|
||||
const auto end = node->GetBegin() + m_tree->m_offset_count;
|
||||
|
||||
auto pos = std::upper_bound(start, end, virtual_address);
|
||||
R_UNLESS(start < pos, ResultOutOfRange);
|
||||
--pos;
|
||||
|
||||
entry_set_index = static_cast<s32>(pos - start);
|
||||
} else {
|
||||
const auto start = node->GetBegin();
|
||||
const auto end = node->GetEnd();
|
||||
|
||||
auto pos = std::upper_bound(start, end, virtual_address);
|
||||
R_UNLESS(start < pos, ResultOutOfRange);
|
||||
--pos;
|
||||
|
||||
if (m_tree->IsExistL2()) {
|
||||
const auto node_index = static_cast<s32>(pos - start);
|
||||
R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count,
|
||||
ResultInvalidBucketTreeNodeOffset);
|
||||
|
||||
R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index));
|
||||
} else {
|
||||
entry_set_index = static_cast<s32>(pos - start);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the entry set index.
|
||||
R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count,
|
||||
ResultInvalidBucketTreeNodeOffset);
|
||||
|
||||
// Find the entry.
|
||||
R_TRY(this->FindEntry(virtual_address, entry_set_index));
|
||||
|
||||
// Set count.
|
||||
m_entry_set_count = m_tree->m_entry_set_count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) {
|
||||
const auto node_size = m_tree->m_node_size;
|
||||
|
||||
PooledBuffer pool(node_size, 1);
|
||||
if (node_size <= pool.GetSize()) {
|
||||
R_RETURN(
|
||||
this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer()));
|
||||
} else {
|
||||
pool.Deallocate();
|
||||
R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index));
|
||||
}
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address,
|
||||
s32 node_index, char* buffer) {
|
||||
// Calculate node extents.
|
||||
const auto node_size = m_tree->m_node_size;
|
||||
const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
|
||||
VirtualFile storage = m_tree->m_node_storage;
|
||||
|
||||
// Read the node.
|
||||
storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset);
|
||||
|
||||
// Validate the header.
|
||||
NodeHeader header;
|
||||
std::memcpy(std::addressof(header), buffer, NodeHeaderSize);
|
||||
R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
|
||||
|
||||
// Create the node, and find.
|
||||
StorageNode node(sizeof(s64), header.count);
|
||||
node.Find(buffer, virtual_address);
|
||||
R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset);
|
||||
|
||||
// Return the index.
|
||||
*out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address,
|
||||
s32 node_index) {
|
||||
// Calculate node extents.
|
||||
const auto node_size = m_tree->m_node_size;
|
||||
const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
|
||||
VirtualFile storage = m_tree->m_node_storage;
|
||||
|
||||
// Read and validate the header.
|
||||
NodeHeader header;
|
||||
storage->ReadObject(std::addressof(header), node_offset);
|
||||
R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
|
||||
|
||||
// Create the node, and find.
|
||||
StorageNode node(node_offset, sizeof(s64), header.count);
|
||||
R_TRY(node.Find(storage, virtual_address));
|
||||
R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
|
||||
|
||||
// Return the index.
|
||||
*out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) {
|
||||
const auto entry_set_size = m_tree->m_node_size;
|
||||
|
||||
PooledBuffer pool(entry_set_size, 1);
|
||||
if (entry_set_size <= pool.GetSize()) {
|
||||
R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer()));
|
||||
} else {
|
||||
pool.Deallocate();
|
||||
R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index));
|
||||
}
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index,
|
||||
char* buffer) {
|
||||
// Calculate entry set extents.
|
||||
const auto entry_size = m_tree->m_entry_size;
|
||||
const auto entry_set_size = m_tree->m_node_size;
|
||||
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
|
||||
VirtualFile storage = m_tree->m_entry_storage;
|
||||
|
||||
// Read the entry set.
|
||||
storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset);
|
||||
|
||||
// Validate the entry_set.
|
||||
EntrySetHeader entry_set;
|
||||
std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader));
|
||||
R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
|
||||
|
||||
// Create the node, and find.
|
||||
StorageNode node(entry_size, entry_set.info.count);
|
||||
node.Find(buffer, virtual_address);
|
||||
R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
|
||||
|
||||
// Copy the data into entry.
|
||||
const auto entry_index = node.GetIndex();
|
||||
const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index);
|
||||
std::memcpy(m_entry, buffer + entry_offset, entry_size);
|
||||
|
||||
// Set our entry set/index.
|
||||
m_entry_set = entry_set;
|
||||
m_entry_index = entry_index;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) {
|
||||
// Calculate entry set extents.
|
||||
const auto entry_size = m_tree->m_entry_size;
|
||||
const auto entry_set_size = m_tree->m_node_size;
|
||||
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
|
||||
VirtualFile storage = m_tree->m_entry_storage;
|
||||
|
||||
// Read and validate the entry_set.
|
||||
EntrySetHeader entry_set;
|
||||
storage->ReadObject(std::addressof(entry_set), entry_set_offset);
|
||||
R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
|
||||
|
||||
// Create the node, and find.
|
||||
StorageNode node(entry_set_offset, entry_size, entry_set.info.count);
|
||||
R_TRY(node.Find(storage, virtual_address));
|
||||
R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
|
||||
|
||||
// Copy the data into entry.
|
||||
const auto entry_index = node.GetIndex();
|
||||
const auto entry_offset =
|
||||
impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index);
|
||||
storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
|
||||
|
||||
// Set our entry set/index.
|
||||
m_entry_set = entry_set;
|
||||
m_entry_index = entry_index;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,416 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
class BucketTree {
|
||||
YUZU_NON_COPYABLE(BucketTree);
|
||||
YUZU_NON_MOVEABLE(BucketTree);
|
||||
|
||||
public:
|
||||
static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R');
|
||||
static constexpr u32 Version = 1;
|
||||
|
||||
static constexpr size_t NodeSizeMin = 1_KiB;
|
||||
static constexpr size_t NodeSizeMax = 512_KiB;
|
||||
|
||||
public:
|
||||
class Visitor;
|
||||
|
||||
struct Header {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
s32 entry_count;
|
||||
s32 reserved;
|
||||
|
||||
void Format(s32 entry_count);
|
||||
Result Verify() const;
|
||||
};
|
||||
static_assert(std::is_trivial_v<Header>);
|
||||
static_assert(sizeof(Header) == 0x10);
|
||||
|
||||
struct NodeHeader {
|
||||
s32 index;
|
||||
s32 count;
|
||||
s64 offset;
|
||||
|
||||
Result Verify(s32 node_index, size_t node_size, size_t entry_size) const;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NodeHeader>);
|
||||
static_assert(sizeof(NodeHeader) == 0x10);
|
||||
|
||||
struct Offsets {
|
||||
s64 start_offset;
|
||||
s64 end_offset;
|
||||
|
||||
constexpr bool IsInclude(s64 offset) const {
|
||||
return this->start_offset <= offset && offset < this->end_offset;
|
||||
}
|
||||
|
||||
constexpr bool IsInclude(s64 offset, s64 size) const {
|
||||
return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset);
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<Offsets>);
|
||||
static_assert(sizeof(Offsets) == 0x10);
|
||||
|
||||
struct OffsetCache {
|
||||
Offsets offsets;
|
||||
std::mutex mutex;
|
||||
bool is_initialized;
|
||||
|
||||
OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {}
|
||||
};
|
||||
|
||||
class ContinuousReadingInfo {
|
||||
public:
|
||||
constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {}
|
||||
|
||||
constexpr void Reset() {
|
||||
m_read_size = 0;
|
||||
m_skip_count = 0;
|
||||
m_done = false;
|
||||
}
|
||||
|
||||
constexpr void SetSkipCount(s32 count) {
|
||||
ASSERT(count >= 0);
|
||||
m_skip_count = count;
|
||||
}
|
||||
constexpr s32 GetSkipCount() const {
|
||||
return m_skip_count;
|
||||
}
|
||||
constexpr bool CheckNeedScan() {
|
||||
return (--m_skip_count) <= 0;
|
||||
}
|
||||
|
||||
constexpr void Done() {
|
||||
m_read_size = 0;
|
||||
m_done = true;
|
||||
}
|
||||
constexpr bool IsDone() const {
|
||||
return m_done;
|
||||
}
|
||||
|
||||
constexpr void SetReadSize(size_t size) {
|
||||
m_read_size = size;
|
||||
}
|
||||
constexpr size_t GetReadSize() const {
|
||||
return m_read_size;
|
||||
}
|
||||
constexpr bool CanDo() const {
|
||||
return m_read_size > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_read_size;
|
||||
s32 m_skip_count;
|
||||
bool m_done;
|
||||
};
|
||||
|
||||
private:
|
||||
class NodeBuffer {
|
||||
YUZU_NON_COPYABLE(NodeBuffer);
|
||||
|
||||
public:
|
||||
NodeBuffer() : m_header() {}
|
||||
|
||||
~NodeBuffer() {
|
||||
ASSERT(m_header == nullptr);
|
||||
}
|
||||
|
||||
NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) {
|
||||
rhs.m_header = nullptr;
|
||||
}
|
||||
|
||||
NodeBuffer& operator=(NodeBuffer&& rhs) {
|
||||
if (this != std::addressof(rhs)) {
|
||||
ASSERT(m_header == nullptr);
|
||||
|
||||
m_header = rhs.m_header;
|
||||
|
||||
rhs.m_header = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Allocate(size_t node_size) {
|
||||
ASSERT(m_header == nullptr);
|
||||
|
||||
m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)});
|
||||
|
||||
// ASSERT(Common::IsAligned(m_header, sizeof(s64)));
|
||||
|
||||
return m_header != nullptr;
|
||||
}
|
||||
|
||||
void Free(size_t node_size) {
|
||||
if (m_header) {
|
||||
::operator delete(m_header, std::align_val_t{sizeof(s64)});
|
||||
m_header = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void FillZero(size_t node_size) const {
|
||||
if (m_header) {
|
||||
std::memset(m_header, 0, node_size);
|
||||
}
|
||||
}
|
||||
|
||||
NodeHeader* Get() const {
|
||||
return reinterpret_cast<NodeHeader*>(m_header);
|
||||
}
|
||||
|
||||
NodeHeader* operator->() const {
|
||||
return this->Get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* Get() const {
|
||||
static_assert(std::is_trivial_v<T>);
|
||||
static_assert(sizeof(T) == sizeof(NodeHeader));
|
||||
return reinterpret_cast<T*>(m_header);
|
||||
}
|
||||
|
||||
private:
|
||||
void* m_header;
|
||||
};
|
||||
|
||||
private:
|
||||
static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) {
|
||||
return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size);
|
||||
}
|
||||
|
||||
static constexpr s32 GetOffsetCount(size_t node_size) {
|
||||
return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64));
|
||||
}
|
||||
|
||||
static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) {
|
||||
const s32 entry_count_per_node = GetEntryCount(node_size, entry_size);
|
||||
return Common::DivideUp(entry_count, entry_count_per_node);
|
||||
}
|
||||
|
||||
static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) {
|
||||
const s32 offset_count_per_node = GetOffsetCount(node_size);
|
||||
const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
|
||||
|
||||
if (entry_set_count <= offset_count_per_node) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node);
|
||||
ASSERT(node_l2_count <= offset_count_per_node);
|
||||
|
||||
return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)),
|
||||
offset_count_per_node);
|
||||
}
|
||||
|
||||
public:
|
||||
BucketTree()
|
||||
: m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(),
|
||||
m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {}
|
||||
~BucketTree() {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
|
||||
size_t entry_size, s32 entry_count);
|
||||
void Initialize(size_t node_size, s64 end_offset);
|
||||
void Finalize();
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_node_size > 0;
|
||||
}
|
||||
bool IsEmpty() const {
|
||||
return m_entry_size == 0;
|
||||
}
|
||||
|
||||
Result Find(Visitor* visitor, s64 virtual_address);
|
||||
Result InvalidateCache();
|
||||
|
||||
s32 GetEntryCount() const {
|
||||
return m_entry_count;
|
||||
}
|
||||
|
||||
Result GetOffsets(Offsets* out) {
|
||||
// Ensure we have an offset cache.
|
||||
R_TRY(this->EnsureOffsetCache());
|
||||
|
||||
// Set the output.
|
||||
*out = m_offset_cache.offsets;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr s64 QueryHeaderStorageSize() {
|
||||
return sizeof(Header);
|
||||
}
|
||||
|
||||
static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size,
|
||||
s32 entry_count) {
|
||||
ASSERT(entry_size >= sizeof(s64));
|
||||
ASSERT(node_size >= entry_size + sizeof(NodeHeader));
|
||||
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
|
||||
ASSERT(Common::IsPowerOfTwo(node_size));
|
||||
ASSERT(entry_count >= 0);
|
||||
|
||||
if (entry_count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) *
|
||||
static_cast<s64>(node_size);
|
||||
}
|
||||
|
||||
static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size,
|
||||
s32 entry_count) {
|
||||
ASSERT(entry_size >= sizeof(s64));
|
||||
ASSERT(node_size >= entry_size + sizeof(NodeHeader));
|
||||
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
|
||||
ASSERT(Common::IsPowerOfTwo(node_size));
|
||||
ASSERT(entry_count >= 0);
|
||||
|
||||
if (entry_count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename EntryType>
|
||||
struct ContinuousReadingParam {
|
||||
s64 offset;
|
||||
size_t size;
|
||||
NodeHeader entry_set;
|
||||
s32 entry_index;
|
||||
Offsets offsets;
|
||||
EntryType entry;
|
||||
};
|
||||
|
||||
private:
|
||||
template <typename EntryType>
|
||||
Result ScanContinuousReading(ContinuousReadingInfo* out_info,
|
||||
const ContinuousReadingParam<EntryType>& param) const;
|
||||
|
||||
bool IsExistL2() const {
|
||||
return m_offset_count < m_entry_set_count;
|
||||
}
|
||||
bool IsExistOffsetL2OnL1() const {
|
||||
return this->IsExistL2() && m_node_l1->count < m_offset_count;
|
||||
}
|
||||
|
||||
s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const {
|
||||
return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index;
|
||||
}
|
||||
|
||||
Result EnsureOffsetCache();
|
||||
|
||||
private:
|
||||
mutable VirtualFile m_node_storage;
|
||||
mutable VirtualFile m_entry_storage;
|
||||
NodeBuffer m_node_l1;
|
||||
size_t m_node_size;
|
||||
size_t m_entry_size;
|
||||
s32 m_entry_count;
|
||||
s32 m_offset_count;
|
||||
s32 m_entry_set_count;
|
||||
OffsetCache m_offset_cache;
|
||||
};
|
||||
|
||||
class BucketTree::Visitor {
|
||||
YUZU_NON_COPYABLE(Visitor);
|
||||
YUZU_NON_MOVEABLE(Visitor);
|
||||
|
||||
public:
|
||||
constexpr Visitor()
|
||||
: m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {}
|
||||
~Visitor() {
|
||||
if (m_entry != nullptr) {
|
||||
::operator delete(m_entry, m_tree->m_entry_size);
|
||||
m_tree = nullptr;
|
||||
m_entry = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return m_entry_index >= 0;
|
||||
}
|
||||
bool CanMoveNext() const {
|
||||
return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count ||
|
||||
m_entry_set.info.index + 1 < m_entry_set_count);
|
||||
}
|
||||
bool CanMovePrevious() const {
|
||||
return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0);
|
||||
}
|
||||
|
||||
Result MoveNext();
|
||||
Result MovePrevious();
|
||||
|
||||
template <typename EntryType>
|
||||
Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const;
|
||||
|
||||
const void* Get() const {
|
||||
ASSERT(this->IsValid());
|
||||
return m_entry;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* Get() const {
|
||||
ASSERT(this->IsValid());
|
||||
return reinterpret_cast<const T*>(m_entry);
|
||||
}
|
||||
|
||||
const BucketTree* GetTree() const {
|
||||
return m_tree;
|
||||
}
|
||||
|
||||
private:
|
||||
Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets);
|
||||
|
||||
Result Find(s64 virtual_address);
|
||||
|
||||
Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index);
|
||||
Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index,
|
||||
char* buffer);
|
||||
Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index);
|
||||
|
||||
Result FindEntry(s64 virtual_address, s32 entry_set_index);
|
||||
Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer);
|
||||
Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index);
|
||||
|
||||
private:
|
||||
friend class BucketTree;
|
||||
|
||||
union EntrySetHeader {
|
||||
NodeHeader header;
|
||||
struct Info {
|
||||
s32 index;
|
||||
s32 count;
|
||||
s64 end;
|
||||
s64 start;
|
||||
} info;
|
||||
static_assert(std::is_trivial_v<Info>);
|
||||
};
|
||||
static_assert(std::is_trivial_v<EntrySetHeader>);
|
||||
|
||||
const BucketTree* m_tree;
|
||||
BucketTree::Offsets m_offsets;
|
||||
void* m_entry;
|
||||
s32 m_entry_index;
|
||||
s32 m_entry_set_count;
|
||||
EntrySetHeader m_entry_set;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
|
||||
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
template <typename EntryType>
|
||||
Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info,
|
||||
const ContinuousReadingParam<EntryType>& param) const {
|
||||
static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>);
|
||||
|
||||
// Validate our preconditions.
|
||||
ASSERT(this->IsInitialized());
|
||||
ASSERT(out_info != nullptr);
|
||||
ASSERT(m_entry_size == sizeof(EntryType));
|
||||
|
||||
// Reset the output.
|
||||
out_info->Reset();
|
||||
|
||||
// If there's nothing to read, we're done.
|
||||
R_SUCCEED_IF(param.size == 0);
|
||||
|
||||
// If we're reading a fragment, we're done.
|
||||
R_SUCCEED_IF(param.entry.IsFragment());
|
||||
|
||||
// Validate the first entry.
|
||||
auto entry = param.entry;
|
||||
auto cur_offset = param.offset;
|
||||
R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange);
|
||||
|
||||
// Create a pooled buffer for our scan.
|
||||
PooledBuffer pool(m_node_size, 1);
|
||||
char* buffer = nullptr;
|
||||
|
||||
s64 entry_storage_size = m_entry_storage->GetSize();
|
||||
|
||||
// Read the node.
|
||||
if (m_node_size <= pool.GetSize()) {
|
||||
buffer = pool.GetBuffer();
|
||||
const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size);
|
||||
R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size),
|
||||
ResultInvalidBucketTreeNodeEntryCount);
|
||||
|
||||
m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs);
|
||||
}
|
||||
|
||||
// Calculate extents.
|
||||
const auto end_offset = cur_offset + static_cast<s64>(param.size);
|
||||
s64 phys_offset = entry.GetPhysicalOffset();
|
||||
|
||||
// Start merge tracking.
|
||||
s64 merge_size = 0;
|
||||
s64 readable_size = 0;
|
||||
bool merged = false;
|
||||
|
||||
// Iterate.
|
||||
auto entry_index = param.entry_index;
|
||||
for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) {
|
||||
// If we're past the end, we're done.
|
||||
if (end_offset <= cur_offset) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Validate the entry offset.
|
||||
const auto entry_offset = entry.GetVirtualOffset();
|
||||
R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
|
||||
|
||||
// Get the next entry.
|
||||
EntryType next_entry = {};
|
||||
s64 next_entry_offset;
|
||||
|
||||
if (entry_index + 1 < entry_count) {
|
||||
if (buffer != nullptr) {
|
||||
const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1);
|
||||
std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size);
|
||||
} else {
|
||||
const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size,
|
||||
m_entry_size, entry_index + 1);
|
||||
m_entry_storage->ReadObject(std::addressof(next_entry), ofs);
|
||||
}
|
||||
|
||||
next_entry_offset = next_entry.GetVirtualOffset();
|
||||
R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
|
||||
} else {
|
||||
next_entry_offset = param.entry_set.offset;
|
||||
}
|
||||
|
||||
// Validate the next entry offset.
|
||||
R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
|
||||
|
||||
// Determine the much data there is.
|
||||
const auto data_size = next_entry_offset - cur_offset;
|
||||
ASSERT(data_size > 0);
|
||||
|
||||
// Determine how much data we should read.
|
||||
const auto remaining_size = end_offset - cur_offset;
|
||||
const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size));
|
||||
ASSERT(read_size <= param.size);
|
||||
|
||||
// Update our merge tracking.
|
||||
if (entry.IsFragment()) {
|
||||
// If we can't merge, stop looping.
|
||||
if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, add the current size to the merge size.
|
||||
merge_size += read_size;
|
||||
} else {
|
||||
// If we can't merge, stop looping.
|
||||
if (phys_offset != entry.GetPhysicalOffset()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the size to the readable amount.
|
||||
readable_size += merge_size + read_size;
|
||||
ASSERT(readable_size <= static_cast<s64>(param.size));
|
||||
|
||||
// Update whether we've merged.
|
||||
merged |= merge_size > 0;
|
||||
merge_size = 0;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_offset += read_size;
|
||||
ASSERT(cur_offset <= end_offset);
|
||||
|
||||
phys_offset += next_entry_offset - entry_offset;
|
||||
entry = next_entry;
|
||||
}
|
||||
|
||||
// If we merged, set our readable size.
|
||||
if (merged) {
|
||||
out_info->SetReadSize(static_cast<size_t>(readable_size));
|
||||
}
|
||||
out_info->SetSkipCount(entry_index - param.entry_index);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename EntryType>
|
||||
Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset,
|
||||
size_t size) const {
|
||||
static_assert(std::is_trivial_v<EntryType>);
|
||||
ASSERT(this->IsValid());
|
||||
|
||||
// Create our parameters.
|
||||
ContinuousReadingParam<EntryType> param = {
|
||||
.offset = offset,
|
||||
.size = size,
|
||||
.entry_set = m_entry_set.header,
|
||||
.entry_index = m_entry_index,
|
||||
.offsets{},
|
||||
.entry{},
|
||||
};
|
||||
std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets),
|
||||
sizeof(BucketTree::Offsets));
|
||||
std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType));
|
||||
|
||||
// Scan.
|
||||
R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param));
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
|
||||
|
||||
namespace FileSys::impl {
|
||||
|
||||
class SafeValue {
|
||||
public:
|
||||
static s64 GetInt64(const void* ptr) {
|
||||
s64 value;
|
||||
std::memcpy(std::addressof(value), ptr, sizeof(s64));
|
||||
return value;
|
||||
}
|
||||
|
||||
static s64 GetInt64(const s64* ptr) {
|
||||
return GetInt64(static_cast<const void*>(ptr));
|
||||
}
|
||||
|
||||
static s64 GetInt64(const s64& v) {
|
||||
return GetInt64(std::addressof(v));
|
||||
}
|
||||
|
||||
static void SetInt64(void* dst, const void* src) {
|
||||
std::memcpy(dst, src, sizeof(s64));
|
||||
}
|
||||
|
||||
static void SetInt64(void* dst, const s64* src) {
|
||||
return SetInt64(dst, static_cast<const void*>(src));
|
||||
}
|
||||
|
||||
static void SetInt64(void* dst, const s64& v) {
|
||||
return SetInt64(dst, std::addressof(v));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename IteratorType>
|
||||
struct BucketTreeNode {
|
||||
using Header = BucketTree::NodeHeader;
|
||||
|
||||
Header header;
|
||||
|
||||
s32 GetCount() const {
|
||||
return this->header.count;
|
||||
}
|
||||
|
||||
void* GetArray() {
|
||||
return std::addressof(this->header) + 1;
|
||||
}
|
||||
template <typename T>
|
||||
T* GetArray() {
|
||||
return reinterpret_cast<T*>(this->GetArray());
|
||||
}
|
||||
const void* GetArray() const {
|
||||
return std::addressof(this->header) + 1;
|
||||
}
|
||||
template <typename T>
|
||||
const T* GetArray() const {
|
||||
return reinterpret_cast<const T*>(this->GetArray());
|
||||
}
|
||||
|
||||
s64 GetBeginOffset() const {
|
||||
return *this->GetArray<s64>();
|
||||
}
|
||||
s64 GetEndOffset() const {
|
||||
return this->header.offset;
|
||||
}
|
||||
|
||||
IteratorType GetBegin() {
|
||||
return IteratorType(this->GetArray<s64>());
|
||||
}
|
||||
IteratorType GetEnd() {
|
||||
return IteratorType(this->GetArray<s64>()) + this->header.count;
|
||||
}
|
||||
IteratorType GetBegin() const {
|
||||
return IteratorType(this->GetArray<s64>());
|
||||
}
|
||||
IteratorType GetEnd() const {
|
||||
return IteratorType(this->GetArray<s64>()) + this->header.count;
|
||||
}
|
||||
|
||||
IteratorType GetBegin(size_t entry_size) {
|
||||
return IteratorType(this->GetArray(), entry_size);
|
||||
}
|
||||
IteratorType GetEnd(size_t entry_size) {
|
||||
return IteratorType(this->GetArray(), entry_size) + this->header.count;
|
||||
}
|
||||
IteratorType GetBegin(size_t entry_size) const {
|
||||
return IteratorType(this->GetArray(), entry_size);
|
||||
}
|
||||
IteratorType GetEnd(size_t entry_size) const {
|
||||
return IteratorType(this->GetArray(), entry_size) + this->header.count;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size,
|
||||
s32 entry_index) {
|
||||
return entry_set_offset + sizeof(BucketTree::NodeHeader) +
|
||||
entry_index * static_cast<s64>(entry_size);
|
||||
}
|
||||
|
||||
constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size,
|
||||
size_t entry_size, s32 entry_index) {
|
||||
return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size,
|
||||
entry_index);
|
||||
}
|
||||
|
||||
} // namespace FileSys::impl
|
||||
|
|
@ -1,963 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/literals.h"
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
|
||||
#include "core/file_sys/fssystem/fssystem_compression_common.h"
|
||||
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
class CompressedStorage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(CompressedStorage);
|
||||
YUZU_NON_MOVEABLE(CompressedStorage);
|
||||
|
||||
public:
|
||||
static constexpr size_t NodeSize = 16_KiB;
|
||||
|
||||
struct Entry {
|
||||
s64 virt_offset;
|
||||
s64 phys_offset;
|
||||
CompressionType compression_type;
|
||||
s32 phys_size;
|
||||
|
||||
s64 GetPhysicalSize() const {
|
||||
return this->phys_size;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<Entry>);
|
||||
static_assert(sizeof(Entry) == 0x18);
|
||||
|
||||
public:
|
||||
static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
|
||||
return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
|
||||
}
|
||||
|
||||
static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
|
||||
return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
|
||||
}
|
||||
|
||||
private:
|
||||
class CompressedStorageCore {
|
||||
YUZU_NON_COPYABLE(CompressedStorageCore);
|
||||
YUZU_NON_MOVEABLE(CompressedStorageCore);
|
||||
|
||||
public:
|
||||
CompressedStorageCore() : m_table(), m_data_storage() {}
|
||||
|
||||
~CompressedStorageCore() {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
public:
|
||||
Result Initialize(VirtualFile data_storage, VirtualFile node_storage,
|
||||
VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max,
|
||||
size_t continuous_reading_size_max,
|
||||
GetDecompressorFunction get_decompressor) {
|
||||
// Check pre-conditions.
|
||||
ASSERT(0 < block_size_max);
|
||||
ASSERT(block_size_max <= continuous_reading_size_max);
|
||||
ASSERT(get_decompressor != nullptr);
|
||||
|
||||
// Initialize our entry table.
|
||||
R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry),
|
||||
bktr_entry_count));
|
||||
|
||||
// Set our other fields.
|
||||
m_block_size_max = block_size_max;
|
||||
m_continuous_reading_size_max = continuous_reading_size_max;
|
||||
m_data_storage = data_storage;
|
||||
m_get_decompressor_function = get_decompressor;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
if (this->IsInitialized()) {
|
||||
m_table.Finalize();
|
||||
m_data_storage = VirtualFile();
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile GetDataStorage() {
|
||||
return m_data_storage;
|
||||
}
|
||||
|
||||
Result GetDataStorageSize(s64* out) {
|
||||
// Check pre-conditions.
|
||||
ASSERT(out != nullptr);
|
||||
|
||||
// Get size.
|
||||
*out = m_data_storage->GetSize();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
BucketTree& GetEntryTable() {
|
||||
return m_table;
|
||||
}
|
||||
|
||||
Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count,
|
||||
s64 offset, s64 size) {
|
||||
// Check pre-conditions.
|
||||
ASSERT(offset >= 0);
|
||||
ASSERT(size >= 0);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
// Check that we can output the count.
|
||||
R_UNLESS(out_read_count != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Check that we have anything to read at all.
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// Check that either we have a buffer, or this is to determine how many we need.
|
||||
if (max_entry_count != 0) {
|
||||
R_UNLESS(out_entries != nullptr, ResultNullptrArgument);
|
||||
}
|
||||
|
||||
// Get the table offsets.
|
||||
BucketTree::Offsets table_offsets;
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
// Validate arguments.
|
||||
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
|
||||
|
||||
// Find the offset in our tree.
|
||||
BucketTree::Visitor visitor;
|
||||
R_TRY(m_table.Find(std::addressof(visitor), offset));
|
||||
{
|
||||
const auto entry_offset = visitor.Get<Entry>()->virt_offset;
|
||||
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
|
||||
ResultUnexpectedInCompressedStorageA);
|
||||
}
|
||||
|
||||
// Get the entries.
|
||||
const auto end_offset = offset + size;
|
||||
s32 read_count = 0;
|
||||
while (visitor.Get<Entry>()->virt_offset < end_offset) {
|
||||
// If we should be setting the output, do so.
|
||||
if (max_entry_count != 0) {
|
||||
// Ensure we only read as many entries as we can.
|
||||
if (read_count >= max_entry_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the current output entry.
|
||||
out_entries[read_count] = *visitor.Get<Entry>();
|
||||
}
|
||||
|
||||
// Increase the read count.
|
||||
++read_count;
|
||||
|
||||
// If we're at the end, we're done.
|
||||
if (!visitor.CanMoveNext()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Move to the next entry.
|
||||
R_TRY(visitor.MoveNext());
|
||||
}
|
||||
|
||||
// Set the output read count.
|
||||
*out_read_count = read_count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetSize(s64* out) {
|
||||
// Check pre-conditions.
|
||||
ASSERT(out != nullptr);
|
||||
|
||||
// Get our table offsets.
|
||||
BucketTree::Offsets offsets;
|
||||
R_TRY(m_table.GetOffsets(std::addressof(offsets)));
|
||||
|
||||
// Set the output.
|
||||
*out = offsets.end_offset;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result OperatePerEntry(s64 offset, s64 size, auto f) {
|
||||
// Check pre-conditions.
|
||||
ASSERT(offset >= 0);
|
||||
ASSERT(size >= 0);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
// Succeed if there's nothing to operate on.
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// Get the table offsets.
|
||||
BucketTree::Offsets table_offsets;
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
// Validate arguments.
|
||||
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
|
||||
|
||||
// Find the offset in our tree.
|
||||
BucketTree::Visitor visitor;
|
||||
R_TRY(m_table.Find(std::addressof(visitor), offset));
|
||||
{
|
||||
const auto entry_offset = visitor.Get<Entry>()->virt_offset;
|
||||
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
|
||||
ResultUnexpectedInCompressedStorageA);
|
||||
}
|
||||
|
||||
// Prepare to operate in chunks.
|
||||
auto cur_offset = offset;
|
||||
const auto end_offset = offset + static_cast<s64>(size);
|
||||
|
||||
while (cur_offset < end_offset) {
|
||||
// Get the current entry.
|
||||
const auto cur_entry = *visitor.Get<Entry>();
|
||||
|
||||
// Get and validate the entry's offset.
|
||||
const auto cur_entry_offset = cur_entry.virt_offset;
|
||||
R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA);
|
||||
|
||||
// Get and validate the next entry offset.
|
||||
s64 next_entry_offset;
|
||||
if (visitor.CanMoveNext()) {
|
||||
R_TRY(visitor.MoveNext());
|
||||
next_entry_offset = visitor.Get<Entry>()->virt_offset;
|
||||
R_UNLESS(table_offsets.IsInclude(next_entry_offset),
|
||||
ResultUnexpectedInCompressedStorageA);
|
||||
} else {
|
||||
next_entry_offset = table_offsets.end_offset;
|
||||
}
|
||||
R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA);
|
||||
|
||||
// Get the offset of the entry in the data we read.
|
||||
const auto data_offset = cur_offset - cur_entry_offset;
|
||||
const auto data_size = (next_entry_offset - cur_entry_offset);
|
||||
ASSERT(data_size > 0);
|
||||
|
||||
// Determine how much is left.
|
||||
const auto remaining_size = end_offset - cur_offset;
|
||||
const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
|
||||
ASSERT(cur_size <= size);
|
||||
|
||||
// Get the data storage size.
|
||||
s64 storage_size = m_data_storage->GetSize();
|
||||
|
||||
// Check that our read remains naively physically in bounds.
|
||||
R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size,
|
||||
ResultUnexpectedInCompressedStorageC);
|
||||
|
||||
// If we have any compression, verify that we remain physically in bounds.
|
||||
if (cur_entry.compression_type != CompressionType::None) {
|
||||
R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size,
|
||||
ResultUnexpectedInCompressedStorageC);
|
||||
}
|
||||
|
||||
// Check that block alignment requirements are met.
|
||||
if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) {
|
||||
R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment),
|
||||
ResultUnexpectedInCompressedStorageA);
|
||||
}
|
||||
|
||||
// Invoke the operator.
|
||||
bool is_continuous = true;
|
||||
R_TRY(
|
||||
f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size));
|
||||
|
||||
// If not continuous, we're done.
|
||||
if (!is_continuous) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_offset += cur_size;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
public:
|
||||
using ReadImplFunction = std::function<Result(void*, size_t)>;
|
||||
using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>;
|
||||
|
||||
public:
|
||||
Result Read(s64 offset, s64 size, const ReadFunction& read_func) {
|
||||
// Check pre-conditions.
|
||||
ASSERT(offset >= 0);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
// Succeed immediately, if we have nothing to read.
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// Declare read lambda.
|
||||
constexpr int EntriesCountMax = 0x80;
|
||||
struct Entries {
|
||||
CompressionType compression_type;
|
||||
u32 gap_from_prev;
|
||||
u32 physical_size;
|
||||
u32 virtual_size;
|
||||
};
|
||||
std::array<Entries, EntriesCountMax> entries;
|
||||
s32 entry_count = 0;
|
||||
Entry prev_entry = {
|
||||
.virt_offset = -1,
|
||||
.phys_offset{},
|
||||
.compression_type{},
|
||||
.phys_size{},
|
||||
};
|
||||
bool will_allocate_pooled_buffer = false;
|
||||
s64 required_access_physical_offset = 0;
|
||||
s64 required_access_physical_size = 0;
|
||||
|
||||
auto PerformRequiredRead = [&]() -> Result {
|
||||
// If there are no entries, we have nothing to do.
|
||||
R_SUCCEED_IF(entry_count == 0);
|
||||
|
||||
// Get the remaining size in a convenient form.
|
||||
const size_t total_required_size =
|
||||
static_cast<size_t>(required_access_physical_size);
|
||||
|
||||
// Perform the read based on whether we need to allocate a buffer.
|
||||
if (will_allocate_pooled_buffer) {
|
||||
// Allocate a pooled buffer.
|
||||
PooledBuffer pooled_buffer;
|
||||
if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) {
|
||||
pooled_buffer.Allocate(total_required_size, m_block_size_max);
|
||||
} else {
|
||||
pooled_buffer.AllocateParticularlyLarge(
|
||||
std::min<size_t>(
|
||||
total_required_size,
|
||||
PooledBuffer::GetAllocatableParticularlyLargeSizeMax()),
|
||||
m_block_size_max);
|
||||
}
|
||||
|
||||
// Read each of the entries.
|
||||
for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) {
|
||||
// Determine the current read size.
|
||||
bool will_use_pooled_buffer = false;
|
||||
const size_t cur_read_size = [&]() -> size_t {
|
||||
if (const size_t target_entry_size =
|
||||
static_cast<size_t>(entries[entry_idx].physical_size) +
|
||||
static_cast<size_t>(entries[entry_idx].gap_from_prev);
|
||||
target_entry_size <= pooled_buffer.GetSize()) {
|
||||
// We'll be using the pooled buffer.
|
||||
will_use_pooled_buffer = true;
|
||||
|
||||
// Determine how much we can read.
|
||||
const size_t max_size = std::min<size_t>(
|
||||
required_access_physical_size, pooled_buffer.GetSize());
|
||||
|
||||
size_t read_size = 0;
|
||||
for (auto n = entry_idx; n < entry_count; ++n) {
|
||||
const size_t cur_entry_size =
|
||||
static_cast<size_t>(entries[n].physical_size) +
|
||||
static_cast<size_t>(entries[n].gap_from_prev);
|
||||
if (read_size + cur_entry_size > max_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
read_size += cur_entry_size;
|
||||
}
|
||||
|
||||
return read_size;
|
||||
} else {
|
||||
// If we don't fit, we must be uncompressed.
|
||||
ASSERT(entries[entry_idx].compression_type ==
|
||||
CompressionType::None);
|
||||
|
||||
// We can perform the whole of an uncompressed read directly.
|
||||
return entries[entry_idx].virtual_size;
|
||||
}
|
||||
}();
|
||||
|
||||
// Perform the read based on whether or not we'll use the pooled buffer.
|
||||
if (will_use_pooled_buffer) {
|
||||
// Read the compressed data into the pooled buffer.
|
||||
auto* const buffer = pooled_buffer.GetBuffer();
|
||||
m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size,
|
||||
required_access_physical_offset);
|
||||
|
||||
// Decompress the data.
|
||||
size_t buffer_offset;
|
||||
for (buffer_offset = 0;
|
||||
entry_idx < entry_count &&
|
||||
((static_cast<size_t>(entries[entry_idx].physical_size) +
|
||||
static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 ||
|
||||
buffer_offset < cur_read_size);
|
||||
buffer_offset += entries[entry_idx++].physical_size) {
|
||||
// Advance by the relevant gap.
|
||||
buffer_offset += entries[entry_idx].gap_from_prev;
|
||||
|
||||
const auto compression_type = entries[entry_idx].compression_type;
|
||||
switch (compression_type) {
|
||||
case CompressionType::None: {
|
||||
// Check that we can remain within bounds.
|
||||
ASSERT(buffer_offset + entries[entry_idx].virtual_size <=
|
||||
cur_read_size);
|
||||
|
||||
// Perform no decompression.
|
||||
R_TRY(read_func(
|
||||
entries[entry_idx].virtual_size,
|
||||
[&](void* dst, size_t dst_size) -> Result {
|
||||
// Check that the size is valid.
|
||||
ASSERT(dst_size == entries[entry_idx].virtual_size);
|
||||
|
||||
// We have no compression, so just copy the data
|
||||
// out.
|
||||
std::memcpy(dst, buffer + buffer_offset,
|
||||
entries[entry_idx].virtual_size);
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
case CompressionType::Zeros: {
|
||||
// Check that we can remain within bounds.
|
||||
ASSERT(buffer_offset <= cur_read_size);
|
||||
|
||||
// Zero the memory.
|
||||
R_TRY(read_func(
|
||||
entries[entry_idx].virtual_size,
|
||||
[&](void* dst, size_t dst_size) -> Result {
|
||||
// Check that the size is valid.
|
||||
ASSERT(dst_size == entries[entry_idx].virtual_size);
|
||||
|
||||
// The data is zeroes, so zero the buffer.
|
||||
std::memset(dst, 0, entries[entry_idx].virtual_size);
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Check that we can remain within bounds.
|
||||
ASSERT(buffer_offset + entries[entry_idx].physical_size <=
|
||||
cur_read_size);
|
||||
|
||||
// Get the decompressor.
|
||||
const auto decompressor =
|
||||
this->GetDecompressor(compression_type);
|
||||
R_UNLESS(decompressor != nullptr,
|
||||
ResultUnexpectedInCompressedStorageB);
|
||||
|
||||
// Decompress the data.
|
||||
R_TRY(read_func(entries[entry_idx].virtual_size,
|
||||
[&](void* dst, size_t dst_size) -> Result {
|
||||
// Check that the size is valid.
|
||||
ASSERT(dst_size ==
|
||||
entries[entry_idx].virtual_size);
|
||||
|
||||
// Perform the decompression.
|
||||
R_RETURN(decompressor(
|
||||
dst, entries[entry_idx].virtual_size,
|
||||
buffer + buffer_offset,
|
||||
entries[entry_idx].physical_size));
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we processed the correct amount of data.
|
||||
ASSERT(buffer_offset == cur_read_size);
|
||||
} else {
|
||||
// Account for the gap from the previous entry.
|
||||
required_access_physical_offset += entries[entry_idx].gap_from_prev;
|
||||
required_access_physical_size -= entries[entry_idx].gap_from_prev;
|
||||
|
||||
// We don't need the buffer (as the data is uncompressed), so just
|
||||
// execute the read.
|
||||
R_TRY(
|
||||
read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result {
|
||||
// Check that the size is valid.
|
||||
ASSERT(dst_size == cur_read_size);
|
||||
|
||||
// Perform the read.
|
||||
m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size,
|
||||
required_access_physical_offset);
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
}
|
||||
|
||||
// Advance on.
|
||||
required_access_physical_offset += cur_read_size;
|
||||
required_access_physical_size -= cur_read_size;
|
||||
}
|
||||
|
||||
// Verify that we have nothing remaining to read.
|
||||
ASSERT(required_access_physical_size == 0);
|
||||
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
// We don't need a buffer, so just execute the read.
|
||||
R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result {
|
||||
// Check that the size is valid.
|
||||
ASSERT(dst_size == total_required_size);
|
||||
|
||||
// Perform the read.
|
||||
m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size,
|
||||
required_access_physical_offset);
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
};
|
||||
|
||||
R_TRY(this->OperatePerEntry(
|
||||
offset, size,
|
||||
[&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
|
||||
s64 data_offset, s64 read_size) -> Result {
|
||||
// Determine the physical extents.
|
||||
s64 physical_offset, physical_size;
|
||||
if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) {
|
||||
physical_offset = entry.phys_offset + data_offset;
|
||||
physical_size = read_size;
|
||||
} else {
|
||||
physical_offset = entry.phys_offset;
|
||||
physical_size = entry.GetPhysicalSize();
|
||||
}
|
||||
|
||||
// If we have a pending data storage operation, perform it if we have to.
|
||||
const s64 required_access_physical_end =
|
||||
required_access_physical_offset + required_access_physical_size;
|
||||
if (required_access_physical_size > 0) {
|
||||
const bool required_by_gap =
|
||||
!(required_access_physical_end <= physical_offset &&
|
||||
physical_offset <= Common::AlignUp(required_access_physical_end,
|
||||
CompressionBlockAlignment));
|
||||
const bool required_by_continuous_size =
|
||||
((physical_size + physical_offset) - required_access_physical_end) +
|
||||
required_access_physical_size >
|
||||
static_cast<s64>(m_continuous_reading_size_max);
|
||||
const bool required_by_entry_count = entry_count == EntriesCountMax;
|
||||
if (required_by_gap || required_by_continuous_size ||
|
||||
required_by_entry_count) {
|
||||
// Check that our planned access is sane.
|
||||
ASSERT(!will_allocate_pooled_buffer ||
|
||||
required_access_physical_size <=
|
||||
static_cast<s64>(m_continuous_reading_size_max));
|
||||
|
||||
// Perform the required read.
|
||||
const Result rc = PerformRequiredRead();
|
||||
if (R_FAILED(rc)) {
|
||||
R_THROW(rc);
|
||||
}
|
||||
|
||||
// Reset our requirements.
|
||||
prev_entry.virt_offset = -1;
|
||||
required_access_physical_size = 0;
|
||||
entry_count = 0;
|
||||
will_allocate_pooled_buffer = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check that we're within bounds on entries.
|
||||
ASSERT(entry_count < EntriesCountMax);
|
||||
|
||||
// Determine if a buffer allocation is needed.
|
||||
if (entry.compression_type != CompressionType::None ||
|
||||
(prev_entry.virt_offset >= 0 &&
|
||||
entry.virt_offset - prev_entry.virt_offset !=
|
||||
entry.phys_offset - prev_entry.phys_offset)) {
|
||||
will_allocate_pooled_buffer = true;
|
||||
}
|
||||
|
||||
// If we need to access the data storage, update our required access parameters.
|
||||
if (CompressionTypeUtility::IsDataStorageAccessRequired(
|
||||
entry.compression_type)) {
|
||||
// If the data is compressed, ensure the access is sane.
|
||||
if (entry.compression_type != CompressionType::None) {
|
||||
R_UNLESS(data_offset == 0, ResultInvalidOffset);
|
||||
R_UNLESS(virtual_data_size == read_size, ResultInvalidSize);
|
||||
R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max),
|
||||
ResultUnexpectedInCompressedStorageD);
|
||||
}
|
||||
|
||||
// Update the required access parameters.
|
||||
s64 gap_from_prev;
|
||||
if (required_access_physical_size > 0) {
|
||||
gap_from_prev = physical_offset - required_access_physical_end;
|
||||
} else {
|
||||
gap_from_prev = 0;
|
||||
required_access_physical_offset = physical_offset;
|
||||
}
|
||||
required_access_physical_size += physical_size + gap_from_prev;
|
||||
|
||||
// Create an entry to access the data storage.
|
||||
entries[entry_count++] = {
|
||||
.compression_type = entry.compression_type,
|
||||
.gap_from_prev = static_cast<u32>(gap_from_prev),
|
||||
.physical_size = static_cast<u32>(physical_size),
|
||||
.virtual_size = static_cast<u32>(read_size),
|
||||
};
|
||||
} else {
|
||||
// Verify that we're allowed to be operating on the non-data-storage-access
|
||||
// type.
|
||||
R_UNLESS(entry.compression_type == CompressionType::Zeros,
|
||||
ResultUnexpectedInCompressedStorageB);
|
||||
|
||||
// If we have entries, create a fake entry for the zero region.
|
||||
if (entry_count != 0) {
|
||||
// We need to have a physical size.
|
||||
R_UNLESS(entry.GetPhysicalSize() != 0,
|
||||
ResultUnexpectedInCompressedStorageD);
|
||||
|
||||
// Create a fake entry.
|
||||
entries[entry_count++] = {
|
||||
.compression_type = CompressionType::Zeros,
|
||||
.gap_from_prev = 0,
|
||||
.physical_size = 0,
|
||||
.virtual_size = static_cast<u32>(read_size),
|
||||
};
|
||||
} else {
|
||||
// We have no entries, so we can just perform the read.
|
||||
const Result rc =
|
||||
read_func(static_cast<size_t>(read_size),
|
||||
[&](void* dst, size_t dst_size) -> Result {
|
||||
// Check the space we should zero is correct.
|
||||
ASSERT(dst_size == static_cast<size_t>(read_size));
|
||||
|
||||
// Zero the memory.
|
||||
std::memset(dst, 0, read_size);
|
||||
R_SUCCEED();
|
||||
});
|
||||
if (R_FAILED(rc)) {
|
||||
R_THROW(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the previous entry.
|
||||
prev_entry = entry;
|
||||
|
||||
// We're continuous.
|
||||
*out_continuous = true;
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
// If we still have a pending access, perform it.
|
||||
if (required_access_physical_size != 0) {
|
||||
R_TRY(PerformRequiredRead());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
private:
|
||||
DecompressorFunction GetDecompressor(CompressionType type) const {
|
||||
// Check that we can get a decompressor for the type.
|
||||
if (CompressionTypeUtility::IsUnknownType(type)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the decompressor.
|
||||
return m_get_decompressor_function(type);
|
||||
}
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_table.IsInitialized();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_block_size_max;
|
||||
size_t m_continuous_reading_size_max;
|
||||
BucketTree m_table;
|
||||
VirtualFile m_data_storage;
|
||||
GetDecompressorFunction m_get_decompressor_function;
|
||||
};
|
||||
|
||||
class CacheManager {
|
||||
YUZU_NON_COPYABLE(CacheManager);
|
||||
YUZU_NON_MOVEABLE(CacheManager);
|
||||
|
||||
private:
|
||||
struct AccessRange {
|
||||
s64 virtual_offset;
|
||||
s64 virtual_size;
|
||||
u32 physical_size;
|
||||
bool is_block_alignment_required;
|
||||
|
||||
s64 GetEndVirtualOffset() const {
|
||||
return this->virtual_offset + this->virtual_size;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<AccessRange>);
|
||||
|
||||
public:
|
||||
CacheManager() = default;
|
||||
|
||||
public:
|
||||
Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1,
|
||||
size_t max_cache_entries) {
|
||||
// Set our fields.
|
||||
m_storage_size = storage_size;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) {
|
||||
// If we have nothing to read, succeed.
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// Check that we have a buffer to read into.
|
||||
R_UNLESS(buffer != nullptr, ResultNullptrArgument);
|
||||
|
||||
// Check that the read is in bounds.
|
||||
R_UNLESS(offset <= m_storage_size, ResultInvalidOffset);
|
||||
|
||||
// Determine how much we can read.
|
||||
const size_t read_size = std::min<size_t>(size, m_storage_size - offset);
|
||||
|
||||
// Create head/tail ranges.
|
||||
AccessRange head_range = {};
|
||||
AccessRange tail_range = {};
|
||||
bool is_tail_set = false;
|
||||
|
||||
// Operate to determine the head range.
|
||||
R_TRY(core.OperatePerEntry(
|
||||
offset, 1,
|
||||
[&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
|
||||
s64 data_offset, s64 data_read_size) -> Result {
|
||||
// Set the head range.
|
||||
head_range = {
|
||||
.virtual_offset = entry.virt_offset,
|
||||
.virtual_size = virtual_data_size,
|
||||
.physical_size = static_cast<u32>(entry.phys_size),
|
||||
.is_block_alignment_required =
|
||||
CompressionTypeUtility::IsBlockAlignmentRequired(
|
||||
entry.compression_type),
|
||||
};
|
||||
|
||||
// If required, set the tail range.
|
||||
if (static_cast<s64>(offset + read_size) <=
|
||||
entry.virt_offset + virtual_data_size) {
|
||||
tail_range = {
|
||||
.virtual_offset = entry.virt_offset,
|
||||
.virtual_size = virtual_data_size,
|
||||
.physical_size = static_cast<u32>(entry.phys_size),
|
||||
.is_block_alignment_required =
|
||||
CompressionTypeUtility::IsBlockAlignmentRequired(
|
||||
entry.compression_type),
|
||||
};
|
||||
is_tail_set = true;
|
||||
}
|
||||
|
||||
// We only want to determine the head range, so we're not continuous.
|
||||
*out_continuous = false;
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
// If necessary, determine the tail range.
|
||||
if (!is_tail_set) {
|
||||
R_TRY(core.OperatePerEntry(
|
||||
offset + read_size - 1, 1,
|
||||
[&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
|
||||
s64 data_offset, s64 data_read_size) -> Result {
|
||||
// Set the tail range.
|
||||
tail_range = {
|
||||
.virtual_offset = entry.virt_offset,
|
||||
.virtual_size = virtual_data_size,
|
||||
.physical_size = static_cast<u32>(entry.phys_size),
|
||||
.is_block_alignment_required =
|
||||
CompressionTypeUtility::IsBlockAlignmentRequired(
|
||||
entry.compression_type),
|
||||
};
|
||||
|
||||
// We only want to determine the tail range, so we're not continuous.
|
||||
*out_continuous = false;
|
||||
R_SUCCEED();
|
||||
}));
|
||||
}
|
||||
|
||||
// Begin performing the accesses.
|
||||
s64 cur_offset = offset;
|
||||
size_t cur_size = read_size;
|
||||
char* cur_dst = static_cast<char*>(buffer);
|
||||
|
||||
// Determine our alignment.
|
||||
const bool head_unaligned = head_range.is_block_alignment_required &&
|
||||
(cur_offset != head_range.virtual_offset ||
|
||||
static_cast<s64>(cur_size) < head_range.virtual_size);
|
||||
const bool tail_unaligned = [&]() -> bool {
|
||||
if (tail_range.is_block_alignment_required) {
|
||||
if (static_cast<s64>(cur_size + cur_offset) ==
|
||||
tail_range.GetEndVirtualOffset()) {
|
||||
return false;
|
||||
} else if (!head_unaligned) {
|
||||
return true;
|
||||
} else {
|
||||
return head_range.GetEndVirtualOffset() <
|
||||
static_cast<s64>(cur_size + cur_offset);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
// Determine start/end offsets.
|
||||
const s64 start_offset =
|
||||
head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset;
|
||||
const s64 end_offset = tail_range.is_block_alignment_required
|
||||
? tail_range.GetEndVirtualOffset()
|
||||
: cur_offset + cur_size;
|
||||
|
||||
// Perform the read.
|
||||
bool is_burst_reading = false;
|
||||
R_TRY(core.Read(
|
||||
start_offset, end_offset - start_offset,
|
||||
[&](size_t size_buffer_required,
|
||||
const CompressedStorageCore::ReadImplFunction& read_impl) -> Result {
|
||||
// Determine whether we're burst reading.
|
||||
const AccessRange* unaligned_range = nullptr;
|
||||
if (!is_burst_reading) {
|
||||
// Check whether we're using head, tail, or none as unaligned.
|
||||
if (head_unaligned && head_range.virtual_offset <= cur_offset &&
|
||||
cur_offset < head_range.GetEndVirtualOffset()) {
|
||||
unaligned_range = std::addressof(head_range);
|
||||
} else if (tail_unaligned && tail_range.virtual_offset <= cur_offset &&
|
||||
cur_offset < tail_range.GetEndVirtualOffset()) {
|
||||
unaligned_range = std::addressof(tail_range);
|
||||
} else {
|
||||
is_burst_reading = true;
|
||||
}
|
||||
}
|
||||
ASSERT((is_burst_reading ^ (unaligned_range != nullptr)));
|
||||
|
||||
// Perform reading by burst, or not.
|
||||
if (is_burst_reading) {
|
||||
// Check that the access is valid for burst reading.
|
||||
ASSERT(size_buffer_required <= cur_size);
|
||||
|
||||
// Perform the read.
|
||||
Result rc = read_impl(cur_dst, size_buffer_required);
|
||||
if (R_FAILED(rc)) {
|
||||
R_THROW(rc);
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_dst += size_buffer_required;
|
||||
cur_offset += size_buffer_required;
|
||||
cur_size -= size_buffer_required;
|
||||
|
||||
// Determine whether we're going to continue burst reading.
|
||||
const s64 offset_aligned =
|
||||
tail_unaligned ? tail_range.virtual_offset : end_offset;
|
||||
ASSERT(cur_offset <= offset_aligned);
|
||||
|
||||
if (offset_aligned <= cur_offset) {
|
||||
is_burst_reading = false;
|
||||
}
|
||||
} else {
|
||||
// We're not burst reading, so we have some unaligned range.
|
||||
ASSERT(unaligned_range != nullptr);
|
||||
|
||||
// Check that the size is correct.
|
||||
ASSERT(size_buffer_required ==
|
||||
static_cast<size_t>(unaligned_range->virtual_size));
|
||||
|
||||
// Get a pooled buffer for our read.
|
||||
PooledBuffer pooled_buffer;
|
||||
pooled_buffer.Allocate(size_buffer_required, size_buffer_required);
|
||||
|
||||
// Perform read.
|
||||
Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required);
|
||||
if (R_FAILED(rc)) {
|
||||
R_THROW(rc);
|
||||
}
|
||||
|
||||
// Copy the data we read to the destination.
|
||||
const size_t skip_size = cur_offset - unaligned_range->virtual_offset;
|
||||
const size_t copy_size = std::min<size_t>(
|
||||
cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset);
|
||||
|
||||
std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size);
|
||||
|
||||
// Advance.
|
||||
cur_dst += copy_size;
|
||||
cur_offset += copy_size;
|
||||
cur_size -= copy_size;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
private:
|
||||
s64 m_storage_size = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
CompressedStorage() = default;
|
||||
virtual ~CompressedStorage() {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
|
||||
s32 bktr_entry_count, size_t block_size_max,
|
||||
size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor,
|
||||
size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) {
|
||||
// Initialize our core.
|
||||
R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count,
|
||||
block_size_max, continuous_reading_size_max, get_decompressor));
|
||||
|
||||
// Get our core size.
|
||||
s64 core_size = 0;
|
||||
R_TRY(m_core.GetSize(std::addressof(core_size)));
|
||||
|
||||
// Initialize our cache manager.
|
||||
R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
m_core.Finalize();
|
||||
}
|
||||
|
||||
VirtualFile GetDataStorage() {
|
||||
return m_core.GetDataStorage();
|
||||
}
|
||||
|
||||
Result GetDataStorageSize(s64* out) {
|
||||
R_RETURN(m_core.GetDataStorageSize(out));
|
||||
}
|
||||
|
||||
Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset,
|
||||
s64 size) {
|
||||
R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size));
|
||||
}
|
||||
|
||||
BucketTree& GetEntryTable() {
|
||||
return m_core.GetEntryTable();
|
||||
}
|
||||
|
||||
public:
|
||||
virtual size_t GetSize() const override {
|
||||
s64 ret{};
|
||||
m_core.GetSize(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
|
||||
if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) {
|
||||
return size;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
mutable CompressedStorageCore m_core;
|
||||
mutable CacheManager m_cache_manager;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class CompressionType : u8 {
|
||||
None = 0,
|
||||
Zeros = 1,
|
||||
Two = 2,
|
||||
Lz4 = 3,
|
||||
Unknown = 4,
|
||||
};
|
||||
|
||||
using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t);
|
||||
using GetDecompressorFunction = DecompressorFunction (*)(CompressionType);
|
||||
|
||||
constexpr s64 CompressionBlockAlignment = 0x10;
|
||||
|
||||
namespace CompressionTypeUtility {
|
||||
|
||||
constexpr bool IsBlockAlignmentRequired(CompressionType type) {
|
||||
return type != CompressionType::None && type != CompressionType::Zeros;
|
||||
}
|
||||
|
||||
constexpr bool IsDataStorageAccessRequired(CompressionType type) {
|
||||
return type != CompressionType::Zeros;
|
||||
}
|
||||
|
||||
constexpr bool IsRandomAccessible(CompressionType type) {
|
||||
return type == CompressionType::None;
|
||||
}
|
||||
|
||||
constexpr bool IsUnknownType(CompressionType type) {
|
||||
return type >= CompressionType::Unknown;
|
||||
}
|
||||
|
||||
} // namespace CompressionTypeUtility
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/lz4_compression.h"
|
||||
#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) {
|
||||
auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size);
|
||||
R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) {
|
||||
switch (type) {
|
||||
case CompressionType::Lz4:
|
||||
return DecompressLz4;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const NcaCompressionConfiguration& GetNcaCompressionConfiguration() {
|
||||
static const NcaCompressionConfiguration configuration = {
|
||||
.get_decompressor = GetNcaDecompressorFunction,
|
||||
};
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
const NcaCompressionConfiguration& GetNcaCompressionConfiguration();
|
||||
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size,
|
||||
s32 key_type) {
|
||||
if (key_type == static_cast<s32>(KeyType::ZeroKey)) {
|
||||
std::memset(dst_key, 0, dst_key_size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_type == static_cast<s32>(KeyType::InvalidKey) ||
|
||||
key_type < static_cast<s32>(KeyType::ZeroKey) ||
|
||||
key_type >= static_cast<s32>(KeyType::NcaExternalKey)) {
|
||||
std::memset(dst_key, 0xFF, dst_key_size);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& instance = Core::Crypto::KeyManager::Instance();
|
||||
|
||||
if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) ||
|
||||
key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) {
|
||||
const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type;
|
||||
const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header);
|
||||
std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const s32 key_generation =
|
||||
std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1;
|
||||
const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount;
|
||||
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
|
||||
instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index),
|
||||
Core::Crypto::Mode::ECB);
|
||||
cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size,
|
||||
reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const NcaCryptoConfiguration& GetCryptoConfiguration() {
|
||||
static const NcaCryptoConfiguration configuration = {
|
||||
.header_1_sign_key_moduli{},
|
||||
.header_1_sign_key_public_exponent{},
|
||||
.key_area_encryption_key_source{},
|
||||
.header_encryption_key_source{},
|
||||
.header_encrypted_encryption_keys{},
|
||||
.generate_key = GenerateKey,
|
||||
.verify_sign1{},
|
||||
.is_plaintext_header_available{},
|
||||
.is_available_sw_key{},
|
||||
};
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
const NcaCryptoConfiguration& GetCryptoConfiguration();
|
||||
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage()
|
||||
: m_data_size(-1) {
|
||||
for (size_t i = 0; i < MaxLayers - 1; i++) {
|
||||
m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>();
|
||||
}
|
||||
}
|
||||
|
||||
Result HierarchicalIntegrityVerificationStorage::Initialize(
|
||||
const HierarchicalIntegrityVerificationInformation& info,
|
||||
HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries,
|
||||
s8 buffer_level) {
|
||||
// Validate preconditions.
|
||||
ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount);
|
||||
|
||||
// Set member variables.
|
||||
m_max_layers = info.max_layers;
|
||||
|
||||
// Initialize the top level verification storage.
|
||||
m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage],
|
||||
storage[HierarchicalStorageInformation::Layer1Storage],
|
||||
static_cast<s64>(1) << info.info[0].block_order, HashSize,
|
||||
false);
|
||||
|
||||
// Ensure we don't leak state if further initialization goes wrong.
|
||||
ON_RESULT_FAILURE {
|
||||
m_verify_storages[0]->Finalize();
|
||||
m_data_size = -1;
|
||||
};
|
||||
|
||||
// Initialize the top level buffer storage.
|
||||
m_buffer_storages[0] = m_verify_storages[0];
|
||||
R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared);
|
||||
|
||||
// Prepare to initialize the level storages.
|
||||
s32 level = 0;
|
||||
|
||||
// Ensure we don't leak state if further initialization goes wrong.
|
||||
ON_RESULT_FAILURE_2 {
|
||||
m_verify_storages[level + 1]->Finalize();
|
||||
for (; level > 0; --level) {
|
||||
m_buffer_storages[level].reset();
|
||||
m_verify_storages[level]->Finalize();
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize the level storages.
|
||||
for (; level < m_max_layers - 3; ++level) {
|
||||
// Initialize the verification storage.
|
||||
auto buffer_storage =
|
||||
std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
|
||||
m_verify_storages[level + 1]->Initialize(
|
||||
std::move(buffer_storage), storage[level + 2],
|
||||
static_cast<s64>(1) << info.info[level + 1].block_order,
|
||||
static_cast<s64>(1) << info.info[level].block_order, false);
|
||||
|
||||
// Initialize the buffer storage.
|
||||
m_buffer_storages[level + 1] = m_verify_storages[level + 1];
|
||||
R_UNLESS(m_buffer_storages[level + 1] != nullptr,
|
||||
ResultAllocationMemoryFailedAllocateShared);
|
||||
}
|
||||
|
||||
// Initialize the final level storage.
|
||||
{
|
||||
// Initialize the verification storage.
|
||||
auto buffer_storage =
|
||||
std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
|
||||
m_verify_storages[level + 1]->Initialize(
|
||||
std::move(buffer_storage), storage[level + 2],
|
||||
static_cast<s64>(1) << info.info[level + 1].block_order,
|
||||
static_cast<s64>(1) << info.info[level].block_order, true);
|
||||
|
||||
// Initialize the buffer storage.
|
||||
m_buffer_storages[level + 1] = m_verify_storages[level + 1];
|
||||
R_UNLESS(m_buffer_storages[level + 1] != nullptr,
|
||||
ResultAllocationMemoryFailedAllocateShared);
|
||||
}
|
||||
|
||||
// Set the data size.
|
||||
m_data_size = info.info[level + 1].size;
|
||||
|
||||
// We succeeded.
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void HierarchicalIntegrityVerificationStorage::Finalize() {
|
||||
if (m_data_size >= 0) {
|
||||
m_data_size = 0;
|
||||
|
||||
for (s32 level = m_max_layers - 2; level >= 0; --level) {
|
||||
m_buffer_storages[level].reset();
|
||||
m_verify_storages[level]->Finalize();
|
||||
}
|
||||
|
||||
m_data_size = -1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size,
|
||||
size_t offset) const {
|
||||
// Validate preconditions.
|
||||
ASSERT(m_data_size >= 0);
|
||||
|
||||
// Succeed if zero-size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// Read the data.
|
||||
return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset);
|
||||
}
|
||||
|
||||
size_t HierarchicalIntegrityVerificationStorage::GetSize() const {
|
||||
return m_data_size;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/fssystem/fs_types.h"
|
||||
#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct HierarchicalIntegrityVerificationLevelInformation {
|
||||
Int64 offset;
|
||||
Int64 size;
|
||||
s32 block_order;
|
||||
std::array<u8, 4> reserved;
|
||||
};
|
||||
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>);
|
||||
static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18);
|
||||
static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4);
|
||||
|
||||
struct HierarchicalIntegrityVerificationInformation {
|
||||
u32 max_layers;
|
||||
std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info;
|
||||
HashSalt seed;
|
||||
|
||||
s64 GetLayeredHashSize() const {
|
||||
return this->info[this->max_layers - 2].offset;
|
||||
}
|
||||
|
||||
s64 GetDataOffset() const {
|
||||
return this->info[this->max_layers - 2].offset;
|
||||
}
|
||||
|
||||
s64 GetDataSize() const {
|
||||
return this->info[this->max_layers - 2].size;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>);
|
||||
|
||||
struct HierarchicalIntegrityVerificationMetaInformation {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 master_hash_size;
|
||||
HierarchicalIntegrityVerificationInformation level_hash_info;
|
||||
};
|
||||
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>);
|
||||
|
||||
struct HierarchicalIntegrityVerificationSizeSet {
|
||||
s64 control_size;
|
||||
s64 master_hash_size;
|
||||
std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes;
|
||||
};
|
||||
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>);
|
||||
|
||||
class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage);
|
||||
YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage);
|
||||
|
||||
public:
|
||||
using GenerateRandomFunction = void (*)(void* dst, size_t size);
|
||||
|
||||
class HierarchicalStorageInformation {
|
||||
public:
|
||||
enum {
|
||||
MasterStorage = 0,
|
||||
Layer1Storage = 1,
|
||||
Layer2Storage = 2,
|
||||
Layer3Storage = 3,
|
||||
Layer4Storage = 4,
|
||||
Layer5Storage = 5,
|
||||
DataStorage = 6,
|
||||
};
|
||||
|
||||
private:
|
||||
std::array<VirtualFile, DataStorage + 1> m_storages;
|
||||
|
||||
public:
|
||||
void SetMasterHashStorage(VirtualFile s) {
|
||||
m_storages[MasterStorage] = s;
|
||||
}
|
||||
void SetLayer1HashStorage(VirtualFile s) {
|
||||
m_storages[Layer1Storage] = s;
|
||||
}
|
||||
void SetLayer2HashStorage(VirtualFile s) {
|
||||
m_storages[Layer2Storage] = s;
|
||||
}
|
||||
void SetLayer3HashStorage(VirtualFile s) {
|
||||
m_storages[Layer3Storage] = s;
|
||||
}
|
||||
void SetLayer4HashStorage(VirtualFile s) {
|
||||
m_storages[Layer4Storage] = s;
|
||||
}
|
||||
void SetLayer5HashStorage(VirtualFile s) {
|
||||
m_storages[Layer5Storage] = s;
|
||||
}
|
||||
void SetDataStorage(VirtualFile s) {
|
||||
m_storages[DataStorage] = s;
|
||||
}
|
||||
|
||||
VirtualFile& operator[](s32 index) {
|
||||
ASSERT(MasterStorage <= index && index <= DataStorage);
|
||||
return m_storages[index];
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
HierarchicalIntegrityVerificationStorage();
|
||||
virtual ~HierarchicalIntegrityVerificationStorage() override {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
Result Initialize(const HierarchicalIntegrityVerificationInformation& info,
|
||||
HierarchicalStorageInformation storage, int max_data_cache_entries,
|
||||
int max_hash_cache_entries, s8 buffer_level);
|
||||
void Finalize();
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
virtual size_t GetSize() const override;
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_data_size >= 0;
|
||||
}
|
||||
|
||||
s64 GetL1HashVerificationBlockSize() const {
|
||||
return m_verify_storages[m_max_layers - 2]->GetBlockSize();
|
||||
}
|
||||
|
||||
VirtualFile GetL1HashStorage() {
|
||||
return std::make_shared<OffsetVfsFile>(
|
||||
m_buffer_storages[m_max_layers - 3],
|
||||
Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0);
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
|
||||
return static_cast<s8>(16 + max_layers - 2);
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr s64 HashSize = 256 / 8;
|
||||
static constexpr size_t MaxLayers = IntegrityMaxLayerCount;
|
||||
|
||||
private:
|
||||
static GenerateRandomFunction s_generate_random;
|
||||
|
||||
static void SetGenerateRandomFunction(GenerateRandomFunction func) {
|
||||
s_generate_random = func;
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct HierarchicalIntegrityVerificationMetaInformation;
|
||||
|
||||
private:
|
||||
std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages;
|
||||
std::array<VirtualFile, MaxLayers - 1> m_buffer_storages;
|
||||
s64 m_data_size;
|
||||
s32 m_max_layers;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
s32 Log2(s32 value) {
|
||||
ASSERT(value > 0);
|
||||
ASSERT(Common::IsPowerOfTwo(value));
|
||||
|
||||
s32 log = 0;
|
||||
while ((value >>= 1) > 0) {
|
||||
++log;
|
||||
}
|
||||
return log;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count,
|
||||
size_t htbs, void* hash_buf, size_t hash_buf_size) {
|
||||
// Validate preconditions.
|
||||
ASSERT(layer_count == LayerCount);
|
||||
ASSERT(Common::IsPowerOfTwo(htbs));
|
||||
ASSERT(hash_buf != nullptr);
|
||||
|
||||
// Set size tracking members.
|
||||
m_hash_target_block_size = static_cast<s32>(htbs);
|
||||
m_log_size_ratio = Log2(m_hash_target_block_size / HashSize);
|
||||
|
||||
// Get the base storage size.
|
||||
m_base_storage_size = base_storages[2]->GetSize();
|
||||
{
|
||||
auto size_guard = SCOPE_GUARD {
|
||||
m_base_storage_size = 0;
|
||||
};
|
||||
R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize)
|
||||
<< m_log_size_ratio << m_log_size_ratio,
|
||||
ResultHierarchicalSha256BaseStorageTooLarge);
|
||||
size_guard.Cancel();
|
||||
}
|
||||
|
||||
// Set hash buffer tracking members.
|
||||
m_base_storage = base_storages[2];
|
||||
m_hash_buffer = static_cast<char*>(hash_buf);
|
||||
m_hash_buffer_size = hash_buf_size;
|
||||
|
||||
// Read the master hash.
|
||||
std::array<u8, HashSize> master_hash{};
|
||||
base_storages[0]->ReadObject(std::addressof(master_hash));
|
||||
|
||||
// Read and validate the data being hashed.
|
||||
s64 hash_storage_size = base_storages[1]->GetSize();
|
||||
ASSERT(Common::IsAligned(hash_storage_size, HashSize));
|
||||
ASSERT(hash_storage_size <= m_hash_target_block_size);
|
||||
ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size));
|
||||
|
||||
base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer),
|
||||
static_cast<size_t>(hash_storage_size), 0);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Succeed if zero-size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate that we have a buffer to read into.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// Read the data.
|
||||
return m_base_storage->Read(buffer, size, offset);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class HierarchicalSha256Storage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(HierarchicalSha256Storage);
|
||||
YUZU_NON_MOVEABLE(HierarchicalSha256Storage);
|
||||
|
||||
public:
|
||||
static constexpr s32 LayerCount = 3;
|
||||
static constexpr size_t HashSize = 256 / 8;
|
||||
|
||||
public:
|
||||
HierarchicalSha256Storage() : m_mutex() {}
|
||||
|
||||
Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf,
|
||||
size_t hash_buf_size);
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
return m_base_storage->GetSize();
|
||||
}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t length, size_t offset) const override;
|
||||
|
||||
private:
|
||||
VirtualFile m_base_storage;
|
||||
s64 m_base_storage_size;
|
||||
char* m_hash_buffer;
|
||||
size_t m_hash_buffer_size;
|
||||
s32 m_hash_target_block_size;
|
||||
s32 m_log_size_ratio;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
Result IndirectStorage::Initialize(VirtualFile table_storage) {
|
||||
// Read and verify the bucket tree header.
|
||||
BucketTree::Header header;
|
||||
table_storage->ReadObject(std::addressof(header));
|
||||
R_TRY(header.Verify());
|
||||
|
||||
// Determine extents.
|
||||
const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
|
||||
const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
|
||||
const auto node_storage_offset = QueryHeaderStorageSize();
|
||||
const auto entry_storage_offset = node_storage_offset + node_storage_size;
|
||||
|
||||
// Initialize.
|
||||
R_RETURN(this->Initialize(
|
||||
std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
|
||||
std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
|
||||
header.entry_count));
|
||||
}
|
||||
|
||||
void IndirectStorage::Finalize() {
|
||||
if (this->IsInitialized()) {
|
||||
m_table.Finalize();
|
||||
for (auto i = 0; i < StorageCount; i++) {
|
||||
m_data_storage[i] = VirtualFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count,
|
||||
s64 offset, s64 size) {
|
||||
// Validate pre-conditions.
|
||||
ASSERT(offset >= 0);
|
||||
ASSERT(size >= 0);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
// Clear the out count.
|
||||
R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
|
||||
*out_entry_count = 0;
|
||||
|
||||
// Succeed if there's no range.
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// If we have an output array, we need it to be non-null.
|
||||
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
|
||||
|
||||
// Check that our range is valid.
|
||||
BucketTree::Offsets table_offsets;
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
|
||||
|
||||
// Find the offset in our tree.
|
||||
BucketTree::Visitor visitor;
|
||||
R_TRY(m_table.Find(std::addressof(visitor), offset));
|
||||
{
|
||||
const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
|
||||
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
|
||||
ResultInvalidIndirectEntryOffset);
|
||||
}
|
||||
|
||||
// Prepare to loop over entries.
|
||||
const auto end_offset = offset + static_cast<s64>(size);
|
||||
s32 count = 0;
|
||||
|
||||
auto cur_entry = *visitor.Get<Entry>();
|
||||
while (cur_entry.GetVirtualOffset() < end_offset) {
|
||||
// Try to write the entry to the out list.
|
||||
if (entry_count != 0) {
|
||||
if (count >= entry_count) {
|
||||
break;
|
||||
}
|
||||
std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
// Advance.
|
||||
if (visitor.CanMoveNext()) {
|
||||
R_TRY(visitor.MoveNext());
|
||||
cur_entry = *visitor.Get<Entry>();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the output count.
|
||||
*out_entry_count = count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Validate pre-conditions.
|
||||
ASSERT(this->IsInitialized());
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// Succeed if there's nothing to read.
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>(
|
||||
offset, size,
|
||||
[=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
|
||||
storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
|
||||
static_cast<size_t>(cur_size), data_offset);
|
||||
R_SUCCEED();
|
||||
});
|
||||
|
||||
return size;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,294 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
|
||||
#include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class IndirectStorage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(IndirectStorage);
|
||||
YUZU_NON_MOVEABLE(IndirectStorage);
|
||||
|
||||
public:
|
||||
static constexpr s32 StorageCount = 2;
|
||||
static constexpr size_t NodeSize = 16_KiB;
|
||||
|
||||
struct Entry {
|
||||
std::array<u8, sizeof(s64)> virt_offset;
|
||||
std::array<u8, sizeof(s64)> phys_offset;
|
||||
s32 storage_index;
|
||||
|
||||
void SetVirtualOffset(const s64& ofs) {
|
||||
std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64));
|
||||
}
|
||||
|
||||
s64 GetVirtualOffset() const {
|
||||
s64 offset;
|
||||
std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64));
|
||||
return offset;
|
||||
}
|
||||
|
||||
void SetPhysicalOffset(const s64& ofs) {
|
||||
std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64));
|
||||
}
|
||||
|
||||
s64 GetPhysicalOffset() const {
|
||||
s64 offset;
|
||||
std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64));
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<Entry>);
|
||||
static_assert(sizeof(Entry) == 0x14);
|
||||
|
||||
struct EntryData {
|
||||
s64 virt_offset;
|
||||
s64 phys_offset;
|
||||
s32 storage_index;
|
||||
|
||||
void Set(const Entry& entry) {
|
||||
this->virt_offset = entry.GetVirtualOffset();
|
||||
this->phys_offset = entry.GetPhysicalOffset();
|
||||
this->storage_index = entry.storage_index;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<EntryData>);
|
||||
|
||||
public:
|
||||
IndirectStorage() : m_table(), m_data_storage() {}
|
||||
virtual ~IndirectStorage() {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
Result Initialize(VirtualFile table_storage);
|
||||
void Finalize();
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_table.IsInitialized();
|
||||
}
|
||||
|
||||
Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) {
|
||||
R_RETURN(
|
||||
m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
|
||||
}
|
||||
|
||||
void SetStorage(s32 idx, VirtualFile storage) {
|
||||
ASSERT(0 <= idx && idx < StorageCount);
|
||||
m_data_storage[idx] = storage;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SetStorage(s32 idx, T storage, s64 offset, s64 size) {
|
||||
ASSERT(0 <= idx && idx < StorageCount);
|
||||
m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset);
|
||||
}
|
||||
|
||||
Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
|
||||
s64 size);
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
BucketTree::Offsets offsets{};
|
||||
m_table.GetOffsets(std::addressof(offsets));
|
||||
|
||||
return offsets.end_offset;
|
||||
}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
|
||||
public:
|
||||
static constexpr s64 QueryHeaderStorageSize() {
|
||||
return BucketTree::QueryHeaderStorageSize();
|
||||
}
|
||||
|
||||
static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
|
||||
return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
|
||||
}
|
||||
|
||||
static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
|
||||
return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
|
||||
}
|
||||
|
||||
protected:
|
||||
BucketTree& GetEntryTable() {
|
||||
return m_table;
|
||||
}
|
||||
|
||||
VirtualFile& GetDataStorage(s32 index) {
|
||||
ASSERT(0 <= index && index < StorageCount);
|
||||
return m_data_storage[index];
|
||||
}
|
||||
|
||||
template <bool ContinuousCheck, bool RangeCheck, typename F>
|
||||
Result OperatePerEntry(s64 offset, s64 size, F func);
|
||||
|
||||
private:
|
||||
struct ContinuousReadingEntry {
|
||||
static constexpr size_t FragmentSizeMax = 4_KiB;
|
||||
|
||||
IndirectStorage::Entry entry;
|
||||
|
||||
s64 GetVirtualOffset() const {
|
||||
return this->entry.GetVirtualOffset();
|
||||
}
|
||||
|
||||
s64 GetPhysicalOffset() const {
|
||||
return this->entry.GetPhysicalOffset();
|
||||
}
|
||||
|
||||
bool IsFragment() const {
|
||||
return this->entry.storage_index != 0;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<ContinuousReadingEntry>);
|
||||
|
||||
private:
|
||||
mutable BucketTree m_table;
|
||||
std::array<VirtualFile, StorageCount> m_data_storage;
|
||||
};
|
||||
|
||||
template <bool ContinuousCheck, bool RangeCheck, typename F>
|
||||
Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
|
||||
// Validate preconditions.
|
||||
ASSERT(offset >= 0);
|
||||
ASSERT(size >= 0);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
// Succeed if there's nothing to operate on.
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
// Get the table offsets.
|
||||
BucketTree::Offsets table_offsets;
|
||||
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
|
||||
|
||||
// Validate arguments.
|
||||
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
|
||||
|
||||
// Find the offset in our tree.
|
||||
BucketTree::Visitor visitor;
|
||||
R_TRY(m_table.Find(std::addressof(visitor), offset));
|
||||
{
|
||||
const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
|
||||
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
|
||||
ResultInvalidIndirectEntryOffset);
|
||||
}
|
||||
|
||||
// Prepare to operate in chunks.
|
||||
auto cur_offset = offset;
|
||||
const auto end_offset = offset + static_cast<s64>(size);
|
||||
BucketTree::ContinuousReadingInfo cr_info;
|
||||
|
||||
while (cur_offset < end_offset) {
|
||||
// Get the current entry.
|
||||
const auto cur_entry = *visitor.Get<Entry>();
|
||||
|
||||
// Get and validate the entry's offset.
|
||||
const auto cur_entry_offset = cur_entry.GetVirtualOffset();
|
||||
R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
|
||||
|
||||
// Validate the storage index.
|
||||
R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount,
|
||||
ResultInvalidIndirectEntryStorageIndex);
|
||||
|
||||
// If we need to check the continuous info, do so.
|
||||
if constexpr (ContinuousCheck) {
|
||||
// Scan, if we need to.
|
||||
if (cr_info.CheckNeedScan()) {
|
||||
R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>(
|
||||
std::addressof(cr_info), cur_offset,
|
||||
static_cast<size_t>(end_offset - cur_offset)));
|
||||
}
|
||||
|
||||
// Process a base storage entry.
|
||||
if (cr_info.CanDo()) {
|
||||
// Ensure that we can process.
|
||||
R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex);
|
||||
|
||||
// Ensure that we remain within range.
|
||||
const auto data_offset = cur_offset - cur_entry_offset;
|
||||
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
|
||||
const auto cur_size = static_cast<s64>(cr_info.GetReadSize());
|
||||
|
||||
// If we should, verify the range.
|
||||
if constexpr (RangeCheck) {
|
||||
// Get the current data storage's size.
|
||||
s64 cur_data_storage_size = m_data_storage[0]->GetSize();
|
||||
|
||||
R_UNLESS(0 <= cur_entry_phys_offset &&
|
||||
cur_entry_phys_offset <= cur_data_storage_size,
|
||||
ResultInvalidIndirectEntryOffset);
|
||||
R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <=
|
||||
cur_data_storage_size,
|
||||
ResultInvalidIndirectStorageSize);
|
||||
}
|
||||
|
||||
// Operate.
|
||||
R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset,
|
||||
cur_size));
|
||||
|
||||
// Mark as done.
|
||||
cr_info.Done();
|
||||
}
|
||||
}
|
||||
|
||||
// Get and validate the next entry offset.
|
||||
s64 next_entry_offset;
|
||||
if (visitor.CanMoveNext()) {
|
||||
R_TRY(visitor.MoveNext());
|
||||
next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
|
||||
R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
|
||||
} else {
|
||||
next_entry_offset = table_offsets.end_offset;
|
||||
}
|
||||
R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
|
||||
|
||||
// Get the offset of the entry in the data we read.
|
||||
const auto data_offset = cur_offset - cur_entry_offset;
|
||||
const auto data_size = (next_entry_offset - cur_entry_offset);
|
||||
ASSERT(data_size > 0);
|
||||
|
||||
// Determine how much is left.
|
||||
const auto remaining_size = end_offset - cur_offset;
|
||||
const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
|
||||
ASSERT(cur_size <= size);
|
||||
|
||||
// Operate, if we need to.
|
||||
bool needs_operate;
|
||||
if constexpr (!ContinuousCheck) {
|
||||
needs_operate = true;
|
||||
} else {
|
||||
needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0;
|
||||
}
|
||||
|
||||
if (needs_operate) {
|
||||
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
|
||||
|
||||
if constexpr (RangeCheck) {
|
||||
// Get the current data storage's size.
|
||||
s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
|
||||
|
||||
// Ensure that we remain within range.
|
||||
R_UNLESS(0 <= cur_entry_phys_offset &&
|
||||
cur_entry_phys_offset <= cur_data_storage_size,
|
||||
ResultIndirectStorageCorrupted);
|
||||
R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size,
|
||||
ResultIndirectStorageCorrupted);
|
||||
}
|
||||
|
||||
R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset,
|
||||
cur_offset, cur_size));
|
||||
}
|
||||
|
||||
cur_offset += cur_size;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
Result IntegrityRomFsStorage::Initialize(
|
||||
HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
|
||||
HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
|
||||
int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
|
||||
// Set master hash.
|
||||
m_master_hash = master_hash;
|
||||
m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value);
|
||||
R_UNLESS(m_master_hash_storage != nullptr,
|
||||
ResultAllocationMemoryFailedInIntegrityRomFsStorageA);
|
||||
|
||||
// Set the master hash storage.
|
||||
storage_info[0] = m_master_hash_storage;
|
||||
|
||||
// Initialize our integrity storage.
|
||||
R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries,
|
||||
max_hash_cache_entries, buffer_level));
|
||||
}
|
||||
|
||||
void IntegrityRomFsStorage::Finalize() {
|
||||
m_integrity_storage.Finalize();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_nca_header.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr inline size_t IntegrityLayerCountRomFs = 7;
|
||||
constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB;
|
||||
|
||||
class IntegrityRomFsStorage : public IReadOnlyStorage {
|
||||
public:
|
||||
IntegrityRomFsStorage() {}
|
||||
virtual ~IntegrityRomFsStorage() override {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
Result Initialize(
|
||||
HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
|
||||
HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
|
||||
int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
|
||||
void Finalize();
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
|
||||
return m_integrity_storage.Read(buffer, size, offset);
|
||||
}
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
return m_integrity_storage.GetSize();
|
||||
}
|
||||
|
||||
private:
|
||||
HierarchicalIntegrityVerificationStorage m_integrity_storage;
|
||||
Hash m_master_hash;
|
||||
std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr inline u32 ILog2(u32 val) {
|
||||
ASSERT(val > 0);
|
||||
return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val));
|
||||
}
|
||||
|
||||
void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
|
||||
s64 upper_layer_verif_block_size, bool is_real_data) {
|
||||
// Validate preconditions.
|
||||
ASSERT(verif_block_size >= HashSize);
|
||||
|
||||
// Set storages.
|
||||
m_hash_storage = hs;
|
||||
m_data_storage = ds;
|
||||
|
||||
// Set verification block sizes.
|
||||
m_verification_block_size = verif_block_size;
|
||||
m_verification_block_order = ILog2(static_cast<u32>(verif_block_size));
|
||||
ASSERT(m_verification_block_size == 1ll << m_verification_block_order);
|
||||
|
||||
// Set upper layer block sizes.
|
||||
upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize);
|
||||
m_upper_layer_verification_block_size = upper_layer_verif_block_size;
|
||||
m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size));
|
||||
ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order);
|
||||
|
||||
// Validate sizes.
|
||||
{
|
||||
s64 hash_size = m_hash_storage->GetSize();
|
||||
s64 data_size = m_data_storage->GetSize();
|
||||
ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size);
|
||||
}
|
||||
|
||||
// Set data.
|
||||
m_is_real_data = is_real_data;
|
||||
}
|
||||
|
||||
void IntegrityVerificationStorage::Finalize() {
|
||||
m_hash_storage = VirtualFile();
|
||||
m_data_storage = VirtualFile();
|
||||
}
|
||||
|
||||
size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Succeed if zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Validate arguments.
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// Validate the offset.
|
||||
s64 data_size = m_data_storage->GetSize();
|
||||
ASSERT(offset <= static_cast<size_t>(data_size));
|
||||
|
||||
// Validate the access range.
|
||||
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(
|
||||
offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size)))));
|
||||
|
||||
// Determine the read extents.
|
||||
size_t read_size = size;
|
||||
if (static_cast<s64>(offset + read_size) > data_size) {
|
||||
// Determine the padding sizes.
|
||||
s64 padding_offset = data_size - offset;
|
||||
size_t padding_size = static_cast<size_t>(
|
||||
m_verification_block_size - (padding_offset & (m_verification_block_size - 1)));
|
||||
ASSERT(static_cast<s64>(padding_size) < m_verification_block_size);
|
||||
|
||||
// Clear the padding.
|
||||
std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size);
|
||||
|
||||
// Set the new in-bounds size.
|
||||
read_size = static_cast<size_t>(data_size - offset);
|
||||
}
|
||||
|
||||
// Perform the read.
|
||||
return m_data_storage->Read(buffer, read_size, offset);
|
||||
}
|
||||
|
||||
size_t IntegrityVerificationStorage::GetSize() const {
|
||||
return m_data_storage->GetSize();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
#include "core/file_sys/fssystem/fs_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class IntegrityVerificationStorage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(IntegrityVerificationStorage);
|
||||
YUZU_NON_MOVEABLE(IntegrityVerificationStorage);
|
||||
|
||||
public:
|
||||
static constexpr s64 HashSize = 256 / 8;
|
||||
|
||||
struct BlockHash {
|
||||
std::array<u8, HashSize> hash;
|
||||
};
|
||||
static_assert(std::is_trivial_v<BlockHash>);
|
||||
|
||||
public:
|
||||
IntegrityVerificationStorage()
|
||||
: m_verification_block_size(0), m_verification_block_order(0),
|
||||
m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {}
|
||||
virtual ~IntegrityVerificationStorage() override {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
|
||||
s64 upper_layer_verif_block_size, bool is_real_data);
|
||||
void Finalize();
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
virtual size_t GetSize() const override;
|
||||
|
||||
s64 GetBlockSize() const {
|
||||
return m_verification_block_size;
|
||||
}
|
||||
|
||||
private:
|
||||
static void SetValidationBit(BlockHash* hash) {
|
||||
ASSERT(hash != nullptr);
|
||||
hash->hash[HashSize - 1] |= 0x80;
|
||||
}
|
||||
|
||||
static bool IsValidationBit(const BlockHash* hash) {
|
||||
ASSERT(hash != nullptr);
|
||||
return (hash->hash[HashSize - 1] & 0x80) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
VirtualFile m_hash_storage;
|
||||
VirtualFile m_data_storage;
|
||||
s64 m_verification_block_size;
|
||||
s64 m_verification_block_order;
|
||||
s64 m_upper_layer_verification_block_size;
|
||||
s64 m_upper_layer_verification_block_order;
|
||||
bool m_is_real_data;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class MemoryResourceBufferHoldStorage : public IStorage {
|
||||
YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage);
|
||||
YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage);
|
||||
|
||||
public:
|
||||
MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size)
|
||||
: m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)),
|
||||
m_buffer_size(buffer_size) {}
|
||||
|
||||
virtual ~MemoryResourceBufferHoldStorage() {
|
||||
// If we have a buffer, deallocate it.
|
||||
if (m_buffer != nullptr) {
|
||||
::operator delete(m_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return m_buffer != nullptr;
|
||||
}
|
||||
void* GetBuffer() const {
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
public:
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
|
||||
// Check pre-conditions.
|
||||
ASSERT(m_storage != nullptr);
|
||||
|
||||
return m_storage->Read(buffer, size, offset);
|
||||
}
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
// Check pre-conditions.
|
||||
ASSERT(m_storage != nullptr);
|
||||
|
||||
return m_storage->GetSize();
|
||||
}
|
||||
|
||||
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
|
||||
// Check pre-conditions.
|
||||
ASSERT(m_storage != nullptr);
|
||||
|
||||
return m_storage->Write(buffer, size, offset);
|
||||
}
|
||||
|
||||
private:
|
||||
VirtualFile m_storage;
|
||||
void* m_buffer;
|
||||
size_t m_buffer_size;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,364 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_compression_common.h"
|
||||
#include "core/file_sys/fssystem/fssystem_nca_header.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class CompressedStorage;
|
||||
class AesCtrCounterExtendedStorage;
|
||||
class IndirectStorage;
|
||||
class SparseStorage;
|
||||
|
||||
struct NcaCryptoConfiguration;
|
||||
|
||||
using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key,
|
||||
size_t src_key_size, s32 key_type);
|
||||
using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data,
|
||||
size_t data_size, u8 generation);
|
||||
|
||||
struct NcaCryptoConfiguration {
|
||||
static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8;
|
||||
static constexpr size_t Rsa2048KeyPublicExponentSize = 3;
|
||||
static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize;
|
||||
|
||||
static constexpr size_t Aes128KeySize = 128 / 8;
|
||||
|
||||
static constexpr size_t Header1SignatureKeyGenerationMax = 1;
|
||||
|
||||
static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3;
|
||||
static constexpr s32 HeaderEncryptionKeyCount = 2;
|
||||
|
||||
static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF;
|
||||
|
||||
static constexpr size_t KeyGenerationMax = 32;
|
||||
|
||||
std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli;
|
||||
std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent;
|
||||
std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount>
|
||||
key_area_encryption_key_source;
|
||||
std::array<u8, Aes128KeySize> header_encryption_key_source;
|
||||
std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount>
|
||||
header_encrypted_encryption_keys;
|
||||
KeyGenerationFunction generate_key;
|
||||
VerifySign1Function verify_sign1;
|
||||
bool is_plaintext_header_available;
|
||||
bool is_available_sw_key;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaCryptoConfiguration>);
|
||||
|
||||
struct NcaCompressionConfiguration {
|
||||
GetDecompressorFunction get_decompressor;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaCompressionConfiguration>);
|
||||
|
||||
constexpr inline s32 KeyAreaEncryptionKeyCount =
|
||||
NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount *
|
||||
NcaCryptoConfiguration::KeyGenerationMax;
|
||||
|
||||
enum class KeyType : s32 {
|
||||
ZeroKey = -2,
|
||||
InvalidKey = -1,
|
||||
NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0,
|
||||
NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1,
|
||||
NcaExternalKey = KeyAreaEncryptionKeyCount + 2,
|
||||
SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3,
|
||||
SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4,
|
||||
SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5,
|
||||
};
|
||||
|
||||
constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) {
|
||||
return key_type < 0;
|
||||
}
|
||||
|
||||
constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) {
|
||||
if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) {
|
||||
return static_cast<s32>(KeyType::ZeroKey);
|
||||
}
|
||||
|
||||
if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) {
|
||||
return static_cast<s32>(KeyType::InvalidKey);
|
||||
}
|
||||
|
||||
return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index;
|
||||
}
|
||||
|
||||
class NcaReader {
|
||||
YUZU_NON_COPYABLE(NcaReader);
|
||||
YUZU_NON_MOVEABLE(NcaReader);
|
||||
|
||||
public:
|
||||
NcaReader();
|
||||
~NcaReader();
|
||||
|
||||
Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
|
||||
const NcaCompressionConfiguration& compression_cfg);
|
||||
|
||||
VirtualFile GetSharedBodyStorage();
|
||||
u32 GetMagic() const;
|
||||
NcaHeader::DistributionType GetDistributionType() const;
|
||||
NcaHeader::ContentType GetContentType() const;
|
||||
u8 GetHeaderSign1KeyGeneration() const;
|
||||
u8 GetKeyGeneration() const;
|
||||
u8 GetKeyIndex() const;
|
||||
u64 GetContentSize() const;
|
||||
u64 GetProgramId() const;
|
||||
u32 GetContentIndex() const;
|
||||
u32 GetSdkAddonVersion() const;
|
||||
void GetRightsId(u8* dst, size_t dst_size) const;
|
||||
bool HasFsInfo(s32 index) const;
|
||||
s32 GetFsCount() const;
|
||||
const Hash& GetFsHeaderHash(s32 index) const;
|
||||
void GetFsHeaderHash(Hash* dst, s32 index) const;
|
||||
void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const;
|
||||
u64 GetFsOffset(s32 index) const;
|
||||
u64 GetFsEndOffset(s32 index) const;
|
||||
u64 GetFsSize(s32 index) const;
|
||||
void GetEncryptedKey(void* dst, size_t size) const;
|
||||
const void* GetDecryptionKey(s32 index) const;
|
||||
bool HasValidInternalKey() const;
|
||||
bool HasInternalDecryptionKeyForAesHw() const;
|
||||
bool IsSoftwareAesPrioritized() const;
|
||||
void PrioritizeSoftwareAes();
|
||||
bool IsAvailableSwKey() const;
|
||||
bool HasExternalDecryptionKey() const;
|
||||
const void* GetExternalDecryptionKey() const;
|
||||
void SetExternalDecryptionKey(const void* src, size_t size);
|
||||
void GetRawData(void* dst, size_t dst_size) const;
|
||||
NcaHeader::EncryptionType GetEncryptionType() const;
|
||||
Result ReadHeader(NcaFsHeader* dst, s32 index) const;
|
||||
|
||||
GetDecompressorFunction GetDecompressor() const;
|
||||
|
||||
bool GetHeaderSign1Valid() const;
|
||||
|
||||
void GetHeaderSign2(void* dst, size_t size) const;
|
||||
|
||||
private:
|
||||
NcaHeader m_header;
|
||||
std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
|
||||
NcaHeader::DecryptionKey_Count>
|
||||
m_decryption_keys;
|
||||
VirtualFile m_body_storage;
|
||||
VirtualFile m_header_storage;
|
||||
std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key;
|
||||
bool m_is_software_aes_prioritized;
|
||||
bool m_is_available_sw_key;
|
||||
NcaHeader::EncryptionType m_header_encryption_type;
|
||||
bool m_is_header_sign1_signature_valid;
|
||||
GetDecompressorFunction m_get_decompressor;
|
||||
};
|
||||
|
||||
class NcaFsHeaderReader {
|
||||
YUZU_NON_COPYABLE(NcaFsHeaderReader);
|
||||
YUZU_NON_MOVEABLE(NcaFsHeaderReader);
|
||||
|
||||
public:
|
||||
NcaFsHeaderReader() : m_fs_index(-1) {
|
||||
std::memset(std::addressof(m_data), 0, sizeof(m_data));
|
||||
}
|
||||
|
||||
Result Initialize(const NcaReader& reader, s32 index);
|
||||
bool IsInitialized() const {
|
||||
return m_fs_index >= 0;
|
||||
}
|
||||
|
||||
void GetRawData(void* dst, size_t dst_size) const;
|
||||
|
||||
NcaFsHeader::HashData& GetHashData();
|
||||
const NcaFsHeader::HashData& GetHashData() const;
|
||||
u16 GetVersion() const;
|
||||
s32 GetFsIndex() const;
|
||||
NcaFsHeader::FsType GetFsType() const;
|
||||
NcaFsHeader::HashType GetHashType() const;
|
||||
NcaFsHeader::EncryptionType GetEncryptionType() const;
|
||||
NcaPatchInfo& GetPatchInfo();
|
||||
const NcaPatchInfo& GetPatchInfo() const;
|
||||
const NcaAesCtrUpperIv GetAesCtrUpperIv() const;
|
||||
|
||||
bool IsSkipLayerHashEncryption() const;
|
||||
Result GetHashTargetOffset(s64* out) const;
|
||||
|
||||
bool ExistsSparseLayer() const;
|
||||
NcaSparseInfo& GetSparseInfo();
|
||||
const NcaSparseInfo& GetSparseInfo() const;
|
||||
|
||||
bool ExistsCompressionLayer() const;
|
||||
NcaCompressionInfo& GetCompressionInfo();
|
||||
const NcaCompressionInfo& GetCompressionInfo() const;
|
||||
|
||||
bool ExistsPatchMetaHashLayer() const;
|
||||
NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo();
|
||||
const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const;
|
||||
NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const;
|
||||
|
||||
bool ExistsSparseMetaHashLayer() const;
|
||||
NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo();
|
||||
const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const;
|
||||
NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const;
|
||||
|
||||
private:
|
||||
NcaFsHeader m_data;
|
||||
s32 m_fs_index;
|
||||
};
|
||||
|
||||
class NcaFileSystemDriver {
|
||||
YUZU_NON_COPYABLE(NcaFileSystemDriver);
|
||||
YUZU_NON_MOVEABLE(NcaFileSystemDriver);
|
||||
|
||||
public:
|
||||
struct StorageContext {
|
||||
bool open_raw_storage;
|
||||
VirtualFile body_substorage;
|
||||
std::shared_ptr<SparseStorage> current_sparse_storage;
|
||||
VirtualFile sparse_storage_meta_storage;
|
||||
std::shared_ptr<SparseStorage> original_sparse_storage;
|
||||
void* external_current_sparse_storage;
|
||||
void* external_original_sparse_storage;
|
||||
VirtualFile aes_ctr_ex_storage_meta_storage;
|
||||
VirtualFile aes_ctr_ex_storage_data_storage;
|
||||
std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage;
|
||||
VirtualFile indirect_storage_meta_storage;
|
||||
std::shared_ptr<IndirectStorage> indirect_storage;
|
||||
VirtualFile fs_data_storage;
|
||||
VirtualFile compressed_storage_meta_storage;
|
||||
std::shared_ptr<CompressedStorage> compressed_storage;
|
||||
|
||||
VirtualFile patch_layer_info_storage;
|
||||
VirtualFile sparse_layer_info_storage;
|
||||
|
||||
VirtualFile external_original_storage;
|
||||
};
|
||||
|
||||
private:
|
||||
enum class AlignmentStorageRequirement {
|
||||
CacheBlockSize = 0,
|
||||
None = 1,
|
||||
};
|
||||
|
||||
public:
|
||||
static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader,
|
||||
s32 fs_index);
|
||||
|
||||
public:
|
||||
NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) {
|
||||
ASSERT(m_reader != nullptr);
|
||||
}
|
||||
|
||||
NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader,
|
||||
std::shared_ptr<NcaReader> reader)
|
||||
: m_original_reader(original_reader), m_reader(reader) {
|
||||
ASSERT(m_reader != nullptr);
|
||||
}
|
||||
|
||||
Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader,
|
||||
s32 fs_index, StorageContext* ctx);
|
||||
|
||||
Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) {
|
||||
// Create a storage context.
|
||||
StorageContext ctx{};
|
||||
|
||||
// Open the storage.
|
||||
R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx)));
|
||||
}
|
||||
|
||||
public:
|
||||
Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
|
||||
VirtualFile raw_storage, StorageContext* ctx);
|
||||
|
||||
private:
|
||||
Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index,
|
||||
StorageContext* ctx);
|
||||
|
||||
Result OpenIndirectableStorageAsOriginal(VirtualFile* out,
|
||||
const NcaFsHeaderReader* header_reader,
|
||||
StorageContext* ctx);
|
||||
|
||||
Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size);
|
||||
|
||||
Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
|
||||
const NcaAesCtrUpperIv& upper_iv,
|
||||
AlignmentStorageRequirement alignment_storage_requirement);
|
||||
Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset);
|
||||
|
||||
Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
|
||||
const NcaAesCtrUpperIv& upper_iv,
|
||||
const NcaSparseInfo& sparse_info);
|
||||
Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage,
|
||||
s64 base_size, VirtualFile meta_storage,
|
||||
const NcaSparseInfo& sparse_info, bool external_info);
|
||||
Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset,
|
||||
std::shared_ptr<SparseStorage>* out_sparse_storage,
|
||||
VirtualFile* out_meta_storage, s32 index,
|
||||
const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info);
|
||||
|
||||
Result CreateSparseStorageMetaStorageWithVerification(
|
||||
VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
|
||||
const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
|
||||
const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
|
||||
Result CreateSparseStorageWithVerification(
|
||||
VirtualFile* out, s64* out_fs_data_offset,
|
||||
std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage,
|
||||
VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv,
|
||||
const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info,
|
||||
NcaFsHeader::MetaDataHashType meta_data_hash_type);
|
||||
|
||||
Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
|
||||
NcaFsHeader::EncryptionType encryption_type,
|
||||
const NcaAesCtrUpperIv& upper_iv,
|
||||
const NcaPatchInfo& patch_info);
|
||||
Result CreateAesCtrExStorage(VirtualFile* out,
|
||||
std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext,
|
||||
VirtualFile base_storage, VirtualFile meta_storage,
|
||||
s64 counter_offset, const NcaAesCtrUpperIv& upper_iv,
|
||||
const NcaPatchInfo& patch_info);
|
||||
|
||||
Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage,
|
||||
const NcaPatchInfo& patch_info);
|
||||
Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind,
|
||||
VirtualFile base_storage, VirtualFile original_data_storage,
|
||||
VirtualFile meta_storage, const NcaPatchInfo& patch_info);
|
||||
|
||||
Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta,
|
||||
VirtualFile* out_verification, VirtualFile base_storage,
|
||||
s64 offset, const NcaAesCtrUpperIv& upper_iv,
|
||||
const NcaPatchInfo& patch_info,
|
||||
const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
|
||||
|
||||
Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage,
|
||||
const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data);
|
||||
|
||||
Result CreateIntegrityVerificationStorage(
|
||||
VirtualFile* out, VirtualFile base_storage,
|
||||
const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info);
|
||||
Result CreateIntegrityVerificationStorageForMeta(
|
||||
VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
|
||||
const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
|
||||
Result CreateIntegrityVerificationStorageImpl(
|
||||
VirtualFile* out, VirtualFile base_storage,
|
||||
const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset,
|
||||
int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
|
||||
|
||||
Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
|
||||
VirtualFile inside_storage, VirtualFile outside_storage);
|
||||
|
||||
Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
|
||||
VirtualFile* out_meta, VirtualFile base_storage,
|
||||
const NcaCompressionInfo& compression_info);
|
||||
|
||||
public:
|
||||
Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
|
||||
VirtualFile* out_meta, VirtualFile base_storage,
|
||||
const NcaCompressionInfo& compression_info,
|
||||
GetDecompressorFunction get_decompressor);
|
||||
|
||||
private:
|
||||
std::shared_ptr<NcaReader> m_original_reader;
|
||||
std::shared_ptr<NcaReader> m_reader;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_nca_header.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
u8 NcaHeader::GetProperKeyGeneration() const {
|
||||
return std::max(this->key_generation, this->key_generation_2);
|
||||
}
|
||||
|
||||
bool NcaPatchInfo::HasIndirectTable() const {
|
||||
return this->indirect_size != 0;
|
||||
}
|
||||
|
||||
bool NcaPatchInfo::HasAesCtrExTable() const {
|
||||
return this->aes_ctr_ex_size != 0;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,338 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fssystem/fs_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
struct Hash {
|
||||
static constexpr std::size_t Size = 256 / 8;
|
||||
std::array<u8, Size> value;
|
||||
};
|
||||
static_assert(sizeof(Hash) == Hash::Size);
|
||||
static_assert(std::is_trivial_v<Hash>);
|
||||
|
||||
using NcaDigest = Hash;
|
||||
|
||||
struct NcaHeader {
|
||||
enum class ContentType : u8 {
|
||||
Program = 0,
|
||||
Meta = 1,
|
||||
Control = 2,
|
||||
Manual = 3,
|
||||
Data = 4,
|
||||
PublicData = 5,
|
||||
|
||||
Start = Program,
|
||||
End = PublicData,
|
||||
};
|
||||
|
||||
enum class DistributionType : u8 {
|
||||
Download = 0,
|
||||
GameCard = 1,
|
||||
|
||||
Start = Download,
|
||||
End = GameCard,
|
||||
};
|
||||
|
||||
enum class EncryptionType : u8 {
|
||||
Auto = 0,
|
||||
None = 1,
|
||||
};
|
||||
|
||||
enum DecryptionKey {
|
||||
DecryptionKey_AesXts = 0,
|
||||
DecryptionKey_AesXts1 = DecryptionKey_AesXts,
|
||||
DecryptionKey_AesXts2 = 1,
|
||||
DecryptionKey_AesCtr = 2,
|
||||
DecryptionKey_AesCtrEx = 3,
|
||||
DecryptionKey_AesCtrHw = 4,
|
||||
DecryptionKey_Count,
|
||||
};
|
||||
|
||||
struct FsInfo {
|
||||
u32 start_sector;
|
||||
u32 end_sector;
|
||||
u32 hash_sectors;
|
||||
u32 reserved;
|
||||
};
|
||||
static_assert(sizeof(FsInfo) == 0x10);
|
||||
static_assert(std::is_trivial_v<FsInfo>);
|
||||
|
||||
static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0');
|
||||
static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1');
|
||||
static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2');
|
||||
static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3');
|
||||
|
||||
static constexpr u32 Magic = Magic3;
|
||||
|
||||
static constexpr std::size_t Size = 1_KiB;
|
||||
static constexpr s32 FsCountMax = 4;
|
||||
static constexpr std::size_t HeaderSignCount = 2;
|
||||
static constexpr std::size_t HeaderSignSize = 0x100;
|
||||
static constexpr std::size_t EncryptedKeyAreaSize = 0x100;
|
||||
static constexpr std::size_t SectorSize = 0x200;
|
||||
static constexpr std::size_t SectorShift = 9;
|
||||
static constexpr std::size_t RightsIdSize = 0x10;
|
||||
static constexpr std::size_t XtsBlockSize = 0x200;
|
||||
static constexpr std::size_t CtrBlockSize = 0x10;
|
||||
|
||||
static_assert(SectorSize == (1 << SectorShift));
|
||||
|
||||
// Data members.
|
||||
std::array<u8, HeaderSignSize> header_sign_1;
|
||||
std::array<u8, HeaderSignSize> header_sign_2;
|
||||
u32 magic;
|
||||
DistributionType distribution_type;
|
||||
ContentType content_type;
|
||||
u8 key_generation;
|
||||
u8 key_index;
|
||||
u64 content_size;
|
||||
u64 program_id;
|
||||
u32 content_index;
|
||||
u32 sdk_addon_version;
|
||||
u8 key_generation_2;
|
||||
u8 header1_signature_key_generation;
|
||||
std::array<u8, 2> reserved_222;
|
||||
std::array<u32, 3> reserved_224;
|
||||
std::array<u8, RightsIdSize> rights_id;
|
||||
std::array<FsInfo, FsCountMax> fs_info;
|
||||
std::array<Hash, FsCountMax> fs_header_hash;
|
||||
std::array<u8, EncryptedKeyAreaSize> encrypted_key_area;
|
||||
|
||||
static constexpr u64 SectorToByte(u32 sector) {
|
||||
return static_cast<u64>(sector) << SectorShift;
|
||||
}
|
||||
|
||||
static constexpr u32 ByteToSector(u64 byte) {
|
||||
return static_cast<u32>(byte >> SectorShift);
|
||||
}
|
||||
|
||||
u8 GetProperKeyGeneration() const;
|
||||
};
|
||||
static_assert(sizeof(NcaHeader) == NcaHeader::Size);
|
||||
static_assert(std::is_trivial_v<NcaHeader>);
|
||||
|
||||
struct NcaBucketInfo {
|
||||
static constexpr size_t HeaderSize = 0x10;
|
||||
Int64 offset;
|
||||
Int64 size;
|
||||
std::array<u8, HeaderSize> header;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaBucketInfo>);
|
||||
|
||||
struct NcaPatchInfo {
|
||||
static constexpr size_t Size = 0x40;
|
||||
static constexpr size_t Offset = 0x100;
|
||||
|
||||
Int64 indirect_offset;
|
||||
Int64 indirect_size;
|
||||
std::array<u8, NcaBucketInfo::HeaderSize> indirect_header;
|
||||
Int64 aes_ctr_ex_offset;
|
||||
Int64 aes_ctr_ex_size;
|
||||
std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header;
|
||||
|
||||
bool HasIndirectTable() const;
|
||||
bool HasAesCtrExTable() const;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaPatchInfo>);
|
||||
|
||||
union NcaAesCtrUpperIv {
|
||||
u64 value;
|
||||
struct {
|
||||
u32 generation;
|
||||
u32 secure_value;
|
||||
} part;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaAesCtrUpperIv>);
|
||||
|
||||
struct NcaSparseInfo {
|
||||
NcaBucketInfo bucket;
|
||||
Int64 physical_offset;
|
||||
u16 generation;
|
||||
std::array<u8, 6> reserved;
|
||||
|
||||
s64 GetPhysicalSize() const {
|
||||
return this->bucket.offset + this->bucket.size;
|
||||
}
|
||||
|
||||
u32 GetGeneration() const {
|
||||
return static_cast<u32>(this->generation) << 16;
|
||||
}
|
||||
|
||||
const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const {
|
||||
NcaAesCtrUpperIv sparse_upper_iv = upper_iv;
|
||||
sparse_upper_iv.part.generation = this->GetGeneration();
|
||||
return sparse_upper_iv;
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaSparseInfo>);
|
||||
|
||||
struct NcaCompressionInfo {
|
||||
NcaBucketInfo bucket;
|
||||
std::array<u8, 8> resreved;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaCompressionInfo>);
|
||||
|
||||
struct NcaMetaDataHashDataInfo {
|
||||
Int64 offset;
|
||||
Int64 size;
|
||||
Hash hash;
|
||||
};
|
||||
static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>);
|
||||
|
||||
struct NcaFsHeader {
|
||||
static constexpr size_t Size = 0x200;
|
||||
static constexpr size_t HashDataOffset = 0x8;
|
||||
|
||||
struct Region {
|
||||
Int64 offset;
|
||||
Int64 size;
|
||||
};
|
||||
static_assert(std::is_trivial_v<Region>);
|
||||
|
||||
enum class FsType : u8 {
|
||||
RomFs = 0,
|
||||
PartitionFs = 1,
|
||||
};
|
||||
|
||||
enum class EncryptionType : u8 {
|
||||
Auto = 0,
|
||||
None = 1,
|
||||
AesXts = 2,
|
||||
AesCtr = 3,
|
||||
AesCtrEx = 4,
|
||||
AesCtrSkipLayerHash = 5,
|
||||
AesCtrExSkipLayerHash = 6,
|
||||
};
|
||||
|
||||
enum class HashType : u8 {
|
||||
Auto = 0,
|
||||
None = 1,
|
||||
HierarchicalSha256Hash = 2,
|
||||
HierarchicalIntegrityHash = 3,
|
||||
AutoSha3 = 4,
|
||||
HierarchicalSha3256Hash = 5,
|
||||
HierarchicalIntegritySha3Hash = 6,
|
||||
};
|
||||
|
||||
enum class MetaDataHashType : u8 {
|
||||
None = 0,
|
||||
HierarchicalIntegrity = 1,
|
||||
};
|
||||
|
||||
union HashData {
|
||||
struct HierarchicalSha256Data {
|
||||
static constexpr size_t HashLayerCountMax = 5;
|
||||
static const size_t MasterHashOffset;
|
||||
|
||||
Hash fs_data_master_hash;
|
||||
s32 hash_block_size;
|
||||
s32 hash_layer_count;
|
||||
std::array<Region, HashLayerCountMax> hash_layer_region;
|
||||
} hierarchical_sha256_data;
|
||||
static_assert(std::is_trivial_v<HierarchicalSha256Data>);
|
||||
|
||||
struct IntegrityMetaInfo {
|
||||
static const size_t MasterHashOffset;
|
||||
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 master_hash_size;
|
||||
|
||||
struct LevelHashInfo {
|
||||
u32 max_layers;
|
||||
|
||||
struct HierarchicalIntegrityVerificationLevelInformation {
|
||||
static constexpr size_t IntegrityMaxLayerCount = 7;
|
||||
Int64 offset;
|
||||
Int64 size;
|
||||
s32 block_order;
|
||||
std::array<u8, 4> reserved;
|
||||
};
|
||||
std::array<
|
||||
HierarchicalIntegrityVerificationLevelInformation,
|
||||
HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1>
|
||||
info;
|
||||
|
||||
struct SignatureSalt {
|
||||
static constexpr size_t Size = 0x20;
|
||||
std::array<u8, Size> value;
|
||||
};
|
||||
SignatureSalt seed;
|
||||
} level_hash_info;
|
||||
|
||||
Hash master_hash;
|
||||
} integrity_meta_info;
|
||||
static_assert(std::is_trivial_v<IntegrityMetaInfo>);
|
||||
|
||||
std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding;
|
||||
};
|
||||
|
||||
u16 version;
|
||||
FsType fs_type;
|
||||
HashType hash_type;
|
||||
EncryptionType encryption_type;
|
||||
MetaDataHashType meta_data_hash_type;
|
||||
std::array<u8, 2> reserved;
|
||||
HashData hash_data;
|
||||
NcaPatchInfo patch_info;
|
||||
NcaAesCtrUpperIv aes_ctr_upper_iv;
|
||||
NcaSparseInfo sparse_info;
|
||||
NcaCompressionInfo compression_info;
|
||||
NcaMetaDataHashDataInfo meta_data_hash_data_info;
|
||||
std::array<u8, 0x30> pad;
|
||||
|
||||
bool IsSkipLayerHashEncryption() const {
|
||||
return this->encryption_type == EncryptionType::AesCtrSkipLayerHash ||
|
||||
this->encryption_type == EncryptionType::AesCtrExSkipLayerHash;
|
||||
}
|
||||
|
||||
Result GetHashTargetOffset(s64* out) const {
|
||||
switch (this->hash_type) {
|
||||
case HashType::HierarchicalIntegrityHash:
|
||||
case HashType::HierarchicalIntegritySha3Hash:
|
||||
*out = this->hash_data.integrity_meta_info.level_hash_info
|
||||
.info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2]
|
||||
.offset;
|
||||
R_SUCCEED();
|
||||
case HashType::HierarchicalSha256Hash:
|
||||
case HashType::HierarchicalSha3256Hash:
|
||||
*out =
|
||||
this->hash_data.hierarchical_sha256_data
|
||||
.hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count -
|
||||
1]
|
||||
.offset;
|
||||
R_SUCCEED();
|
||||
default:
|
||||
R_THROW(ResultInvalidNcaFsHeader);
|
||||
}
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
|
||||
static_assert(std::is_trivial_v<NcaFsHeader>);
|
||||
static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset);
|
||||
|
||||
inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset =
|
||||
offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
|
||||
inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset =
|
||||
offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash);
|
||||
|
||||
struct NcaMetaDataHashData {
|
||||
s64 layer_info_offset;
|
||||
NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
|
||||
};
|
||||
static_assert(sizeof(NcaMetaDataHashData) ==
|
||||
sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
|
||||
static_assert(std::is_trivial_v<NcaMetaDataHashData>);
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,531 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
|
||||
#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline u32 SdkAddonVersionMin = 0x000B0000;
|
||||
constexpr inline size_t Aes128KeySize = 0x10;
|
||||
constexpr const std::array<u8, Aes128KeySize> ZeroKey{};
|
||||
|
||||
constexpr Result CheckNcaMagic(u32 magic) {
|
||||
// Verify the magic is not a deprecated one.
|
||||
R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion);
|
||||
R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion);
|
||||
R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion);
|
||||
|
||||
// Verify the magic is the current one.
|
||||
R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NcaReader::NcaReader()
|
||||
: m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false),
|
||||
m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto),
|
||||
m_get_decompressor() {
|
||||
std::memset(std::addressof(m_header), 0, sizeof(m_header));
|
||||
std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys));
|
||||
std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key));
|
||||
}
|
||||
|
||||
NcaReader::~NcaReader() {}
|
||||
|
||||
Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
|
||||
const NcaCompressionConfiguration& compression_cfg) {
|
||||
// Validate preconditions.
|
||||
ASSERT(base_storage != nullptr);
|
||||
ASSERT(m_body_storage == nullptr);
|
||||
|
||||
// Create the work header storage storage.
|
||||
VirtualFile work_header_storage;
|
||||
|
||||
// We need to be able to generate keys.
|
||||
R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument);
|
||||
|
||||
// Generate keys for header.
|
||||
using AesXtsStorageForNcaHeader = AesXtsStorage;
|
||||
|
||||
constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount>
|
||||
HeaderKeyTypeValues = {
|
||||
static_cast<s32>(KeyType::NcaHeaderKey1),
|
||||
static_cast<s32>(KeyType::NcaHeaderKey2),
|
||||
};
|
||||
|
||||
std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
|
||||
NcaCryptoConfiguration::HeaderEncryptionKeyCount>
|
||||
header_decryption_keys;
|
||||
for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) {
|
||||
crypto_cfg.generate_key(header_decryption_keys[i].data(),
|
||||
AesXtsStorageForNcaHeader::KeySize,
|
||||
crypto_cfg.header_encrypted_encryption_keys[i].data(),
|
||||
AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]);
|
||||
}
|
||||
|
||||
// Create the header storage.
|
||||
std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {};
|
||||
work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(
|
||||
base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(),
|
||||
AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize,
|
||||
NcaHeader::XtsBlockSize);
|
||||
|
||||
// Check that we successfully created the storage.
|
||||
R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
|
||||
|
||||
// Read the header.
|
||||
work_header_storage->ReadObject(std::addressof(m_header), 0);
|
||||
|
||||
// Validate the magic.
|
||||
if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) {
|
||||
// Try to use a plaintext header.
|
||||
base_storage->ReadObject(std::addressof(m_header), 0);
|
||||
R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result);
|
||||
|
||||
// Configure to use the plaintext header.
|
||||
auto base_storage_size = base_storage->GetSize();
|
||||
work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0);
|
||||
R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
|
||||
|
||||
// Set encryption type as plaintext.
|
||||
m_header_encryption_type = NcaHeader::EncryptionType::None;
|
||||
}
|
||||
|
||||
// Verify the header sign1.
|
||||
if (crypto_cfg.verify_sign1 != nullptr) {
|
||||
const u8* sig = m_header.header_sign_1.data();
|
||||
const size_t sig_size = NcaHeader::HeaderSignSize;
|
||||
const u8* msg =
|
||||
static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic)));
|
||||
const size_t msg_size =
|
||||
NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount;
|
||||
|
||||
m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(
|
||||
sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation);
|
||||
|
||||
if (!m_is_header_sign1_signature_valid) {
|
||||
LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the sdk version.
|
||||
R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion);
|
||||
|
||||
// Validate the key index.
|
||||
R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount ||
|
||||
m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey,
|
||||
ResultInvalidNcaKeyIndex);
|
||||
|
||||
// Check if we have a rights id.
|
||||
constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{};
|
||||
if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) {
|
||||
// If we don't, then we don't have an external key, so we need to generate decryption keys.
|
||||
crypto_cfg.generate_key(
|
||||
m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize,
|
||||
m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize,
|
||||
Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
|
||||
crypto_cfg.generate_key(
|
||||
m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize,
|
||||
m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize,
|
||||
Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
|
||||
crypto_cfg.generate_key(
|
||||
m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize,
|
||||
m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize,
|
||||
Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
|
||||
crypto_cfg.generate_key(
|
||||
m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize,
|
||||
m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize,
|
||||
Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
|
||||
|
||||
// Copy the hardware speed emulation key.
|
||||
std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(),
|
||||
m_header.encrypted_key_area.data() +
|
||||
NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize,
|
||||
Aes128KeySize);
|
||||
}
|
||||
|
||||
// Clear the external decryption key.
|
||||
std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size());
|
||||
|
||||
// Set software key availability.
|
||||
m_is_available_sw_key = crypto_cfg.is_available_sw_key;
|
||||
|
||||
// Set our decompressor function getter.
|
||||
m_get_decompressor = compression_cfg.get_decompressor;
|
||||
|
||||
// Set our storages.
|
||||
m_header_storage = std::move(work_header_storage);
|
||||
m_body_storage = std::move(base_storage);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
VirtualFile NcaReader::GetSharedBodyStorage() {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_body_storage;
|
||||
}
|
||||
|
||||
u32 NcaReader::GetMagic() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.magic;
|
||||
}
|
||||
|
||||
NcaHeader::DistributionType NcaReader::GetDistributionType() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.distribution_type;
|
||||
}
|
||||
|
||||
NcaHeader::ContentType NcaReader::GetContentType() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.content_type;
|
||||
}
|
||||
|
||||
u8 NcaReader::GetHeaderSign1KeyGeneration() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.header1_signature_key_generation;
|
||||
}
|
||||
|
||||
u8 NcaReader::GetKeyGeneration() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.GetProperKeyGeneration();
|
||||
}
|
||||
|
||||
u8 NcaReader::GetKeyIndex() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.key_index;
|
||||
}
|
||||
|
||||
u64 NcaReader::GetContentSize() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.content_size;
|
||||
}
|
||||
|
||||
u64 NcaReader::GetProgramId() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.program_id;
|
||||
}
|
||||
|
||||
u32 NcaReader::GetContentIndex() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.content_index;
|
||||
}
|
||||
|
||||
u32 NcaReader::GetSdkAddonVersion() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
return m_header.sdk_addon_version;
|
||||
}
|
||||
|
||||
void NcaReader::GetRightsId(u8* dst, size_t dst_size) const {
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(dst_size >= NcaHeader::RightsIdSize);
|
||||
|
||||
std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize);
|
||||
}
|
||||
|
||||
bool NcaReader::HasFsInfo(s32 index) const {
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0;
|
||||
}
|
||||
|
||||
s32 NcaReader::GetFsCount() const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
for (s32 i = 0; i < NcaHeader::FsCountMax; i++) {
|
||||
if (!this->HasFsInfo(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return NcaHeader::FsCountMax;
|
||||
}
|
||||
|
||||
const Hash& NcaReader::GetFsHeaderHash(s32 index) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
return m_header.fs_header_hash[index];
|
||||
}
|
||||
|
||||
void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
ASSERT(dst != nullptr);
|
||||
std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst));
|
||||
}
|
||||
|
||||
void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
ASSERT(dst != nullptr);
|
||||
std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst));
|
||||
}
|
||||
|
||||
u64 NcaReader::GetFsOffset(s32 index) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector);
|
||||
}
|
||||
|
||||
u64 NcaReader::GetFsEndOffset(s32 index) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector);
|
||||
}
|
||||
|
||||
u64 NcaReader::GetFsSize(s32 index) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector -
|
||||
m_header.fs_info[index].start_sector);
|
||||
}
|
||||
|
||||
void NcaReader::GetEncryptedKey(void* dst, size_t size) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(size >= NcaHeader::EncryptedKeyAreaSize);
|
||||
|
||||
std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize);
|
||||
}
|
||||
|
||||
const void* NcaReader::GetDecryptionKey(s32 index) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count);
|
||||
return m_decryption_keys[index].data();
|
||||
}
|
||||
|
||||
bool NcaReader::HasValidInternalKey() const {
|
||||
for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) {
|
||||
if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize,
|
||||
Aes128KeySize) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NcaReader::HasInternalDecryptionKeyForAesHw() const {
|
||||
return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw),
|
||||
Aes128KeySize) != 0;
|
||||
}
|
||||
|
||||
bool NcaReader::IsSoftwareAesPrioritized() const {
|
||||
return m_is_software_aes_prioritized;
|
||||
}
|
||||
|
||||
void NcaReader::PrioritizeSoftwareAes() {
|
||||
m_is_software_aes_prioritized = true;
|
||||
}
|
||||
|
||||
bool NcaReader::IsAvailableSwKey() const {
|
||||
return m_is_available_sw_key;
|
||||
}
|
||||
|
||||
bool NcaReader::HasExternalDecryptionKey() const {
|
||||
return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0;
|
||||
}
|
||||
|
||||
const void* NcaReader::GetExternalDecryptionKey() const {
|
||||
return m_external_decryption_key.data();
|
||||
}
|
||||
|
||||
void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) {
|
||||
ASSERT(src != nullptr);
|
||||
ASSERT(size == sizeof(m_external_decryption_key));
|
||||
|
||||
std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key));
|
||||
}
|
||||
|
||||
void NcaReader::GetRawData(void* dst, size_t dst_size) const {
|
||||
ASSERT(m_body_storage != nullptr);
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(dst_size >= sizeof(NcaHeader));
|
||||
|
||||
std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader));
|
||||
}
|
||||
|
||||
GetDecompressorFunction NcaReader::GetDecompressor() const {
|
||||
ASSERT(m_get_decompressor != nullptr);
|
||||
return m_get_decompressor;
|
||||
}
|
||||
|
||||
NcaHeader::EncryptionType NcaReader::GetEncryptionType() const {
|
||||
return m_header_encryption_type;
|
||||
}
|
||||
|
||||
Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const {
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(0 <= index && index < NcaHeader::FsCountMax);
|
||||
|
||||
const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index;
|
||||
m_header_storage->ReadObject(dst, offset);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
bool NcaReader::GetHeaderSign1Valid() const {
|
||||
return m_is_header_sign1_signature_valid;
|
||||
}
|
||||
|
||||
void NcaReader::GetHeaderSign2(void* dst, size_t size) const {
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(size == NcaHeader::HeaderSignSize);
|
||||
|
||||
std::memcpy(dst, m_header.header_sign_2.data(), size);
|
||||
}
|
||||
|
||||
Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) {
|
||||
// Reset ourselves to uninitialized.
|
||||
m_fs_index = -1;
|
||||
|
||||
// Read the header.
|
||||
R_TRY(reader.ReadHeader(std::addressof(m_data), index));
|
||||
|
||||
// Set our index.
|
||||
m_fs_index = index;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const {
|
||||
ASSERT(this->IsInitialized());
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(dst_size >= sizeof(NcaFsHeader));
|
||||
|
||||
std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader));
|
||||
}
|
||||
|
||||
NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.hash_data;
|
||||
}
|
||||
|
||||
const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.hash_data;
|
||||
}
|
||||
|
||||
u16 NcaFsHeaderReader::GetVersion() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.version;
|
||||
}
|
||||
|
||||
s32 NcaFsHeaderReader::GetFsIndex() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_fs_index;
|
||||
}
|
||||
|
||||
NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.fs_type;
|
||||
}
|
||||
|
||||
NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.hash_type;
|
||||
}
|
||||
|
||||
NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.encryption_type;
|
||||
}
|
||||
|
||||
NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.patch_info;
|
||||
}
|
||||
|
||||
const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.patch_info;
|
||||
}
|
||||
|
||||
const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.aes_ctr_upper_iv;
|
||||
}
|
||||
|
||||
bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.IsSkipLayerHashEncryption();
|
||||
}
|
||||
|
||||
Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const {
|
||||
ASSERT(out != nullptr);
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
R_RETURN(m_data.GetHashTargetOffset(out));
|
||||
}
|
||||
|
||||
bool NcaFsHeaderReader::ExistsSparseLayer() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.sparse_info.generation != 0;
|
||||
}
|
||||
|
||||
NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.sparse_info;
|
||||
}
|
||||
|
||||
const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.sparse_info;
|
||||
}
|
||||
|
||||
bool NcaFsHeaderReader::ExistsCompressionLayer() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0;
|
||||
}
|
||||
|
||||
NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.compression_info;
|
||||
}
|
||||
|
||||
const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.compression_info;
|
||||
}
|
||||
|
||||
bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
|
||||
}
|
||||
|
||||
NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_data_info;
|
||||
}
|
||||
|
||||
const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_data_info;
|
||||
}
|
||||
|
||||
NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_type;
|
||||
}
|
||||
|
||||
bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
|
||||
}
|
||||
|
||||
NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_data_info;
|
||||
}
|
||||
|
||||
const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_data_info;
|
||||
}
|
||||
|
||||
NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const {
|
||||
ASSERT(this->IsInitialized());
|
||||
return m_data.meta_data_hash_type;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t HeapBlockSize = BufferPoolAlignment;
|
||||
static_assert(HeapBlockSize == 4_KiB);
|
||||
|
||||
// A heap block is 4KiB. An order is a power of two.
|
||||
// This gives blocks of the order 32KiB, 512KiB, 4MiB.
|
||||
constexpr s32 HeapOrderMax = 7;
|
||||
constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3;
|
||||
|
||||
constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax);
|
||||
constexpr size_t HeapAllocatableSizeMaxForLarge =
|
||||
HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge);
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) {
|
||||
return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax;
|
||||
}
|
||||
|
||||
void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) {
|
||||
// Ensure preconditions.
|
||||
ASSERT(m_buffer == nullptr);
|
||||
|
||||
// Check that we can allocate this size.
|
||||
ASSERT(required_size <= GetAllocatableSizeMaxCore(large));
|
||||
|
||||
const size_t target_size =
|
||||
std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large));
|
||||
|
||||
// Dummy implementation for allocate.
|
||||
if (target_size > 0) {
|
||||
m_buffer =
|
||||
reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize}));
|
||||
m_size = target_size;
|
||||
|
||||
// Ensure postconditions.
|
||||
ASSERT(m_buffer != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PooledBuffer::Shrink(size_t ideal_size) {
|
||||
ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true));
|
||||
|
||||
// Shrinking to zero means that we have no buffer.
|
||||
if (ideal_size == 0) {
|
||||
::operator delete(m_buffer, std::align_val_t{HeapBlockSize});
|
||||
m_buffer = nullptr;
|
||||
m_size = ideal_size;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/literals.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
constexpr inline size_t BufferPoolAlignment = 4_KiB;
|
||||
constexpr inline size_t BufferPoolWorkSize = 320;
|
||||
|
||||
class PooledBuffer {
|
||||
YUZU_NON_COPYABLE(PooledBuffer);
|
||||
|
||||
public:
|
||||
// Constructor/Destructor.
|
||||
constexpr PooledBuffer() : m_buffer(), m_size() {}
|
||||
|
||||
PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() {
|
||||
this->Allocate(ideal_size, required_size);
|
||||
}
|
||||
|
||||
~PooledBuffer() {
|
||||
this->Deallocate();
|
||||
}
|
||||
|
||||
// Move and assignment.
|
||||
explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) {
|
||||
rhs.m_buffer = nullptr;
|
||||
rhs.m_size = 0;
|
||||
}
|
||||
|
||||
PooledBuffer& operator=(PooledBuffer&& rhs) {
|
||||
PooledBuffer(std::move(rhs)).Swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Allocation API.
|
||||
void Allocate(size_t ideal_size, size_t required_size) {
|
||||
return this->AllocateCore(ideal_size, required_size, false);
|
||||
}
|
||||
|
||||
void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) {
|
||||
return this->AllocateCore(ideal_size, required_size, true);
|
||||
}
|
||||
|
||||
void Shrink(size_t ideal_size);
|
||||
|
||||
void Deallocate() {
|
||||
// Shrink the buffer to empty.
|
||||
this->Shrink(0);
|
||||
ASSERT(m_buffer == nullptr);
|
||||
}
|
||||
|
||||
char* GetBuffer() const {
|
||||
ASSERT(m_buffer != nullptr);
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
size_t GetSize() const {
|
||||
ASSERT(m_buffer != nullptr);
|
||||
return m_size;
|
||||
}
|
||||
|
||||
public:
|
||||
static size_t GetAllocatableSizeMax() {
|
||||
return GetAllocatableSizeMaxCore(false);
|
||||
}
|
||||
static size_t GetAllocatableParticularlyLargeSizeMax() {
|
||||
return GetAllocatableSizeMaxCore(true);
|
||||
}
|
||||
|
||||
private:
|
||||
static size_t GetAllocatableSizeMaxCore(bool large);
|
||||
|
||||
private:
|
||||
void Swap(PooledBuffer& rhs) {
|
||||
std::swap(m_buffer, rhs.m_buffer);
|
||||
std::swap(m_size, rhs.m_size);
|
||||
}
|
||||
|
||||
void AllocateCore(size_t ideal_size, size_t required_size, bool large);
|
||||
|
||||
private:
|
||||
char* m_buffer;
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_sparse_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
|
||||
// Validate preconditions.
|
||||
ASSERT(this->IsInitialized());
|
||||
ASSERT(buffer != nullptr);
|
||||
|
||||
// Allow zero size.
|
||||
if (size == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
SparseStorage* self = const_cast<SparseStorage*>(this);
|
||||
|
||||
if (self->GetEntryTable().IsEmpty()) {
|
||||
BucketTree::Offsets table_offsets;
|
||||
ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets))));
|
||||
ASSERT(table_offsets.IsInclude(offset, size));
|
||||
|
||||
std::memset(buffer, 0, size);
|
||||
} else {
|
||||
self->OperatePerEntry<false, true>(
|
||||
offset, size,
|
||||
[=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
|
||||
storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
|
||||
static_cast<size_t>(cur_size), data_offset);
|
||||
R_SUCCEED();
|
||||
});
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class SparseStorage : public IndirectStorage {
|
||||
YUZU_NON_COPYABLE(SparseStorage);
|
||||
YUZU_NON_MOVEABLE(SparseStorage);
|
||||
|
||||
private:
|
||||
class ZeroStorage : public IReadOnlyStorage {
|
||||
public:
|
||||
ZeroStorage() {}
|
||||
virtual ~ZeroStorage() {}
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
|
||||
ASSERT(buffer != nullptr || size == 0);
|
||||
|
||||
if (size > 0) {
|
||||
std::memset(buffer, 0, size);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {}
|
||||
virtual ~SparseStorage() {}
|
||||
|
||||
using IndirectStorage::Initialize;
|
||||
|
||||
void Initialize(s64 end_offset) {
|
||||
this->GetEntryTable().Initialize(NodeSize, end_offset);
|
||||
this->SetZeroStorage();
|
||||
}
|
||||
|
||||
void SetDataStorage(VirtualFile storage) {
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
this->SetStorage(0, storage);
|
||||
this->SetZeroStorage();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SetDataStorage(T storage, s64 offset, s64 size) {
|
||||
ASSERT(this->IsInitialized());
|
||||
|
||||
this->SetStorage(0, storage, offset, size);
|
||||
this->SetZeroStorage();
|
||||
}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
|
||||
|
||||
private:
|
||||
void SetZeroStorage() {
|
||||
return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max());
|
||||
}
|
||||
|
||||
private:
|
||||
VirtualFile m_zero_storage;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/fssystem/fs_i_storage.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class RegionSwitchStorage : public IReadOnlyStorage {
|
||||
YUZU_NON_COPYABLE(RegionSwitchStorage);
|
||||
YUZU_NON_MOVEABLE(RegionSwitchStorage);
|
||||
|
||||
public:
|
||||
struct Region {
|
||||
s64 offset;
|
||||
s64 size;
|
||||
};
|
||||
|
||||
public:
|
||||
RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r)
|
||||
: m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)),
|
||||
m_region(r) {}
|
||||
|
||||
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
|
||||
// Process until we're done.
|
||||
size_t processed = 0;
|
||||
while (processed < size) {
|
||||
// Process on the appropriate storage.
|
||||
s64 cur_size = 0;
|
||||
if (this->CheckRegions(std::addressof(cur_size), offset + processed,
|
||||
size - processed)) {
|
||||
m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed);
|
||||
} else {
|
||||
m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed);
|
||||
}
|
||||
|
||||
// Advance.
|
||||
processed += cur_size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
virtual size_t GetSize() const override {
|
||||
return m_inside_region_storage->GetSize();
|
||||
}
|
||||
|
||||
private:
|
||||
bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const {
|
||||
// Check if our region contains the access.
|
||||
if (m_region.offset <= offset) {
|
||||
if (offset < m_region.offset + m_region.size) {
|
||||
if (m_region.offset + m_region.size <= offset + size) {
|
||||
*out_current_size = m_region.offset + m_region.size - offset;
|
||||
} else {
|
||||
*out_current_size = size;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
*out_current_size = size;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (m_region.offset <= offset + size) {
|
||||
*out_current_size = m_region.offset - offset;
|
||||
} else {
|
||||
*out_current_size = size;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
VirtualFile m_inside_region_storage;
|
||||
VirtualFile m_outside_region_storage;
|
||||
Region m_region;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/file_sys/fssystem/fssystem_utility.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
void AddCounter(void* counter_, size_t counter_size, u64 value) {
|
||||
u8* counter = static_cast<u8*>(counter_);
|
||||
u64 remaining = value;
|
||||
u8 carry = 0;
|
||||
|
||||
for (size_t i = 0; i < counter_size; i++) {
|
||||
auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry;
|
||||
carry = static_cast<u8>(sum >> (sizeof(u8) * 8));
|
||||
auto sum8 = static_cast<u8>(sum & 0xFF);
|
||||
|
||||
counter[counter_size - 1 - i] = sum8;
|
||||
|
||||
remaining >>= (sizeof(u8) * 8);
|
||||
if (carry == 0 && remaining == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
void AddCounter(void* counter, size_t counter_size, u64 value);
|
||||
|
||||
}
|
||||
|
|
@ -1,344 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class IPSFileType {
|
||||
IPS,
|
||||
IPS32,
|
||||
Error,
|
||||
};
|
||||
|
||||
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
|
||||
{"\\a", "\a"},
|
||||
{"\\b", "\b"},
|
||||
{"\\f", "\f"},
|
||||
{"\\n", "\n"},
|
||||
{"\\r", "\r"},
|
||||
{"\\t", "\t"},
|
||||
{"\\v", "\v"},
|
||||
{"\\\\", "\\"},
|
||||
{"\\\'", "\'"},
|
||||
{"\\\"", "\""},
|
||||
{"\\\?", "\?"},
|
||||
}};
|
||||
|
||||
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
|
||||
if (magic.size() != 5) {
|
||||
return IPSFileType::Error;
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
|
||||
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
|
||||
return IPSFileType::IPS;
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
|
||||
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
|
||||
return IPSFileType::IPS32;
|
||||
}
|
||||
|
||||
return IPSFileType::Error;
|
||||
}
|
||||
|
||||
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
|
||||
static constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
|
||||
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
|
||||
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
|
||||
}
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
if (in == nullptr || ips == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
|
||||
if (type == IPSFileType::Error)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
if (in_data.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
|
||||
u64 offset = 5; // After header
|
||||
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
|
||||
offset += temp.size();
|
||||
if (IsEOF(type, temp)) {
|
||||
break;
|
||||
}
|
||||
|
||||
u32 real_offset{};
|
||||
if (type == IPSFileType::IPS32)
|
||||
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
|
||||
else
|
||||
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
|
||||
|
||||
if (real_offset > in_data.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u16 data_size{};
|
||||
if (ips->ReadObject(&data_size, offset) != sizeof(u16))
|
||||
return nullptr;
|
||||
data_size = Common::swap16(data_size);
|
||||
offset += sizeof(u16);
|
||||
|
||||
if (data_size == 0) { // RLE
|
||||
u16 rle_size{};
|
||||
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
|
||||
return nullptr;
|
||||
rle_size = Common::swap16(rle_size);
|
||||
offset += sizeof(u16);
|
||||
|
||||
const auto data = ips->ReadByte(offset++);
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
if (real_offset + rle_size > in_data.size())
|
||||
rle_size = static_cast<u16>(in_data.size() - real_offset);
|
||||
std::memset(in_data.data() + real_offset, *data, rle_size);
|
||||
} else { // Standard Patch
|
||||
auto read = data_size;
|
||||
if (real_offset + read > in_data.size())
|
||||
read = static_cast<u16>(in_data.size() - real_offset);
|
||||
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
|
||||
return nullptr;
|
||||
offset += data_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsEOF(type, temp)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
struct IPSwitchCompiler::IPSwitchPatch {
|
||||
std::string name;
|
||||
bool enabled;
|
||||
std::map<u32, std::vector<u8>> records;
|
||||
};
|
||||
|
||||
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
|
||||
Parse();
|
||||
}
|
||||
|
||||
IPSwitchCompiler::~IPSwitchCompiler() = default;
|
||||
|
||||
std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
|
||||
return nso_build_id;
|
||||
}
|
||||
|
||||
bool IPSwitchCompiler::IsValid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool StartsWith(std::string_view base, std::string_view check) {
|
||||
return base.size() >= check.size() && base.substr(0, check.size()) == check;
|
||||
}
|
||||
|
||||
static std::string EscapeStringSequences(std::string in) {
|
||||
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
|
||||
for (auto index = in.find(seq.first); index != std::string::npos;
|
||||
index = in.find(seq.first, index)) {
|
||||
in.replace(index, std::strlen(seq.first), seq.second);
|
||||
index += std::strlen(seq.second);
|
||||
}
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void IPSwitchCompiler::ParseFlag(const std::string& line) {
|
||||
if (StartsWith(line, "@flag offset_shift ")) {
|
||||
// Offset Shift Flag
|
||||
offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
|
||||
} else if (StartsWith(line, "@little-endian")) {
|
||||
// Set values to read as little endian
|
||||
is_little_endian = true;
|
||||
} else if (StartsWith(line, "@big-endian")) {
|
||||
// Set values to read as big endian
|
||||
is_little_endian = false;
|
||||
} else if (StartsWith(line, "@flag print_values")) {
|
||||
// Force printing of applied values
|
||||
print_values = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IPSwitchCompiler::Parse() {
|
||||
const auto bytes = patch_text->ReadAllBytes();
|
||||
std::stringstream s;
|
||||
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(s, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
// Remove midline comments
|
||||
std::size_t comment_index = std::string::npos;
|
||||
bool within_string = false;
|
||||
for (std::size_t k = 0; k < line.size(); ++k) {
|
||||
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
|
||||
within_string = !within_string;
|
||||
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
|
||||
comment_index = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
|
||||
last_comment = line.substr(comment_index + 2);
|
||||
line = line.substr(0, comment_index);
|
||||
}
|
||||
|
||||
if (StartsWith(line, "@stop")) {
|
||||
// Force stop
|
||||
break;
|
||||
} else if (StartsWith(line, "@nsobid-")) {
|
||||
// NSO Build ID Specifier
|
||||
const auto raw_build_id = fmt::format("{:0<64}", line.substr(8));
|
||||
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
|
||||
} else if (StartsWith(line, "#")) {
|
||||
// Mandatory Comment
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
|
||||
patch_text->GetName(), line.substr(1));
|
||||
} else if (StartsWith(line, "//")) {
|
||||
// Normal Comment
|
||||
last_comment = line.substr(2);
|
||||
if (last_comment.find_first_not_of(' ') == std::string::npos)
|
||||
continue;
|
||||
if (last_comment.find_first_not_of(' ') != 0)
|
||||
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
|
||||
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
|
||||
// Start of patch
|
||||
const auto enabled = StartsWith(line, "@enabled");
|
||||
if (i == 0)
|
||||
return;
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
|
||||
patch_text->GetName(), last_comment, line.substr(1));
|
||||
|
||||
IPSwitchPatch patch{last_comment, enabled, {}};
|
||||
|
||||
// Read rest of patch
|
||||
while (true) {
|
||||
if (i + 1 >= lines.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& patch_line = lines[++i];
|
||||
|
||||
// Start of new patch
|
||||
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for a flag
|
||||
if (StartsWith(patch_line, "@")) {
|
||||
ParseFlag(patch_line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
|
||||
if (patch_line.length() < 11)
|
||||
break;
|
||||
auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
|
||||
offset += static_cast<unsigned long>(offset_shift);
|
||||
|
||||
std::vector<u8> replace;
|
||||
// 9 - first char of replacement val
|
||||
if (patch_line[9] == '\"') {
|
||||
// string replacement
|
||||
auto end_index = patch_line.find('\"', 10);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
while (patch_line[end_index - 1] == '\\') {
|
||||
end_index = patch_line.find('\"', end_index + 1);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
}
|
||||
|
||||
auto value = patch_line.substr(10, end_index - 10);
|
||||
value = EscapeStringSequences(value);
|
||||
replace.reserve(value.size());
|
||||
std::copy(value.begin(), value.end(), std::back_inserter(replace));
|
||||
} else {
|
||||
// hex replacement
|
||||
const auto value =
|
||||
patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9);
|
||||
replace = Common::HexStringToVector(value, is_little_endian);
|
||||
}
|
||||
|
||||
if (print_values) {
|
||||
LOG_INFO(Loader,
|
||||
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
|
||||
"with byte string '{}'",
|
||||
patch_text->GetName(), offset, Common::HexToString(replace));
|
||||
}
|
||||
|
||||
patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace));
|
||||
}
|
||||
|
||||
patches.push_back(std::move(patch));
|
||||
} else if (StartsWith(line, "@")) {
|
||||
ParseFlag(line);
|
||||
}
|
||||
}
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
|
||||
if (in == nullptr || !valid)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
|
||||
for (const auto& patch : patches) {
|
||||
if (!patch.enabled)
|
||||
continue;
|
||||
|
||||
for (const auto& record : patch.records) {
|
||||
if (record.first >= in_data.size())
|
||||
continue;
|
||||
auto replace_size = record.second.size();
|
||||
if (record.first + replace_size > in_data.size())
|
||||
replace_size = in_data.size() - record.first;
|
||||
for (std::size_t i = 0; i < replace_size; ++i)
|
||||
in_data[i + record.first] = record.second[i];
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
|
||||
|
||||
class IPSwitchCompiler {
|
||||
public:
|
||||
explicit IPSwitchCompiler(VirtualFile patch_text);
|
||||
~IPSwitchCompiler();
|
||||
|
||||
std::array<u8, 0x20> GetBuildID() const;
|
||||
bool IsValid() const;
|
||||
VirtualFile Apply(const VirtualFile& in) const;
|
||||
|
||||
private:
|
||||
struct IPSwitchPatch;
|
||||
|
||||
void ParseFlag(const std::string& flag);
|
||||
void Parse();
|
||||
|
||||
bool valid = false;
|
||||
|
||||
VirtualFile patch_text;
|
||||
std::vector<IPSwitchPatch> patches;
|
||||
std::array<u8, 0x20> nso_build_id{};
|
||||
bool is_little_endian = false;
|
||||
s64 offset_shift = 0;
|
||||
bool print_values = false;
|
||||
std::string last_comment = "";
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/kernel_executable.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u32 INI_MAX_KIPS = 0x50;
|
||||
|
||||
namespace {
|
||||
bool DecompressBLZ(std::vector<u8>& data) {
|
||||
if (data.size() < 0xC)
|
||||
return {};
|
||||
|
||||
const auto data_size = data.size() - 0xC;
|
||||
|
||||
u32 compressed_size{};
|
||||
u32 init_index{};
|
||||
u32 additional_size{};
|
||||
std::memcpy(&compressed_size, data.data() + data_size, sizeof(u32));
|
||||
std::memcpy(&init_index, data.data() + data_size + 0x4, sizeof(u32));
|
||||
std::memcpy(&additional_size, data.data() + data_size + 0x8, sizeof(u32));
|
||||
|
||||
const auto start_offset = data.size() - compressed_size;
|
||||
data.resize(compressed_size + additional_size + start_offset);
|
||||
|
||||
std::size_t index = compressed_size - init_index;
|
||||
std::size_t out_index = compressed_size + additional_size;
|
||||
|
||||
while (out_index > 0) {
|
||||
--index;
|
||||
auto control = data[index + start_offset];
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
if (((control << i) & 0x80) > 0) {
|
||||
if (index < 2) {
|
||||
return false;
|
||||
}
|
||||
index -= 2;
|
||||
std::size_t segment_offset =
|
||||
data[index + start_offset] | data[index + start_offset + 1] << 8;
|
||||
std::size_t segment_size = ((segment_offset >> 12) & 0xF) + 3;
|
||||
segment_offset &= 0xFFF;
|
||||
segment_offset += 3;
|
||||
|
||||
if (out_index < segment_size)
|
||||
segment_size = out_index;
|
||||
|
||||
if (out_index < segment_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out_index -= segment_size;
|
||||
|
||||
for (size_t j = 0; j < segment_size; ++j) {
|
||||
if (out_index + j + segment_offset + start_offset >= data.size()) {
|
||||
return false;
|
||||
}
|
||||
data[out_index + j + start_offset] =
|
||||
data[out_index + j + segment_offset + start_offset];
|
||||
}
|
||||
} else {
|
||||
if (out_index < 1) {
|
||||
return false;
|
||||
}
|
||||
--out_index;
|
||||
--index;
|
||||
data[out_index + start_offset] = data[index + start_offset];
|
||||
}
|
||||
|
||||
if (out_index == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
KIP::KIP(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
|
||||
if (file->GetSize() < sizeof(KIPHeader) || file->ReadObject(&header) != sizeof(KIPHeader)) {
|
||||
status = Loader::ResultStatus::ErrorBadKIPHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.magic != Common::MakeMagic('K', 'I', 'P', '1')) {
|
||||
status = Loader::ResultStatus::ErrorBadKIPHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
u64 offset = sizeof(KIPHeader);
|
||||
for (std::size_t i = 0; i < header.sections.size(); ++i) {
|
||||
auto compressed = file->ReadBytes(header.sections[i].compressed_size, offset);
|
||||
offset += header.sections[i].compressed_size;
|
||||
|
||||
if (header.sections[i].compressed_size == 0 && header.sections[i].decompressed_size != 0) {
|
||||
decompressed_sections[i] = std::vector<u8>(header.sections[i].decompressed_size);
|
||||
} else if (header.sections[i].compressed_size == header.sections[i].decompressed_size) {
|
||||
decompressed_sections[i] = std::move(compressed);
|
||||
} else {
|
||||
decompressed_sections[i] = compressed;
|
||||
if (!DecompressBLZ(decompressed_sections[i])) {
|
||||
status = Loader::ResultStatus::ErrorBLZDecompressionFailed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader::ResultStatus KIP::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::string KIP::GetName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(header.name.data(), header.name.size());
|
||||
}
|
||||
|
||||
u64 KIP::GetTitleID() const {
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
std::vector<u8> KIP::GetSectionDecompressed(u8 index) const {
|
||||
return decompressed_sections[index];
|
||||
}
|
||||
|
||||
bool KIP::Is64Bit() const {
|
||||
return (header.flags & 0x8) != 0;
|
||||
}
|
||||
|
||||
bool KIP::Is39BitAddressSpace() const {
|
||||
return (header.flags & 0x10) != 0;
|
||||
}
|
||||
|
||||
bool KIP::IsService() const {
|
||||
return (header.flags & 0x20) != 0;
|
||||
}
|
||||
|
||||
std::vector<u32> KIP::GetKernelCapabilities() const {
|
||||
return std::vector<u32>(header.capabilities.begin(), header.capabilities.end());
|
||||
}
|
||||
|
||||
s32 KIP::GetMainThreadPriority() const {
|
||||
return static_cast<s32>(header.main_thread_priority);
|
||||
}
|
||||
|
||||
u32 KIP::GetMainThreadStackSize() const {
|
||||
return header.sections[1].attribute;
|
||||
}
|
||||
|
||||
u32 KIP::GetMainThreadCpuCore() const {
|
||||
return header.default_core;
|
||||
}
|
||||
|
||||
const std::vector<u8>& KIP::GetTextSection() const {
|
||||
return decompressed_sections[0];
|
||||
}
|
||||
|
||||
const std::vector<u8>& KIP::GetRODataSection() const {
|
||||
return decompressed_sections[1];
|
||||
}
|
||||
|
||||
const std::vector<u8>& KIP::GetDataSection() const {
|
||||
return decompressed_sections[2];
|
||||
}
|
||||
|
||||
u32 KIP::GetTextOffset() const {
|
||||
return header.sections[0].offset;
|
||||
}
|
||||
|
||||
u32 KIP::GetRODataOffset() const {
|
||||
return header.sections[1].offset;
|
||||
}
|
||||
|
||||
u32 KIP::GetDataOffset() const {
|
||||
return header.sections[2].offset;
|
||||
}
|
||||
|
||||
u32 KIP::GetBSSSize() const {
|
||||
return header.sections[3].decompressed_size;
|
||||
}
|
||||
|
||||
u32 KIP::GetBSSOffset() const {
|
||||
return header.sections[3].offset;
|
||||
}
|
||||
|
||||
INI::INI(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
|
||||
if (file->GetSize() < sizeof(INIHeader) || file->ReadObject(&header) != sizeof(INIHeader)) {
|
||||
status = Loader::ResultStatus::ErrorBadINIHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.magic != Common::MakeMagic('I', 'N', 'I', '1')) {
|
||||
status = Loader::ResultStatus::ErrorBadINIHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.kip_count > INI_MAX_KIPS) {
|
||||
status = Loader::ResultStatus::ErrorINITooManyKIPs;
|
||||
return;
|
||||
}
|
||||
|
||||
u64 offset = sizeof(INIHeader);
|
||||
for (std::size_t i = 0; i < header.kip_count; ++i) {
|
||||
const auto kip_file =
|
||||
std::make_shared<OffsetVfsFile>(file, file->GetSize() - offset, offset);
|
||||
KIP kip(kip_file);
|
||||
if (kip.GetStatus() == Loader::ResultStatus::Success) {
|
||||
kips.push_back(std::move(kip));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader::ResultStatus INI::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
const std::vector<KIP>& INI::GetKIPs() const {
|
||||
return kips;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct KIPSectionHeader {
|
||||
u32_le offset;
|
||||
u32_le decompressed_size;
|
||||
u32_le compressed_size;
|
||||
u32_le attribute;
|
||||
};
|
||||
static_assert(sizeof(KIPSectionHeader) == 0x10, "KIPSectionHeader has incorrect size.");
|
||||
|
||||
struct KIPHeader {
|
||||
u32_le magic;
|
||||
std::array<char, 0xC> name;
|
||||
u64_le title_id;
|
||||
u32_le process_category;
|
||||
u8 main_thread_priority;
|
||||
u8 default_core;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u8 flags;
|
||||
std::array<KIPSectionHeader, 6> sections;
|
||||
std::array<u32, 0x20> capabilities;
|
||||
};
|
||||
static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
|
||||
|
||||
struct INIHeader {
|
||||
u32_le magic;
|
||||
u32_le size;
|
||||
u32_le kip_count;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
|
||||
|
||||
// Kernel Internal Process
|
||||
class KIP {
|
||||
public:
|
||||
explicit KIP(const VirtualFile& file);
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::string GetName() const;
|
||||
u64 GetTitleID() const;
|
||||
std::vector<u8> GetSectionDecompressed(u8 index) const;
|
||||
|
||||
// Executable Flags
|
||||
bool Is64Bit() const;
|
||||
bool Is39BitAddressSpace() const;
|
||||
bool IsService() const;
|
||||
|
||||
std::vector<u32> GetKernelCapabilities() const;
|
||||
|
||||
s32 GetMainThreadPriority() const;
|
||||
u32 GetMainThreadStackSize() const;
|
||||
u32 GetMainThreadCpuCore() const;
|
||||
|
||||
const std::vector<u8>& GetTextSection() const;
|
||||
const std::vector<u8>& GetRODataSection() const;
|
||||
const std::vector<u8>& GetDataSection() const;
|
||||
|
||||
u32 GetTextOffset() const;
|
||||
u32 GetRODataOffset() const;
|
||||
u32 GetDataOffset() const;
|
||||
|
||||
u32 GetBSSSize() const;
|
||||
u32 GetBSSOffset() const;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus status;
|
||||
|
||||
KIPHeader header{};
|
||||
std::array<std::vector<u8>, 6> decompressed_sections;
|
||||
};
|
||||
|
||||
class INI {
|
||||
public:
|
||||
explicit INI(const VirtualFile& file);
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
const std::vector<KIP>& GetKIPs() const;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus status;
|
||||
|
||||
INIHeader header{};
|
||||
std::vector<KIP> kips;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
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_)) {}
|
||||
|
||||
CNMT::~CNMT() = default;
|
||||
|
||||
const CNMTHeader& CNMT::GetHeader() const {
|
||||
return header;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
u64_le 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
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs/vfs_types.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,
|
||||
};
|
||||
|
||||
enum class ContentRecordType : u8 {
|
||||
Meta = 0,
|
||||
Program = 1,
|
||||
Data = 2,
|
||||
Control = 3,
|
||||
HtmlDocument = 4,
|
||||
LegalInformation = 5,
|
||||
DeltaFragment = 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;
|
||||
u8 reserved;
|
||||
u16_le table_offset;
|
||||
u16_le number_content_entries;
|
||||
u16_le number_meta_entries;
|
||||
u8 attributes;
|
||||
std::array<u8, 2> reserved2;
|
||||
u8 is_committed;
|
||||
u32_le required_download_system_version;
|
||||
std::array<u8, 4> reserved3;
|
||||
};
|
||||
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_);
|
||||
~CNMT();
|
||||
|
||||
const CNMTHeader& GetHeader() const;
|
||||
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
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
bool PartitionFilesystem::Header::HasValidMagicValue() const {
|
||||
return magic == Common::MakeMagic('H', 'F', 'S', '0') ||
|
||||
magic == Common::MakeMagic('P', 'F', 'S', '0');
|
||||
}
|
||||
|
||||
PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
|
||||
// At least be as large as the header
|
||||
if (file->GetSize() < sizeof(Header)) {
|
||||
status = Loader::ResultStatus::ErrorBadPFSHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
// For cartridges, HFSs can get very large, so we need to calculate the size up to
|
||||
// the actual content itself instead of just blindly reading in the entire file.
|
||||
if (sizeof(Header) != file->ReadObject(&pfs_header)) {
|
||||
status = Loader::ResultStatus::ErrorBadPFSHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pfs_header.HasValidMagicValue()) {
|
||||
status = Loader::ResultStatus::ErrorBadPFSHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
|
||||
|
||||
std::size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
|
||||
std::size_t metadata_size =
|
||||
sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size;
|
||||
|
||||
// Actually read in now...
|
||||
std::vector<u8> file_data = file->ReadBytes(metadata_size);
|
||||
const std::size_t total_size = file_data.size();
|
||||
file_data.push_back(0);
|
||||
|
||||
if (total_size != metadata_size) {
|
||||
status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t entries_offset = sizeof(Header);
|
||||
std::size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size);
|
||||
content_offset = strtab_offset + pfs_header.strtab_size;
|
||||
for (u16 i = 0; i < pfs_header.num_entries; i++) {
|
||||
FSEntry entry;
|
||||
|
||||
memcpy(&entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry));
|
||||
std::string name(
|
||||
reinterpret_cast<const char*>(&file_data[strtab_offset + entry.strtab_offset]));
|
||||
|
||||
offsets.insert_or_assign(name, content_offset + entry.offset);
|
||||
sizes.insert_or_assign(name, entry.size);
|
||||
|
||||
pfs_files.emplace_back(std::make_shared<OffsetVfsFile>(
|
||||
file, entry.size, content_offset + entry.offset, std::move(name)));
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
PartitionFilesystem::~PartitionFilesystem() = default;
|
||||
|
||||
Loader::ResultStatus PartitionFilesystem::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::map<std::string, u64> PartitionFilesystem::GetFileOffsets() const {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
std::map<std::string, u64> PartitionFilesystem::GetFileSizes() const {
|
||||
return sizes;
|
||||
}
|
||||
|
||||
std::vector<VirtualFile> PartitionFilesystem::GetFiles() const {
|
||||
return pfs_files;
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> PartitionFilesystem::GetSubdirectories() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string PartitionFilesystem::GetName() const {
|
||||
return is_hfs ? "HFS0" : "PFS0";
|
||||
}
|
||||
|
||||
VirtualDir PartitionFilesystem::GetParentDirectory() const {
|
||||
// TODO(DarkLordZach): Add support for nested containers.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PartitionFilesystem::PrintDebugInfo() const {
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic);
|
||||
LOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries);
|
||||
for (u32 i = 0; i < pfs_header.num_entries; i++) {
|
||||
LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes)", i,
|
||||
pfs_files[i]->GetName(), pfs_files[i]->GetSize());
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/**
|
||||
* Helper which implements an interface to parse PFS/HFS filesystems.
|
||||
* Data can either be loaded from a file path or data with an offset into it.
|
||||
*/
|
||||
class PartitionFilesystem : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit PartitionFilesystem(VirtualFile file);
|
||||
~PartitionFilesystem() override;
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::map<std::string, u64> GetFileOffsets() const;
|
||||
std::map<std::string, u64> GetFileSizes() const;
|
||||
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
std::string GetName() const override;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
void PrintDebugInfo() const;
|
||||
|
||||
private:
|
||||
struct Header {
|
||||
u32_le magic;
|
||||
u32_le num_entries;
|
||||
u32_le strtab_size;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
|
||||
bool HasValidMagicValue() const;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == 0x10, "PFS/HFS header structure size is wrong");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct FSEntry {
|
||||
u64_le offset;
|
||||
u64_le size;
|
||||
u32_le strtab_offset;
|
||||
};
|
||||
|
||||
static_assert(sizeof(FSEntry) == 0x14, "FS entry structure size is wrong");
|
||||
|
||||
struct PFSEntry {
|
||||
FSEntry fs_entry;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
|
||||
static_assert(sizeof(PFSEntry) == 0x18, "PFS entry structure size is wrong");
|
||||
|
||||
struct HFSEntry {
|
||||
FSEntry fs_entry;
|
||||
u32_le hash_region_size;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
std::array<char, 0x20> hash;
|
||||
};
|
||||
|
||||
static_assert(sizeof(HFSEntry) == 0x40, "HFS entry structure size is wrong");
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
Header pfs_header{};
|
||||
bool is_hfs = false;
|
||||
std::size_t content_offset = 0;
|
||||
|
||||
std::map<std::string, u64> offsets;
|
||||
std::map<std::string, u64> sizes;
|
||||
|
||||
std::vector<VirtualFile> pfs_files;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,696 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#ifndef _WIN32
|
||||
#include "common/string_util.h"
|
||||
#endif
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs/vfs_cached.h"
|
||||
#include "core/file_sys/vfs/vfs_layered.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/set/settings_server.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
|
||||
constexpr u32 SINGLE_BYTE_MODULUS = 0x100;
|
||||
|
||||
constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
|
||||
"main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2",
|
||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
||||
};
|
||||
|
||||
enum class TitleVersionFormat : u8 {
|
||||
ThreeElements, ///< vX.Y.Z
|
||||
FourElements, ///< vX.Y.Z.W
|
||||
};
|
||||
|
||||
std::string FormatTitleVersion(u32 version,
|
||||
TitleVersionFormat format = TitleVersionFormat::ThreeElements) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
bytes[0] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
|
||||
for (std::size_t i = 1; i < bytes.size(); ++i) {
|
||||
version /= SINGLE_BYTE_MODULUS;
|
||||
bytes[i] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
|
||||
}
|
||||
|
||||
if (format == TitleVersionFormat::FourElements) {
|
||||
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
}
|
||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
|
||||
// doesn't have a directory with name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
|
||||
#ifdef _WIN32
|
||||
return dir->GetSubdirectory(name);
|
||||
#else
|
||||
const auto subdirs = dir->GetSubdirectories();
|
||||
for (const auto& subdir : subdirs) {
|
||||
std::string dir_name = Common::ToLower(subdir->GetName());
|
||||
if (dir_name == name) {
|
||||
return subdir;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
|
||||
u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
|
||||
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
|
||||
if (file == nullptr) {
|
||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
}
|
||||
|
||||
void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||
if (to.empty()) {
|
||||
to += with;
|
||||
} else {
|
||||
to += ", ";
|
||||
to += with;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
const Service::FileSystem::FileSystemController& fs_controller_,
|
||||
const ContentProvider& content_provider_)
|
||||
: title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
|
||||
|
||||
PatchManager::~PatchManager() = default;
|
||||
|
||||
u64 PatchManager::GetTitleID() const {
|
||||
return title_id;
|
||||
}
|
||||
|
||||
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
|
||||
|
||||
if (exefs == nullptr)
|
||||
return exefs;
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
|
||||
|
||||
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
|
||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
|
||||
exefs = update->GetExeFS();
|
||||
}
|
||||
|
||||
// LayeredExeFS
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
|
||||
std::vector<VirtualDir> patch_dirs = {sdmc_load_dir};
|
||||
if (load_dir != nullptr) {
|
||||
const auto load_patch_dirs = load_dir->GetSubdirectories();
|
||||
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
|
||||
}
|
||||
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<VirtualDir> layers;
|
||||
layers.reserve(patch_dirs.size() + 1);
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
|
||||
continue;
|
||||
|
||||
auto exefs_dir = FindSubdirectoryCaseless(subdir, "exefs");
|
||||
if (exefs_dir != nullptr)
|
||||
layers.push_back(std::move(exefs_dir));
|
||||
}
|
||||
layers.push_back(exefs);
|
||||
|
||||
auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
|
||||
if (layered != nullptr) {
|
||||
LOG_INFO(Loader, " ExeFS: LayeredExeFS patches applied successfully");
|
||||
exefs = std::move(layered);
|
||||
}
|
||||
|
||||
if (Settings::values.dump_exefs) {
|
||||
LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
|
||||
const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
|
||||
VfsRawCopyD(exefs, exefs_dir);
|
||||
}
|
||||
}
|
||||
|
||||
return exefs;
|
||||
}
|
||||
|
||||
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const {
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto nso_build_id = fmt::format("{:0<64}", build_id);
|
||||
|
||||
std::vector<VirtualFile> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) != disabled.cend())
|
||||
continue;
|
||||
|
||||
auto exefs_dir = FindSubdirectoryCaseless(subdir, "exefs");
|
||||
if (exefs_dir != nullptr) {
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() == "ips") {
|
||||
auto name = file->GetName();
|
||||
|
||||
const auto this_build_id =
|
||||
fmt::format("{:0<64}", name.substr(0, name.find('.')));
|
||||
if (nso_build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
} else if (file->GetExtension() == "pchtxt") {
|
||||
IPSwitchCompiler compiler{file};
|
||||
if (!compiler.IsValid())
|
||||
continue;
|
||||
|
||||
const auto this_build_id = Common::HexToString(compiler.GetBuildID());
|
||||
if (nso_build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::string& name) const {
|
||||
if (nso.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
Loader::NSOHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(header));
|
||||
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
const auto build_id_raw = Common::HexToString(header.build_id);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
if (Settings::values.dump_nso) {
|
||||
LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
|
||||
title_id);
|
||||
const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
|
||||
const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
|
||||
|
||||
file->Resize(nso.size());
|
||||
file->WriteBytes(nso);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
|
||||
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return nso;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
const auto patches = CollectPatches(patch_dirs, build_id);
|
||||
|
||||
auto out = nso;
|
||||
for (const auto& patch_file : patches) {
|
||||
if (patch_file->GetExtension() == "ips") {
|
||||
LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
|
||||
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
} else if (patch_file->GetExtension() == "pchtxt") {
|
||||
LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
|
||||
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const IPSwitchCompiler compiler{patch_file};
|
||||
const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
std::memcpy(out.data(), &header, sizeof(header));
|
||||
return out;
|
||||
}
|
||||
|
||||
bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
|
||||
const auto build_id_raw = Common::HexToString(build_id_);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
|
||||
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
|
||||
const BuildID& build_id_) const {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<Core::Memory::CheatEntry> out;
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
|
||||
if (cheats_dir != nullptr) {
|
||||
if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
|
||||
const Service::FileSystem::FileSystemController& fs_controller) {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
|
||||
type != ContentRecordType::HtmlDocument) ||
|
||||
(load_dir == nullptr && sdmc_load_dir == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
|
||||
patch_dirs.push_back(sdmc_load_dir);
|
||||
}
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<VirtualDir> layers;
|
||||
std::vector<VirtualDir> layers_ext;
|
||||
layers.reserve(patch_dirs.size() + 1);
|
||||
layers_ext.reserve(patch_dirs.size() + 1);
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto romfs_dir = FindSubdirectoryCaseless(subdir, "romfs");
|
||||
if (romfs_dir != nullptr)
|
||||
layers.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(romfs_dir)));
|
||||
|
||||
auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext");
|
||||
if (ext_dir != nullptr)
|
||||
layers_ext.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(ext_dir)));
|
||||
|
||||
if (type == ContentRecordType::HtmlDocument) {
|
||||
auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html");
|
||||
if (manual_dir != nullptr)
|
||||
layers.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(manual_dir)));
|
||||
}
|
||||
}
|
||||
|
||||
// When there are no layers to apply, return early as there is no need to rebuild the RomFS
|
||||
if (layers.empty() && layers_ext.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
layers.emplace_back(std::move(extracted));
|
||||
|
||||
auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
|
||||
if (layered == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
|
||||
|
||||
auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
|
||||
if (packed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully");
|
||||
romfs = std::move(packed);
|
||||
}
|
||||
|
||||
VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
|
||||
ContentRecordType type, VirtualFile packed_update_raw,
|
||||
bool apply_layeredfs) const {
|
||||
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
|
||||
title_id, static_cast<u8>(type));
|
||||
if (type == ContentRecordType::Program || type == ContentRecordType::Data) {
|
||||
LOG_INFO(Loader, "{}", log_string);
|
||||
} else {
|
||||
LOG_DEBUG(Loader, "{}", log_string);
|
||||
}
|
||||
|
||||
auto romfs = base_romfs;
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update_raw = content_provider.GetEntryRaw(update_tid, type);
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
|
||||
if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
const auto version =
|
||||
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0));
|
||||
}
|
||||
} else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
}
|
||||
|
||||
// LayeredFS
|
||||
if (apply_layeredfs) {
|
||||
ApplyLayeredFS(romfs, title_id, type, fs_controller);
|
||||
}
|
||||
|
||||
return romfs;
|
||||
}
|
||||
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
if (title_id == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Patch> out;
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
PatchManager update{update_tid, fs_controller, content_provider};
|
||||
const auto metadata = update.GetControlMetadata();
|
||||
const auto& nacp = metadata.first;
|
||||
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
.name = "Update",
|
||||
.version = "",
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id};
|
||||
|
||||
if (nacp != nullptr) {
|
||||
update_patch.version = nacp->GetVersionString();
|
||||
out.push_back(update_patch);
|
||||
} else {
|
||||
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
|
||||
if (meta_ver.value_or(0) == 0) {
|
||||
out.push_back(update_patch);
|
||||
} else {
|
||||
update_patch.version = FormatTitleVersion(*meta_ver);
|
||||
out.push_back(update_patch);
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
update_patch.version = "PACKED";
|
||||
out.push_back(update_patch);
|
||||
}
|
||||
}
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
|
||||
const auto exefs_dir = FindSubdirectoryCaseless(mod, "exefs");
|
||||
if (IsDirValidAndNonEmpty(exefs_dir)) {
|
||||
bool ips = false;
|
||||
bool ipswitch = false;
|
||||
bool layeredfs = false;
|
||||
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() == "ips") {
|
||||
ips = true;
|
||||
} else if (file->GetExtension() == "pchtxt") {
|
||||
ipswitch = true;
|
||||
} else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(),
|
||||
file->GetName()) != EXEFS_FILE_NAMES.end()) {
|
||||
layeredfs = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ips)
|
||||
AppendCommaIfNotEmpty(types, "IPS");
|
||||
if (ipswitch)
|
||||
AppendCommaIfNotEmpty(types, "IPSwitch");
|
||||
if (layeredfs)
|
||||
AppendCommaIfNotEmpty(types, "LayeredExeFS");
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats")))
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
|
||||
const auto mod_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
|
||||
out.push_back({.enabled = !mod_disabled,
|
||||
.name = mod->GetName(),
|
||||
.version = types,
|
||||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id});
|
||||
}
|
||||
}
|
||||
|
||||
// SDMC mod directory (RomFS LayeredFS)
|
||||
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
if (sdmc_mod_dir != nullptr) {
|
||||
std::string types;
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) {
|
||||
AppendCommaIfNotEmpty(types, "LayeredExeFS");
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) {
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
}
|
||||
|
||||
if (!types.empty()) {
|
||||
const auto mod_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
|
||||
out.push_back({.enabled = !mod_disabled,
|
||||
.name = "SDMC",
|
||||
.version = types,
|
||||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id});
|
||||
}
|
||||
}
|
||||
|
||||
// DLC
|
||||
const auto dlc_entries =
|
||||
content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
|
||||
std::vector<ContentProviderEntry> dlc_match;
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[this](const ContentProviderEntry& entry) {
|
||||
return GetBaseTitleID(entry.title_id) == title_id &&
|
||||
content_provider.GetEntry(entry)->GetStatus() ==
|
||||
Loader::ResultStatus::Success;
|
||||
});
|
||||
if (!dlc_match.empty()) {
|
||||
// Ensure sorted so DLC IDs show in order.
|
||||
std::sort(dlc_match.begin(), dlc_match.end());
|
||||
|
||||
std::string list;
|
||||
for (size_t i = 0; i < dlc_match.size() - 1; ++i)
|
||||
list += fmt::format("{}, ", dlc_match[i].title_id & 0x7FF);
|
||||
|
||||
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
|
||||
|
||||
const auto dlc_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
|
||||
out.push_back({.enabled = !dlc_disabled,
|
||||
.name = "DLC",
|
||||
.version = std::move(list),
|
||||
.type = PatchType::DLC,
|
||||
.program_id = title_id,
|
||||
.title_id = dlc_match.back().title_id});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<u32> PatchManager::GetGameVersion() const {
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
return content_provider.GetEntryVersion(update_tid);
|
||||
}
|
||||
|
||||
return content_provider.GetEntryVersion(title_id);
|
||||
}
|
||||
|
||||
PatchManager::Metadata PatchManager::GetControlMetadata() const {
|
||||
const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return ParseControlNCA(*base_control_nca);
|
||||
}
|
||||
|
||||
PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
const auto base_romfs = nca.GetRomFS();
|
||||
if (base_romfs == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control);
|
||||
if (romfs == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (nacp_file == nullptr) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
|
||||
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
|
||||
|
||||
// Get language code from settings
|
||||
const auto language_code = Service::Set::GetLanguageCodeFromIndex(
|
||||
static_cast<u32>(Settings::values.language_index.GetValue()));
|
||||
|
||||
// Convert to application language and get priority list
|
||||
const auto application_language =
|
||||
Service::NS::ConvertToApplicationLanguage(language_code)
|
||||
.value_or(Service::NS::ApplicationLanguage::AmericanEnglish);
|
||||
const auto language_priority_list =
|
||||
Service::NS::GetApplicationLanguagePriorityList(application_language);
|
||||
|
||||
// Convert to language names
|
||||
auto priority_language_names = FileSys::LANGUAGE_NAMES; // Copy
|
||||
if (language_priority_list) {
|
||||
for (size_t i = 0; i < priority_language_names.size(); ++i) {
|
||||
// Relies on FileSys::LANGUAGE_NAMES being in the same order as
|
||||
// Service::NS::ApplicationLanguage
|
||||
const auto language_index = static_cast<u8>(language_priority_list->at(i));
|
||||
|
||||
if (language_index < FileSys::LANGUAGE_NAMES.size()) {
|
||||
priority_language_names[i] = FileSys::LANGUAGE_NAMES[language_index];
|
||||
} else {
|
||||
// Not a catastrophe, unlikely to happen
|
||||
LOG_WARNING(Loader, "Invalid language index {}", language_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get first matching icon
|
||||
VirtualFile icon_file;
|
||||
for (const auto& language : priority_language_names) {
|
||||
icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat"));
|
||||
if (icon_file != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {std::move(nacp), icon_file};
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::FileSystem {
|
||||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class ContentProvider;
|
||||
class NCA;
|
||||
class NACP;
|
||||
|
||||
enum class PatchType { Update, DLC, Mod };
|
||||
|
||||
struct Patch {
|
||||
bool enabled;
|
||||
std::string name;
|
||||
std::string version;
|
||||
PatchType type;
|
||||
u64 program_id;
|
||||
u64 title_id;
|
||||
};
|
||||
|
||||
// A centralized class to manage patches to games.
|
||||
class PatchManager {
|
||||
public:
|
||||
using BuildID = std::array<u8, 0x20>;
|
||||
using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
|
||||
|
||||
explicit PatchManager(u64 title_id_,
|
||||
const Service::FileSystem::FileSystemController& fs_controller_,
|
||||
const ContentProvider& content_provider_);
|
||||
~PatchManager();
|
||||
|
||||
[[nodiscard]] u64 GetTitleID() const;
|
||||
|
||||
// Currently tracked ExeFS patches:
|
||||
// - Game Updates
|
||||
[[nodiscard]] VirtualDir PatchExeFS(VirtualDir exefs) const;
|
||||
|
||||
// Currently tracked NSO patches:
|
||||
// - IPS
|
||||
// - IPSwitch
|
||||
[[nodiscard]] std::vector<u8> PatchNSO(const std::vector<u8>& nso,
|
||||
const std::string& name) const;
|
||||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
||||
const BuildID& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
// - LayeredFS
|
||||
[[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
|
||||
ContentRecordType type = ContentRecordType::Program,
|
||||
VirtualFile packed_update_raw = nullptr,
|
||||
bool apply_layeredfs = true) const;
|
||||
|
||||
// Returns a vector of patches
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
|
||||
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
|
||||
// std::nullopt
|
||||
[[nodiscard]] std::optional<u32> GetGameVersion() const;
|
||||
|
||||
// Given title_id of the program, attempts to get the control data of the update and parse
|
||||
// it, falling back to the base control data.
|
||||
[[nodiscard]] Metadata GetControlMetadata() const;
|
||||
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
[[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const;
|
||||
|
||||
u64 title_id;
|
||||
const Service::FileSystem::FileSystemController& fs_controller;
|
||||
const ContentProvider& content_provider;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
ProgramMetadata::ProgramMetadata() = default;
|
||||
|
||||
ProgramMetadata::~ProgramMetadata() = default;
|
||||
|
||||
Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
||||
const std::size_t total_size = file->GetSize();
|
||||
if (total_size < sizeof(Header)) {
|
||||
return Loader::ResultStatus::ErrorBadNPDMHeader;
|
||||
}
|
||||
|
||||
if (sizeof(Header) != file->ReadObject(&npdm_header)) {
|
||||
return Loader::ResultStatus::ErrorBadNPDMHeader;
|
||||
}
|
||||
|
||||
if (sizeof(AcidHeader) != file->ReadObject(&acid_header, npdm_header.acid_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadACIDHeader;
|
||||
}
|
||||
|
||||
if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadACIHeader;
|
||||
}
|
||||
|
||||
// Load acid_file_access per-component instead of the entire struct, since this struct does not
|
||||
// reflect the layout of the real data.
|
||||
std::size_t current_offset = acid_header.fac_offset;
|
||||
if (sizeof(FileAccessControl::version) != file->ReadBytes(&acid_file_access.version,
|
||||
sizeof(FileAccessControl::version),
|
||||
current_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
}
|
||||
if (sizeof(FileAccessControl::permissions) !=
|
||||
file->ReadBytes(&acid_file_access.permissions, sizeof(FileAccessControl::permissions),
|
||||
current_offset += sizeof(FileAccessControl::version) + 3)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
}
|
||||
if (sizeof(FileAccessControl::unknown) !=
|
||||
file->ReadBytes(&acid_file_access.unknown, sizeof(FileAccessControl::unknown),
|
||||
current_offset + sizeof(FileAccessControl::permissions))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
}
|
||||
|
||||
// Load aci_file_access per-component instead of the entire struct, same as acid_file_access
|
||||
current_offset = aci_header.fah_offset;
|
||||
if (sizeof(FileAccessHeader::version) != file->ReadBytes(&aci_file_access.version,
|
||||
sizeof(FileAccessHeader::version),
|
||||
current_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::permissions) !=
|
||||
file->ReadBytes(&aci_file_access.permissions, sizeof(FileAccessHeader::permissions),
|
||||
current_offset += sizeof(FileAccessHeader::version) + 3)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_offset) !=
|
||||
file->ReadBytes(&aci_file_access.unk_offset, sizeof(FileAccessHeader::unk_offset),
|
||||
current_offset += sizeof(FileAccessHeader::permissions))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_size) !=
|
||||
file->ReadBytes(&aci_file_access.unk_size, sizeof(FileAccessHeader::unk_size),
|
||||
current_offset += sizeof(FileAccessHeader::unk_offset))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_offset_2) !=
|
||||
file->ReadBytes(&aci_file_access.unk_offset_2, sizeof(FileAccessHeader::unk_offset_2),
|
||||
current_offset += sizeof(FileAccessHeader::unk_size))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_size_2) !=
|
||||
file->ReadBytes(&aci_file_access.unk_size_2, sizeof(FileAccessHeader::unk_size_2),
|
||||
current_offset + sizeof(FileAccessHeader::unk_offset_2))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
|
||||
aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32));
|
||||
const u64 read_size = aci_header.kac_size;
|
||||
const u64 read_offset = npdm_header.aci_offset + aci_header.kac_offset;
|
||||
if (file->ReadBytes(aci_kernel_capabilities.data(), read_size, read_offset) != read_size) {
|
||||
return Loader::ResultStatus::ErrorBadKernelCapabilityDescriptors;
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
|
||||
const u64 original_program_id = aci_header.title_id;
|
||||
SCOPE_EXIT {
|
||||
aci_header.title_id = original_program_id;
|
||||
};
|
||||
|
||||
return this->Load(file);
|
||||
}
|
||||
|
||||
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
|
||||
// Allow use of cores 0~3 and thread priorities 16~63.
|
||||
constexpr u32 default_thread_info_capability = 0x30043F7;
|
||||
|
||||
ProgramMetadata result;
|
||||
|
||||
result.LoadManual(
|
||||
true /*is_64_bit*/, FileSys::ProgramAddressSpaceType::Is39Bit /*address_space*/,
|
||||
0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x100000 /*main_thread_stack_size*/,
|
||||
0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, 0 /*system_resource_size*/,
|
||||
{default_thread_info_capability} /*capabilities*/);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space,
|
||||
s32 main_thread_prio, u32 main_thread_core,
|
||||
u32 main_thread_stack_size, u64 title_id,
|
||||
u64 filesystem_permissions, u32 system_resource_size,
|
||||
KernelCapabilityDescriptors capabilities) {
|
||||
npdm_header.has_64_bit_instructions.Assign(is_64_bit);
|
||||
npdm_header.address_space_type.Assign(address_space);
|
||||
npdm_header.main_thread_priority = static_cast<u8>(main_thread_prio);
|
||||
npdm_header.main_thread_cpu = static_cast<u8>(main_thread_core);
|
||||
npdm_header.main_stack_size = main_thread_stack_size;
|
||||
aci_header.title_id = title_id;
|
||||
aci_file_access.permissions = filesystem_permissions;
|
||||
npdm_header.system_resource_size = system_resource_size;
|
||||
aci_kernel_capabilities = std::move(capabilities);
|
||||
}
|
||||
|
||||
bool ProgramMetadata::Is64BitProgram() const {
|
||||
return npdm_header.has_64_bit_instructions.As<bool>();
|
||||
}
|
||||
|
||||
ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const {
|
||||
return npdm_header.address_space_type;
|
||||
}
|
||||
|
||||
u8 ProgramMetadata::GetMainThreadPriority() const {
|
||||
return npdm_header.main_thread_priority;
|
||||
}
|
||||
|
||||
u8 ProgramMetadata::GetMainThreadCore() const {
|
||||
return npdm_header.main_thread_cpu;
|
||||
}
|
||||
|
||||
u32 ProgramMetadata::GetMainThreadStackSize() const {
|
||||
return npdm_header.main_stack_size;
|
||||
}
|
||||
|
||||
u64 ProgramMetadata::GetTitleID() const {
|
||||
return aci_header.title_id;
|
||||
}
|
||||
|
||||
u64 ProgramMetadata::GetFilesystemPermissions() const {
|
||||
return aci_file_access.permissions;
|
||||
}
|
||||
|
||||
u32 ProgramMetadata::GetSystemResourceSize() const {
|
||||
return npdm_header.system_resource_size;
|
||||
}
|
||||
|
||||
PoolPartition ProgramMetadata::GetPoolPartition() const {
|
||||
return acid_header.pool_partition;
|
||||
}
|
||||
|
||||
const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
|
||||
return aci_kernel_capabilities;
|
||||
}
|
||||
|
||||
void ProgramMetadata::Print() const {
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority);
|
||||
LOG_DEBUG(Service_FS, "Main thread core: {}", npdm_header.main_thread_cpu);
|
||||
LOG_DEBUG(Service_FS, "Main thread stack size: 0x{:X} bytes", npdm_header.main_stack_size);
|
||||
LOG_DEBUG(Service_FS, "Process category: {}", npdm_header.process_category);
|
||||
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", npdm_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > 64-bit instructions: {}",
|
||||
npdm_header.has_64_bit_instructions ? "YES" : "NO");
|
||||
|
||||
const char* address_space = "Unknown";
|
||||
switch (npdm_header.address_space_type) {
|
||||
case ProgramAddressSpaceType::Is36Bit:
|
||||
address_space = "64-bit (36-bit address space)";
|
||||
break;
|
||||
case ProgramAddressSpaceType::Is39Bit:
|
||||
address_space = "64-bit (39-bit address space)";
|
||||
break;
|
||||
case ProgramAddressSpaceType::Is32Bit:
|
||||
address_space = "32-bit";
|
||||
break;
|
||||
case ProgramAddressSpaceType::Is32BitNoMap:
|
||||
address_space = "32-bit (no map region)";
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, " > Address space: {}\n", address_space);
|
||||
|
||||
// Begin ACID printing (potential perms, signed)
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.production_flag ? "YES" : "NO");
|
||||
LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min);
|
||||
LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
|
||||
|
||||
// Begin ACI0 printing (actual perms, unsigned)
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Title ID: 0x{:016X}", aci_header.title_id);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", aci_file_access.permissions);
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class ProgramAddressSpaceType : u8 {
|
||||
Is32Bit = 0,
|
||||
Is36Bit = 1,
|
||||
Is32BitNoMap = 2,
|
||||
Is39Bit = 3,
|
||||
};
|
||||
|
||||
enum class ProgramFilePermission : u64 {
|
||||
MountContent = 1ULL << 0,
|
||||
SaveDataBackup = 1ULL << 5,
|
||||
SdCard = 1ULL << 21,
|
||||
Calibration = 1ULL << 34,
|
||||
Bit62 = 1ULL << 62,
|
||||
Everything = 1ULL << 63,
|
||||
};
|
||||
|
||||
enum class PoolPartition : u32 {
|
||||
Application = 0,
|
||||
Applet = 1,
|
||||
System = 2,
|
||||
SystemNonSecure = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper which implements an interface to parse Program Description Metadata (NPDM)
|
||||
* Data can either be loaded from a file path or with data and an offset into it.
|
||||
*/
|
||||
class ProgramMetadata {
|
||||
public:
|
||||
using KernelCapabilityDescriptors = std::vector<u32>;
|
||||
|
||||
ProgramMetadata();
|
||||
~ProgramMetadata();
|
||||
|
||||
ProgramMetadata(const ProgramMetadata&) = default;
|
||||
ProgramMetadata& operator=(const ProgramMetadata&) = default;
|
||||
|
||||
ProgramMetadata(ProgramMetadata&&) = default;
|
||||
ProgramMetadata& operator=(ProgramMetadata&&) = default;
|
||||
|
||||
/// Gets a default ProgramMetadata configuration, should only be used for homebrew formats where
|
||||
/// we do not have an NPDM file
|
||||
static ProgramMetadata GetDefault();
|
||||
|
||||
Loader::ResultStatus Load(VirtualFile file);
|
||||
Loader::ResultStatus Reload(VirtualFile file);
|
||||
|
||||
/// Load from parameters instead of NPDM file, used for KIP
|
||||
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
|
||||
u32 main_thread_core, u32 main_thread_stack_size, u64 title_id,
|
||||
u64 filesystem_permissions, u32 system_resource_size,
|
||||
KernelCapabilityDescriptors capabilities);
|
||||
|
||||
bool Is64BitProgram() const;
|
||||
ProgramAddressSpaceType GetAddressSpaceType() const;
|
||||
u8 GetMainThreadPriority() const;
|
||||
u8 GetMainThreadCore() const;
|
||||
u32 GetMainThreadStackSize() const;
|
||||
u64 GetTitleID() const;
|
||||
u64 GetFilesystemPermissions() const;
|
||||
u32 GetSystemResourceSize() const;
|
||||
PoolPartition GetPoolPartition() const;
|
||||
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
|
||||
const std::array<u8, 0x10>& GetName() const {
|
||||
return npdm_header.application_name;
|
||||
}
|
||||
|
||||
void Print() const;
|
||||
|
||||
private:
|
||||
struct Header {
|
||||
std::array<char, 4> magic;
|
||||
std::array<u8, 8> reserved;
|
||||
union {
|
||||
u8 flags;
|
||||
|
||||
BitField<0, 1, u8> has_64_bit_instructions;
|
||||
BitField<1, 3, ProgramAddressSpaceType> address_space_type;
|
||||
BitField<4, 4, u8> reserved_2;
|
||||
};
|
||||
u8 reserved_3;
|
||||
u8 main_thread_priority;
|
||||
u8 main_thread_cpu;
|
||||
std::array<u8, 4> reserved_4;
|
||||
u32_le system_resource_size;
|
||||
u32_le process_category;
|
||||
u32_le main_stack_size;
|
||||
std::array<u8, 0x10> application_name;
|
||||
std::array<u8, 0x40> reserved_5;
|
||||
u32_le aci_offset;
|
||||
u32_le aci_size;
|
||||
u32_le acid_offset;
|
||||
u32_le acid_size;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong");
|
||||
|
||||
struct AcidHeader {
|
||||
std::array<u8, 0x100> signature;
|
||||
std::array<u8, 0x100> nca_modulus;
|
||||
std::array<char, 4> magic;
|
||||
u32_le nca_size;
|
||||
std::array<u8, 0x4> reserved;
|
||||
union {
|
||||
u32 flags;
|
||||
|
||||
BitField<0, 1, u32> production_flag;
|
||||
BitField<1, 1, u32> unqualified_approval;
|
||||
BitField<2, 4, PoolPartition> pool_partition;
|
||||
};
|
||||
u64_le title_id_min;
|
||||
u64_le title_id_max;
|
||||
u32_le fac_offset;
|
||||
u32_le fac_size;
|
||||
u32_le sac_offset;
|
||||
u32_le sac_size;
|
||||
u32_le kac_offset;
|
||||
u32_le kac_size;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
};
|
||||
|
||||
static_assert(sizeof(AcidHeader) == 0x240, "ACID header structure size is wrong");
|
||||
|
||||
struct AciHeader {
|
||||
std::array<char, 4> magic;
|
||||
std::array<u8, 0xC> reserved;
|
||||
u64_le title_id;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
u32_le fah_offset;
|
||||
u32_le fah_size;
|
||||
u32_le sac_offset;
|
||||
u32_le sac_size;
|
||||
u32_le kac_offset;
|
||||
u32_le kac_size;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
};
|
||||
|
||||
static_assert(sizeof(AciHeader) == 0x40, "ACI0 header structure size is wrong");
|
||||
|
||||
// FileAccessControl and FileAccessHeader need loaded per-component: this layout does not
|
||||
// reflect the real layout to avoid reference binding to misaligned addresses
|
||||
struct FileAccessControl {
|
||||
u8 version;
|
||||
// 3 padding bytes
|
||||
u64_le permissions;
|
||||
std::array<u8, 0x20> unknown;
|
||||
};
|
||||
|
||||
struct FileAccessHeader {
|
||||
u8 version;
|
||||
// 3 padding bytes
|
||||
u64_le permissions;
|
||||
u32_le unk_offset;
|
||||
u32_le unk_size;
|
||||
u32_le unk_offset_2;
|
||||
u32_le unk_size_2;
|
||||
};
|
||||
|
||||
Header npdm_header{};
|
||||
AciHeader aci_header{};
|
||||
AcidHeader acid_header{};
|
||||
|
||||
FileAccessControl acid_file_access{};
|
||||
FileAccessHeader aci_file_access{};
|
||||
|
||||
KernelCapabilityDescriptors aci_kernel_capabilities{};
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,265 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/fs/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
class CNMT;
|
||||
class NCA;
|
||||
class NSP;
|
||||
class XCI;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
enum class NCAContentType : u8;
|
||||
enum class TitleType : u8;
|
||||
|
||||
struct ContentRecord;
|
||||
struct CNMTHeader;
|
||||
struct MetaRecord;
|
||||
class RegisteredCache;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
|
||||
|
||||
enum class InstallResult {
|
||||
Success,
|
||||
OverwriteExisting,
|
||||
ErrorAlreadyExists,
|
||||
ErrorCopyFailed,
|
||||
ErrorMetaFailed,
|
||||
ErrorBaseInstall,
|
||||
};
|
||||
|
||||
struct ContentProviderEntry {
|
||||
u64 title_id;
|
||||
ContentRecordType type;
|
||||
|
||||
std::string DebugInfo() const;
|
||||
};
|
||||
|
||||
constexpr u64 GetUpdateTitleID(u64 base_title_id) {
|
||||
return base_title_id | 0x800;
|
||||
}
|
||||
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type);
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
|
||||
// std unique requires operator== to identify duplicates.
|
||||
bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
|
||||
class ContentProvider {
|
||||
public:
|
||||
virtual ~ContentProvider();
|
||||
|
||||
virtual void Refresh() = 0;
|
||||
|
||||
virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
|
||||
bool HasEntry(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::optional<u32> GetEntryVersion(u64 title_id) const = 0;
|
||||
|
||||
virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0;
|
||||
VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const;
|
||||
|
||||
virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0;
|
||||
VirtualFile GetEntryRaw(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const = 0;
|
||||
std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::vector<ContentProviderEntry> ListEntries() const;
|
||||
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
virtual std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const = 0;
|
||||
|
||||
protected:
|
||||
// A single instance of KeyManager to be used by GetEntry()
|
||||
Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
|
||||
};
|
||||
|
||||
class PlaceholderCache {
|
||||
public:
|
||||
explicit PlaceholderCache(VirtualDir dir);
|
||||
|
||||
bool Create(const NcaID& id, u64 size) const;
|
||||
bool Delete(const NcaID& id) const;
|
||||
bool Exists(const NcaID& id) const;
|
||||
bool Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const;
|
||||
bool Register(RegisteredCache* cache, const NcaID& placeholder, const NcaID& install) const;
|
||||
bool CleanAll() const;
|
||||
std::optional<std::array<u8, 0x10>> GetRightsID(const NcaID& id) const;
|
||||
u64 Size(const NcaID& id) const;
|
||||
bool SetSize(const NcaID& id, u64 new_size) const;
|
||||
std::vector<NcaID> List() const;
|
||||
|
||||
static NcaID Generate();
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 ContentProvider {
|
||||
friend class PlaceholderCache;
|
||||
|
||||
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, ContentProviderParsingFunction parsing_function =
|
||||
[](const VirtualFile& file, const NcaID& id) { return file; });
|
||||
~RegisteredCache() override;
|
||||
|
||||
void Refresh() override;
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const override;
|
||||
|
||||
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
|
||||
// there is a meta NCA and all of them are accessible.
|
||||
InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
InstallResult InstallEntry(const NSP& nsp, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
// Due to the fact that we must use Meta-type NCAs to determine the existence 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(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
|
||||
const ContentRecord& base_record, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
// Removes an existing entry based on title id
|
||||
bool RemoveExistingEntry(u64 title_id) const;
|
||||
|
||||
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();
|
||||
std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetFileAtID(NcaID id) const;
|
||||
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& open_dir, std::string_view path) const;
|
||||
InstallResult RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
|
||||
bool overwrite_if_exists, std::optional<NcaID> override_id = {});
|
||||
bool RawInstallYuzuMeta(const CNMT& cnmt);
|
||||
|
||||
VirtualDir dir;
|
||||
ContentProviderParsingFunction parser;
|
||||
|
||||
// maps tid -> NcaID of meta
|
||||
std::map<u64, NcaID> meta_id;
|
||||
// maps tid -> meta
|
||||
std::map<u64, CNMT> meta;
|
||||
// maps tid -> meta for CNMT in yuzu_meta
|
||||
std::map<u64, CNMT> yuzu_meta;
|
||||
};
|
||||
|
||||
enum class ContentProviderUnionSlot {
|
||||
SysNAND, ///< System NAND
|
||||
UserNAND, ///< User NAND
|
||||
SDMC, ///< SD Card
|
||||
FrontendManual, ///< Frontend-defined game list or similar
|
||||
};
|
||||
|
||||
// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class ContentProviderUnion : public ContentProvider {
|
||||
public:
|
||||
~ContentProviderUnion() override;
|
||||
|
||||
void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
|
||||
void ClearSlot(ContentProviderUnionSlot slot);
|
||||
|
||||
void Refresh() override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const override;
|
||||
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
|
||||
std::optional<ContentProviderUnionSlot> origin = {},
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const;
|
||||
|
||||
std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id,
|
||||
ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
std::map<ContentProviderUnionSlot, ContentProvider*> providers;
|
||||
};
|
||||
|
||||
class ManualContentProvider : public ContentProvider {
|
||||
public:
|
||||
~ManualContentProvider() override;
|
||||
|
||||
void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
VirtualFile file);
|
||||
void ClearAllEntries();
|
||||
|
||||
void Refresh() override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const override;
|
||||
|
||||
private:
|
||||
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
167
WIP/fs/romfs.cpp
167
WIP/fs/romfs.cpp
|
|
@ -1,167 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/fs/fs_string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/fs/fsmitm_romfsbuild.h"
|
||||
#include "core/fs/romfs.h"
|
||||
#include "core/fs/vfs/vfs.h"
|
||||
#include "core/fs/vfs/vfs_cached.h"
|
||||
#include "core/fs/vfs/vfs_concat.h"
|
||||
#include "core/fs/vfs/vfs_offset.h"
|
||||
#include "core/fs/vfs/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
|
||||
|
||||
struct TableLocation {
|
||||
u64_le offset;
|
||||
u64_le size;
|
||||
};
|
||||
static_assert(sizeof(TableLocation) == 0x10, "TableLocation has incorrect size.");
|
||||
|
||||
struct RomFSHeader {
|
||||
u64_le header_size;
|
||||
TableLocation directory_hash;
|
||||
TableLocation directory_meta;
|
||||
TableLocation file_hash;
|
||||
TableLocation file_meta;
|
||||
u64_le data_offset;
|
||||
};
|
||||
static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
|
||||
|
||||
struct DirectoryEntry {
|
||||
u32_le parent;
|
||||
u32_le sibling;
|
||||
u32_le child_dir;
|
||||
u32_le child_file;
|
||||
u32_le hash;
|
||||
u32_le name_length;
|
||||
};
|
||||
static_assert(sizeof(DirectoryEntry) == 0x18, "DirectoryEntry has incorrect size.");
|
||||
|
||||
struct FileEntry {
|
||||
u32_le parent;
|
||||
u32_le sibling;
|
||||
u64_le offset;
|
||||
u64_le size;
|
||||
u32_le hash;
|
||||
u32_le name_length;
|
||||
};
|
||||
static_assert(sizeof(FileEntry) == 0x20, "FileEntry has incorrect size.");
|
||||
|
||||
struct RomFSTraversalContext {
|
||||
RomFSHeader header;
|
||||
VirtualFile file;
|
||||
std::vector<u8> directory_meta;
|
||||
std::vector<u8> file_meta;
|
||||
};
|
||||
|
||||
template <typename EntryType, auto Member>
|
||||
std::pair<EntryType, std::string> GetEntry(const RomFSTraversalContext& ctx, size_t offset) {
|
||||
const size_t entry_end = offset + sizeof(EntryType);
|
||||
const std::vector<u8>& vec = ctx.*Member;
|
||||
const size_t size = vec.size();
|
||||
const u8* data = vec.data();
|
||||
EntryType entry{};
|
||||
|
||||
if (entry_end > size) {
|
||||
return {};
|
||||
}
|
||||
std::memcpy(&entry, data + offset, sizeof(EntryType));
|
||||
|
||||
const size_t name_length = std::min(entry_end + entry.name_length, size) - entry_end;
|
||||
std::string name(reinterpret_cast<const char*>(data + entry_end), name_length);
|
||||
|
||||
return {entry, std::move(name)};
|
||||
}
|
||||
|
||||
std::pair<DirectoryEntry, std::string> GetDirectoryEntry(const RomFSTraversalContext& ctx,
|
||||
size_t directory_offset) {
|
||||
return GetEntry<DirectoryEntry, &RomFSTraversalContext::directory_meta>(ctx, directory_offset);
|
||||
}
|
||||
|
||||
std::pair<FileEntry, std::string> GetFileEntry(const RomFSTraversalContext& ctx,
|
||||
size_t file_offset) {
|
||||
return GetEntry<FileEntry, &RomFSTraversalContext::file_meta>(ctx, file_offset);
|
||||
}
|
||||
|
||||
void ProcessFile(const RomFSTraversalContext& ctx, u32 this_file_offset,
|
||||
std::shared_ptr<VectorVfsDirectory>& parent) {
|
||||
while (this_file_offset != ROMFS_ENTRY_EMPTY) {
|
||||
auto entry = GetFileEntry(ctx, this_file_offset);
|
||||
|
||||
parent->AddFile(std::make_shared<OffsetVfsFile>(ctx.file, entry.first.size,
|
||||
entry.first.offset + ctx.header.data_offset,
|
||||
std::move(entry.second)));
|
||||
|
||||
this_file_offset = entry.first.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessDirectory(const RomFSTraversalContext& ctx, u32 this_dir_offset,
|
||||
std::shared_ptr<VectorVfsDirectory>& parent) {
|
||||
while (this_dir_offset != ROMFS_ENTRY_EMPTY) {
|
||||
auto entry = GetDirectoryEntry(ctx, this_dir_offset);
|
||||
auto current = std::make_shared<VectorVfsDirectory>(
|
||||
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
|
||||
|
||||
if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
|
||||
ProcessFile(ctx, entry.first.child_file, current);
|
||||
}
|
||||
|
||||
if (entry.first.child_dir != ROMFS_ENTRY_EMPTY) {
|
||||
ProcessDirectory(ctx, entry.first.child_dir, current);
|
||||
}
|
||||
|
||||
parent->AddDirectory(current);
|
||||
this_dir_offset = entry.first.sibling;
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
VirtualDir ExtractRomFS(VirtualFile file) {
|
||||
auto root_container = std::make_shared<VectorVfsDirectory>();
|
||||
if (!file) {
|
||||
return root_container;
|
||||
}
|
||||
|
||||
RomFSTraversalContext ctx{};
|
||||
|
||||
if (file->ReadObject(&ctx.header) != sizeof(RomFSHeader)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ctx.header.header_size != sizeof(RomFSHeader)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ctx.file = file;
|
||||
ctx.directory_meta =
|
||||
file->ReadBytes(ctx.header.directory_meta.size, ctx.header.directory_meta.offset);
|
||||
ctx.file_meta = file->ReadBytes(ctx.header.file_meta.size, ctx.header.file_meta.offset);
|
||||
|
||||
ProcessDirectory(ctx, 0, root_container);
|
||||
|
||||
if (auto root = root_container->GetSubdirectory(""); root) {
|
||||
return root;
|
||||
}
|
||||
|
||||
ASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
|
||||
if (dir == nullptr)
|
||||
return nullptr;
|
||||
|
||||
RomFSBuildContext ctx{dir, ext};
|
||||
return ConcatenatedVfsFile::MakeConcatenatedFile(0, dir->GetName(), ctx.Build());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/fs/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Converts a RomFS binary blob to VFS Filesystem
|
||||
// Returns nullptr on failure
|
||||
VirtualDir ExtractRomFS(VirtualFile file);
|
||||
|
||||
// Converts a VFS filesystem into a RomFS binary
|
||||
// Returns nullptr on failure
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <memory>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
|
||||
Service::FileSystem::FileSystemController& controller)
|
||||
: content_provider{provider}, filesystem_controller{controller} {
|
||||
// Load the RomFS from the app
|
||||
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
|
||||
LOG_WARNING(Service_FS, "Unable to read base RomFS");
|
||||
}
|
||||
|
||||
updatable = app_loader.IsRomFSUpdatable();
|
||||
}
|
||||
|
||||
RomFSFactory::~RomFSFactory() = default;
|
||||
|
||||
void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) {
|
||||
packed_update_raw = std::move(update_raw_file);
|
||||
}
|
||||
|
||||
VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
|
||||
if (!updatable) {
|
||||
return file;
|
||||
}
|
||||
|
||||
const auto type = ContentRecordType::Program;
|
||||
const auto nca = content_provider.GetEntry(current_process_title_id, type);
|
||||
const PatchManager patch_manager{current_process_title_id, filesystem_controller,
|
||||
content_provider};
|
||||
return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw);
|
||||
}
|
||||
|
||||
VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
|
||||
auto nca = content_provider.GetEntry(title_id, type);
|
||||
|
||||
if (nca == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const PatchManager patch_manager{title_id, filesystem_controller, content_provider};
|
||||
|
||||
return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type);
|
||||
}
|
||||
|
||||
VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
|
||||
ContentRecordType type) const {
|
||||
const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index);
|
||||
|
||||
return OpenPatchedRomFS(res_title_id, type);
|
||||
}
|
||||
|
||||
VirtualFile RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) const {
|
||||
const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);
|
||||
if (res == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return res->GetRomFS();
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage,
|
||||
ContentRecordType type) const {
|
||||
switch (storage) {
|
||||
case StorageId::None:
|
||||
return content_provider.GetEntry(title_id, type);
|
||||
case StorageId::NandSystem:
|
||||
return filesystem_controller.GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
case StorageId::NandUser:
|
||||
return filesystem_controller.GetUserNANDContents()->GetEntry(title_id, type);
|
||||
case StorageId::SdCard:
|
||||
return filesystem_controller.GetSDMCContents()->GetEntry(title_id, type);
|
||||
case StorageId::Host:
|
||||
case StorageId::GameCard:
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Loader {
|
||||
class AppLoader;
|
||||
} // namespace Loader
|
||||
|
||||
namespace Service::FileSystem {
|
||||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class ContentProvider;
|
||||
class NCA;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
|
||||
enum class StorageId : u8 {
|
||||
None = 0,
|
||||
Host = 1,
|
||||
GameCard = 2,
|
||||
NandSystem = 3,
|
||||
NandUser = 4,
|
||||
SdCard = 5,
|
||||
};
|
||||
|
||||
/// File system interface to the RomFS archive
|
||||
class RomFSFactory {
|
||||
public:
|
||||
explicit RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
|
||||
Service::FileSystem::FileSystemController& controller);
|
||||
~RomFSFactory();
|
||||
|
||||
void SetPackedUpdate(VirtualFile packed_update_raw);
|
||||
[[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const;
|
||||
[[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const;
|
||||
[[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
|
||||
ContentRecordType type) const;
|
||||
[[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const;
|
||||
[[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage,
|
||||
ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
VirtualFile packed_update_raw;
|
||||
|
||||
VirtualFile base;
|
||||
|
||||
bool updatable;
|
||||
|
||||
ContentProvider& content_provider;
|
||||
Service::FileSystem::FileSystemController& filesystem_controller;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <memory>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataAttribute& attr) {
|
||||
return attr.type == SaveDataType::Cache || attr.type == SaveDataType::Temporary ||
|
||||
(space == SaveDataSpaceId::User && ///< Normal Save Data -- Current Title & User
|
||||
(attr.type == SaveDataType::Account || attr.type == SaveDataType::Device) &&
|
||||
attr.program_id == 0 && attr.system_save_data_id == 0);
|
||||
}
|
||||
|
||||
std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u64 title_id,
|
||||
u128 user_id) {
|
||||
// Only detect nand user saves.
|
||||
const auto space_id_path = [space_id]() -> std::string_view {
|
||||
switch (space_id) {
|
||||
case SaveDataSpaceId::User:
|
||||
return "/user/save";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
|
||||
if (space_id_path.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Common::UUID uuid;
|
||||
std::memcpy(uuid.uuid.data(), user_id.data(), sizeof(Common::UUID));
|
||||
|
||||
// Only detect account/device saves from the future location.
|
||||
switch (type) {
|
||||
case SaveDataType::Account:
|
||||
return fmt::format("{}/account/{}/{:016X}/0", space_id_path, uuid.RawString(), title_id);
|
||||
case SaveDataType::Device:
|
||||
return fmt::format("{}/device/{:016X}/0", space_id_path, title_id);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
return dir->CreateDirectoryRelative(save_directory);
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto out = dir->GetDirectoryRelative(save_directory);
|
||||
|
||||
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
|
||||
return Create(space, meta);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
|
||||
return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
switch (space) {
|
||||
case SaveDataSpaceId::System:
|
||||
return "/system/";
|
||||
case SaveDataSpaceId::User:
|
||||
return "/user/";
|
||||
case SaveDataSpaceId::Temporary:
|
||||
return "/temp/";
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
|
||||
}
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
SaveDataSpaceId space, SaveDataType type, u64 title_id,
|
||||
u128 user_id, u64 save_id) {
|
||||
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
|
||||
// be interpreted as the title id of the current process.
|
||||
if (type == SaveDataType::Account || type == SaveDataType::Device) {
|
||||
if (title_id == 0) {
|
||||
title_id = program_id;
|
||||
}
|
||||
}
|
||||
|
||||
// For compat with a future impl.
|
||||
if (std::string future_path =
|
||||
GetFutureSaveDataPath(space, type, title_id & ~(0xFFULL), user_id);
|
||||
!future_path.empty()) {
|
||||
// Check if this location exists, and prefer it over the old.
|
||||
if (const auto future_dir = dir->GetDirectoryRelative(future_path); future_dir != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using save at new location: {}", future_path);
|
||||
return future_path;
|
||||
}
|
||||
}
|
||||
|
||||
std::string out = GetSaveDataSpaceIdPath(space);
|
||||
|
||||
switch (type) {
|
||||
case SaveDataType::System:
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
|
||||
case SaveDataType::Account:
|
||||
case SaveDataType::Device:
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::Temporary:
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::Cache:
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
|
||||
return fmt::format("{}save/unknown_{:X}/{:016X}", out, static_cast<u8>(type), title_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future) {
|
||||
if (future) {
|
||||
Common::UUID uuid;
|
||||
std::memcpy(uuid.uuid.data(), user_id.data(), sizeof(Common::UUID));
|
||||
return fmt::format("/user/save/account/{}", uuid.RawString());
|
||||
}
|
||||
return fmt::format("/user/save/{:016X}/{:016X}{:016X}", 0, user_id[1], user_id[0]);
|
||||
}
|
||||
|
||||
SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
|
||||
u128 user_id) const {
|
||||
const auto path =
|
||||
GetFullPath(program_id, dir, SaveDataSpaceId::User, type, title_id, user_id, 0);
|
||||
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
|
||||
|
||||
const auto size_file = relative_dir->GetFile(GetSaveDataSizeFileName());
|
||||
if (size_file == nullptr || size_file->GetSize() < sizeof(SaveDataSize)) {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
SaveDataSize out;
|
||||
if (size_file->ReadObject(&out) != sizeof(SaveDataSize)) {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
|
||||
SaveDataSize new_value) const {
|
||||
const auto path =
|
||||
GetFullPath(program_id, dir, SaveDataSpaceId::User, type, title_id, user_id, 0);
|
||||
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
|
||||
|
||||
const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName());
|
||||
if (size_file == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_file->Resize(sizeof(SaveDataSize));
|
||||
size_file->WriteObject(new_value);
|
||||
}
|
||||
|
||||
void SaveDataFactory::SetAutoCreate(bool state) {
|
||||
auto_create = state;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/fs_save_data_types.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr const char* GetSaveDataSizeFileName() {
|
||||
return ".yuzu_save_size";
|
||||
}
|
||||
|
||||
using ProgramId = u64;
|
||||
|
||||
/// File system interface to the SaveData archive
|
||||
class SaveDataFactory {
|
||||
public:
|
||||
explicit SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_);
|
||||
~SaveDataFactory();
|
||||
|
||||
VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
|
||||
VirtualDir Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
|
||||
|
||||
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
|
||||
|
||||
static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
|
||||
static std::string GetFullPath(ProgramId program_id, VirtualDir dir, SaveDataSpaceId space,
|
||||
SaveDataType type, u64 title_id, u128 user_id, u64 save_id);
|
||||
static std::string GetUserGameSaveDataRoot(u128 user_id, bool future);
|
||||
|
||||
SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const;
|
||||
void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
|
||||
SaveDataSize new_value) const;
|
||||
|
||||
void SetAutoCreate(bool state);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
ProgramId program_id;
|
||||
VirtualDir dir;
|
||||
bool auto_create{true};
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <memory>
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB
|
||||
|
||||
SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_)
|
||||
: sd_dir(std::move(sd_dir_)), sd_mod_dir(std::move(sd_mod_dir_)),
|
||||
contents(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/registered"),
|
||||
[](const VirtualFile& file, const NcaID& id) {
|
||||
return NAX{file, id}.GetDecrypted();
|
||||
})),
|
||||
placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/placehld"))) {}
|
||||
|
||||
SDMCFactory::~SDMCFactory() = default;
|
||||
|
||||
VirtualDir SDMCFactory::Open() const {
|
||||
return sd_dir;
|
||||
}
|
||||
|
||||
VirtualDir SDMCFactory::GetSDMCModificationLoadRoot(u64 title_id) const {
|
||||
// LayeredFS doesn't work on updates and title id-less homebrew
|
||||
if (title_id == 0 || (title_id & 0xFFF) == 0x800) {
|
||||
return nullptr;
|
||||
}
|
||||
return GetOrCreateDirectoryRelative(sd_mod_dir, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
|
||||
VirtualDir SDMCFactory::GetSDMCContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents");
|
||||
}
|
||||
|
||||
RegisteredCache* SDMCFactory::GetSDMCContents() const {
|
||||
return contents.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {
|
||||
return placeholder.get();
|
||||
}
|
||||
|
||||
VirtualDir SDMCFactory::GetImageDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Album");
|
||||
}
|
||||
|
||||
u64 SDMCFactory::GetSDMCFreeSpace() const {
|
||||
return GetSDMCTotalSpace() - sd_dir->GetSize();
|
||||
}
|
||||
|
||||
u64 SDMCFactory::GetSDMCTotalSpace() const {
|
||||
return SDMC_TOTAL_SIZE;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class RegisteredCache;
|
||||
class PlaceholderCache;
|
||||
|
||||
/// File system interface to the SDCard archive
|
||||
class SDMCFactory {
|
||||
public:
|
||||
explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_);
|
||||
~SDMCFactory();
|
||||
|
||||
VirtualDir Open() const;
|
||||
|
||||
VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
|
||||
VirtualDir GetSDMCContentDirectory() const;
|
||||
|
||||
RegisteredCache* GetSDMCContents() const;
|
||||
PlaceholderCache* GetSDMCPlaceholder() const;
|
||||
|
||||
VirtualDir GetImageDirectory() const;
|
||||
|
||||
u64 GetSDMCFreeSpace() const;
|
||||
u64 GetSDMCTotalSpace() const;
|
||||
|
||||
private:
|
||||
VirtualDir sd_dir;
|
||||
VirtualDir sd_mod_dir;
|
||||
|
||||
std::unique_ptr<RegisteredCache> contents;
|
||||
std::unique_ptr<PlaceholderCache> placeholder;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
NSP::NSP(VirtualFile file_, u64 title_id_, std::size_t program_index_)
|
||||
: file(std::move(file_)), expected_program_id(title_id_),
|
||||
program_index(program_index_), status{Loader::ResultStatus::Success},
|
||||
pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} {
|
||||
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
|
||||
status = pfs->GetStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto files = pfs->GetFiles();
|
||||
|
||||
if (IsDirectoryExeFS(pfs)) {
|
||||
extracted = true;
|
||||
InitializeExeFSAndRomFS(files);
|
||||
return;
|
||||
}
|
||||
|
||||
SetTicketKeys(files);
|
||||
ReadNCAs(files);
|
||||
}
|
||||
|
||||
NSP::~NSP() = default;
|
||||
|
||||
Loader::ResultStatus NSP::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NSP::GetProgramStatus() const {
|
||||
if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) {
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
const auto iter = program_status.find(GetProgramTitleID());
|
||||
if (iter == program_status.end())
|
||||
return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
u64 NSP::GetProgramTitleID() const {
|
||||
if (IsExtractedType()) {
|
||||
return GetExtractedTitleID() + program_index;
|
||||
}
|
||||
|
||||
auto program_id = expected_program_id;
|
||||
if (program_id == 0) {
|
||||
if (!program_status.empty()) {
|
||||
program_id = program_status.begin()->first;
|
||||
}
|
||||
}
|
||||
|
||||
program_id = program_id + program_index;
|
||||
if (program_status.find(program_id) != program_status.end()) {
|
||||
return program_id;
|
||||
}
|
||||
|
||||
const auto ids = GetProgramTitleIDs();
|
||||
const auto iter =
|
||||
std::find_if(ids.begin(), ids.end(), [](u64 tid) { return (tid & 0x800) == 0; });
|
||||
return iter == ids.end() ? 0 : *iter;
|
||||
}
|
||||
|
||||
u64 NSP::GetExtractedTitleID() const {
|
||||
if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ProgramMetadata meta;
|
||||
if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) {
|
||||
return meta.GetTitleID();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u64> NSP::GetProgramTitleIDs() const {
|
||||
if (IsExtractedType()) {
|
||||
return {GetExtractedTitleID()};
|
||||
}
|
||||
|
||||
std::vector<u64> out{program_ids.cbegin(), program_ids.cend()};
|
||||
return out;
|
||||
}
|
||||
|
||||
bool NSP::IsExtractedType() const {
|
||||
return extracted;
|
||||
}
|
||||
|
||||
VirtualFile NSP::GetRomFS() const {
|
||||
return romfs;
|
||||
}
|
||||
|
||||
VirtualDir NSP::GetExeFS() const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<NCA>> NSP::GetNCAsCollapsed() const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
std::vector<std::shared_ptr<NCA>> out;
|
||||
for (const auto& map : ncas) {
|
||||
for (const auto& inner_map : map.second)
|
||||
out.push_back(inner_map.second);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
std::multimap<u64, std::shared_ptr<NCA>> out;
|
||||
for (const auto& map : ncas) {
|
||||
for (const auto& inner_map : map.second)
|
||||
out.emplace(map.first, inner_map.second);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>>
|
||||
NSP::GetNCAs() const {
|
||||
return ncas;
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType title_type) const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
|
||||
const auto title_id_iter = ncas.find(title_id);
|
||||
if (title_id_iter == ncas.end())
|
||||
return nullptr;
|
||||
|
||||
const auto type_iter = title_id_iter->second.find({title_type, type});
|
||||
if (type_iter == title_id_iter->second.end())
|
||||
return nullptr;
|
||||
|
||||
return type_iter->second;
|
||||
}
|
||||
|
||||
VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type) const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
const auto nca = GetNCA(title_id, type, title_type);
|
||||
if (nca != nullptr)
|
||||
return nca->GetBaseFile();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<VirtualFile> NSP::GetFiles() const {
|
||||
return pfs->GetFiles();
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> NSP::GetSubdirectories() const {
|
||||
return pfs->GetSubdirectories();
|
||||
}
|
||||
|
||||
std::string NSP::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
VirtualDir NSP::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
|
||||
for (const auto& ticket_file : files) {
|
||||
if (ticket_file == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ticket_file->GetExtension() != "tik") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto ticket = Core::Crypto::Ticket::Read(ticket_file);
|
||||
if (!keys.AddTicket(ticket)) {
|
||||
LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
|
||||
exefs = pfs;
|
||||
|
||||
const auto iter = std::find_if(files.begin(), files.end(), [](const VirtualFile& entry) {
|
||||
return entry->GetName().rfind(".romfs") != std::string::npos;
|
||||
});
|
||||
|
||||
if (iter == files.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
romfs = *iter;
|
||||
}
|
||||
|
||||
void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().size() < 9 ||
|
||||
outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto nca = std::make_shared<NCA>(outer_file);
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) {
|
||||
program_status[nca->GetTitleId()] = nca->GetStatus();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& inner_file : section0->GetFiles()) {
|
||||
if (inner_file->GetExtension() != "cnmt") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CNMT cnmt(inner_file);
|
||||
|
||||
ncas[cnmt.GetTitleID()][{cnmt.GetType(), ContentRecordType::Meta}] = nca;
|
||||
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto id_string = Common::HexToString(rec.nca_id, false);
|
||||
auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
|
||||
if (next_file == nullptr) {
|
||||
if (rec.type != ContentRecordType::DeltaFragment) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"NCA with ID {}.nca is listed in content metadata, but cannot "
|
||||
"be found in PFS. NSP appears to be corrupted.",
|
||||
id_string);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto next_nca = std::make_shared<NCA>(std::move(next_file));
|
||||
|
||||
if (next_nca->GetType() == NCAContentType::Program) {
|
||||
program_status[next_nca->GetTitleId()] = next_nca->GetStatus();
|
||||
program_ids.insert(next_nca->GetTitleId() & 0xFFFFFFFFFFFFF000);
|
||||
}
|
||||
|
||||
if (next_nca->GetStatus() != Loader::ResultStatus::Success &&
|
||||
next_nca->GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the last 3 hexadecimal digits of the CNMT TitleID is 0x800 or is missing the
|
||||
// BKTRBaseRomFS, this is an update NCA. Otherwise, this is a base NCA.
|
||||
if ((cnmt.GetTitleID() & 0x800) != 0 ||
|
||||
next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
||||
// If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and
|
||||
// 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular
|
||||
// update NCA.
|
||||
if ((next_nca->GetTitleId() & 0x7FF) != 0 &&
|
||||
(next_nca->GetTitleId() & 0x800) == 0) {
|
||||
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
|
||||
std::move(next_nca);
|
||||
} else {
|
||||
ncas[cnmt.GetTitleID()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
|
||||
}
|
||||
} else {
|
||||
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
class KeyManager;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
class PartitionFilesystem;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
|
||||
class NSP : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NSP(VirtualFile file_, u64 title_id = 0, std::size_t program_index_ = 0);
|
||||
~NSP() override;
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
Loader::ResultStatus GetProgramStatus() const;
|
||||
// Should only be used when one title id can be assured.
|
||||
u64 GetProgramTitleID() const;
|
||||
u64 GetExtractedTitleID() const;
|
||||
std::vector<u64> GetProgramTitleIDs() const;
|
||||
|
||||
bool IsExtractedType() const;
|
||||
|
||||
// Common (Can be safely called on both types)
|
||||
VirtualFile GetRomFS() const;
|
||||
VirtualDir GetExeFS() const;
|
||||
|
||||
// Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
|
||||
std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
|
||||
std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> GetNCAs()
|
||||
const;
|
||||
std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type,
|
||||
TitleType title_type = TitleType::Application) const;
|
||||
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
|
||||
TitleType title_type = TitleType::Application) const;
|
||||
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
private:
|
||||
void SetTicketKeys(const std::vector<VirtualFile>& files);
|
||||
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
|
||||
void ReadNCAs(const std::vector<VirtualFile>& files);
|
||||
|
||||
VirtualFile file;
|
||||
|
||||
const u64 expected_program_id;
|
||||
const std::size_t program_index;
|
||||
|
||||
bool extracted = false;
|
||||
Loader::ResultStatus status;
|
||||
std::map<u64, Loader::ResultStatus> program_status;
|
||||
|
||||
std::shared_ptr<PartitionFilesystem> pfs;
|
||||
// Map title id -> {map type -> NCA}
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
|
||||
std::set<u64> program_ids;
|
||||
std::vector<VirtualFile> ticket_files;
|
||||
|
||||
Core::Crypto::KeyManager& keys;
|
||||
|
||||
VirtualFile romfs;
|
||||
VirtualDir exefs;
|
||||
};
|
||||
} // namespace FileSys
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace FileSys::SystemArchive::SharedFontData {
|
||||
|
||||
extern const std::array<unsigned char, 217276> FONT_CHINESE_SIMPLIFIED;
|
||||
|
||||
} // namespace FileSys::SystemArchive::SharedFontData
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace FileSys::SystemArchive::SharedFontData {
|
||||
|
||||
extern const std::array<unsigned char, 222236> FONT_CHINESE_TRADITIONAL;
|
||||
|
||||
} // namespace FileSys::SystemArchive::SharedFontData
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue