Explorar o código

patch_manager: Add PatchNSO function
While PatchExeFS operated on the entire directory, this function operates on the uncompressed NSO. Avoids copying decompression code to PatchManager.

Zach Hilman %!s(int64=7) %!d(string=hai) anos
pai
achega
42fb4e82d3

+ 2 - 0
src/core/CMakeLists.txt

@@ -34,6 +34,8 @@ add_library(core STATIC
     file_sys/errors.h
     file_sys/errors.h
     file_sys/fsmitm_romfsbuild.cpp
     file_sys/fsmitm_romfsbuild.cpp
     file_sys/fsmitm_romfsbuild.h
     file_sys/fsmitm_romfsbuild.h
+    file_sys/ips_layer.cpp
+    file_sys/ips_layer.h
     file_sys/mode.h
     file_sys/mode.h
     file_sys/nca_metadata.cpp
     file_sys/nca_metadata.cpp
     file_sys/nca_metadata.h
     file_sys/nca_metadata.h

+ 94 - 0
src/core/file_sys/patch_manager.cpp

@@ -6,13 +6,16 @@
 #include <array>
 #include <array>
 #include <cstddef>
 #include <cstddef>
 
 
+#include "common/hex_util.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/control_metadata.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/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/romfs.h"
 #include "core/file_sys/romfs.h"
 #include "core/file_sys/vfs_layered.h"
 #include "core/file_sys/vfs_layered.h"
+#include "core/file_sys/vfs_vector.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 
 
@@ -21,6 +24,14 @@ namespace FileSys {
 constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
 constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
 constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
 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::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
     std::array<u8, sizeof(u32)> bytes{};
     std::array<u8, sizeof(u32)> bytes{};
     bytes[0] = version % SINGLE_BYTE_MODULUS;
     bytes[0] = version % SINGLE_BYTE_MODULUS;
@@ -61,6 +72,89 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
     return exefs;
     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) {
 static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
     const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
     const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
     if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
     if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {

+ 8 - 0
src/core/file_sys/patch_manager.h

@@ -34,6 +34,14 @@ public:
     // - Game Updates
     // - Game Updates
     VirtualDir PatchExeFS(VirtualDir exefs) const;
     VirtualDir PatchExeFS(VirtualDir exefs) const;
 
 
+    // Currently tracked NSO patches:
+    // - IPS
+    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.
+    // Used to prevent expensive copies in NSO loader.
+    bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
+
     // Currently tracked RomFS patches:
     // Currently tracked RomFS patches:
     // - Game Updates
     // - Game Updates
     // - LayeredFS
     // - LayeredFS