mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-27 01:02:48 +01:00
Merge pull request #2280 from lioncash/nso
loader/nso: Minor refactoring
This commit is contained in:
commit
6af322a347
3 changed files with 93 additions and 74 deletions
|
@ -20,6 +20,7 @@
|
||||||
#include "core/file_sys/vfs_vector.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"
|
||||||
|
#include "core/loader/nso.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
@ -32,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
|
||||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
|
@ -163,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||||
if (nso.size() < 0x100)
|
if (nso.size() < sizeof(Loader::NSOHeader)) {
|
||||||
return nso;
|
return nso;
|
||||||
|
}
|
||||||
|
|
||||||
NSOBuildHeader header;
|
Loader::NSOHeader header;
|
||||||
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
|
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;
|
return nso;
|
||||||
|
}
|
||||||
|
|
||||||
const auto build_id_raw = Common::HexArrayToString(header.build_id);
|
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);
|
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out.size() < 0x100)
|
if (out.size() < sizeof(Loader::NSOHeader)) {
|
||||||
return nso;
|
return nso;
|
||||||
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
|
}
|
||||||
|
|
||||||
|
std::memcpy(out.data(), &header, sizeof(header));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,36 +21,8 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
namespace Loader {
|
namespace Loader {
|
||||||
|
namespace {
|
||||||
struct NsoSegmentHeader {
|
struct MODHeader {
|
||||||
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 {
|
|
||||||
u32_le magic;
|
|
||||||
u32_le version;
|
|
||||||
INSERT_PADDING_WORDS(1);
|
|
||||||
u8 flags;
|
|
||||||
std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
|
|
||||||
std::array<u8, 0x20> build_id;
|
|
||||||
std::array<u32_le, 3> segments_compressed_size;
|
|
||||||
|
|
||||||
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(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable.");
|
|
||||||
|
|
||||||
struct ModHeader {
|
|
||||||
u32_le magic;
|
u32_le magic;
|
||||||
u32_le dynamic_offset;
|
u32_le dynamic_offset;
|
||||||
u32_le bss_start_offset;
|
u32_le bss_start_offset;
|
||||||
|
@ -59,7 +31,32 @@ struct ModHeader {
|
||||||
u32_le eh_frame_hdr_end_offset;
|
u32_le eh_frame_hdr_end_offset;
|
||||||
u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base
|
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.");
|
||||||
|
|
||||||
|
std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
|
||||||
|
const NSOSegmentHeader& header) {
|
||||||
|
std::vector<u8> uncompressed_data(header.size);
|
||||||
|
const int bytes_uncompressed =
|
||||||
|
LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()),
|
||||||
|
reinterpret_cast<char*>(uncompressed_data.data()),
|
||||||
|
static_cast<int>(compressed_data.size()), header.size);
|
||||||
|
|
||||||
|
ASSERT_MSG(bytes_uncompressed == static_cast<int>(header.size) &&
|
||||||
|
bytes_uncompressed == static_cast<int>(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;
|
||||||
|
}
|
||||||
|
|
||||||
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
|
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
|
||||||
|
|
||||||
|
@ -76,38 +73,22 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
|
||||||
return FileType::NSO;
|
return FileType::NSO;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
|
|
||||||
const NsoSegmentHeader& header) {
|
|
||||||
std::vector<u8> uncompressed_data(header.size);
|
|
||||||
const int bytes_uncompressed =
|
|
||||||
LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()),
|
|
||||||
reinterpret_cast<char*>(uncompressed_data.data()),
|
|
||||||
static_cast<int>(compressed_data.size()), header.size);
|
|
||||||
|
|
||||||
ASSERT_MSG(bytes_uncompressed == static_cast<int>(header.size) &&
|
|
||||||
bytes_uncompressed == static_cast<int>(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<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||||
const FileSys::VfsFile& file, VAddr load_base,
|
const FileSys::VfsFile& file, VAddr load_base,
|
||||||
bool should_pass_arguments,
|
bool should_pass_arguments,
|
||||||
std::optional<FileSys::PatchManager> pm) {
|
std::optional<FileSys::PatchManager> pm) {
|
||||||
if (file.GetSize() < sizeof(NsoHeader))
|
if (file.GetSize() < sizeof(NSOHeader)) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
NsoHeader nso_header{};
|
NSOHeader nso_header{};
|
||||||
if (sizeof(NsoHeader) != file.ReadObject(&nso_header))
|
if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// Build program image
|
// Build program image
|
||||||
Kernel::CodeSet codeset;
|
Kernel::CodeSet codeset;
|
||||||
|
@ -143,10 +124,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||||
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
|
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
|
||||||
|
|
||||||
// Read MOD header
|
// Read MOD header
|
||||||
ModHeader mod_header{};
|
MODHeader mod_header{};
|
||||||
// Default .bss to size in segment header if MOD0 section doesn't exist
|
// Default .bss to size in segment header if MOD0 section doesn't exist
|
||||||
u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)};
|
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')};
|
const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
|
||||||
if (has_mod_header) {
|
if (has_mod_header) {
|
||||||
// Resize program image to include .bss section and page align each section
|
// 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,
|
||||||
|
|
||||||
// Apply patches if necessary
|
// Apply patches if necessary
|
||||||
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
|
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
|
||||||
std::vector<u8> pi_header(program_image.size() + 0x100);
|
std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
|
||||||
std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
|
pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
|
||||||
std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
|
reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
|
||||||
|
pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
|
||||||
|
program_image.end());
|
||||||
|
|
||||||
pi_header = pm->PatchNSO(pi_header);
|
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
|
// Apply cheats if they exist and the program has a valid title ID
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <type_traits>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/file_sys/patch_manager.h"
|
#include "core/file_sys/patch_manager.h"
|
||||||
|
@ -16,6 +18,43 @@ class Process;
|
||||||
|
|
||||||
namespace Loader {
|
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<u8, 0x20>;
|
||||||
|
|
||||||
|
struct RODataRelativeExtent {
|
||||||
|
u32_le data_offset;
|
||||||
|
u32_le size;
|
||||||
|
};
|
||||||
|
|
||||||
|
u32_le magic;
|
||||||
|
u32_le version;
|
||||||
|
u32 reserved;
|
||||||
|
u32_le flags;
|
||||||
|
std::array<NSOSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
|
||||||
|
std::array<u8, 0x20> build_id;
|
||||||
|
std::array<u32_le, 3> segments_compressed_size;
|
||||||
|
std::array<u8, 0x1C> padding;
|
||||||
|
RODataRelativeExtent api_info_extent;
|
||||||
|
RODataRelativeExtent dynstr_extent;
|
||||||
|
RODataRelativeExtent dynsyn_extent;
|
||||||
|
std::array<SHA256Hash, 3> 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>, "NSOHeader must be trivially copyable.");
|
||||||
|
|
||||||
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
|
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
|
||||||
|
|
||||||
struct NSOArgumentHeader {
|
struct NSOArgumentHeader {
|
||||||
|
|
Loading…
Reference in a new issue