aboutsummaryrefslogtreecommitdiff
path: root/src/core/file_sys
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/file_sys')
-rw-r--r--src/core/file_sys/bis_factory.cpp12
-rw-r--r--src/core/file_sys/bis_factory.h8
-rw-r--r--src/core/file_sys/card_image.cpp14
-rw-r--r--src/core/file_sys/card_image.h5
-rw-r--r--src/core/file_sys/content_archive.cpp561
-rw-r--r--src/core/file_sys/content_archive.h29
-rw-r--r--src/core/file_sys/control_metadata.cpp13
-rw-r--r--src/core/file_sys/control_metadata.h1
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp26
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h7
-rw-r--r--src/core/file_sys/ips_layer.cpp276
-rw-r--r--src/core/file_sys/ips_layer.h29
-rw-r--r--src/core/file_sys/partition_filesystem.cpp16
-rw-r--r--src/core/file_sys/partition_filesystem.h4
-rw-r--r--src/core/file_sys/patch_manager.cpp158
-rw-r--r--src/core/file_sys/patch_manager.h12
-rw-r--r--src/core/file_sys/registered_cache.cpp91
-rw-r--r--src/core/file_sys/registered_cache.h43
-rw-r--r--src/core/file_sys/romfs.cpp4
-rw-r--r--src/core/file_sys/romfs.h2
-rw-r--r--src/core/file_sys/romfs_factory.cpp7
-rw-r--r--src/core/file_sys/romfs_factory.h2
-rw-r--r--src/core/file_sys/savedata_factory.cpp13
-rw-r--r--src/core/file_sys/sdmc_factory.cpp8
-rw-r--r--src/core/file_sys/sdmc_factory.h4
-rw-r--r--src/core/file_sys/submission_package.cpp9
-rw-r--r--src/core/file_sys/submission_package.h3
-rw-r--r--src/core/file_sys/vfs.cpp12
-rw-r--r--src/core/file_sys/vfs.h46
-rw-r--r--src/core/file_sys/vfs_layered.cpp3
-rw-r--r--src/core/file_sys/vfs_layered.h3
-rw-r--r--src/core/file_sys/vfs_offset.cpp4
-rw-r--r--src/core/file_sys/vfs_offset.h2
-rw-r--r--src/core/file_sys/vfs_real.cpp3
-rw-r--r--src/core/file_sys/vfs_real.h3
-rw-r--r--src/core/file_sys/vfs_static.h4
-rw-r--r--src/core/file_sys/vfs_types.h21
-rw-r--r--src/core/file_sys/vfs_vector.cpp7
-rw-r--r--src/core/file_sys/vfs_vector.h3
-rw-r--r--src/core/file_sys/xts_archive.cpp3
-rw-r--r--src/core/file_sys/xts_archive.h3
41 files changed, 916 insertions, 558 deletions
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 6102ef476..76a2b7e86 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -10,19 +10,19 @@ namespace FileSys {
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
- sysnand_cache(std::make_shared<RegisteredCache>(
+ sysnand_cache(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
- usrnand_cache(std::make_shared<RegisteredCache>(
+ usrnand_cache(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
BISFactory::~BISFactory() = default;
-std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
- return sysnand_cache;
+RegisteredCache* BISFactory::GetSystemNANDContents() const {
+ return sysnand_cache.get();
}
-std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
- return usrnand_cache;
+RegisteredCache* BISFactory::GetUserNANDContents() const {
+ return usrnand_cache.get();
}
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index c352e0925..364d309bd 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -20,8 +20,8 @@ public:
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
~BISFactory();
- std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
- std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
+ RegisteredCache* GetSystemNANDContents() const;
+ RegisteredCache* GetUserNANDContents() const;
VirtualDir GetModificationLoadRoot(u64 title_id) const;
@@ -29,8 +29,8 @@ private:
VirtualDir nand_root;
VirtualDir load_root;
- std::shared_ptr<RegisteredCache> sysnand_cache;
- std::shared_ptr<RegisteredCache> usrnand_cache;
+ std::unique_ptr<RegisteredCache> sysnand_cache;
+ std::unique_ptr<RegisteredCache> usrnand_cache;
};
} // namespace FileSys
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 8f5142a07..1ece55731 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -122,14 +122,16 @@ u64 XCI::GetProgramTitleID() const {
return secure_partition->GetProgramTitleID();
}
-std::shared_ptr<NCA> XCI::GetProgramNCA() const {
- return program;
+bool XCI::HasProgramNCA() const {
+ return program != nullptr;
}
VirtualFile XCI::GetProgramNCAFile() const {
- if (GetProgramNCA() == nullptr)
+ if (!HasProgramNCA()) {
return nullptr;
- return GetProgramNCA()->GetBaseFile();
+ }
+
+ return program->GetBaseFile();
}
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
@@ -166,10 +168,6 @@ VirtualDir XCI::GetParentDirectory() const {
return file->GetContainingDirectory();
}
-bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
-
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
if (partitions[static_cast<std::size_t>(part)] == nullptr) {
return Loader::ResultStatus::ErrorXCIMissingPartition;
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index ce514dfa0..8f62571cf 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -80,7 +80,7 @@ public:
u64 GetProgramTitleID() const;
- std::shared_ptr<NCA> GetProgramNCA() const;
+ bool HasProgramNCA() const;
VirtualFile GetProgramNCAFile() const;
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
@@ -94,9 +94,6 @@ public:
VirtualDir GetParentDirectory() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index aa1b3c17d..b46fe893c 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -4,10 +4,9 @@
#include <algorithm>
#include <cstring>
+#include <optional>
#include <utility>
-#include <boost/optional.hpp>
-
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/ctr_encryption_layer.h"
@@ -97,11 +96,288 @@ union NCASectionHeader {
};
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
-bool IsValidNCA(const NCAHeader& header) {
+static bool IsValidNCA(const NCAHeader& header) {
// TODO(DarkLordZach): Add NCA2/NCA0 support.
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
+NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
+ : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
+ if (file == nullptr) {
+ status = Loader::ResultStatus::ErrorNullFile;
+ return;
+ }
+
+ if (sizeof(NCAHeader) != file->ReadObject(&header)) {
+ LOG_ERROR(Loader, "File reader errored out during header read.");
+ status = Loader::ResultStatus::ErrorBadNCAHeader;
+ return;
+ }
+
+ if (!HandlePotentialHeaderDecryption()) {
+ return;
+ }
+
+ has_rights_id = std::any_of(header.rights_id.begin(), header.rights_id.end(),
+ [](char c) { return c != '\0'; });
+
+ const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
+ is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
+ return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
+ });
+
+ if (!ReadSections(sections, bktr_base_ivfc_offset)) {
+ return;
+ }
+
+ status = Loader::ResultStatus::Success;
+}
+
+NCA::~NCA() = default;
+
+bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
+ if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
+ status = Loader::ResultStatus::ErrorNCA2;
+ return false;
+ }
+
+ if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
+ status = Loader::ResultStatus::ErrorNCA0;
+ return false;
+ }
+
+ return true;
+}
+
+bool NCA::HandlePotentialHeaderDecryption() {
+ if (IsValidNCA(header)) {
+ return true;
+ }
+
+ if (!CheckSupportedNCA(header)) {
+ return false;
+ }
+
+ NCAHeader dec_header{};
+ Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
+ keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
+ cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
+ Core::Crypto::Op::Decrypt);
+ if (IsValidNCA(dec_header)) {
+ header = dec_header;
+ encrypted = true;
+ } else {
+ if (!CheckSupportedNCA(dec_header)) {
+ return false;
+ }
+
+ if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
+ status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
+ } else {
+ status = Loader::ResultStatus::ErrorMissingHeaderKey;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
+ const std::ptrdiff_t number_sections =
+ std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
+ [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
+
+ std::vector<NCASectionHeader> sections(number_sections);
+ const auto length_sections = SECTION_HEADER_SIZE * number_sections;
+
+ if (encrypted) {
+ auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
+ Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
+ keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
+ cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
+ Core::Crypto::Op::Decrypt);
+ } else {
+ file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
+ }
+
+ return sections;
+}
+
+bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
+ for (std::size_t i = 0; i < sections.size(); ++i) {
+ const auto& section = sections[i];
+
+ if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
+ if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
+ return false;
+ }
+ } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
+ if (!ReadPFS0Section(section, header.section_tables[i])) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
+ u64 bktr_base_ivfc_offset) {
+ const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
+ ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
+ const std::size_t romfs_offset = base_offset + ivfc_offset;
+ const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
+ auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
+ auto dec = Decrypt(section, raw, romfs_offset);
+
+ if (dec == nullptr) {
+ if (status != Loader::ResultStatus::Success)
+ return false;
+ if (has_rights_id)
+ status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
+ else
+ status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
+ return false;
+ }
+
+ if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
+ if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
+ section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
+ status = Loader::ResultStatus::ErrorBadBKTRHeader;
+ return false;
+ }
+
+ if (section.bktr.relocation.offset + section.bktr.relocation.size !=
+ section.bktr.subsection.offset) {
+ status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
+ return false;
+ }
+
+ const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
+ if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
+ status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
+ return false;
+ }
+
+ const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
+ RelocationBlock relocation_block{};
+ if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
+ sizeof(RelocationBlock)) {
+ status = Loader::ResultStatus::ErrorBadRelocationBlock;
+ return false;
+ }
+ SubsectionBlock subsection_block{};
+ if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
+ sizeof(RelocationBlock)) {
+ status = Loader::ResultStatus::ErrorBadSubsectionBlock;
+ return false;
+ }
+
+ std::vector<RelocationBucketRaw> relocation_buckets_raw(
+ (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
+ if (dec->ReadBytes(relocation_buckets_raw.data(),
+ section.bktr.relocation.size - sizeof(RelocationBlock),
+ section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
+ section.bktr.relocation.size - sizeof(RelocationBlock)) {
+ status = Loader::ResultStatus::ErrorBadRelocationBuckets;
+ return false;
+ }
+
+ std::vector<SubsectionBucketRaw> subsection_buckets_raw(
+ (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
+ if (dec->ReadBytes(subsection_buckets_raw.data(),
+ section.bktr.subsection.size - sizeof(SubsectionBlock),
+ section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
+ section.bktr.subsection.size - sizeof(SubsectionBlock)) {
+ status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
+ return false;
+ }
+
+ std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
+ std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
+ relocation_buckets.begin(), &ConvertRelocationBucketRaw);
+ std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
+ std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
+ subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
+
+ u32 ctr_low;
+ std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
+ subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
+ subsection_buckets.back().entries.push_back({size, {0}, 0});
+
+ std::optional<Core::Crypto::Key128> key = {};
+ if (encrypted) {
+ if (has_rights_id) {
+ status = Loader::ResultStatus::Success;
+ key = GetTitlekey();
+ if (!key) {
+ status = Loader::ResultStatus::ErrorMissingTitlekey;
+ return false;
+ }
+ } else {
+ key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
+ if (!key) {
+ status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
+ return false;
+ }
+ }
+ }
+
+ if (bktr_base_romfs == nullptr) {
+ status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
+ return false;
+ }
+
+ auto bktr = std::make_shared<BKTR>(
+ bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
+ relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
+ encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
+ section.raw.section_ctr);
+
+ // BKTR applies to entire IVFC, so make an offset version to level 6
+ files.push_back(std::make_shared<OffsetVfsFile>(
+ bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
+ } else {
+ files.push_back(std::move(dec));
+ }
+
+ romfs = files.back();
+ return true;
+}
+
+bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
+ const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
+ section.pfs0.pfs0_header_offset;
+ const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
+
+ auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
+ if (dec != nullptr) {
+ auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
+
+ if (npfs->GetStatus() == Loader::ResultStatus::Success) {
+ dirs.push_back(std::move(npfs));
+ if (IsDirectoryExeFS(dirs.back()))
+ exefs = dirs.back();
+ } else {
+ if (has_rights_id)
+ status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
+ else
+ status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
+ return false;
+ }
+ } else {
+ if (status != Loader::ResultStatus::Success)
+ return false;
+ if (has_rights_id)
+ status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
+ else
+ status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
+ return false;
+ }
+
+ return true;
+}
+
u8 NCA::GetCryptoRevision() const {
u8 master_key_id = header.crypto_type;
if (header.crypto_type_2 > master_key_id)
@@ -111,11 +387,11 @@ u8 NCA::GetCryptoRevision() const {
return master_key_id;
}
-boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
+std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
const auto master_key_id = GetCryptoRevision();
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
- return boost::none;
+ return {};
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -133,31 +409,31 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
static_cast<u8>(type));
u128 out_128{};
memcpy(out_128.data(), out.data(), 16);
- LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
+ LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
master_key_id, header.key_index, out_128[1], out_128[0]);
return out;
}
-boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
+std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
const auto master_key_id = GetCryptoRevision();
u128 rights_id{};
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
- return boost::none;
+ return {};
}
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
- return boost::none;
+ return {};
}
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
- return boost::none;
+ return {};
}
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -167,7 +443,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
return titlekey;
}
-VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) {
+VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
if (!encrypted)
return in;
@@ -181,25 +457,25 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
case NCASectionCryptoType::BKTR:
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
- boost::optional<Core::Crypto::Key128> key = boost::none;
+ std::optional<Core::Crypto::Key128> key = {};
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
- if (key == boost::none) {
+ if (!key) {
if (status == Loader::ResultStatus::Success)
status = Loader::ResultStatus::ErrorMissingTitlekey;
return nullptr;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
- if (key == boost::none) {
+ if (!key) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return nullptr;
}
}
- auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
- std::move(in), key.value(), starting_offset);
+ auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
+ starting_offset);
std::vector<u8> iv(16);
for (u8 i = 0; i < 8; ++i)
iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
@@ -215,256 +491,6 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
}
}
-NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
- : file(std::move(file_)),
- bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
- status = Loader::ResultStatus::Success;
-
- if (file == nullptr) {
- status = Loader::ResultStatus::ErrorNullFile;
- return;
- }
-
- if (sizeof(NCAHeader) != file->ReadObject(&header)) {
- LOG_ERROR(Loader, "File reader errored out during header read.");
- status = Loader::ResultStatus::ErrorBadNCAHeader;
- return;
- }
-
- encrypted = false;
-
- if (!IsValidNCA(header)) {
- if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
- status = Loader::ResultStatus::ErrorNCA2;
- return;
- }
- if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
- status = Loader::ResultStatus::ErrorNCA0;
- return;
- }
-
- NCAHeader dec_header{};
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
- Core::Crypto::Op::Decrypt);
- if (IsValidNCA(dec_header)) {
- header = dec_header;
- encrypted = true;
- } else {
- if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
- status = Loader::ResultStatus::ErrorNCA2;
- return;
- }
- if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
- status = Loader::ResultStatus::ErrorNCA0;
- return;
- }
-
- if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
- status = Loader::ResultStatus::ErrorMissingHeaderKey;
- else
- status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
- return;
- }
- }
-
- has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
- [](char c) { return c == '\0'; }) != header.rights_id.end();
-
- const std::ptrdiff_t number_sections =
- std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
- [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
-
- std::vector<NCASectionHeader> sections(number_sections);
- const auto length_sections = SECTION_HEADER_SIZE * number_sections;
-
- if (encrypted) {
- auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
- Core::Crypto::Op::Decrypt);
- } else {
- file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
- }
-
- is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
- return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
- }) != sections.end();
- ivfc_offset = 0;
-
- for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
- auto section = sections[i];
-
- if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
- const std::size_t base_offset =
- header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
- ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- const std::size_t romfs_offset = base_offset + ivfc_offset;
- const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
- auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
- auto dec = Decrypt(section, raw, romfs_offset);
-
- if (dec == nullptr) {
- if (status != Loader::ResultStatus::Success)
- return;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return;
- }
-
- if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
- if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
- section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
- status = Loader::ResultStatus::ErrorBadBKTRHeader;
- return;
- }
-
- if (section.bktr.relocation.offset + section.bktr.relocation.size !=
- section.bktr.subsection.offset) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
- return;
- }
-
- const u64 size =
- MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
- header.section_tables[i].media_offset);
- if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
- return;
- }
-
- const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- RelocationBlock relocation_block{};
- if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBlock;
- return;
- }
- SubsectionBlock subsection_block{};
- if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBlock;
- return;
- }
-
- std::vector<RelocationBucketRaw> relocation_buckets_raw(
- (section.bktr.relocation.size - sizeof(RelocationBlock)) /
- sizeof(RelocationBucketRaw));
- if (dec->ReadBytes(relocation_buckets_raw.data(),
- section.bktr.relocation.size - sizeof(RelocationBlock),
- section.bktr.relocation.offset + sizeof(RelocationBlock) -
- offset) !=
- section.bktr.relocation.size - sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBuckets;
- return;
- }
-
- std::vector<SubsectionBucketRaw> subsection_buckets_raw(
- (section.bktr.subsection.size - sizeof(SubsectionBlock)) /
- sizeof(SubsectionBucketRaw));
- if (dec->ReadBytes(subsection_buckets_raw.data(),
- section.bktr.subsection.size - sizeof(SubsectionBlock),
- section.bktr.subsection.offset + sizeof(SubsectionBlock) -
- offset) !=
- section.bktr.subsection.size - sizeof(SubsectionBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
- return;
- }
-
- std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
- std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
- relocation_buckets.begin(), &ConvertRelocationBucketRaw);
- std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
- std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
- subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
-
- u32 ctr_low;
- std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
- subsection_buckets.back().entries.push_back(
- {section.bktr.relocation.offset, {0}, ctr_low});
- subsection_buckets.back().entries.push_back({size, {0}, 0});
-
- boost::optional<Core::Crypto::Key128> key = boost::none;
- if (encrypted) {
- if (has_rights_id) {
- status = Loader::ResultStatus::Success;
- key = GetTitlekey();
- if (key == boost::none) {
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return;
- }
- } else {
- key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
- if (key == boost::none) {
- status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
- return;
- }
- }
- }
-
- if (bktr_base_romfs == nullptr) {
- status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
- return;
- }
-
- auto bktr = std::make_shared<BKTR>(
- bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
- relocation_block, relocation_buckets, subsection_block, subsection_buckets,
- encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
- bktr_base_ivfc_offset, section.raw.section_ctr);
-
- // BKTR applies to entire IVFC, so make an offset version to level 6
-
- files.push_back(std::make_shared<OffsetVfsFile>(
- bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
- romfs = files.back();
- } else {
- files.push_back(std::move(dec));
- romfs = files.back();
- }
- } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
- u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
- MEDIA_OFFSET_MULTIPLIER) +
- section.pfs0.pfs0_header_offset;
- u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
- header.section_tables[i].media_offset);
- auto dec =
- Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
- if (dec != nullptr) {
- auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
-
- if (npfs->GetStatus() == Loader::ResultStatus::Success) {
- dirs.push_back(std::move(npfs));
- if (IsDirectoryExeFS(dirs.back()))
- exefs = dirs.back();
- } else {
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return;
- }
- } else {
- if (status != Loader::ResultStatus::Success)
- return;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return;
- }
- }
- }
-
- status = Loader::ResultStatus::Success;
-}
-
-NCA::~NCA() = default;
-
Loader::ResultStatus NCA::GetStatus() const {
return status;
}
@@ -519,7 +545,4 @@ u64 NCA::GetBaseIVFCOffset() const {
return ivfc_offset;
}
-bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index f9f66cae9..4bba55607 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -6,9 +6,10 @@
#include <array>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -73,8 +74,6 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
}
-bool IsValidNCA(const NCAHeader& header);
-
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
@@ -102,14 +101,20 @@ public:
// Returns the base ivfc offset used in BKTR patching.
u64 GetBaseIVFCOffset() const;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
+ bool CheckSupportedNCA(const NCAHeader& header);
+ bool HandlePotentialHeaderDecryption();
+
+ std::vector<NCASectionHeader> ReadSectionHeaders() const;
+ bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
+ bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
+ u64 bktr_base_ivfc_offset);
+ bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
+
u8 GetCryptoRevision() const;
- boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
- boost::optional<Core::Crypto::Key128> GetTitlekey();
- VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
+ std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
+ std::optional<Core::Crypto::Key128> GetTitlekey();
+ VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
std::vector<VirtualDir> dirs;
std::vector<VirtualFile> files;
@@ -118,15 +123,15 @@ private:
VirtualDir exefs = nullptr;
VirtualFile file;
VirtualFile bktr_base_romfs;
- u64 ivfc_offset;
+ u64 ivfc_offset = 0;
NCAHeader header{};
bool has_rights_id{};
Loader::ResultStatus status{};
- bool encrypted;
- bool is_update;
+ bool encrypted = false;
+ bool is_update = false;
Core::Crypto::KeyManager keys;
};
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 5b1177a03..a012c2be9 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -17,11 +17,13 @@ const std::array<const char*, 15> LANGUAGE_NAMES = {
};
std::string LanguageEntry::GetApplicationName() const {
- return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200);
+ return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(),
+ application_name.size());
}
std::string LanguageEntry::GetDeveloperName() const {
- return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
+ return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(),
+ developer_name.size());
}
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
@@ -56,7 +58,12 @@ u64 NACP::GetTitleId() const {
return raw->title_id;
}
+u64 NACP::GetDLCBaseTitleId() const {
+ return raw->dlc_base_title_id;
+}
+
std::string NACP::GetVersionString() const {
- return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10);
+ return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(),
+ raw->version_string.size());
}
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 43d6f0719..141f7e056 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -79,6 +79,7 @@ public:
std::string GetApplicationName(Language language = Language::Default) const;
std::string GetDeveloperName(Language language = Language::Default) const;
u64 GetTitleId() const;
+ u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
private:
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index 2a913ce82..47b7526c7 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -26,6 +26,7 @@
#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.h"
#include "core/file_sys/vfs_vector.h"
@@ -123,7 +124,7 @@ static u64 romfs_get_hash_table_count(u64 num_entries) {
return count;
}
-void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
+void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext,
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
@@ -144,6 +145,9 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
+ if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
+ continue;
+
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
@@ -157,11 +161,24 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
+ if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
+ continue;
+
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
child->source = root_romfs->GetFileRelative(child->path);
+ if (ext != nullptr) {
+ const auto ips = ext->GetFileRelative(child->path + ".ips");
+
+ if (ips != nullptr) {
+ auto patched = PatchIPS(child->source, ips);
+ if (patched != nullptr)
+ child->source = std::move(patched);
+ }
+ }
+
child->size = child->source->GetSize();
AddFile(parent, child);
@@ -169,7 +186,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
}
for (auto& child : child_dirs) {
- this->VisitDirectory(root_romfs, child);
+ this->VisitDirectory(root_romfs, ext, child);
}
}
@@ -208,14 +225,15 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare
return true;
}
-RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
+RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
+ : base(std::move(base_)), ext(std::move(ext_)) {
root = std::make_shared<RomFSBuildDirectoryContext>();
root->path = "\0";
directories.emplace(root->path, root);
num_dirs = 1;
dir_table_size = 0x18;
- VisitDirectory(base, root);
+ VisitDirectory(base, ext, root);
}
RomFSBuildContext::~RomFSBuildContext() = default;
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
index b0c3c123b..a62502193 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.h
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -27,7 +27,6 @@
#include <map>
#include <memory>
#include <string>
-#include <boost/detail/container_fwd.hpp>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
@@ -40,7 +39,7 @@ struct RomFSFileEntry;
class RomFSBuildContext {
public:
- explicit RomFSBuildContext(VirtualDir base);
+ explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
~RomFSBuildContext();
// This finalizes the context.
@@ -48,6 +47,7 @@ public:
private:
VirtualDir base;
+ VirtualDir ext;
std::shared_ptr<RomFSBuildDirectoryContext> root;
std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
@@ -59,7 +59,8 @@ private:
u64 file_hash_table_size = 0;
u64 file_partition_size = 0;
- void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
+ void VisitDirectory(VirtualDir filesys, VirtualDir ext,
+ std::shared_ptr<RomFSBuildDirectoryContext> parent);
bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index df933ee36..485c4913a 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -2,7 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/assert.h"
+#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_vector.h"
@@ -15,16 +23,48 @@ enum class IPSFileType {
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)
+ if (magic.size() != 5) {
return IPSFileType::Error;
- if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
+ }
+
+ 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;
- if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
+ }
+
+ 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) {
+ constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
+ if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
+ return true;
+ }
+
+ 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;
@@ -39,8 +79,7 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
offset += temp.size();
- if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
- type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
+ if (IsEOF(type, temp)) {
break;
}
@@ -60,29 +99,240 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
u16 rle_size{};
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
return nullptr;
- rle_size = Common::swap16(data_size);
+ rle_size = Common::swap16(rle_size);
offset += sizeof(u16);
const auto data = ips->ReadByte(offset++);
- if (data == boost::none)
+ if (!data)
return nullptr;
if (real_offset + rle_size > in_data.size())
- rle_size = in_data.size() - real_offset;
- std::memset(in_data.data() + real_offset, data.get(), rle_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 = in_data.size() - real_offset;
+ 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 (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
+ if (!IsEOF(type, temp)) {
return nullptr;
- return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
+ }
+
+ 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::stoll(line.substr(19), 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
+ auto raw_build_id = line.substr(8);
+ if (raw_build_id.size() != 0x40)
+ raw_build_id.resize(0x40, '0');
+ 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::stoul(patch_line.substr(0, 8), 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);
+ replace.reserve(value.size() / 2);
+ 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::HexVectorToString(replace));
+ }
+
+ patch.records.insert_or_assign(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
diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h
index 81c163494..450b2f71e 100644
--- a/src/core/file_sys/ips_layer.h
+++ b/src/core/file_sys/ips_layer.h
@@ -4,12 +4,41 @@
#pragma once
+#include <array>
#include <memory>
+#include <vector>
+#include "common/common_types.h"
#include "core/file_sys/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
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 5791c76ff..a5259a593 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -83,7 +83,7 @@ std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
}
std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
- return pfs_dirs;
+ return {};
}
std::string PartitionFilesystem::GetName() const {
@@ -103,18 +103,4 @@ void PartitionFilesystem::PrintDebugInfo() const {
pfs_files[i]->GetName(), pfs_files[i]->GetSize());
}
}
-
-bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- const auto iter = std::find(pfs_files.begin(), pfs_files.end(), file);
- if (iter == pfs_files.end())
- return false;
-
- const std::ptrdiff_t offset = std::distance(pfs_files.begin(), iter);
- pfs_files[offset] = std::move(pfs_files.back());
- pfs_files.pop_back();
-
- pfs_dirs.emplace_back(std::move(dir));
-
- return true;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 739c63a7f..248fdfdeb 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -35,9 +35,6 @@ public:
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
void PrintDebugInfo() const;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
struct Header {
u32_le magic;
@@ -84,7 +81,6 @@ private:
std::size_t content_offset = 0;
std::vector<VirtualFile> pfs_files;
- std::vector<VirtualDir> pfs_dirs;
};
} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 539698f6e..0c1156989 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -61,39 +61,49 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
- if (update != nullptr) {
- if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
- update->GetExeFS() != nullptr) {
- LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
- FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
- exefs = update->GetExeFS();
- }
+
+ if (update != nullptr && update->GetExeFS() != nullptr &&
+ update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
+ FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
+ exefs = update->GetExeFS();
}
return exefs;
}
-static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs,
- const std::string& build_id) {
- std::vector<VirtualFile> ips;
- ips.reserve(patch_dirs.size());
+static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) {
+ std::vector<VirtualFile> out;
+ out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
- if (file->GetExtension() != "ips")
- continue;
- auto name = file->GetName();
- const auto p1 = name.substr(0, name.find('.'));
- const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
-
- if (build_id == this_build_id)
- ips.push_back(file);
+ if (file->GetExtension() == "ips") {
+ auto name = file->GetName();
+ const auto p1 = name.substr(0, name.find('.'));
+ const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
+
+ if (build_id == this_build_id)
+ out.push_back(file);
+ } else if (file->GetExtension() == "pchtxt") {
+ IPSwitchCompiler compiler{file};
+ if (!compiler.IsValid())
+ continue;
+
+ auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
+ this_build_id =
+ this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
+
+ if (build_id == this_build_id)
+ out.push_back(file);
+ }
}
}
}
- return ips;
+ return out;
}
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
@@ -115,15 +125,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
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 ips = CollectIPSPatches(patch_dirs, build_id);
+ const auto patches = CollectPatches(patch_dirs, build_id);
auto out = nso;
- for (const auto& ips_file : ips) {
- LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"",
- ips_file->GetContainingDirectory()->GetParentDirectory()->GetName());
- const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file);
- if (patched != nullptr)
- out = patched->ReadAllBytes();
+ 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() < 0x100)
@@ -143,12 +162,13 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
- return !CollectIPSPatches(patch_dirs, build_id).empty();
+ return !CollectPatches(patch_dirs, build_id).empty();
}
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
- if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
+ if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
+ load_dir == nullptr || load_dir->GetSize() <= 0) {
return;
}
@@ -162,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
[](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) {
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
+
+ auto ext_dir = subdir->GetSubdirectory("romfs_ext");
+ if (ext_dir != nullptr)
+ layers_ext.push_back(std::move(ext_dir));
}
layers.push_back(std::move(extracted));
@@ -175,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
- auto packed = CreateRomFS(std::move(layered));
+ auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
+
+ auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
if (packed == nullptr) {
return;
}
@@ -184,10 +212,16 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
romfs = std::move(packed);
}
-VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
- ContentRecordType type) const {
- LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
- static_cast<u8>(type));
+VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
+ VirtualFile update_raw) const {
+ const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
+ title_id, static_cast<u8>(type))
+ .c_str();
+
+ if (type == ContentRecordType::Program || type == ContentRecordType::Data)
+ LOG_INFO(Loader, log_string);
+ else
+ LOG_DEBUG(Loader, log_string);
if (romfs == nullptr)
return romfs;
@@ -202,7 +236,14 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
- FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
+ FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
+ romfs = new_nca->GetRomFS();
+ }
+ } else if (update_raw != nullptr) {
+ const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
+ if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
+ new_nca->GetRomFS() != nullptr) {
+ LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
romfs = new_nca->GetRomFS();
}
}
@@ -224,7 +265,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
-std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const {
+std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
+ VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
@@ -238,13 +280,14 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
} else {
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = installed->GetEntryVersion(update_tid);
- if (meta_ver == boost::none || meta_ver.get() == 0) {
+ if (meta_ver.value_or(0) == 0) {
out.insert_or_assign("Update", "");
} else {
out.insert_or_assign(
- "Update",
- FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
+ "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
}
+ } else if (update_raw != nullptr) {
+ out.insert_or_assign("Update", "PACKED");
}
}
@@ -253,8 +296,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
for (const auto& mod : mod_dir->GetSubdirectories()) {
std::string types;
- if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs")))
- AppendCommaIfNotEmpty(types, "IPS");
+
+ const auto exefs_dir = mod->GetSubdirectory("exefs");
+ if (IsDirValidAndNonEmpty(exefs_dir)) {
+ bool ips = false;
+ bool ipswitch = false;
+
+ for (const auto& file : exefs_dir->GetFiles()) {
+ if (file->GetExtension() == "ips")
+ ips = true;
+ else if (file->GetExtension() == "pchtxt")
+ ipswitch = true;
+ }
+
+ if (ips)
+ AppendCommaIfNotEmpty(types, "IPS");
+ if (ipswitch)
+ AppendCommaIfNotEmpty(types, "IPSwitch");
+ }
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
@@ -291,23 +350,22 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
return out;
}
-std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
- const auto& installed{Service::FileSystem::GetUnionContents()};
+std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
+ const auto installed{Service::FileSystem::GetUnionContents()};
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr)
return {};
- return ParseControlNCA(base_control_nca);
+ return ParseControlNCA(*base_control_nca);
}
-std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
- const std::shared_ptr<NCA>& nca) const {
- const auto base_romfs = nca->GetRomFS();
+std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
+ const auto base_romfs = nca.GetRomFS();
if (base_romfs == nullptr)
return {};
- const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control);
+ const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
if (romfs == nullptr)
return {};
@@ -319,7 +377,7 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
if (nacp_file == nullptr)
nacp_file = extracted->GetFile("Control.nacp");
- const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
+ auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
VirtualFile icon_file;
for (const auto& language : FileSys::LANGUAGE_NAMES) {
@@ -328,6 +386,6 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
break;
}
- return {nacp, icon_file};
+ return {std::move(nacp), icon_file};
}
} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 6a864ec43..7d168837f 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -36,6 +36,7 @@ public:
// Currently tracked NSO patches:
// - IPS
+ // - IPSwitch
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
@@ -46,19 +47,20 @@ public:
// - Game Updates
// - LayeredFS
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
- ContentRecordType type = ContentRecordType::Program) const;
+ ContentRecordType type = ContentRecordType::Program,
+ VirtualFile update_raw = nullptr) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
- std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const;
+ std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
+ VirtualFile update_raw = nullptr) 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.
- std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
+ std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA
- std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
- const std::shared_ptr<NCA>& nca) const;
+ std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
private:
u64 title_id;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index e9b040689..96302a241 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <regex>
#include <mbedtls/sha256.h>
#include "common/assert.h"
@@ -30,6 +31,14 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
}
+bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+ return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
+}
+
+bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+ return !operator==(lhs, rhs);
+}
+
static bool FollowsTwoDigitDirFormat(std::string_view name) {
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
std::regex_constants::icase);
@@ -150,28 +159,28 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file;
}
-static boost::optional<NcaID> CheckMapForContentRecord(
+static std::optional<NcaID> CheckMapForContentRecord(
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
if (map.find(title_id) == map.end())
- return boost::none;
+ return {};
const auto& cnmt = map.at(title_id);
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
[type](const ContentRecord& rec) { return rec.type == type; });
if (iter == cnmt.GetContentRecords().end())
- return boost::none;
+ return {};
- return boost::make_optional(iter->nca_id);
+ return std::make_optional(iter->nca_id);
}
-boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
- ContentRecordType type) const {
+std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
+ ContentRecordType type) const {
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
return meta_id.at(title_id);
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
- if (res1 != boost::none)
+ if (res1)
return res1;
return CheckMapForContentRecord(meta, title_id, type);
}
@@ -274,17 +283,14 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
- if (id == boost::none)
- return nullptr;
-
- return GetFileAtID(id.get());
+ return id ? GetFileAtID(*id) : nullptr;
}
VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
return GetEntryUnparsed(entry.title_id, entry.type);
}
-boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
const auto meta_iter = meta.find(title_id);
if (meta_iter != meta.end())
return meta_iter->second.GetTitleVersion();
@@ -293,29 +299,26 @@ boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
if (yuzu_meta_iter != yuzu_meta.end())
return yuzu_meta_iter->second.GetTitleVersion();
- return boost::none;
+ return {};
}
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
- if (id == boost::none)
- return nullptr;
-
- return parser(GetFileAtID(id.get()), id.get());
+ return id ? parser(GetFileAtID(*id), *id) : nullptr;
}
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
return GetEntryRaw(entry.title_id, entry.type);
}
-std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
+std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_shared<NCA>(raw);
+ return std::make_unique<NCA>(raw);
}
-std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
+std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}
@@ -355,8 +358,8 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
}
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
- boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
- boost::optional<u64> title_id) const {
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
IterateAllMetadata<RegisteredCacheEntry>(
out,
@@ -364,11 +367,11 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type != boost::none && title_type.get() != c.GetType())
+ if (title_type && *title_type != c.GetType())
return false;
- if (record_type != boost::none && record_type.get() != r.type)
+ if (record_type && *record_type != r.type)
return false;
- if (title_id != boost::none && title_id.get() != c.GetTitleID())
+ if (title_id && *title_id != c.GetTitleID())
return false;
return true;
});
@@ -450,7 +453,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
- boost::optional<NcaID> override_id) {
+ std::optional<NcaID> override_id) {
const auto in = nca->GetBaseFile();
Core::Crypto::SHA256Hash hash{};
@@ -459,12 +462,12 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
// game is massive), we're going to cheat and only hash the first MB of the NCA.
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
NcaID id{};
- if (override_id == boost::none) {
+ if (override_id) {
+ id = *override_id;
+ } else {
const auto& data = in->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
memcpy(id.data(), hash.data(), 16);
- } else {
- id = override_id.get();
}
std::string path = GetRelativePathFromNcaID(id, false, true);
@@ -516,7 +519,7 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
}) != yuzu_meta.end();
}
-RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
+RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
: caches(std::move(caches)) {}
void RegisteredCacheUnion::Refresh() {
@@ -534,14 +537,14 @@ bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
return HasEntry(entry.title_id, entry.type);
}
-boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
for (const auto& c : caches) {
const auto res = c->GetEntryVersion(title_id);
- if (res != boost::none)
+ if (res)
return res;
}
- return boost::none;
+ return {};
}
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
@@ -572,14 +575,14 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const
return GetEntryRaw(entry.title_id, entry.type);
}
-std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
+std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_shared<NCA>(raw);
+ return std::make_unique<NCA>(raw);
}
-std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
+std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}
@@ -593,12 +596,15 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
},
[](const CNMT& c, const ContentRecord& r) { return true; });
}
+
+ std::sort(out.begin(), out.end());
+ out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
- boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
- boost::optional<u64> title_id) const {
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
for (const auto& c : caches) {
c->IterateAllMetadata<RegisteredCacheEntry>(
@@ -607,15 +613,18 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type != boost::none && title_type.get() != c.GetType())
+ if (title_type && *title_type != c.GetType())
return false;
- if (record_type != boost::none && record_type.get() != r.type)
+ if (record_type && *record_type != r.type)
return false;
- if (title_id != boost::none && title_id.get() != c.GetTitleID())
+ if (title_id && *title_id != c.GetTitleID())
return false;
return true;
});
}
+
+ std::sort(out.begin(), out.end());
+ out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index c0cd59fc5..6cfb16017 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -50,6 +50,10 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
// boost flat_map requires operator< for O(log(n)) lookups.
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+// std unique requires operator== to identify duplicates.
+bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+
/*
* A class that catalogues NCAs in the registered directory structure.
* Nintendo's registered format follows this structure:
@@ -60,8 +64,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
* | 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.)
+ * (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 {
friend class RegisteredCacheUnion;
@@ -80,7 +84,7 @@ public:
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) const;
- boost::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const;
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -88,15 +92,14 @@ public:
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
- std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
+ std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not boost::none, it will be filtered for from all entries.
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
std::vector<RegisteredCacheEntry> ListEntriesFilter(
- boost::optional<TitleType> title_type = boost::none,
- boost::optional<ContentRecordType> record_type = boost::none,
- boost::optional<u64> title_id = boost::none) const;
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const;
// 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.
@@ -121,12 +124,11 @@ private:
std::vector<NcaID> AccumulateFiles() const;
void ProcessFiles(const std::vector<NcaID>& ids);
void AccumulateYuzuMeta();
- boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
+ std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
VirtualFile GetFileAtID(NcaID id) const;
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
- bool overwrite_if_exists,
- boost::optional<NcaID> override_id = boost::none);
+ bool overwrite_if_exists, std::optional<NcaID> override_id = {});
bool RawInstallYuzuMeta(const CNMT& cnmt);
VirtualDir dir;
@@ -142,14 +144,14 @@ private:
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
class RegisteredCacheUnion {
public:
- explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
+ explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
void Refresh();
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) const;
- boost::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const;
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -157,18 +159,17 @@ public:
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
- std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
+ std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not boost::none, it will be filtered for from all entries.
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
std::vector<RegisteredCacheEntry> ListEntriesFilter(
- boost::optional<TitleType> title_type = boost::none,
- boost::optional<ContentRecordType> record_type = boost::none,
- boost::optional<u64> title_id = boost::none) const;
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const;
private:
- std::vector<std::shared_ptr<RegisteredCache>> caches;
+ std::vector<RegisteredCache*> caches;
};
} // namespace FileSys
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 5910f7046..81e1f66ac 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -129,11 +129,11 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
return out;
}
-VirtualFile CreateRomFS(VirtualDir dir) {
+VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
if (dir == nullptr)
return nullptr;
- RomFSBuildContext ctx{dir};
+ RomFSBuildContext ctx{dir, ext};
return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
}
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index ecd1eb725..0ec404731 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -44,6 +44,6 @@ VirtualDir ExtractRomFS(VirtualFile file,
// Converts a VFS filesystem into a RomFS binary
// Returns nullptr on failure
-VirtualFile CreateRomFS(VirtualDir dir);
+VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 4994c2532..0b645b106 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -30,12 +30,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
RomFSFactory::~RomFSFactory() = default;
+void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
+ this->update_raw = std::move(update_raw);
+}
+
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
if (!updatable)
return MakeResult<VirtualFile>(file);
const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
- return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
+ return MakeResult<VirtualFile>(
+ patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 2cace8180..7724c0b23 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -32,11 +32,13 @@ public:
explicit RomFSFactory(Loader::AppLoader& app_loader);
~RomFSFactory();
+ void SetPackedUpdate(VirtualFile update_raw);
ResultVal<VirtualFile> OpenCurrentProcess();
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
private:
VirtualFile file;
+ VirtualFile update_raw;
bool updatable;
u64 ivfc_offset;
};
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 47f2ab9e0..ef1aaebbb 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -51,6 +51,13 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
meta.title_id);
}
+ if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
+ LOG_WARNING(Service_FS,
+ "Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
+ "non-zero ({:016X}{:016X})",
+ meta.user_id[1], meta.user_id[0]);
+ }
+
std::string save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -92,6 +99,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataSpaceId::NandUser:
out = "/user/";
break;
+ case SaveDataSpaceId::TemporaryStorage:
+ out = "/temp/";
+ break;
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
}
@@ -100,10 +110,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::SystemSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
case SaveDataType::SaveData:
+ case SaveDataType::DeviceSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
case SaveDataType::TemporaryStorage:
- return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
+ return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index d66a9c9a4..bd3a57058 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -10,10 +10,10 @@
namespace FileSys {
SDMCFactory::SDMCFactory(VirtualDir dir_)
- : dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>(
+ : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
[](const VirtualFile& file, const NcaID& id) {
- return std::make_shared<NAX>(file, id)->GetDecrypted();
+ return NAX{file, id}.GetDecrypted();
})) {}
SDMCFactory::~SDMCFactory() = default;
@@ -22,8 +22,8 @@ ResultVal<VirtualDir> SDMCFactory::Open() {
return MakeResult<VirtualDir>(dir);
}
-std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
- return contents;
+RegisteredCache* SDMCFactory::GetSDMCContents() const {
+ return contents.get();
}
} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index ea12149de..42794ba5b 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -19,12 +19,12 @@ public:
~SDMCFactory();
ResultVal<VirtualDir> Open();
- std::shared_ptr<RegisteredCache> GetSDMCContents() const;
+ RegisteredCache* GetSDMCContents() const;
private:
VirtualDir dir;
- std::shared_ptr<RegisteredCache> contents;
+ std::unique_ptr<RegisteredCache> contents;
};
} // namespace FileSys
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 09bf077cd..2aaba4179 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -205,10 +205,6 @@ VirtualDir NSP::GetParentDirectory() const {
return file->GetContainingDirectory();
}
-bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
-
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
exefs = pfs;
@@ -259,8 +255,11 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
auto next_nca = std::make_shared<NCA>(next_file);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
- if (next_nca->GetStatus() == Loader::ResultStatus::Success)
+ if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
+ (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
+ (cnmt.GetTitleID() & 0x800) != 0)) {
ncas_title[rec.type] = std::move(next_nca);
+ }
}
break;
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index da3dc5e9f..338080b7e 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -55,9 +55,6 @@ public:
VirtualDir GetParentDirectory() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
void ReadNCAs(const std::vector<VirtualFile>& files);
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index bfe50da73..7b584de7f 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -167,13 +167,13 @@ std::string VfsFile::GetExtension() const {
VfsDirectory::~VfsDirectory() = default;
-boost::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
+std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
u8 out{};
std::size_t size = Read(&out, 1, offset);
if (size == 1)
return out;
- return boost::none;
+ return {};
}
std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
@@ -472,10 +472,14 @@ bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t blo
std::vector<u8> temp(std::min(block_size, src->GetSize()));
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
const auto read = std::min(block_size, src->GetSize() - i);
- const auto block = src->Read(temp.data(), read, i);
- if (dest->Write(temp.data(), read, i) != read)
+ if (src->Read(temp.data(), read, i) != read) {
return false;
+ }
+
+ if (dest->Write(temp.data(), read, i) != read) {
+ return false;
+ }
}
return true;
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 270291631..002f99d4e 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -4,28 +4,22 @@
#pragma once
+#include <functional>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
-class VfsDirectory;
-class VfsFile;
-class VfsFilesystem;
-
enum class Mode : u32;
-// Convenience typedefs to use Vfs* interfaces
-using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
-using VirtualDir = std::shared_ptr<VfsDirectory>;
-using VirtualFile = std::shared_ptr<VfsFile>;
-
// An enumeration representing what can be at the end of a path in a VfsFilesystem
enum class VfsEntryType {
None,
@@ -111,8 +105,8 @@ public:
// into file. Returns number of bytes successfully written.
virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
- // Reads exactly one byte at the offset provided, returning boost::none on error.
- virtual boost::optional<u8> ReadByte(std::size_t offset = 0) const;
+ // Reads exactly one byte at the offset provided, returning std::nullopt on error.
+ virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
// Reads size bytes starting at offset in file into a vector.
virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
// Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
@@ -270,36 +264,8 @@ public:
// item name -> type.
virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
- // Interprets the file with name file instead as a directory of type directory.
- // The directory must have a constructor that takes a single argument of type
- // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
- // subdirectory in one call.
- template <typename Directory>
- bool InterpretAsDirectory(std::string_view file) {
- auto file_p = GetFile(file);
-
- if (file_p == nullptr) {
- return false;
- }
-
- return ReplaceFileWithSubdirectory(file_p, std::make_shared<Directory>(file_p));
- }
-
- bool InterpretAsDirectory(const std::function<VirtualDir(VirtualFile)>& function,
- const std::string& file) {
- auto file_p = GetFile(file);
- if (file_p == nullptr)
- return false;
- return ReplaceFileWithSubdirectory(file_p, function(file_p));
- }
-
// Returns the full path of this directory as a string, recursively
virtual std::string GetFullPath() const;
-
-protected:
- // Backend for InterpretAsDirectory.
- // Removes all references to file and adds a reference to dir in the directory's implementation.
- virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0;
};
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
index bfee01725..338e398da 100644
--- a/src/core/file_sys/vfs_layered.cpp
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -126,7 +126,4 @@ bool LayeredVfsDirectory::Rename(std::string_view name_) {
return true;
}
-bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h
index d85310f57..8a25c3428 100644
--- a/src/core/file_sys/vfs_layered.h
+++ b/src/core/file_sys/vfs_layered.h
@@ -39,9 +39,6 @@ public:
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
std::vector<VirtualDir> dirs;
std::string name;
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index a4c6719a0..c96f88488 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -57,11 +57,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
}
-boost::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
+std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
if (r_offset < size)
return file->ReadByte(offset + r_offset);
- return boost::none;
+ return {};
}
std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h
index 8062702a7..f7b7a3256 100644
--- a/src/core/file_sys/vfs_offset.h
+++ b/src/core/file_sys/vfs_offset.h
@@ -29,7 +29,7 @@ public:
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
- boost::optional<u8> ReadByte(std::size_t offset) const override;
+ std::optional<u8> ReadByte(std::size_t offset) const override;
std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
std::vector<u8> ReadAllBytes() const override;
bool WriteByte(u8 data, std::size_t offset) override;
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 9defad04c..e21300a7c 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -430,7 +430,4 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
return out;
}
-bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 5b61db90d..a0a857a31 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -100,9 +100,6 @@ public:
std::string GetFullPath() const override;
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index 44fab51d1..9f5a90b1b 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.h
@@ -53,10 +53,10 @@ public:
return 0;
}
- boost::optional<u8> ReadByte(std::size_t offset) const override {
+ std::optional<u8> ReadByte(std::size_t offset) const override {
if (offset < size)
return value;
- return boost::none;
+ return {};
}
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
diff --git a/src/core/file_sys/vfs_types.h b/src/core/file_sys/vfs_types.h
new file mode 100644
index 000000000..6215ed7af
--- /dev/null
+++ b/src/core/file_sys/vfs_types.h
@@ -0,0 +1,21 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+namespace FileSys {
+
+class VfsDirectory;
+class VfsFile;
+class VfsFilesystem;
+
+// Declarations for Vfs* pointer types
+
+using VirtualDir = std::shared_ptr<VfsDirectory>;
+using VirtualFile = std::shared_ptr<VfsFile>;
+using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index 389c7e003..808f31e81 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -132,11 +132,4 @@ void VectorVfsDirectory::AddFile(VirtualFile file) {
void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
dirs.push_back(std::move(dir));
}
-
-bool VectorVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- if (!DeleteFile(file->GetName()))
- return false;
- dirs.emplace_back(std::move(dir));
- return true;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index 48a414c98..3e3f790c3 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -57,9 +57,6 @@ public:
virtual void AddFile(VirtualFile file);
virtual void AddDirectory(VirtualDir dir);
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
std::vector<VirtualFile> files;
std::vector<VirtualDir> dirs;
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index b2b164368..eec51c64e 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -163,7 +163,4 @@ std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
return file->GetContainingDirectory();
}
-bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 8fedd8585..7704dee90 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -51,9 +51,6 @@ public:
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
Loader::ResultStatus Parse(std::string_view path);