mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-29 09:58:05 +00:00
54cd5fb8eb
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- - Dynamic shader cache reloading capability for qlaunch - Multi-process improvements (thanks to @frank1734), instead of just using the main application we now respect caller process (also did it for HID devices while I was at it) - Layer stack masks & shared buffer screenshot for video core, added different masks (screenshot, recording, etc.) this was discovered as an issue due to how in qlaunch the transition was not right between applications. May not be perfect but also fixes screenshots while using qlaunch - Reworked overlay display management (input and visibility) - instead of random numbers as I've previously did, I decided to add an AppletZIndex enum for better readability. Also split capability of input by touch and gamepad. - etc. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4238 Reviewed-by: Samuel <lizzie@eden-emu.dev> Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
254 lines
11 KiB
C++
254 lines
11 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <algorithm>
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "common/common_funcs.h"
|
|
#include "common/hex_util.h"
|
|
#include "common/logging.h"
|
|
#include "common/lz4_compression.h"
|
|
#include "common/settings.h"
|
|
#include "common/swap.h"
|
|
#include "core/core.h"
|
|
#include "core/file_sys/patch_manager.h"
|
|
#include "core/hle/kernel/code_set.h"
|
|
#include "core/hle/kernel/k_page_table.h"
|
|
#include "core/hle/kernel/k_process.h"
|
|
#include "core/hle/kernel/k_thread.h"
|
|
#include "core/loader/nso.h"
|
|
#include "core/memory.h"
|
|
|
|
#ifdef HAS_NCE
|
|
#include "core/arm/nce/patcher.h"
|
|
#endif
|
|
|
|
namespace Loader {
|
|
namespace {
|
|
struct MODHeader {
|
|
u32_le magic;
|
|
u32_le dynamic_offset;
|
|
u32_le bss_start_offset;
|
|
u32_le bss_end_offset;
|
|
u32_le eh_frame_hdr_start_offset;
|
|
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.");
|
|
|
|
constexpr u32 PageAlignSize(u32 size) {
|
|
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
|
|
}
|
|
} // 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_)) {}
|
|
|
|
FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) {
|
|
u32 magic = 0;
|
|
if (in_file->ReadObject(&magic) != sizeof(magic)) {
|
|
return FileType::Error;
|
|
}
|
|
|
|
if (Common::MakeMagic('N', 'S', 'O', '0') != magic) {
|
|
return FileType::Error;
|
|
}
|
|
|
|
return FileType::NSO;
|
|
}
|
|
|
|
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system, const FileSys::VfsFile& nso_file, VAddr load_base, bool should_pass_arguments, bool load_into_process, std::optional<FileSys::PatchManager> pm, std::vector<Core::NCE::Patcher>* patches, s32 patch_index) {
|
|
if (nso_file.GetSize() < sizeof(NSOHeader))
|
|
return std::nullopt;
|
|
NSOHeader nso_header{};
|
|
if (sizeof(NSOHeader) != nso_file.ReadObject(&nso_header))
|
|
return std::nullopt;
|
|
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
|
return std::nullopt;
|
|
if (nso_header.segments.empty())
|
|
return std::nullopt;
|
|
|
|
// Allocate some space at the beginning if we are patching in PreText mode.
|
|
const size_t module_start = [&]() -> size_t {
|
|
#ifdef HAS_NCE
|
|
if (patches && load_into_process) {
|
|
auto* patch = &patches->operator[](patch_index);
|
|
if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
|
|
return patch->GetSectionSize();
|
|
} else if (patch->GetPatchMode() == Core::NCE::PatchMode::Split) {
|
|
return patch->GetPreSectionSize();
|
|
}
|
|
}
|
|
#endif
|
|
return 0;
|
|
}();
|
|
|
|
auto const last_segment_it = &nso_header.segments[nso_header.segments.size() - 1];
|
|
// Build program image directly in codeset memory :)
|
|
Kernel::CodeSet codeset;
|
|
codeset.memory.resize(module_start + last_segment_it->location + last_segment_it->size);
|
|
{
|
|
std::vector<u8> compressed_data(*std::ranges::max_element(nso_header.segments_compressed_size));
|
|
std::vector<u8> decompressed_size(std::ranges::max_element(nso_header.segments, [](auto const& a, auto const& b) {
|
|
return a.size < b.size;
|
|
})->size);
|
|
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
|
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
|
if (nso_header.IsSegmentCompressed(i)) {
|
|
int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
|
|
ASSERT(r == int(nso_header.segments[i].size));
|
|
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
|
|
} else {
|
|
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
|
|
}
|
|
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
|
|
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
|
|
codeset.segments[i].size = nso_header.segments[i].size;
|
|
}
|
|
}
|
|
|
|
if (should_pass_arguments && !Settings::values.program_args.GetValue().empty()) {
|
|
const auto arg_data{Settings::values.program_args.GetValue()};
|
|
|
|
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
|
|
NSOArgumentHeader args_header{NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
|
const auto end_offset = codeset.memory.size();
|
|
codeset.memory.resize(u32(codeset.memory.size()) + NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
|
|
std::memcpy(codeset.memory.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
|
|
std::memcpy(codeset.memory.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), arg_data.size());
|
|
}
|
|
|
|
codeset.DataSegment().size += nso_header.segments[2].bss_size;
|
|
u32 image_size = PageAlignSize(u32(codeset.memory.size()) + nso_header.segments[2].bss_size);
|
|
codeset.memory.resize(image_size);
|
|
|
|
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
|
codeset.segments[i].size = PageAlignSize(codeset.segments[i].size);
|
|
}
|
|
|
|
// Apply patches if necessary
|
|
const auto name = nso_file.GetName();
|
|
if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
|
|
std::span<u8> patchable_section(codeset.memory.data() + module_start, codeset.memory.size() - module_start);
|
|
std::vector<u8> pi_header(sizeof(NSOHeader) + patchable_section.size());
|
|
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
|
|
std::memcpy(pi_header.data() + sizeof(NSOHeader), patchable_section.data(),
|
|
patchable_section.size());
|
|
|
|
pi_header = pm->PatchNSO(pi_header, name);
|
|
|
|
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), patchable_section.data());
|
|
}
|
|
|
|
#ifdef HAS_NCE
|
|
// If we are computing the process code layout and using nce backend, patch.
|
|
const auto& code = codeset.CodeSegment();
|
|
auto* patch = patches ? &patches->operator[](patch_index) : nullptr;
|
|
if (patch && !load_into_process) {
|
|
//Set module ID using build_id from the NSO header
|
|
patch->SetModuleID(nso_header.build_id);
|
|
// Patch SVCs and MRS calls in the guest code
|
|
while (!patch->PatchText(codeset.memory, code)) {
|
|
patch = &patches->emplace_back();
|
|
patch->SetModuleID(nso_header.build_id); // In case the patcher is changed for big modules, the new patcher should also have the build_id
|
|
}
|
|
} else if (patch) {
|
|
// Relocate code patch and copy to the program image.
|
|
// Save size before RelocateAndCopy (which may resize)
|
|
const size_t size_before_relocate = codeset.memory.size();
|
|
if (patch->RelocateAndCopy(load_base, code, codeset.memory, &process.GetPostHandlers())) {
|
|
// Update patch section.
|
|
auto& patch_segment = codeset.PatchSegment();
|
|
auto& post_patch_segment = codeset.PostPatchSegment();
|
|
const auto patch_mode = patch->GetPatchMode();
|
|
if (patch_mode == Core::NCE::PatchMode::PreText) {
|
|
patch_segment.addr = 0;
|
|
patch_segment.size = static_cast<u32>(patch->GetSectionSize());
|
|
} else if (patch_mode == Core::NCE::PatchMode::Split) {
|
|
// For Split-mode, we are using pre-patch buffer at start, post-patch buffer at end
|
|
patch_segment.addr = 0;
|
|
patch_segment.size = static_cast<u32>(patch->GetPreSectionSize());
|
|
post_patch_segment.addr = size_before_relocate;
|
|
post_patch_segment.size = static_cast<u32>(patch->GetSectionSize());
|
|
} else {
|
|
patch_segment.addr = image_size;
|
|
patch_segment.size = static_cast<u32>(patch->GetSectionSize());
|
|
}
|
|
}
|
|
|
|
// Refresh image_size to take account the patch section if it was added by RelocateAndCopy
|
|
image_size = static_cast<u32>(codeset.memory.size());
|
|
}
|
|
#endif
|
|
|
|
// If we aren't actually loading (i.e. just computing the process code layout), we are done
|
|
if (!load_into_process) {
|
|
#ifdef HAS_NCE
|
|
// Ok, so for Split mode, we need to account for pre-patch and post-patch space
|
|
// which will be added during RelocateAndCopy in the second pass. Where it crashed
|
|
// in Android Studio at PreText. May be a better way. Works for now.
|
|
if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::Split) {
|
|
return load_base + patch->GetPreSectionSize() + image_size + patch->GetSectionSize();
|
|
} else if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
|
|
return load_base + patch->GetSectionSize() + image_size;
|
|
} else if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PostData) {
|
|
return load_base + image_size + patch->GetSectionSize();
|
|
}
|
|
#endif
|
|
return load_base + image_size;
|
|
}
|
|
|
|
// Apply cheats if they exist and the program has a valid title ID
|
|
if (pm) {
|
|
// TODO(Maufeat): Check if there is a better way to check
|
|
if (name == "main")
|
|
system.SetApplicationProcessBuildID(nso_header.build_id);
|
|
|
|
const auto cheats = pm->CreateCheatList(nso_header.build_id);
|
|
if (!cheats.empty()) {
|
|
system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
|
|
}
|
|
}
|
|
|
|
// Load codeset for current process
|
|
process.LoadModule(system.Kernel(), std::move(codeset), load_base);
|
|
return load_base + image_size;
|
|
}
|
|
|
|
AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::System& system) {
|
|
if (is_loaded) {
|
|
return {ResultStatus::ErrorAlreadyLoaded, {}};
|
|
}
|
|
|
|
modules.clear();
|
|
|
|
// Load module
|
|
const VAddr base_address = GetInteger(process.GetEntryPoint());
|
|
if (!LoadModule(process, system, *file, base_address, true, true)) {
|
|
return {ResultStatus::ErrorLoadingNSO, {}};
|
|
}
|
|
|
|
modules.insert_or_assign(base_address, file->GetName());
|
|
LOG_DEBUG(Loader, "loaded module {} @ {:#x}", file->GetName(), base_address);
|
|
|
|
is_loaded = true;
|
|
return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
|
|
Core::Memory::DEFAULT_STACK_SIZE}};
|
|
}
|
|
|
|
ResultStatus AppLoader_NSO::ReadNSOModules(Modules& out_modules) {
|
|
out_modules = this->modules;
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
} // namespace Loader
|