mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-02 02:58:46 +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>
302 lines
9.8 KiB
C++
302 lines
9.8 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 <utility>
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
|
|
#include "common/hex_util.h"
|
|
#include "common/scope_exit.h"
|
|
#include "core/core.h"
|
|
#include "core/file_sys/content_archive.h"
|
|
#include "core/file_sys/control_metadata.h"
|
|
#include "core/file_sys/nca_metadata.h"
|
|
#include "core/file_sys/patch_manager.h"
|
|
#include "core/file_sys/registered_cache.h"
|
|
#include "core/file_sys/romfs_factory.h"
|
|
#include "core/hle/kernel/k_process.h"
|
|
#include "core/hle/service/filesystem/filesystem.h"
|
|
#include "core/loader/deconstructed_rom_directory.h"
|
|
#include "core/loader/nca.h"
|
|
#include "common/literals.h"
|
|
|
|
namespace Loader {
|
|
|
|
static u32 CalculatePointerBufferSize(size_t heap_size) {
|
|
if (heap_size > 1073741824) { // Games with 1 GiB
|
|
return 0x10000;
|
|
} else if (heap_size > 536870912) { // Games with 512 MiB
|
|
return 0xC000;
|
|
} else {
|
|
return 0x8000; // Default for all other games
|
|
}
|
|
}
|
|
|
|
AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_, u64 update_only_program_id_)
|
|
: AppLoader(std::move(file_)),
|
|
nca(std::make_unique<FileSys::NCA>(file, nullptr, update_only_program_id_ != 0)),
|
|
update_only_program_id(update_only_program_id_) {}
|
|
|
|
u64 AppLoader_NCA::GetProgramId() const {
|
|
return update_only_program_id != 0 ? update_only_program_id : nca->GetTitleId();
|
|
}
|
|
|
|
AppLoader_NCA::~AppLoader_NCA() = default;
|
|
|
|
FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& nca_file) {
|
|
const FileSys::NCA nca(nca_file);
|
|
|
|
if (nca.GetStatus() == ResultStatus::Success &&
|
|
nca.GetType() == FileSys::NCAContentType::Program) {
|
|
return FileType::NCA;
|
|
}
|
|
|
|
return FileType::Error;
|
|
}
|
|
|
|
AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::System& system) {
|
|
if (is_loaded) {
|
|
return {ResultStatus::ErrorAlreadyLoaded, {}};
|
|
}
|
|
|
|
const auto result = nca->GetStatus();
|
|
if (result != ResultStatus::Success) {
|
|
return {result, {}};
|
|
}
|
|
|
|
if (nca->GetType() != FileSys::NCAContentType::Program) {
|
|
return {ResultStatus::ErrorNCANotProgram, {}};
|
|
}
|
|
|
|
auto exefs = nca->GetExeFS();
|
|
if (exefs == nullptr) {
|
|
LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
|
|
|
|
const auto& installed = system.GetContentProvider();
|
|
const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()),
|
|
FileSys::ContentRecordType::Program);
|
|
|
|
if (update_nca) {
|
|
exefs = update_nca->GetExeFS();
|
|
}
|
|
|
|
if (exefs == nullptr) {
|
|
return {ResultStatus::ErrorNoExeFS, {}};
|
|
}
|
|
}
|
|
|
|
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
|
|
|
|
// Read heap size from main.npdm in ExeFS
|
|
u64 heap_size = 0;
|
|
|
|
if (exefs) {
|
|
const auto npdm_file = exefs->GetFile("main.npdm");
|
|
if (npdm_file) {
|
|
auto npdm_data = npdm_file->ReadAllBytes();
|
|
if (npdm_data.size() >= 0x30) {
|
|
heap_size = *reinterpret_cast<const u64*>(&npdm_data[0x28]);
|
|
LOG_INFO(Loader, "Read heap size {:#x} bytes from main.npdm", heap_size);
|
|
} else {
|
|
LOG_WARNING(Loader, "main.npdm too small to read heap size!");
|
|
}
|
|
} else {
|
|
LOG_WARNING(Loader, "No main.npdm found in ExeFS!");
|
|
}
|
|
}
|
|
|
|
// Set pointer buffer size based on heap size
|
|
process.SetPointerBufferSize(CalculatePointerBufferSize(heap_size));
|
|
|
|
// Load modules
|
|
const auto load_result = directory_loader->Load(process, system);
|
|
if (load_result.first != ResultStatus::Success) {
|
|
return load_result;
|
|
}
|
|
|
|
LOG_INFO(Loader, "Set pointer buffer size to {:#x} bytes for ProgramID {:#018x} (Heap size: {:#x})",
|
|
process.GetPointerBufferSize(), GetProgramId(), heap_size);
|
|
|
|
auto metadata =
|
|
FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, this->GetProgramId());
|
|
if (metadata.first != nullptr) {
|
|
nacp = std::move(metadata.first);
|
|
LOG_INFO(Loader, "Control data for {:016X}: name=\"{}\" supported_languages={:08X}",
|
|
this->GetProgramId(), nacp->GetApplicationName(), nacp->GetSupportedLanguages());
|
|
} else {
|
|
LOG_WARNING(Loader, "No control data found for program {:016X}", this->GetProgramId());
|
|
}
|
|
|
|
// Register the process in the file system controller
|
|
system.GetFileSystemController().RegisterProcess(
|
|
process.GetProcessId(), GetProgramId(),
|
|
std::make_shared<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
|
|
system.GetFileSystemController()));
|
|
|
|
is_loaded = true;
|
|
return load_result;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
|
|
using namespace Common::Literals;
|
|
|
|
constexpr size_t NcaFileNameWithHashLength = 36;
|
|
constexpr size_t NcaFileNameHashLength = 32;
|
|
constexpr size_t NcaSha256HashLength = 32;
|
|
constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2;
|
|
|
|
// Get the file name.
|
|
const auto name = file->GetName();
|
|
|
|
// We won't try to verify meta NCAs.
|
|
if (name.ends_with(".cnmt.nca"))
|
|
return ResultStatus::Success;
|
|
|
|
// Check if we can verify this file. NCAs should be named after their hashes.
|
|
if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) {
|
|
LOG_WARNING(Loader, "Unable to validate NCA with name {}", name);
|
|
return ResultStatus::ErrorIntegrityVerificationNotImplemented;
|
|
}
|
|
|
|
// Get the expected truncated hash of the NCA.
|
|
const auto input_hash =
|
|
Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false);
|
|
|
|
// Declare buffer to read into.
|
|
std::vector<u8> buffer(4_MiB);
|
|
|
|
// Initialize sha256 verification context.
|
|
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
|
if (!ctx)
|
|
return ResultStatus::ErrorNotInitialized;
|
|
|
|
// Ensure we maintain a clean state on exit.
|
|
SCOPE_EXIT {
|
|
EVP_MD_CTX_free(ctx);
|
|
};
|
|
|
|
if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr))
|
|
return ResultStatus::ErrorIntegrityVerificationFailed;
|
|
|
|
// Declare counters.
|
|
const size_t total_size = file->GetSize();
|
|
size_t processed_size = 0;
|
|
|
|
// Begin iterating the file.
|
|
while (processed_size < total_size) {
|
|
// Refill the buffer.
|
|
const size_t intended_read_size = (std::min)(buffer.size(), total_size - processed_size);
|
|
const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size);
|
|
|
|
// Update the hash function with the buffer contents.
|
|
if (!EVP_DigestUpdate(ctx, buffer.data(), read_size)) {
|
|
return ResultStatus::ErrorIntegrityVerificationFailed;
|
|
}
|
|
|
|
// Update counters.
|
|
processed_size += read_size;
|
|
|
|
// Call the progress function.
|
|
if (!progress_callback(processed_size, total_size)) {
|
|
return ResultStatus::ErrorIntegrityVerificationFailed;
|
|
}
|
|
}
|
|
|
|
// Finalize context and compute the output hash.
|
|
std::array<u8, NcaSha256HashLength> output_hash;
|
|
unsigned int output_len = 0;
|
|
if (!EVP_DigestFinal_ex(ctx, output_hash.data(), &output_len)) {
|
|
return ResultStatus::ErrorIntegrityVerificationFailed;
|
|
}
|
|
|
|
// Compare to expected.
|
|
if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) {
|
|
LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name);
|
|
return ResultStatus::ErrorIntegrityVerificationFailed;
|
|
}
|
|
|
|
// File verified.
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
|
|
if (nca == nullptr) {
|
|
return ResultStatus::ErrorNotInitialized;
|
|
}
|
|
|
|
if (nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) {
|
|
return ResultStatus::ErrorNoRomFS;
|
|
}
|
|
|
|
dir = nca->GetRomFS();
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
|
|
if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) {
|
|
return ResultStatus::ErrorNotInitialized;
|
|
}
|
|
|
|
out_program_id = GetProgramId();
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::ReadControlData(FileSys::NACP& out_control) {
|
|
if (nacp == nullptr) {
|
|
return ResultStatus::ErrorNoControl;
|
|
}
|
|
|
|
out_control = *nacp;
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::ReadTitle(std::string& out_title) {
|
|
if (nacp == nullptr) {
|
|
return ResultStatus::ErrorNoControl;
|
|
}
|
|
|
|
out_title = nacp->GetApplicationName();
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::ReadBanner(std::vector<u8>& buffer) {
|
|
if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) {
|
|
return ResultStatus::ErrorNotInitialized;
|
|
}
|
|
|
|
const auto logo = nca->GetLogoPartition();
|
|
if (logo == nullptr) {
|
|
return ResultStatus::ErrorNoIcon;
|
|
}
|
|
|
|
buffer = logo->GetFile("StartupMovie.gif")->ReadAllBytes();
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) {
|
|
if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) {
|
|
return ResultStatus::ErrorNotInitialized;
|
|
}
|
|
|
|
const auto logo = nca->GetLogoPartition();
|
|
if (logo == nullptr) {
|
|
return ResultStatus::ErrorNoIcon;
|
|
}
|
|
|
|
buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes();
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus AppLoader_NCA::ReadNSOModules(Modules& modules) {
|
|
if (directory_loader == nullptr) {
|
|
return ResultStatus::ErrorNotInitialized;
|
|
}
|
|
|
|
return directory_loader->ReadNSOModules(modules);
|
|
}
|
|
|
|
} // namespace Loader
|