From ee49e1fcb6c89d0206ea65d5c0adc523e33baddb Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 22 Mar 2019 11:52:55 -0400 Subject: [PATCH 1/5] file_sys/patch_manager: Remove two magic values These correspond to the NSOBuildHeader. --- src/core/file_sys/patch_manager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 2b09e5d359..efc572c72b 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -163,8 +163,9 @@ std::vector PatchManager::CollectPatches(const std::vector PatchManager::PatchNSO(const std::vector& nso) const { - if (nso.size() < 0x100) + if (nso.size() < sizeof(NSOBuildHeader)) { return nso; + } NSOBuildHeader header; std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader)); @@ -213,8 +214,10 @@ std::vector PatchManager::PatchNSO(const std::vector& nso) const { } } - if (out.size() < 0x100) + if (out.size() < sizeof(NSOBuildHeader)) { return nso; + } + std::memcpy(out.data(), &header, sizeof(NSOBuildHeader)); return out; } From 90e27ea00355dfe6f189ad116bb5d1bb3e278517 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 22 Mar 2019 12:55:03 -0400 Subject: [PATCH 2/5] loader/nso: Fix definition of the NSO header struct The total struct itself is 0x100 (256) bytes in size, so we should be providing that amount of data. Without the data, this can result in omitted data from the final loaded NSO file. --- src/core/loader/nso.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 0eb9fd7f70..a521047929 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -34,20 +34,32 @@ struct NsoSegmentHeader { static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); struct NsoHeader { + using SHA256Hash = std::array; + + struct RODataRelativeExtent { + u32 data_offset; + u32 size; + }; + u32_le magic; u32_le version; - INSERT_PADDING_WORDS(1); - u8 flags; + u32 reserved; + u32_le flags; std::array segments; // Text, RoData, Data (in that order) std::array build_id; std::array segments_compressed_size; + std::array padding; + RODataRelativeExtent api_info_extent; + RODataRelativeExtent dynstr_extent; + RODataRelativeExtent dynsyn_extent; + std::array segment_hashes; bool IsSegmentCompressed(size_t segment_num) const { ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); return ((flags >> segment_num) & 1); } }; -static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); +static_assert(sizeof(NsoHeader) == 0x100, "NsoHeader has incorrect size."); static_assert(std::is_trivially_copyable_v, "NsoHeader isn't trivially copyable."); struct ModHeader { From 1cf90f45704373cd61d274d1e3c4dc6e5be87eaa Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 22 Mar 2019 13:04:41 -0400 Subject: [PATCH 3/5] file_sys/patch_manager: Deduplicate NSO header This source file was utilizing its own version of the NSO header. Instead of keeping this around, we can have the patch manager also use the version of the header that we have defined in loader/nso.h --- src/core/file_sys/patch_manager.cpp | 22 ++++------ src/core/loader/nso.cpp | 68 ++++++++--------------------- src/core/loader/nso.h | 39 +++++++++++++++++ 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index efc572c72b..fd21d3ad18 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -20,6 +20,7 @@ #include "core/file_sys/vfs_vector.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" +#include "core/loader/nso.h" #include "core/settings.h" namespace FileSys { @@ -32,14 +33,6 @@ constexpr std::array EXEFS_FILE_NAMES{ "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", }; -struct NSOBuildHeader { - u32_le magic; - INSERT_PADDING_BYTES(0x3C); - std::array build_id; - INSERT_PADDING_BYTES(0xA0); -}; -static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size."); - std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { std::array bytes{}; bytes[0] = version % SINGLE_BYTE_MODULUS; @@ -163,15 +156,16 @@ std::vector PatchManager::CollectPatches(const std::vector PatchManager::PatchNSO(const std::vector& nso) const { - if (nso.size() < sizeof(NSOBuildHeader)) { + if (nso.size() < sizeof(Loader::NSOHeader)) { return nso; } - NSOBuildHeader header; - std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader)); + Loader::NSOHeader header; + std::memcpy(&header, nso.data(), sizeof(header)); - if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) + 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); @@ -214,11 +208,11 @@ std::vector PatchManager::PatchNSO(const std::vector& nso) const { } } - if (out.size() < sizeof(NSOBuildHeader)) { + if (out.size() < sizeof(Loader::NSOHeader)) { return nso; } - std::memcpy(out.data(), &header, sizeof(NSOBuildHeader)); + std::memcpy(out.data(), &header, sizeof(header)); return out; } diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index a521047929..262eaeaeef 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -22,47 +22,7 @@ namespace Loader { -struct NsoSegmentHeader { - u32_le offset; - u32_le location; - u32_le size; - union { - u32_le alignment; - u32_le bss_size; - }; -}; -static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); - -struct NsoHeader { - using SHA256Hash = std::array; - - struct RODataRelativeExtent { - u32 data_offset; - u32 size; - }; - - u32_le magic; - u32_le version; - u32 reserved; - u32_le flags; - std::array segments; // Text, RoData, Data (in that order) - std::array build_id; - std::array segments_compressed_size; - std::array padding; - RODataRelativeExtent api_info_extent; - RODataRelativeExtent dynstr_extent; - RODataRelativeExtent dynsyn_extent; - std::array segment_hashes; - - bool IsSegmentCompressed(size_t segment_num) const { - ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); - return ((flags >> segment_num) & 1); - } -}; -static_assert(sizeof(NsoHeader) == 0x100, "NsoHeader has incorrect size."); -static_assert(std::is_trivially_copyable_v, "NsoHeader isn't trivially copyable."); - -struct ModHeader { +struct MODHeader { u32_le magic; u32_le dynamic_offset; u32_le bss_start_offset; @@ -71,7 +31,12 @@ struct ModHeader { u32_le eh_frame_hdr_end_offset; u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base }; -static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); +static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size."); + +bool NSOHeader::IsSegmentCompressed(size_t segment_num) const { + ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); + return ((flags >> segment_num) & 1) != 0; +} AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} @@ -89,7 +54,7 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { } static std::vector DecompressSegment(const std::vector& compressed_data, - const NsoSegmentHeader& header) { + const NSOSegmentHeader& header) { std::vector uncompressed_data(header.size); const int bytes_uncompressed = LZ4_decompress_safe(reinterpret_cast(compressed_data.data()), @@ -111,15 +76,18 @@ std::optional AppLoader_NSO::LoadModule(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base, bool should_pass_arguments, std::optional pm) { - if (file.GetSize() < sizeof(NsoHeader)) + if (file.GetSize() < sizeof(NSOHeader)) { return {}; + } - NsoHeader nso_header{}; - if (sizeof(NsoHeader) != file.ReadObject(&nso_header)) + NSOHeader nso_header{}; + if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) { return {}; + } - if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) + if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { return {}; + } // Build program image Kernel::CodeSet codeset; @@ -155,10 +123,10 @@ std::optional AppLoader_NSO::LoadModule(Kernel::Process& process, std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); // Read MOD header - ModHeader mod_header{}; + MODHeader mod_header{}; // Default .bss to size in segment header if MOD0 section doesn't exist u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; - std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); + std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(MODHeader)); const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; if (has_mod_header) { // Resize program image to include .bss section and page align each section @@ -171,7 +139,7 @@ std::optional AppLoader_NSO::LoadModule(Kernel::Process& process, // Apply patches if necessary if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { std::vector pi_header(program_image.size() + 0x100); - std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader)); + std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size()); pi_header = pm->PatchNSO(pi_header); diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 167c8a694c..4674c37242 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -4,7 +4,9 @@ #pragma once +#include #include +#include #include "common/common_types.h" #include "common/swap.h" #include "core/file_sys/patch_manager.h" @@ -16,6 +18,43 @@ class Process; namespace Loader { +struct NSOSegmentHeader { + u32_le offset; + u32_le location; + u32_le size; + union { + u32_le alignment; + u32_le bss_size; + }; +}; +static_assert(sizeof(NSOSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); + +struct NSOHeader { + using SHA256Hash = std::array; + + struct RODataRelativeExtent { + u32_le data_offset; + u32_le size; + }; + + u32_le magic; + u32_le version; + u32 reserved; + u32_le flags; + std::array segments; // Text, RoData, Data (in that order) + std::array build_id; + std::array segments_compressed_size; + std::array padding; + RODataRelativeExtent api_info_extent; + RODataRelativeExtent dynstr_extent; + RODataRelativeExtent dynsyn_extent; + std::array segment_hashes; + + bool IsSegmentCompressed(size_t segment_num) const; +}; +static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); +static_assert(std::is_trivially_copyable_v, "NSOHeader must be trivially copyable."); + constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; struct NSOArgumentHeader { From 611f4666fd32c7b55830d24d0c4eb68afe12414a Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 22 Mar 2019 13:18:48 -0400 Subject: [PATCH 4/5] loader/nso: Clean up use of magic constants Now that the NSO header has the proper size, we can just use sizeof on it instead of having magic constants. --- src/core/loader/nso.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 262eaeaeef..fc71ad1899 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -138,13 +138,15 @@ std::optional AppLoader_NSO::LoadModule(Kernel::Process& process, // Apply patches if necessary if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { - std::vector pi_header(program_image.size() + 0x100); - std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); - std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size()); + std::vector pi_header(sizeof(NSOHeader) + program_image.size()); + pi_header.insert(pi_header.begin(), reinterpret_cast(&nso_header), + reinterpret_cast(&nso_header) + sizeof(NSOHeader)); + pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(), + program_image.end()); pi_header = pm->PatchNSO(pi_header); - std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); + std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin()); } // Apply cheats if they exist and the program has a valid title ID From f3297d8cd1b8e25f5f2dc887053c8b6001dcdf66 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 22 Mar 2019 14:00:01 -0400 Subject: [PATCH 5/5] loader/nso: Place translation unit specific functions into an anonymous namespace Makes it impossible to indirectly violate the ODR in some other translation unit due to these existing. --- src/core/loader/nso.cpp | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index fc71ad1899..381530addb 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -21,7 +21,7 @@ #include "core/settings.h" namespace Loader { - +namespace { struct MODHeader { u32_le magic; u32_le dynamic_offset; @@ -33,6 +33,26 @@ struct MODHeader { }; static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size."); +std::vector DecompressSegment(const std::vector& compressed_data, + const NSOSegmentHeader& header) { + std::vector uncompressed_data(header.size); + const int bytes_uncompressed = + LZ4_decompress_safe(reinterpret_cast(compressed_data.data()), + reinterpret_cast(uncompressed_data.data()), + static_cast(compressed_data.size()), header.size); + + ASSERT_MSG(bytes_uncompressed == static_cast(header.size) && + bytes_uncompressed == static_cast(uncompressed_data.size()), + "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); + + return uncompressed_data; +} + +constexpr u32 PageAlignSize(u32 size) { + return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; +} +} // Anonymous namespace + bool NSOHeader::IsSegmentCompressed(size_t segment_num) const { ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); return ((flags >> segment_num) & 1) != 0; @@ -53,25 +73,6 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { return FileType::NSO; } -static std::vector DecompressSegment(const std::vector& compressed_data, - const NSOSegmentHeader& header) { - std::vector uncompressed_data(header.size); - const int bytes_uncompressed = - LZ4_decompress_safe(reinterpret_cast(compressed_data.data()), - reinterpret_cast(uncompressed_data.data()), - static_cast(compressed_data.size()), header.size); - - ASSERT_MSG(bytes_uncompressed == static_cast(header.size) && - bytes_uncompressed == static_cast(uncompressed_data.size()), - "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); - - return uncompressed_data; -} - -static constexpr u32 PageAlignSize(u32 size) { - return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; -} - std::optional AppLoader_NSO::LoadModule(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base, bool should_pass_arguments,