Browse Source
Merge pull request #1179 from DarkLordZach/bktr
Merge pull request #1179 from DarkLordZach/bktr
file_sys: Add support for BKTR format (Game Updates)pull/15/merge
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1132 additions and 101 deletions
-
4src/core/CMakeLists.txt
-
14src/core/crypto/aes_util.cpp
-
2src/core/crypto/ctr_encryption_layer.cpp
-
6src/core/file_sys/card_image.cpp
-
179src/core/file_sys/content_archive.cpp
-
11src/core/file_sys/content_archive.h
-
206src/core/file_sys/nca_patch.cpp
-
147src/core/file_sys/nca_patch.h
-
153src/core/file_sys/patch_manager.cpp
-
62src/core/file_sys/patch_manager.h
-
115src/core/file_sys/registered_cache.cpp
-
40src/core/file_sys/registered_cache.h
-
13src/core/file_sys/romfs_factory.cpp
-
2src/core/file_sys/romfs_factory.h
-
5src/core/file_sys/submission_package.cpp
-
7src/core/hle/service/filesystem/filesystem.cpp
-
3src/core/hle/service/filesystem/filesystem.h
-
42src/core/loader/deconstructed_rom_directory.cpp
-
8src/core/loader/deconstructed_rom_directory.h
-
13src/core/loader/loader.cpp
-
29src/core/loader/loader.h
-
8src/core/loader/nca.cpp
-
1src/core/loader/nca.h
-
5src/core/loader/nro.cpp
-
1src/core/loader/nro.h
-
20src/core/loader/nsp.cpp
-
18src/core/loader/xci.cpp
-
24src/core/telemetry_session.cpp
-
72src/yuzu/game_list.cpp
-
1src/yuzu/game_list.h
-
2src/yuzu/game_list_p.h
-
20src/yuzu/main.cpp
@ -0,0 +1,206 @@ |
|||||
|
// Copyright 2018 yuzu emulator team
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/assert.h"
|
||||
|
#include "core/crypto/aes_util.h"
|
||||
|
#include "core/file_sys/nca_patch.h"
|
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, |
||||
|
std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, |
||||
|
std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, |
||||
|
Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, |
||||
|
std::array<u8, 8> section_ctr_) |
||||
|
: base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), |
||||
|
relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), |
||||
|
subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), |
||||
|
encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), |
||||
|
section_ctr(section_ctr_) { |
||||
|
for (size_t i = 0; i < relocation.number_buckets - 1; ++i) { |
||||
|
relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); |
||||
|
} |
||||
|
|
||||
|
for (size_t i = 0; i < subsection.number_buckets - 1; ++i) { |
||||
|
subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, |
||||
|
{0}, |
||||
|
subsection_buckets[i + 1].entries[0].ctr}); |
||||
|
} |
||||
|
|
||||
|
relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); |
||||
|
} |
||||
|
|
||||
|
BKTR::~BKTR() = default; |
||||
|
|
||||
|
size_t BKTR::Read(u8* data, size_t length, size_t offset) const { |
||||
|
// Read out of bounds.
|
||||
|
if (offset >= relocation.size) |
||||
|
return 0; |
||||
|
const auto relocation = GetRelocationEntry(offset); |
||||
|
const auto section_offset = offset - relocation.address_patch + relocation.address_source; |
||||
|
const auto bktr_read = relocation.from_patch; |
||||
|
|
||||
|
const auto next_relocation = GetNextRelocationEntry(offset); |
||||
|
|
||||
|
if (offset + length > next_relocation.address_patch) { |
||||
|
const u64 partition = next_relocation.address_patch - offset; |
||||
|
return Read(data, partition, offset) + |
||||
|
Read(data + partition, length - partition, offset + partition); |
||||
|
} |
||||
|
|
||||
|
if (!bktr_read) { |
||||
|
ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative."); |
||||
|
return base_romfs->Read(data, length, section_offset - ivfc_offset); |
||||
|
} |
||||
|
|
||||
|
if (!encrypted) { |
||||
|
return bktr_romfs->Read(data, length, section_offset); |
||||
|
} |
||||
|
|
||||
|
const auto subsection = GetSubsectionEntry(section_offset); |
||||
|
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); |
||||
|
|
||||
|
// Calculate AES IV
|
||||
|
std::vector<u8> iv(16); |
||||
|
auto subsection_ctr = subsection.ctr; |
||||
|
auto offset_iv = section_offset + base_offset; |
||||
|
for (size_t i = 0; i < section_ctr.size(); ++i) |
||||
|
iv[i] = section_ctr[0x8 - i - 1]; |
||||
|
offset_iv >>= 4; |
||||
|
for (size_t i = 0; i < sizeof(u64); ++i) { |
||||
|
iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); |
||||
|
offset_iv >>= 8; |
||||
|
} |
||||
|
for (size_t i = 0; i < sizeof(u32); ++i) { |
||||
|
iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); |
||||
|
subsection_ctr >>= 8; |
||||
|
} |
||||
|
cipher.SetIV(iv); |
||||
|
|
||||
|
const auto next_subsection = GetNextSubsectionEntry(section_offset); |
||||
|
|
||||
|
if (section_offset + length > next_subsection.address_patch) { |
||||
|
const u64 partition = next_subsection.address_patch - section_offset; |
||||
|
return Read(data, partition, offset) + |
||||
|
Read(data + partition, length - partition, offset + partition); |
||||
|
} |
||||
|
|
||||
|
const auto block_offset = section_offset & 0xF; |
||||
|
if (block_offset != 0) { |
||||
|
auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); |
||||
|
cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt); |
||||
|
if (length + block_offset < 0x10) { |
||||
|
std::memcpy(data, block.data() + block_offset, std::min(length, block.size())); |
||||
|
return std::min(length, block.size()); |
||||
|
} |
||||
|
|
||||
|
const auto read = 0x10 - block_offset; |
||||
|
std::memcpy(data, block.data() + block_offset, read); |
||||
|
return read + Read(data + read, length - read, offset + read); |
||||
|
} |
||||
|
|
||||
|
const auto raw_read = bktr_romfs->Read(data, length, section_offset); |
||||
|
cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); |
||||
|
return raw_read; |
||||
|
} |
||||
|
|
||||
|
template <bool Subsection, typename BlockType, typename BucketType> |
||||
|
std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block, |
||||
|
BucketType buckets) const { |
||||
|
if constexpr (Subsection) { |
||||
|
const auto last_bucket = buckets[block.number_buckets - 1]; |
||||
|
if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) |
||||
|
return {block.number_buckets - 1, last_bucket.number_entries}; |
||||
|
} else { |
||||
|
ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); |
||||
|
} |
||||
|
|
||||
|
size_t bucket_id = std::count_if(block.base_offsets.begin() + 1, |
||||
|
block.base_offsets.begin() + block.number_buckets, |
||||
|
[&offset](u64 base_offset) { return base_offset <= offset; }); |
||||
|
|
||||
|
const auto bucket = buckets[bucket_id]; |
||||
|
|
||||
|
if (bucket.number_entries == 1) |
||||
|
return {bucket_id, 0}; |
||||
|
|
||||
|
size_t low = 0; |
||||
|
size_t mid = 0; |
||||
|
size_t high = bucket.number_entries - 1; |
||||
|
while (low <= high) { |
||||
|
mid = (low + high) / 2; |
||||
|
if (bucket.entries[mid].address_patch > offset) { |
||||
|
high = mid - 1; |
||||
|
} else { |
||||
|
if (mid == bucket.number_entries - 1 || |
||||
|
bucket.entries[mid + 1].address_patch > offset) { |
||||
|
return {bucket_id, mid}; |
||||
|
} |
||||
|
|
||||
|
low = mid + 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
UNREACHABLE_MSG("Offset could not be found in BKTR block."); |
||||
|
} |
||||
|
|
||||
|
RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { |
||||
|
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); |
||||
|
return relocation_buckets[res.first].entries[res.second]; |
||||
|
} |
||||
|
|
||||
|
RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { |
||||
|
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); |
||||
|
const auto bucket = relocation_buckets[res.first]; |
||||
|
if (res.second + 1 < bucket.entries.size()) |
||||
|
return bucket.entries[res.second + 1]; |
||||
|
return relocation_buckets[res.first + 1].entries[0]; |
||||
|
} |
||||
|
|
||||
|
SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { |
||||
|
const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); |
||||
|
return subsection_buckets[res.first].entries[res.second]; |
||||
|
} |
||||
|
|
||||
|
SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { |
||||
|
const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); |
||||
|
const auto bucket = subsection_buckets[res.first]; |
||||
|
if (res.second + 1 < bucket.entries.size()) |
||||
|
return bucket.entries[res.second + 1]; |
||||
|
return subsection_buckets[res.first + 1].entries[0]; |
||||
|
} |
||||
|
|
||||
|
std::string BKTR::GetName() const { |
||||
|
return base_romfs->GetName(); |
||||
|
} |
||||
|
|
||||
|
size_t BKTR::GetSize() const { |
||||
|
return relocation.size; |
||||
|
} |
||||
|
|
||||
|
bool BKTR::Resize(size_t new_size) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const { |
||||
|
return base_romfs->GetContainingDirectory(); |
||||
|
} |
||||
|
|
||||
|
bool BKTR::IsWritable() const { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool BKTR::IsReadable() const { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
size_t BKTR::Write(const u8* data, size_t length, size_t offset) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
bool BKTR::Rename(std::string_view name) { |
||||
|
return base_romfs->Rename(name); |
||||
|
} |
||||
|
|
||||
|
} // namespace FileSys
|
||||
@ -0,0 +1,147 @@ |
|||||
|
// Copyright 2018 yuzu emulator team |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <vector> |
||||
|
#include <common/common_funcs.h> |
||||
|
#include "core/crypto/key_manager.h" |
||||
|
#include "core/file_sys/romfs.h" |
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
#pragma pack(push, 1) |
||||
|
struct RelocationEntry { |
||||
|
u64_le address_patch; |
||||
|
u64_le address_source; |
||||
|
u32 from_patch; |
||||
|
}; |
||||
|
#pragma pack(pop) |
||||
|
static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); |
||||
|
|
||||
|
struct RelocationBucketRaw { |
||||
|
INSERT_PADDING_BYTES(4); |
||||
|
u32_le number_entries; |
||||
|
u64_le end_offset; |
||||
|
std::array<RelocationEntry, 0x332> relocation_entries; |
||||
|
INSERT_PADDING_BYTES(8); |
||||
|
}; |
||||
|
static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); |
||||
|
|
||||
|
// Vector version of RelocationBucketRaw |
||||
|
struct RelocationBucket { |
||||
|
u32 number_entries; |
||||
|
u64 end_offset; |
||||
|
std::vector<RelocationEntry> entries; |
||||
|
}; |
||||
|
|
||||
|
struct RelocationBlock { |
||||
|
INSERT_PADDING_BYTES(4); |
||||
|
u32_le number_buckets; |
||||
|
u64_le size; |
||||
|
std::array<u64, 0x7FE> base_offsets; |
||||
|
}; |
||||
|
static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); |
||||
|
|
||||
|
struct SubsectionEntry { |
||||
|
u64_le address_patch; |
||||
|
INSERT_PADDING_BYTES(0x4); |
||||
|
u32_le ctr; |
||||
|
}; |
||||
|
static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); |
||||
|
|
||||
|
struct SubsectionBucketRaw { |
||||
|
INSERT_PADDING_BYTES(4); |
||||
|
u32_le number_entries; |
||||
|
u64_le end_offset; |
||||
|
std::array<SubsectionEntry, 0x3FF> subsection_entries; |
||||
|
}; |
||||
|
static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); |
||||
|
|
||||
|
// Vector version of SubsectionBucketRaw |
||||
|
struct SubsectionBucket { |
||||
|
u32 number_entries; |
||||
|
u64 end_offset; |
||||
|
std::vector<SubsectionEntry> entries; |
||||
|
}; |
||||
|
|
||||
|
struct SubsectionBlock { |
||||
|
INSERT_PADDING_BYTES(4); |
||||
|
u32_le number_buckets; |
||||
|
u64_le size; |
||||
|
std::array<u64, 0x7FE> base_offsets; |
||||
|
}; |
||||
|
static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); |
||||
|
|
||||
|
inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { |
||||
|
return {raw.number_entries, |
||||
|
raw.end_offset, |
||||
|
{raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; |
||||
|
} |
||||
|
|
||||
|
inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { |
||||
|
return {raw.number_entries, |
||||
|
raw.end_offset, |
||||
|
{raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; |
||||
|
} |
||||
|
|
||||
|
class BKTR : public VfsFile { |
||||
|
public: |
||||
|
BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, |
||||
|
std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, |
||||
|
std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, |
||||
|
Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); |
||||
|
~BKTR() override; |
||||
|
|
||||
|
size_t Read(u8* data, size_t length, size_t offset) const override; |
||||
|
|
||||
|
std::string GetName() const override; |
||||
|
|
||||
|
size_t GetSize() const override; |
||||
|
|
||||
|
bool Resize(size_t new_size) override; |
||||
|
|
||||
|
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; |
||||
|
|
||||
|
bool IsWritable() const override; |
||||
|
|
||||
|
bool IsReadable() const override; |
||||
|
|
||||
|
size_t Write(const u8* data, size_t length, size_t offset) override; |
||||
|
|
||||
|
bool Rename(std::string_view name) override; |
||||
|
|
||||
|
private: |
||||
|
template <bool Subsection, typename BlockType, typename BucketType> |
||||
|
std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block, |
||||
|
BucketType buckets) const; |
||||
|
|
||||
|
RelocationEntry GetRelocationEntry(u64 offset) const; |
||||
|
RelocationEntry GetNextRelocationEntry(u64 offset) const; |
||||
|
|
||||
|
SubsectionEntry GetSubsectionEntry(u64 offset) const; |
||||
|
SubsectionEntry GetNextSubsectionEntry(u64 offset) const; |
||||
|
|
||||
|
RelocationBlock relocation; |
||||
|
std::vector<RelocationBucket> relocation_buckets; |
||||
|
SubsectionBlock subsection; |
||||
|
std::vector<SubsectionBucket> subsection_buckets; |
||||
|
|
||||
|
// Should be the raw base romfs, decrypted. |
||||
|
VirtualFile base_romfs; |
||||
|
// Should be the raw BKTR romfs, (located at media_offset with size media_size). |
||||
|
VirtualFile bktr_romfs; |
||||
|
|
||||
|
bool encrypted; |
||||
|
Core::Crypto::Key128 key; |
||||
|
|
||||
|
// Base offset into NCA, used for IV calculation. |
||||
|
u64 base_offset; |
||||
|
// Distance between IVFC start and RomFS start, used for base reads |
||||
|
u64 ivfc_offset; |
||||
|
std::array<u8, 8> section_ctr; |
||||
|
}; |
||||
|
|
||||
|
} // namespace FileSys |
||||
@ -0,0 +1,153 @@ |
|||||
|
// Copyright 2018 yuzu emulator team
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "core/file_sys/content_archive.h"
|
||||
|
#include "core/file_sys/control_metadata.h"
|
||||
|
#include "core/file_sys/patch_manager.h"
|
||||
|
#include "core/file_sys/registered_cache.h"
|
||||
|
#include "core/file_sys/romfs.h"
|
||||
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
#include "core/loader/loader.h"
|
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
constexpr u64 SINGLE_BYTE_MODULUS = 0x100; |
||||
|
|
||||
|
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { |
||||
|
std::array<u8, sizeof(u32)> bytes{}; |
||||
|
bytes[0] = version % SINGLE_BYTE_MODULUS; |
||||
|
for (size_t i = 1; i < bytes.size(); ++i) { |
||||
|
version /= SINGLE_BYTE_MODULUS; |
||||
|
bytes[i] = version % SINGLE_BYTE_MODULUS; |
||||
|
} |
||||
|
|
||||
|
if (format == TitleVersionFormat::FourElements) |
||||
|
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); |
||||
|
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); |
||||
|
} |
||||
|
|
||||
|
constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ |
||||
|
"Update", |
||||
|
}; |
||||
|
|
||||
|
std::string FormatPatchTypeName(PatchType type) { |
||||
|
return PATCH_TYPE_NAMES.at(static_cast<size_t>(type)); |
||||
|
} |
||||
|
|
||||
|
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} |
||||
|
|
||||
|
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { |
||||
|
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); |
||||
|
|
||||
|
if (exefs == nullptr) |
||||
|
return exefs; |
||||
|
|
||||
|
const auto installed = Service::FileSystem::GetUnionContents(); |
||||
|
|
||||
|
// 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(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return exefs; |
||||
|
} |
||||
|
|
||||
|
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)); |
||||
|
|
||||
|
if (romfs == nullptr) |
||||
|
return romfs; |
||||
|
|
||||
|
const auto installed = Service::FileSystem::GetUnionContents(); |
||||
|
|
||||
|
// Game Updates
|
||||
|
const auto update_tid = GetUpdateTitleID(title_id); |
||||
|
const auto update = installed->GetEntryRaw(update_tid, type); |
||||
|
if (update != nullptr) { |
||||
|
const auto new_nca = std::make_shared<NCA>(update, romfs, 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))); |
||||
|
romfs = new_nca->GetRomFS(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return romfs; |
||||
|
} |
||||
|
|
||||
|
std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const { |
||||
|
std::map<PatchType, std::string> out; |
||||
|
const auto installed = Service::FileSystem::GetUnionContents(); |
||||
|
|
||||
|
const auto update_tid = GetUpdateTitleID(title_id); |
||||
|
PatchManager update{update_tid}; |
||||
|
auto [nacp, discard_icon_file] = update.GetControlMetadata(); |
||||
|
|
||||
|
if (nacp != nullptr) { |
||||
|
out[PatchType::Update] = nacp->GetVersionString(); |
||||
|
} 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) { |
||||
|
out[PatchType::Update] = ""; |
||||
|
} else { |
||||
|
out[PatchType::Update] = |
||||
|
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return out; |
||||
|
} |
||||
|
|
||||
|
std::pair<std::shared_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); |
||||
|
} |
||||
|
|
||||
|
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( |
||||
|
const std::shared_ptr<NCA>& nca) const { |
||||
|
const auto base_romfs = nca->GetRomFS(); |
||||
|
if (base_romfs == nullptr) |
||||
|
return {}; |
||||
|
|
||||
|
const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control); |
||||
|
if (romfs == nullptr) |
||||
|
return {}; |
||||
|
|
||||
|
const auto extracted = ExtractRomFS(romfs); |
||||
|
if (extracted == nullptr) |
||||
|
return {}; |
||||
|
|
||||
|
auto nacp_file = extracted->GetFile("control.nacp"); |
||||
|
if (nacp_file == nullptr) |
||||
|
nacp_file = extracted->GetFile("Control.nacp"); |
||||
|
|
||||
|
const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file); |
||||
|
|
||||
|
VirtualFile icon_file; |
||||
|
for (const auto& language : FileSys::LANGUAGE_NAMES) { |
||||
|
icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat"); |
||||
|
if (icon_file != nullptr) |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return {nacp, icon_file}; |
||||
|
} |
||||
|
} // namespace FileSys
|
||||
@ -0,0 +1,62 @@ |
|||||
|
// Copyright 2018 yuzu emulator team |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
#include <string> |
||||
|
#include "common/common_types.h" |
||||
|
#include "core/file_sys/nca_metadata.h" |
||||
|
#include "core/file_sys/vfs.h" |
||||
|
|
||||
|
namespace FileSys { |
||||
|
|
||||
|
class NCA; |
||||
|
class NACP; |
||||
|
|
||||
|
enum class TitleVersionFormat : u8 { |
||||
|
ThreeElements, ///< vX.Y.Z |
||||
|
FourElements, ///< vX.Y.Z.W |
||||
|
}; |
||||
|
|
||||
|
std::string FormatTitleVersion(u32 version, |
||||
|
TitleVersionFormat format = TitleVersionFormat::ThreeElements); |
||||
|
|
||||
|
enum class PatchType { |
||||
|
Update, |
||||
|
}; |
||||
|
|
||||
|
std::string FormatPatchTypeName(PatchType type); |
||||
|
|
||||
|
// A centralized class to manage patches to games. |
||||
|
class PatchManager { |
||||
|
public: |
||||
|
explicit PatchManager(u64 title_id); |
||||
|
|
||||
|
// Currently tracked ExeFS patches: |
||||
|
// - Game Updates |
||||
|
VirtualDir PatchExeFS(VirtualDir exefs) const; |
||||
|
|
||||
|
// Currently tracked RomFS patches: |
||||
|
// - Game Updates |
||||
|
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, |
||||
|
ContentRecordType type = ContentRecordType::Program) const; |
||||
|
|
||||
|
// Returns a vector of pairs between patch names and patch versions. |
||||
|
// i.e. Update v80 will return {Update, 80} |
||||
|
std::map<PatchType, std::string> GetPatchVersionNames() 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; |
||||
|
|
||||
|
// Version of GetControlMetadata that takes an arbitrary NCA |
||||
|
std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA( |
||||
|
const std::shared_ptr<NCA>& nca) const; |
||||
|
|
||||
|
private: |
||||
|
u64 title_id; |
||||
|
}; |
||||
|
|
||||
|
} // namespace FileSys |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue