From a91983b11c9aab00065e258ab94b1b99a1f62201 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 9 Aug 2018 20:52:27 -0400 Subject: file_sys: Add RegisteredCache Manages NAND NCA get and install. --- src/core/file_sys/registered_cache.cpp | 435 +++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 src/core/file_sys/registered_cache.cpp (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp new file mode 100644 index 000000000..5440cdefb --- /dev/null +++ b/src/core/file_sys/registered_cache.cpp @@ -0,0 +1,435 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/assert.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/crypto/encryption_layer.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/vfs_concat.h" + +namespace FileSys { +std::string RegisteredCacheEntry::DebugInfo() const { + return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast(type)); +} + +bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { + return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type); +} + +static bool FollowsTwoDigitDirFormat(std::string_view name) { + const static std::regex two_digit_regex( + "000000[0123456789abcdefABCDEF][0123456789abcdefABCDEF]"); + return std::regex_match(name.begin(), name.end(), two_digit_regex); +} + +static bool FollowsNcaIdFormat(std::string_view name) { + const static std::regex nca_id_regex("[0123456789abcdefABCDEF]+.nca"); + return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); +} + +static std::string GetRelativePathFromNcaID(const std::array& nca_id, bool second_hex_upper, + bool within_two_digit) { + if (!within_two_digit) + return fmt::format("/{}.nca", HexArrayToString(nca_id, second_hex_upper)); + + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); + return fmt::format("/000000{:02X}/{}.nca", hash[0], HexArrayToString(nca_id, second_hex_upper)); +} + +static std::string GetCNMTName(TitleType type, u64 title_id) { + constexpr std::array TITLE_TYPE_NAMES{ + "SystemProgram", + "SystemData", + "SystemUpdate", + "BootImagePackage", + "BootImagePackageSafe", + "Application", + "Patch", + "AddOnContent", + "" ///< Currently unknown 'DeltaTitle' + }; + + size_t index = static_cast(type); + if (index >= 0x80) + index -= 0x80; + return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); +} + +static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { + switch (type) { + case NCAContentType::Program: + // TODO(DarkLordZach): Differentiate between Program and Patch + return ContentRecordType::Program; + case NCAContentType::Meta: + return ContentRecordType::Meta; + case NCAContentType::Control: + return ContentRecordType::Control; + case NCAContentType::Data: + return ContentRecordType::Data; + case NCAContentType::Manual: + // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. + return ContentRecordType::Manual; + default: + UNREACHABLE(); + } +} + +VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, + std::string_view path) const { + if (dir->GetFileRelative(path) != nullptr) + return dir->GetFileRelative(path); + if (dir->GetDirectoryRelative(path) != nullptr) { + const auto nca_dir = dir->GetDirectoryRelative(path); + VirtualFile file = nullptr; + + const auto files = nca_dir->GetFiles(); + if (files.size() == 1 && files[0]->GetName() == "00") + file = files[0]; + else { + std::vector concat; + for (u8 i = 0; i < 0x10; ++i) { + auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); + if (next != nullptr) + concat.push_back(std::move(next)); + else { + next = nca_dir->GetFile(fmt::format("{:02x}", i)); + if (next != nullptr) + concat.push_back(std::move(next)); + else + break; + } + } + + if (concat.empty()) + return nullptr; + + file = FileSys::ConcatenateFiles(concat); + } + + return file; + } + return nullptr; +} + +VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { + VirtualFile file; + for (u8 i = 0; i < 4; ++i) { + file = OpenFileOrDirectoryConcat( + dir, GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0)); + if (file != nullptr) + return file; + } + return file; +} + +boost::optional 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); + if (meta.find(title_id) == meta.end()) + return boost::none; + + const auto& cnmt = meta.at(title_id); + + const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(), + [type](const ContentRecord& rec) { return rec.type == type; }); + if (iter == cnmt.GetContentRecords().end()) + return boost::none; + + return boost::make_optional(iter->nca_id); +} + +void RegisteredCache::AccumulateFiles(std::vector& ids) const { + for (const auto& d2_dir : dir->GetSubdirectories()) { + if (FollowsNcaIdFormat(d2_dir->GetName())) { + ids.push_back(HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20))); + continue; + } + + if (!FollowsTwoDigitDirFormat(d2_dir->GetName())) + continue; + + for (const auto& nca_dir : d2_dir->GetSubdirectories()) { + if (!FollowsNcaIdFormat(nca_dir->GetName())) + continue; + + ids.push_back(HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20))); + } + + for (const auto& nca_file : d2_dir->GetFiles()) { + if (!FollowsNcaIdFormat(nca_file->GetName())) + continue; + + ids.push_back(HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20))); + } + } + + for (const auto& d2_file : dir->GetFiles()) { + if (FollowsNcaIdFormat(d2_file->GetName())) + ids.push_back(HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20))); + } +} + +void RegisteredCache::ProcessFiles(const std::vector& ids) { + for (const auto& id : ids) { + const auto file = GetFileAtID(id); + + if (file == nullptr) + continue; + const auto nca = std::make_shared(parser(file, id)); + if (nca->GetStatus() != Loader::ResultStatus::Success || + nca->GetType() != NCAContentType::Meta) + continue; + + const auto section0 = nca->GetSubdirectories()[0]; + + for (const auto& file : section0->GetFiles()) { + if (file->GetExtension() != "cnmt") + continue; + + meta.insert_or_assign(nca->GetTitleId(), CNMT(file)); + meta_id.insert_or_assign(nca->GetTitleId(), id); + break; + } + } +} + +void RegisteredCache::AccumulateYuzuMeta() { + const auto dir = this->dir->GetSubdirectory("yuzu_meta"); + if (dir == nullptr) + return; + + for (const auto& file : dir->GetFiles()) { + if (file->GetExtension() != "cnmt") + continue; + + CNMT cnmt(file); + yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt)); + } +} + +void RegisteredCache::Refresh() { + if (dir == nullptr) + return; + std::vector ids; + AccumulateFiles(ids); + ProcessFiles(ids); + AccumulateYuzuMeta(); +} + +RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function) + : dir(std::move(dir_)), parser(std::move(parsing_function)) { + Refresh(); +} + +bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const { + return GetEntryRaw(title_id, type) != nullptr; +} + +bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const { + return GetEntryRaw(entry) != nullptr; +} + +VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { + const auto id = GetNcaIDFromMetadata(title_id, type); + if (id == boost::none) + return nullptr; + + return parser(GetFileAtID(id.get()), id.get()); +} + +VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const { + return GetEntryRaw(entry.title_id, entry.type); +} + +std::shared_ptr RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const { + const auto raw = GetEntryRaw(title_id, type); + if (raw == nullptr) + return nullptr; + return std::make_shared(raw); +} + +std::shared_ptr RegisteredCache::GetEntry(RegisteredCacheEntry entry) const { + return GetEntry(entry.title_id, entry.type); +} + +template +void RegisteredCache::IterateAllMetadata( + std::vector& out, std::function proc, + std::function filter) const { + for (const auto& kv : meta) { + const auto& cnmt = kv.second; + if (filter(cnmt, EMPTY_META_CONTENT_RECORD)) + out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD)); + for (const auto& rec : cnmt.GetContentRecords()) { + if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) { + out.push_back(proc(cnmt, rec)); + } + } + } + for (const auto& kv : yuzu_meta) { + const auto& cnmt = kv.second; + for (const auto& rec : cnmt.GetContentRecords()) { + if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) { + out.push_back(proc(cnmt, rec)); + } + } + } +} + +std::vector RegisteredCache::ListEntries() const { + std::vector out; + IterateAllMetadata( + out, + [](const CNMT& c, const ContentRecord& r) { + return RegisteredCacheEntry{c.GetTitleID(), r.type}; + }, + [](const CNMT& c, const ContentRecord& r) { return true; }); + return out; +} + +std::vector RegisteredCache::ListEntriesFilter( + boost::optional title_type, boost::optional record_type, + boost::optional title_id) const { + std::vector out; + IterateAllMetadata( + out, + [](const CNMT& c, const ContentRecord& r) { + return RegisteredCacheEntry{c.GetTitleID(), r.type}; + }, + [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) { + if (title_type != boost::none && title_type.get() != c.GetType()) + return false; + if (record_type != boost::none && record_type.get() != r.type) + return false; + if (title_id != boost::none && title_id.get() != c.GetTitleID()) + return false; + return true; + }); + return out; +} + +static std::shared_ptr GetNCAFromXCIForID(std::shared_ptr xci, const NcaID& id) { + const auto filename = fmt::format("{}.nca", HexArrayToString(id, false)); + const auto iter = + std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(), + [&filename](std::shared_ptr nca) { return nca->GetName() == filename; }); + return iter == xci->GetNCAs().end() ? nullptr : *iter; +} + +bool RegisteredCache::InstallEntry(std::shared_ptr xci) { + const auto& ncas = xci->GetNCAs(); + const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr nca) { + return nca->GetType() == NCAContentType::Meta; + }); + + if (meta_iter == ncas.end()) { + LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and " + "is therefore malformed. Double check your encryption keys."); + return false; + } + + // Install Metadata File + const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); + const auto meta_id = HexStringToArray<16>(meta_id_raw); + if (!RawInstallNCA(*meta_iter, meta_id)) + return false; + + // Install all the other NCAs + const auto section0 = (*meta_iter)->GetSubdirectories()[0]; + const auto cnmt_file = section0->GetFiles()[0]; + const CNMT cnmt(cnmt_file); + for (const auto& record : cnmt.GetContentRecords()) { + const auto nca = GetNCAFromXCIForID(xci, record.nca_id); + if (nca == nullptr || !RawInstallNCA(nca, record.nca_id)) + return false; + } + + Refresh(); + return true; +} + +bool RegisteredCache::InstallEntry(std::shared_ptr nca, TitleType type) { + CNMTHeader header{ + nca->GetTitleId(), ///< Title ID + 0, ///< Ignore/Default title version + type, ///< Type + {}, ///< Padding + 0x10, ///< Default table offset + 1, ///< 1 Content Entry + 0, ///< No Meta Entries + {}, ///< Padding + }; + OptionalHeader opt_header{0, 0}; + ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}}; + const auto& data = nca->GetBaseFile()->ReadBytes(0x100000); + mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); + memcpy(&c_rec.nca_id, &c_rec.hash, 16); + const CNMT new_cnmt(header, opt_header, {c_rec}, {}); + return RawInstallYuzuMeta(new_cnmt) && RawInstallNCA(nca, c_rec.nca_id); +} + +bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, boost::optional override_id) { + const auto in = nca->GetBaseFile(); + Core::Crypto::SHA256Hash hash{}; + + // Calculate NcaID + // NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the + // game is massive), we're going to cheat and only hash the first MB of the NCA. + // Also, for XCIs the NcaID matters, so if the override id isn't none, use that. + NcaID id{}; + if (override_id == boost::none) { + const auto& data = in->ReadBytes(0x100000); + mbedtls_sha256(data.data(), data.size(), hash.data(), 0); + memcpy(id.data(), hash.data(), 16); + } else { + id = override_id.get(); + } + + std::string path = GetRelativePathFromNcaID(id, false, true); + + if (GetFileAtID(id) != nullptr) { + LOG_WARNING(Loader, "OW Attempt"); + return false; + } + + auto out = dir->CreateFileRelative(path); + if (out == nullptr) + return false; + return VfsRawCopy(in, out); +} + +bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { + const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta"); + const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID()); + if (dir->GetFile(filename) == nullptr) { + auto out = dir->CreateFile(filename); + const auto buffer = cnmt.Serialize(); + out->Resize(buffer.size()); + out->WriteBytes(buffer); + } else { + auto out = dir->GetFile(filename); + CNMT old_cnmt(out); + // Returns true on change + if (old_cnmt.UnionRecords(cnmt)) { + out->Resize(0); + const auto buffer = old_cnmt.Serialize(); + out->Resize(buffer.size()); + out->WriteBytes(buffer); + } + } + Refresh(); + return std::find_if(yuzu_meta.begin(), yuzu_meta.end(), + [&cnmt](const std::pair& kv) { + return kv.second.GetType() == cnmt.GetType() && + kv.second.GetTitleID() == cnmt.GetTitleID(); + }) != yuzu_meta.end(); +} +} // namespace FileSys -- cgit v1.2.3 From bfb945c2431ca1ccf1c5c43d4e3c7eaedf0bed31 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 9 Aug 2018 21:33:13 -0400 Subject: qt: Add 'Install to NAND' option to menu Prompts for title type on NCA files. --- src/core/file_sys/registered_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 5440cdefb..766fef254 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -427,7 +427,7 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { } Refresh(); return std::find_if(yuzu_meta.begin(), yuzu_meta.end(), - [&cnmt](const std::pair& kv) { + [&cnmt](const std::pair& kv) { return kv.second.GetType() == cnmt.GetType() && kv.second.GetTitleID() == cnmt.GetTitleID(); }) != yuzu_meta.end(); -- cgit v1.2.3 From 167bfddafadb843236c0fa683cf97eaffaa5ea1a Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 9 Aug 2018 23:10:32 -0400 Subject: file_sys: Comply to style guidelines --- src/core/file_sys/registered_cache.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 766fef254..3e7706171 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -23,13 +23,13 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) } static bool FollowsTwoDigitDirFormat(std::string_view name) { - const static std::regex two_digit_regex( + static const std::regex two_digit_regex( "000000[0123456789abcdefABCDEF][0123456789abcdefABCDEF]"); return std::regex_match(name.begin(), name.end(), two_digit_regex); } static bool FollowsNcaIdFormat(std::string_view name) { - const static std::regex nca_id_regex("[0123456789abcdefABCDEF]+.nca"); + static const std::regex nca_id_regex("[0123456789abcdefABCDEF]+.nca"); return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); } @@ -56,7 +56,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { "" ///< Currently unknown 'DeltaTitle' }; - size_t index = static_cast(type); + auto index = static_cast(type); if (index >= 0x80) index -= 0x80; return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); @@ -90,15 +90,15 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, VirtualFile file = nullptr; const auto files = nca_dir->GetFiles(); - if (files.size() == 1 && files[0]->GetName() == "00") + if (files.size() == 1 && files[0]->GetName() == "00") { file = files[0]; - else { + } else { std::vector concat; for (u8 i = 0; i < 0x10; ++i) { auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); - if (next != nullptr) + if (next != nullptr) { concat.push_back(std::move(next)); - else { + } else { next = nca_dir->GetFile(fmt::format("{:02x}", i)); if (next != nullptr) concat.push_back(std::move(next)); @@ -146,7 +146,8 @@ boost::optional RegisteredCache::GetNcaIDFromMetadata(u64 title_id, return boost::make_optional(iter->nca_id); } -void RegisteredCache::AccumulateFiles(std::vector& ids) const { +std::vector RegisteredCache::AccumulateFiles() const { + std::vector ids; for (const auto& d2_dir : dir->GetSubdirectories()) { if (FollowsNcaIdFormat(d2_dir->GetName())) { ids.push_back(HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20))); @@ -175,6 +176,7 @@ void RegisteredCache::AccumulateFiles(std::vector& ids) const { if (FollowsNcaIdFormat(d2_file->GetName())) ids.push_back(HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20))); } + return ids; } void RegisteredCache::ProcessFiles(const std::vector& ids) { @@ -185,8 +187,9 @@ void RegisteredCache::ProcessFiles(const std::vector& ids) { continue; const auto nca = std::make_shared(parser(file, id)); if (nca->GetStatus() != Loader::ResultStatus::Success || - nca->GetType() != NCAContentType::Meta) + nca->GetType() != NCAContentType::Meta) { continue; + } const auto section0 = nca->GetSubdirectories()[0]; @@ -218,8 +221,7 @@ void RegisteredCache::AccumulateYuzuMeta() { void RegisteredCache::Refresh() { if (dir == nullptr) return; - std::vector ids; - AccumulateFiles(ids); + const auto ids = AccumulateFiles(); ProcessFiles(ids); AccumulateYuzuMeta(); } -- cgit v1.2.3 From e5504a060d4af12682abbdf674834397d566502a Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Fri, 10 Aug 2018 11:10:44 -0400 Subject: registered_cache: Fix missing reading from yuzu_meta --- src/core/file_sys/registered_cache.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 3e7706171..665126c1c 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -129,14 +129,12 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { return file; } -boost::optional 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); - if (meta.find(title_id) == meta.end()) +static boost::optional CheckMapForContentRecord( + const boost::container::flat_map& map, u64 title_id, ContentRecordType type) { + if (map.find(title_id) == map.end()) return boost::none; - const auto& cnmt = meta.at(title_id); + 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; }); @@ -146,6 +144,17 @@ boost::optional RegisteredCache::GetNcaIDFromMetadata(u64 title_id, return boost::make_optional(iter->nca_id); } +boost::optional RegisteredCache::GetNcaIDFromMetadata(u64 title_id, + ContentRecordType type) const { + if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end()) + return meta_id.at(title_id); + + const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type); + if (res1 != boost::none) + return res1; + return CheckMapForContentRecord(meta, title_id, type); +} + std::vector RegisteredCache::AccumulateFiles() const { std::vector ids; for (const auto& d2_dir : dir->GetSubdirectories()) { @@ -398,7 +407,7 @@ bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, boost::optional Date: Fri, 10 Aug 2018 15:06:37 -0400 Subject: registration: Take RawCopy function as parameter Instead of defaulting to VfsRawCopy --- src/core/file_sys/registered_cache.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 665126c1c..aaadb7463 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -335,7 +335,7 @@ static std::shared_ptr GetNCAFromXCIForID(std::shared_ptr xci, const N return iter == xci->GetNCAs().end() ? nullptr : *iter; } -bool RegisteredCache::InstallEntry(std::shared_ptr xci) { +bool RegisteredCache::InstallEntry(std::shared_ptr xci, const VfsCopyFunction& copy) { const auto& ncas = xci->GetNCAs(); const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr nca) { return nca->GetType() == NCAContentType::Meta; @@ -350,7 +350,7 @@ bool RegisteredCache::InstallEntry(std::shared_ptr xci) { // Install Metadata File const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); const auto meta_id = HexStringToArray<16>(meta_id_raw); - if (!RawInstallNCA(*meta_iter, meta_id)) + if (!RawInstallNCA(*meta_iter, copy, meta_id)) return false; // Install all the other NCAs @@ -359,7 +359,7 @@ bool RegisteredCache::InstallEntry(std::shared_ptr xci) { const CNMT cnmt(cnmt_file); for (const auto& record : cnmt.GetContentRecords()) { const auto nca = GetNCAFromXCIForID(xci, record.nca_id); - if (nca == nullptr || !RawInstallNCA(nca, record.nca_id)) + if (nca == nullptr || !RawInstallNCA(nca, copy, record.nca_id)) return false; } @@ -367,7 +367,8 @@ bool RegisteredCache::InstallEntry(std::shared_ptr xci) { return true; } -bool RegisteredCache::InstallEntry(std::shared_ptr nca, TitleType type) { +bool RegisteredCache::InstallEntry(std::shared_ptr nca, TitleType type, + const VfsCopyFunction& copy) { CNMTHeader header{ nca->GetTitleId(), ///< Title ID 0, ///< Ignore/Default title version @@ -384,10 +385,11 @@ bool RegisteredCache::InstallEntry(std::shared_ptr nca, TitleType type) { mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); memcpy(&c_rec.nca_id, &c_rec.hash, 16); const CNMT new_cnmt(header, opt_header, {c_rec}, {}); - return RawInstallYuzuMeta(new_cnmt) && RawInstallNCA(nca, c_rec.nca_id); + return RawInstallYuzuMeta(new_cnmt) && RawInstallNCA(nca, copy, c_rec.nca_id); } -bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, boost::optional override_id) { +bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, const VfsCopyFunction& copy, + boost::optional override_id) { const auto in = nca->GetBaseFile(); Core::Crypto::SHA256Hash hash{}; @@ -414,7 +416,7 @@ bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, boost::optionalCreateFileRelative(path); if (out == nullptr) return false; - return VfsRawCopy(in, out); + return copy(in, out); } bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { -- cgit v1.2.3 From 893447b6b0f5068f3cc2111b5f21c3cff68002e2 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 11 Aug 2018 15:39:09 -0400 Subject: registration: Update documentation and style --- src/core/file_sys/registered_cache.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index aaadb7463..20fec2391 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -23,13 +23,14 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) } static bool FollowsTwoDigitDirFormat(std::string_view name) { - static const std::regex two_digit_regex( - "000000[0123456789abcdefABCDEF][0123456789abcdefABCDEF]"); + static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript | + std::regex_constants::icase); return std::regex_match(name.begin(), name.end(), two_digit_regex); } static bool FollowsNcaIdFormat(std::string_view name) { - static const std::regex nca_id_regex("[0123456789abcdefABCDEF]+.nca"); + static const std::regex nca_id_regex("[0-9A-F]{32}.nca", std::regex_constants::ECMAScript | + std::regex_constants::icase); return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); } @@ -57,8 +58,9 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { }; auto index = static_cast(type); - if (index >= 0x80) - index -= 0x80; + // If the index is after the jump in TitleType, subtract it out. + if (index >= static_cast(TitleType::Application)) + index -= static_cast(TitleType::Application); return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); } @@ -120,9 +122,15 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { VirtualFile file; + // Try all four modes of file storage: + // (bit 1 = uppercase/lower, bit 0 = within a two-digit dir) + // 00: /000000**/{:032X}.nca + // 01: /{:032X}.nca + // 10: /000000**/{:032x}.nca + // 11: /{:032x}.nca for (u8 i = 0; i < 4; ++i) { - file = OpenFileOrDirectoryConcat( - dir, GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0)); + const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0); + file = OpenFileOrDirectoryConcat(dir, path); if (file != nullptr) return file; } @@ -420,6 +428,7 @@ bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, const VfsCopyFunct } bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { + // Reasoning behind this method can be found in the comment for InstallEntry, NCA overload. const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta"); const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID()); if (dir->GetFile(filename) == nullptr) { -- cgit v1.2.3 From 6b76b774007020befdaa8d7475a9a4edd6d0a0a4 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 11 Aug 2018 23:01:38 -0400 Subject: registration: Add support for force overwrite of installed --- src/core/file_sys/registered_cache.cpp | 50 ++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 20fec2391..e916d5610 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -60,7 +60,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { auto index = static_cast(type); // If the index is after the jump in TitleType, subtract it out. if (index >= static_cast(TitleType::Application)) - index -= static_cast(TitleType::Application); + index -= 0x7B; return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); } @@ -343,7 +343,8 @@ static std::shared_ptr GetNCAFromXCIForID(std::shared_ptr xci, const N return iter == xci->GetNCAs().end() ? nullptr : *iter; } -bool RegisteredCache::InstallEntry(std::shared_ptr xci, const VfsCopyFunction& copy) { +InstallResult RegisteredCache::InstallEntry(std::shared_ptr xci, bool overwrite_if_exists, + const VfsCopyFunction& copy) { const auto& ncas = xci->GetNCAs(); const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr nca) { return nca->GetType() == NCAContentType::Meta; @@ -352,14 +353,16 @@ bool RegisteredCache::InstallEntry(std::shared_ptr xci, const VfsCopyFuncti if (meta_iter == ncas.end()) { LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and " "is therefore malformed. Double check your encryption keys."); - return false; + return InstallResult::ErrorMetaFailed; } // Install Metadata File const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); const auto meta_id = HexStringToArray<16>(meta_id_raw); - if (!RawInstallNCA(*meta_iter, copy, meta_id)) - return false; + + const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id); + if (res != InstallResult::Success) + return res; // Install all the other NCAs const auto section0 = (*meta_iter)->GetSubdirectories()[0]; @@ -367,16 +370,19 @@ bool RegisteredCache::InstallEntry(std::shared_ptr xci, const VfsCopyFuncti const CNMT cnmt(cnmt_file); for (const auto& record : cnmt.GetContentRecords()) { const auto nca = GetNCAFromXCIForID(xci, record.nca_id); - if (nca == nullptr || !RawInstallNCA(nca, copy, record.nca_id)) - return false; + if (nca == nullptr) + return InstallResult::ErrorCopyFailed; + const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); + if (res2 != InstallResult::Success) + return res2; } Refresh(); - return true; + return InstallResult::Success; } -bool RegisteredCache::InstallEntry(std::shared_ptr nca, TitleType type, - const VfsCopyFunction& copy) { +InstallResult RegisteredCache::InstallEntry(std::shared_ptr nca, TitleType type, + bool overwrite_if_exists, const VfsCopyFunction& copy) { CNMTHeader header{ nca->GetTitleId(), ///< Title ID 0, ///< Ignore/Default title version @@ -393,11 +399,14 @@ bool RegisteredCache::InstallEntry(std::shared_ptr nca, TitleType type, mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); memcpy(&c_rec.nca_id, &c_rec.hash, 16); const CNMT new_cnmt(header, opt_header, {c_rec}, {}); - return RawInstallYuzuMeta(new_cnmt) && RawInstallNCA(nca, copy, c_rec.nca_id); + if (!RawInstallYuzuMeta(new_cnmt)) + return InstallResult::ErrorMetaFailed; + return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); } -bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, const VfsCopyFunction& copy, - boost::optional override_id) { +InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr nca, const VfsCopyFunction& copy, + bool overwrite_if_exists, + boost::optional override_id) { const auto in = nca->GetBaseFile(); Core::Crypto::SHA256Hash hash{}; @@ -416,15 +425,22 @@ bool RegisteredCache::RawInstallNCA(std::shared_ptr nca, const VfsCopyFunct std::string path = GetRelativePathFromNcaID(id, false, true); - if (GetFileAtID(id) != nullptr) { + if (GetFileAtID(id) != nullptr && !overwrite_if_exists) { LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); - return false; + return InstallResult::ErrorAlreadyExists; + } + + if (GetFileAtID(id) != nullptr) { + LOG_WARNING(Loader, "Overwriting existing NCA..."); + VirtualDir c_dir; + { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); } + c_dir->DeleteFile(FileUtil::GetFilename(path)); } auto out = dir->CreateFileRelative(path); if (out == nullptr) - return false; - return copy(in, out); + return InstallResult::ErrorCopyFailed; + return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; } bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { -- cgit v1.2.3 From 35e4a47be0c4ef25f860d51851d2c02c02dff53d Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sun, 12 Aug 2018 15:55:44 -0400 Subject: registration: Various style and documentation improvements Fix logic in RealVfsFilesystem Create methods Remove magic numbers Fix regex errors --- src/core/file_sys/registered_cache.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src/core/file_sys/registered_cache.cpp') diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index e916d5610..a5e935f64 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -29,8 +29,8 @@ static bool FollowsTwoDigitDirFormat(std::string_view name) { } static bool FollowsNcaIdFormat(std::string_view name) { - static const std::regex nca_id_regex("[0-9A-F]{32}.nca", std::regex_constants::ECMAScript | - std::regex_constants::icase); + static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript | + std::regex_constants::icase); return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); } @@ -59,8 +59,10 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { auto index = static_cast(type); // If the index is after the jump in TitleType, subtract it out. - if (index >= static_cast(TitleType::Application)) - index -= 0x7B; + if (index >= static_cast(TitleType::Application)) { + index -= static_cast(TitleType::Application) - + static_cast(TitleType::FirmwarePackageB); + } return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); } @@ -96,7 +98,8 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, file = files[0]; } else { std::vector concat; - for (u8 i = 0; i < 0x10; ++i) { + // Since the files are a two-digit hex number, max is FF. + for (size_t i = 0; i < 0x100; ++i) { auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); if (next != nullptr) { concat.push_back(std::move(next)); -- cgit v1.2.3