|
|
|
@ -6,13 +6,16 @@
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
|
|
#include "common/hex_util.h"
|
|
|
|
|
#include "common/logging/log.h"
|
|
|
|
|
#include "core/file_sys/content_archive.h"
|
|
|
|
|
#include "core/file_sys/control_metadata.h"
|
|
|
|
|
#include "core/file_sys/ips_layer.h"
|
|
|
|
|
#include "core/file_sys/patch_manager.h"
|
|
|
|
|
#include "core/file_sys/registered_cache.h"
|
|
|
|
|
#include "core/file_sys/romfs.h"
|
|
|
|
|
#include "core/file_sys/vfs_layered.h"
|
|
|
|
|
#include "core/file_sys/vfs_vector.h"
|
|
|
|
|
#include "core/hle/service/filesystem/filesystem.h"
|
|
|
|
|
#include "core/loader/loader.h"
|
|
|
|
|
|
|
|
|
@ -21,6 +24,14 @@ namespace FileSys {
|
|
|
|
|
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
|
|
|
|
|
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
|
|
|
|
|
|
|
|
|
struct NSOBuildHeader {
|
|
|
|
|
u32_le magic;
|
|
|
|
|
INSERT_PADDING_BYTES(0x3C);
|
|
|
|
|
std::array<u8, 0x20> build_id;
|
|
|
|
|
INSERT_PADDING_BYTES(0xA0);
|
|
|
|
|
};
|
|
|
|
|
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
|
|
|
|
|
|
|
|
|
|
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
|
|
|
|
std::array<u8, sizeof(u32)> bytes{};
|
|
|
|
|
bytes[0] = version % SINGLE_BYTE_MODULUS;
|
|
|
|
@ -61,6 +72,89 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|
|
|
|
return exefs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
|
|
|
|
if (nso.size() < 0x100)
|
|
|
|
|
return nso;
|
|
|
|
|
|
|
|
|
|
NSOBuildHeader header{};
|
|
|
|
|
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
|
|
|
|
|
|
|
|
|
|
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
|
|
|
|
return nso;
|
|
|
|
|
|
|
|
|
|
const auto build_id_raw = Common::HexArrayToString(header.build_id);
|
|
|
|
|
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
|
|
|
|
|
|
|
|
|
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
|
|
|
|
|
|
|
|
|
|
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
|
|
|
|
auto patch_dirs = load_dir->GetSubdirectories();
|
|
|
|
|
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
|
|
|
|
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
|
|
|
|
|
|
|
|
|
std::vector<VirtualFile> ips;
|
|
|
|
|
ips.reserve(patch_dirs.size() - 1);
|
|
|
|
|
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_first_of('.'));
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (out.size() < 0x100)
|
|
|
|
|
return nso;
|
|
|
|
|
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
|
|
|
|
const auto build_id_raw = Common::HexArrayToString(build_id_);
|
|
|
|
|
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
|
|
|
|
|
|
|
|
|
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
|
|
|
|
|
|
|
|
|
|
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
|
|
|
|
auto patch_dirs = load_dir->GetSubdirectories();
|
|
|
|
|
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
|
|
|
|
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
|
|
|
|
|
|
|
|
|
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_first_of('.'));
|
|
|
|
|
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
|
|
|
|
|
|
|
|
|
|
if (build_id == this_build_id)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|