diff options
| -rw-r--r-- | src/core/file_sys/patch_manager.cpp | 25 | ||||
| -rw-r--r-- | src/core/loader/nso.cpp | 101 | ||||
| -rw-r--r-- | src/core/loader/nso.h | 39 |
3 files changed, 92 insertions, 73 deletions
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 58884b4a0..e11217708 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -20,6 +20,7 @@ | |||
| 20 | #include "core/file_sys/vfs_vector.h" | 20 | #include "core/file_sys/vfs_vector.h" |
| 21 | #include "core/hle/service/filesystem/filesystem.h" | 21 | #include "core/hle/service/filesystem/filesystem.h" |
| 22 | #include "core/loader/loader.h" | 22 | #include "core/loader/loader.h" |
| 23 | #include "core/loader/nso.h" | ||
| 23 | #include "core/settings.h" | 24 | #include "core/settings.h" |
| 24 | 25 | ||
| 25 | namespace FileSys { | 26 | namespace FileSys { |
| @@ -32,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{ | |||
| 32 | "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", | 33 | "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", |
| 33 | }; | 34 | }; |
| 34 | 35 | ||
| 35 | struct NSOBuildHeader { | ||
| 36 | u32_le magic; | ||
| 37 | INSERT_PADDING_BYTES(0x3C); | ||
| 38 | std::array<u8, 0x20> build_id; | ||
| 39 | INSERT_PADDING_BYTES(0xA0); | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size."); | ||
| 42 | |||
| 43 | std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { | 36 | std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { |
| 44 | std::array<u8, sizeof(u32)> bytes{}; | 37 | std::array<u8, sizeof(u32)> bytes{}; |
| 45 | bytes[0] = version % SINGLE_BYTE_MODULUS; | 38 | bytes[0] = version % SINGLE_BYTE_MODULUS; |
| @@ -163,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD | |||
| 163 | } | 156 | } |
| 164 | 157 | ||
| 165 | std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { | 158 | std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { |
| 166 | if (nso.size() < 0x100) | 159 | if (nso.size() < sizeof(Loader::NSOHeader)) { |
| 167 | return nso; | 160 | return nso; |
| 161 | } | ||
| 168 | 162 | ||
| 169 | NSOBuildHeader header; | 163 | Loader::NSOHeader header; |
| 170 | std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader)); | 164 | std::memcpy(&header, nso.data(), sizeof(header)); |
| 171 | 165 | ||
| 172 | if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) | 166 | if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { |
| 173 | return nso; | 167 | return nso; |
| 168 | } | ||
| 174 | 169 | ||
| 175 | const auto build_id_raw = Common::HexArrayToString(header.build_id); | 170 | const auto build_id_raw = Common::HexArrayToString(header.build_id); |
| 176 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); | 171 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); |
| @@ -213,9 +208,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { | |||
| 213 | } | 208 | } |
| 214 | } | 209 | } |
| 215 | 210 | ||
| 216 | if (out.size() < 0x100) | 211 | if (out.size() < sizeof(Loader::NSOHeader)) { |
| 217 | return nso; | 212 | return nso; |
| 218 | std::memcpy(out.data(), &header, sizeof(NSOBuildHeader)); | 213 | } |
| 214 | |||
| 215 | std::memcpy(out.data(), &header, sizeof(header)); | ||
| 219 | return out; | 216 | return out; |
| 220 | } | 217 | } |
| 221 | 218 | ||
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 7494f8a28..714d85a59 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp | |||
| @@ -21,36 +21,8 @@ | |||
| 21 | #include "core/settings.h" | 21 | #include "core/settings.h" |
| 22 | 22 | ||
| 23 | namespace Loader { | 23 | namespace Loader { |
| 24 | 24 | namespace { | |
| 25 | struct NsoSegmentHeader { | 25 | struct MODHeader { |
| 26 | u32_le offset; | ||
| 27 | u32_le location; | ||
| 28 | u32_le size; | ||
| 29 | union { | ||
| 30 | u32_le alignment; | ||
| 31 | u32_le bss_size; | ||
| 32 | }; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); | ||
| 35 | |||
| 36 | struct NsoHeader { | ||
| 37 | u32_le magic; | ||
| 38 | u32_le version; | ||
| 39 | INSERT_PADDING_WORDS(1); | ||
| 40 | u8 flags; | ||
| 41 | std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order) | ||
| 42 | std::array<u8, 0x20> build_id; | ||
| 43 | std::array<u32_le, 3> segments_compressed_size; | ||
| 44 | |||
| 45 | bool IsSegmentCompressed(size_t segment_num) const { | ||
| 46 | ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); | ||
| 47 | return ((flags >> segment_num) & 1); | ||
| 48 | } | ||
| 49 | }; | ||
| 50 | static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); | ||
| 51 | static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable."); | ||
| 52 | |||
| 53 | struct ModHeader { | ||
| 54 | u32_le magic; | 26 | u32_le magic; |
| 55 | u32_le dynamic_offset; | 27 | u32_le dynamic_offset; |
| 56 | u32_le bss_start_offset; | 28 | u32_le bss_start_offset; |
| @@ -59,25 +31,10 @@ struct ModHeader { | |||
| 59 | u32_le eh_frame_hdr_end_offset; | 31 | u32_le eh_frame_hdr_end_offset; |
| 60 | u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base | 32 | u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base |
| 61 | }; | 33 | }; |
| 62 | static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); | 34 | static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size."); |
| 63 | |||
| 64 | AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} | ||
| 65 | |||
| 66 | FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { | ||
| 67 | u32 magic = 0; | ||
| 68 | if (file->ReadObject(&magic) != sizeof(magic)) { | ||
| 69 | return FileType::Error; | ||
| 70 | } | ||
| 71 | |||
| 72 | if (Common::MakeMagic('N', 'S', 'O', '0') != magic) { | ||
| 73 | return FileType::Error; | ||
| 74 | } | ||
| 75 | |||
| 76 | return FileType::NSO; | ||
| 77 | } | ||
| 78 | 35 | ||
| 79 | static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, | 36 | std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, |
| 80 | const NsoSegmentHeader& header) { | 37 | const NSOSegmentHeader& header) { |
| 81 | std::vector<u8> uncompressed_data(header.size); | 38 | std::vector<u8> uncompressed_data(header.size); |
| 82 | const int bytes_uncompressed = | 39 | const int bytes_uncompressed = |
| 83 | LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()), | 40 | LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()), |
| @@ -91,23 +48,47 @@ static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, | |||
| 91 | return uncompressed_data; | 48 | return uncompressed_data; |
| 92 | } | 49 | } |
| 93 | 50 | ||
| 94 | static constexpr u32 PageAlignSize(u32 size) { | 51 | constexpr u32 PageAlignSize(u32 size) { |
| 95 | return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; | 52 | return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; |
| 96 | } | 53 | } |
| 54 | } // Anonymous namespace | ||
| 55 | |||
| 56 | bool NSOHeader::IsSegmentCompressed(size_t segment_num) const { | ||
| 57 | ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); | ||
| 58 | return ((flags >> segment_num) & 1) != 0; | ||
| 59 | } | ||
| 60 | |||
| 61 | AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} | ||
| 62 | |||
| 63 | FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { | ||
| 64 | u32 magic = 0; | ||
| 65 | if (file->ReadObject(&magic) != sizeof(magic)) { | ||
| 66 | return FileType::Error; | ||
| 67 | } | ||
| 68 | |||
| 69 | if (Common::MakeMagic('N', 'S', 'O', '0') != magic) { | ||
| 70 | return FileType::Error; | ||
| 71 | } | ||
| 72 | |||
| 73 | return FileType::NSO; | ||
| 74 | } | ||
| 97 | 75 | ||
| 98 | std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, | 76 | std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, |
| 99 | const FileSys::VfsFile& file, VAddr load_base, | 77 | const FileSys::VfsFile& file, VAddr load_base, |
| 100 | bool should_pass_arguments, | 78 | bool should_pass_arguments, |
| 101 | std::optional<FileSys::PatchManager> pm) { | 79 | std::optional<FileSys::PatchManager> pm) { |
| 102 | if (file.GetSize() < sizeof(NsoHeader)) | 80 | if (file.GetSize() < sizeof(NSOHeader)) { |
| 103 | return {}; | 81 | return {}; |
| 82 | } | ||
| 104 | 83 | ||
| 105 | NsoHeader nso_header{}; | 84 | NSOHeader nso_header{}; |
| 106 | if (sizeof(NsoHeader) != file.ReadObject(&nso_header)) | 85 | if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) { |
| 107 | return {}; | 86 | return {}; |
| 87 | } | ||
| 108 | 88 | ||
| 109 | if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) | 89 | if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { |
| 110 | return {}; | 90 | return {}; |
| 91 | } | ||
| 111 | 92 | ||
| 112 | // Build program image | 93 | // Build program image |
| 113 | Kernel::CodeSet codeset; | 94 | Kernel::CodeSet codeset; |
| @@ -143,10 +124,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, | |||
| 143 | std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); | 124 | std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); |
| 144 | 125 | ||
| 145 | // Read MOD header | 126 | // Read MOD header |
| 146 | ModHeader mod_header{}; | 127 | MODHeader mod_header{}; |
| 147 | // Default .bss to size in segment header if MOD0 section doesn't exist | 128 | // Default .bss to size in segment header if MOD0 section doesn't exist |
| 148 | u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; | 129 | u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; |
| 149 | std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); | 130 | std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(MODHeader)); |
| 150 | const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; | 131 | const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; |
| 151 | if (has_mod_header) { | 132 | if (has_mod_header) { |
| 152 | // Resize program image to include .bss section and page align each section | 133 | // Resize program image to include .bss section and page align each section |
| @@ -158,13 +139,15 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, | |||
| 158 | 139 | ||
| 159 | // Apply patches if necessary | 140 | // Apply patches if necessary |
| 160 | if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { | 141 | if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { |
| 161 | std::vector<u8> pi_header(program_image.size() + 0x100); | 142 | std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); |
| 162 | std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader)); | 143 | pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header), |
| 163 | std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size()); | 144 | reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader)); |
| 145 | pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(), | ||
| 146 | program_image.end()); | ||
| 164 | 147 | ||
| 165 | pi_header = pm->PatchNSO(pi_header); | 148 | pi_header = pm->PatchNSO(pi_header); |
| 166 | 149 | ||
| 167 | std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); | 150 | std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin()); |
| 168 | } | 151 | } |
| 169 | 152 | ||
| 170 | // Apply cheats if they exist and the program has a valid title ID | 153 | // Apply cheats if they exist and the program has a valid title ID |
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 167c8a694..4674c3724 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h | |||
| @@ -4,7 +4,9 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | ||
| 7 | #include <optional> | 8 | #include <optional> |
| 9 | #include <type_traits> | ||
| 8 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 9 | #include "common/swap.h" | 11 | #include "common/swap.h" |
| 10 | #include "core/file_sys/patch_manager.h" | 12 | #include "core/file_sys/patch_manager.h" |
| @@ -16,6 +18,43 @@ class Process; | |||
| 16 | 18 | ||
| 17 | namespace Loader { | 19 | namespace Loader { |
| 18 | 20 | ||
| 21 | struct NSOSegmentHeader { | ||
| 22 | u32_le offset; | ||
| 23 | u32_le location; | ||
| 24 | u32_le size; | ||
| 25 | union { | ||
| 26 | u32_le alignment; | ||
| 27 | u32_le bss_size; | ||
| 28 | }; | ||
| 29 | }; | ||
| 30 | static_assert(sizeof(NSOSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); | ||
| 31 | |||
| 32 | struct NSOHeader { | ||
| 33 | using SHA256Hash = std::array<u8, 0x20>; | ||
| 34 | |||
| 35 | struct RODataRelativeExtent { | ||
| 36 | u32_le data_offset; | ||
| 37 | u32_le size; | ||
| 38 | }; | ||
| 39 | |||
| 40 | u32_le magic; | ||
| 41 | u32_le version; | ||
| 42 | u32 reserved; | ||
| 43 | u32_le flags; | ||
| 44 | std::array<NSOSegmentHeader, 3> segments; // Text, RoData, Data (in that order) | ||
| 45 | std::array<u8, 0x20> build_id; | ||
| 46 | std::array<u32_le, 3> segments_compressed_size; | ||
| 47 | std::array<u8, 0x1C> padding; | ||
| 48 | RODataRelativeExtent api_info_extent; | ||
| 49 | RODataRelativeExtent dynstr_extent; | ||
| 50 | RODataRelativeExtent dynsyn_extent; | ||
| 51 | std::array<SHA256Hash, 3> segment_hashes; | ||
| 52 | |||
| 53 | bool IsSegmentCompressed(size_t segment_num) const; | ||
| 54 | }; | ||
| 55 | static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); | ||
| 56 | static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); | ||
| 57 | |||
| 19 | constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; | 58 | constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; |
| 20 | 59 | ||
| 21 | struct NSOArgumentHeader { | 60 | struct NSOArgumentHeader { |