Add 3GX plugin loader (#6172)
* Initial plugin loader support * More plugin loader progress * Organize code and more plugin features * Fix clang-format * Fix compilation and add android gui * Fix clang-format * Fix macos build * Fix copy-paste bug and clang-format * More merge fixes * Make suggestions * Move global variable to static member * Fix typo * Apply suggestions * Proper initialization order * Allocate plugin memory from SYSTEM instead of APPLICATION * Do not mark free pages as RWX * Fix plugins in old 3DS mode. * Implement KernelSetState and notif 0x203 * Apply changes * Remove unused variable * Fix dynarmic commit * Sublicense files with MIT License * Remove non-ascii characters from license
This commit is contained in:
parent
48ee112ceb
commit
016ce6c286
38 changed files with 1911 additions and 42 deletions
364
src/core/file_sys/plugin_3gx.cpp
Normal file
364
src/core/file_sys/plugin_3gx.cpp
Normal file
|
@ -0,0 +1,364 @@
|
|||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/file_sys/plugin_3gx.h"
|
||||
#include "core/file_sys/plugin_3gx_bootloader.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
static std::string ReadTextInfo(FileUtil::IOFile& file, std::size_t offset, std::size_t max_size) {
|
||||
if (max_size > 0x400) { // Limit read string size to 0x400 bytes, just in case
|
||||
return "";
|
||||
}
|
||||
std::vector<char> char_data(max_size);
|
||||
|
||||
const u64 prev_offset = file.Tell();
|
||||
if (!file.Seek(offset, SEEK_SET)) {
|
||||
return "";
|
||||
}
|
||||
if (file.ReadBytes(char_data.data(), max_size) != max_size) {
|
||||
file.Seek(prev_offset, SEEK_SET);
|
||||
return "";
|
||||
}
|
||||
char_data[max_size - 1] = '\0';
|
||||
return std::string(char_data.data());
|
||||
}
|
||||
|
||||
static bool ReadSection(std::vector<u8>& data_out, FileUtil::IOFile& file, std::size_t offset,
|
||||
std::size_t size) {
|
||||
if (size > 0x5000000) { // Limit read section size to 5MiB, just in case
|
||||
return false;
|
||||
}
|
||||
data_out.resize(size);
|
||||
|
||||
const u64 prev_offset = file.Tell();
|
||||
|
||||
if (!file.Seek(offset, SEEK_SET)) {
|
||||
return false;
|
||||
}
|
||||
if (file.ReadBytes(data_out.data(), size) != size) {
|
||||
file.Seek(prev_offset, SEEK_SET);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Loader::ResultStatus FileSys::Plugin3GXLoader::Load(
|
||||
Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process,
|
||||
Kernel::KernelSystem& kernel) {
|
||||
FileUtil::IOFile file(plg_context.plugin_path, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not found: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load CIA Header
|
||||
std::vector<u8> header_data(sizeof(_3gx_Header));
|
||||
if (file.ReadBytes(header_data.data(), sizeof(_3gx_Header)) != sizeof(_3gx_Header)) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
std::memcpy(&header, header_data.data(), sizeof(_3gx_Header));
|
||||
|
||||
// Check magic value
|
||||
if (std::memcmp(&header.magic, _3GX_magic, 8) != 0) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Outdated or invalid 3GX plugin: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
if (header.infos.flags.compatibility == static_cast<u32>(_3gx_Infos::Compatibility::CONSOLE)) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not compatible with Citra: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load strings
|
||||
author = ReadTextInfo(file, header.infos.author_msg_offset, header.infos.author_len);
|
||||
title = ReadTextInfo(file, header.infos.title_msg_offset, header.infos.title_len);
|
||||
description =
|
||||
ReadTextInfo(file, header.infos.description_msg_offset, header.infos.description_len);
|
||||
summary = ReadTextInfo(file, header.infos.summary_msg_offset, header.infos.summary_len);
|
||||
|
||||
LOG_INFO(Service_PLGLDR, "Trying to load plugin - Title: {} - Author: {}", title, author);
|
||||
|
||||
// Load compatible TIDs
|
||||
{
|
||||
std::vector<u8> raw_TID_data;
|
||||
if (!ReadSection(raw_TID_data, file, header.targets.title_offsets,
|
||||
header.targets.count * sizeof(u32))) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
for (u32 i = 0; i < u32(header.targets.count); i++) {
|
||||
compatible_TID.push_back(
|
||||
u32_le(*reinterpret_cast<u32*>(raw_TID_data.data() + i * sizeof(u32))));
|
||||
}
|
||||
}
|
||||
|
||||
if (!compatible_TID.empty() &&
|
||||
std::find(compatible_TID.begin(), compatible_TID.end(),
|
||||
static_cast<u32>(process.codeset->program_id)) == compatible_TID.end()) {
|
||||
LOG_ERROR(Service_PLGLDR,
|
||||
"Failed to load 3GX plugin. Not compatible with loaded process: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load exe load func and args
|
||||
if (header.infos.flags.embedded_exe_func.Value() &&
|
||||
header.executable.exe_load_func_offset != 0) {
|
||||
exe_load_func.clear();
|
||||
std::vector<u8> out;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
ReadSection(out, file, header.executable.exe_load_func_offset + i * sizeof(u32),
|
||||
sizeof(u32));
|
||||
u32 instruction = *reinterpret_cast<u32_le*>(out.data());
|
||||
if (instruction == 0xE320F000) {
|
||||
break;
|
||||
}
|
||||
exe_load_func.push_back(instruction);
|
||||
}
|
||||
memcpy(exe_load_args, header.infos.builtin_load_exe_args,
|
||||
sizeof(_3gx_Infos::builtin_load_exe_args));
|
||||
}
|
||||
|
||||
// Load code sections
|
||||
if (!ReadSection(text_section, file, header.executable.code_offset,
|
||||
header.executable.code_size) ||
|
||||
!ReadSection(rodata_section, file, header.executable.rodata_offset,
|
||||
header.executable.rodata_size) ||
|
||||
!ReadSection(data_section, file, header.executable.data_offset,
|
||||
header.executable.data_size)) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
return Map(plg_context, process, kernel);
|
||||
}
|
||||
|
||||
Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process,
|
||||
Kernel::KernelSystem& kernel) {
|
||||
|
||||
// Verify exe load checksum function is available
|
||||
if (exe_load_func.empty() && plg_context.load_exe_func.empty()) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Missing checksum function: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
const std::array<u32, 4> mem_region_sizes = {
|
||||
5 * 1024 * 1024, // 5 MiB
|
||||
2 * 1024 * 1024, // 2 MiB
|
||||
3 * 1024 * 1024, // 3 MiB
|
||||
4 * 1024 * 1024 // 4 MiB
|
||||
};
|
||||
|
||||
// Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
|
||||
// Calculate the sizes of the different memory regions
|
||||
const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()];
|
||||
const u32 exe_size = (sizeof(PluginHeader) + text_section.size() + rodata_section.size() +
|
||||
data_section.size() + header.executable.bss_size + 0x1000) &
|
||||
~0xFFF;
|
||||
|
||||
// Allocate the framebuffer block so that is in the highest FCRAM position possible
|
||||
auto offset_fb =
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->RLinearAllocate(_3GX_fb_size);
|
||||
if (!offset_fb) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
auto backing_memory_fb = kernel.memory.GetFCRAMRef(*offset_fb);
|
||||
Service::PLGLDR::PLG_LDR::SetPluginFBAddr(Memory::FCRAM_PADDR + *offset_fb);
|
||||
std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0);
|
||||
|
||||
auto vma_heap_fb = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size, Kernel::MemoryState::Continuous);
|
||||
ASSERT(vma_heap_fb.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(block_size - _3GX_fb_size);
|
||||
if (!offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
auto backing_memory = kernel.memory.GetFCRAMRef(*offset);
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + block_size - _3GX_fb_size, 0);
|
||||
|
||||
// Then we map part of the memory, which contains the executable
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size,
|
||||
Kernel::MemoryState::Continuous);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Write text section
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
|
||||
text_section.data(), header.executable.code_size);
|
||||
// Write rodata section
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
|
||||
rodata_section.data(), header.executable.rodata_size);
|
||||
// Write data section
|
||||
kernel.memory.WriteBlock(process,
|
||||
_3GX_exe_load_addr + sizeof(PluginHeader) +
|
||||
header.executable.code_size + header.executable.rodata_size,
|
||||
data_section.data(), header.executable.data_size);
|
||||
// Prepare plugin header and write it
|
||||
PluginHeader plugin_header = {0};
|
||||
plugin_header.version = header.version;
|
||||
plugin_header.exe_size = exe_size;
|
||||
plugin_header.heap_VA = _3GX_heap_load_addr;
|
||||
plugin_header.heap_size = block_size - exe_size;
|
||||
plg_context.plg_event = _3GX_exe_load_addr - 0x4;
|
||||
plg_context.plg_reply = _3GX_exe_load_addr - 0x8;
|
||||
plugin_header.plgldr_event = plg_context.plg_event;
|
||||
plugin_header.plgldr_reply = plg_context.plg_reply;
|
||||
plugin_header.is_default_plugin = plg_context.is_default_path;
|
||||
if (plg_context.use_user_load_parameters) {
|
||||
memcpy(plugin_header.config, plg_context.user_load_parameters.config,
|
||||
sizeof(PluginHeader::config));
|
||||
}
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr, &plugin_header, sizeof(PluginHeader));
|
||||
|
||||
// Map plugin heap
|
||||
auto backing_memory_heap = kernel.memory.GetFCRAMRef(*offset + exe_size);
|
||||
|
||||
// Map the rest of the memory at the heap location
|
||||
auto vma_heap = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap,
|
||||
block_size - exe_size - _3GX_fb_size, Kernel::MemoryState::Continuous);
|
||||
ASSERT(vma_heap.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto bootloader_offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(bootloader_memory_size);
|
||||
if (!bootloader_offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->Free(*offset, block_size - _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
const bool use_internal = plg_context.load_exe_func.empty();
|
||||
MapBootloader(
|
||||
process, kernel, *bootloader_offset,
|
||||
(use_internal) ? exe_load_func : plg_context.load_exe_func,
|
||||
(use_internal) ? exe_load_args : plg_context.load_exe_args,
|
||||
header.executable.code_size + header.executable.rodata_size + header.executable.data_size,
|
||||
header.infos.exe_load_checksum,
|
||||
plg_context.use_user_load_parameters ? plg_context.user_load_parameters.no_flash : 0);
|
||||
|
||||
plg_context.plugin_loaded = true;
|
||||
plg_context.use_user_load_parameters = false;
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
u32 memory_offset,
|
||||
const std::vector<u32>& exe_load_func,
|
||||
const u32_le* exe_load_args, u32 checksum_size,
|
||||
u32 exe_checksum, bool no_flash) {
|
||||
|
||||
u32_le game_instructions[2];
|
||||
kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr, game_instructions,
|
||||
sizeof(u32) * 2);
|
||||
|
||||
std::array<u32_le, g_plugin_loader_bootloader.size() / sizeof(u32)> bootloader;
|
||||
memcpy(bootloader.data(), g_plugin_loader_bootloader.data(), g_plugin_loader_bootloader.size());
|
||||
|
||||
for (auto it = bootloader.begin(); it < bootloader.end(); it++) {
|
||||
switch (static_cast<u32>(*it)) {
|
||||
case 0xDEAD0000: {
|
||||
*it = game_instructions[0];
|
||||
} break;
|
||||
case 0xDEAD0001: {
|
||||
*it = game_instructions[1];
|
||||
} break;
|
||||
case 0xDEAD0002: {
|
||||
*it = process.codeset->CodeSegment().addr;
|
||||
} break;
|
||||
case 0xDEAD0003: {
|
||||
for (u32 i = 0;
|
||||
i <
|
||||
sizeof(Service::PLGLDR::PLG_LDR::PluginLoaderContext::load_exe_args) / sizeof(u32);
|
||||
i++) {
|
||||
bootloader[i + (it - bootloader.begin())] = exe_load_args[i];
|
||||
}
|
||||
} break;
|
||||
case 0xDEAD0004: {
|
||||
*it = _3GX_exe_load_addr + sizeof(PluginHeader);
|
||||
} break;
|
||||
case 0xDEAD0005: {
|
||||
*it = _3GX_exe_load_addr + sizeof(PluginHeader) + checksum_size;
|
||||
} break;
|
||||
case 0xDEAD0006: {
|
||||
*it = exe_checksum;
|
||||
} break;
|
||||
case 0xDEAD0007: {
|
||||
*it = _3GX_exe_load_addr - 0xC;
|
||||
} break;
|
||||
case 0xDEAD0008: {
|
||||
*it = _3GX_exe_load_addr + sizeof(PluginHeader);
|
||||
} break;
|
||||
case 0xDEAD0009: {
|
||||
*it = no_flash ? 1 : 0;
|
||||
} break;
|
||||
case 0xDEAD000A: {
|
||||
for (u32 i = 0; i < exe_load_func.size(); i++) {
|
||||
bootloader[i + (it - bootloader.begin())] = exe_load_func[i];
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Map bootloader to the offset provided
|
||||
auto backing_memory = kernel.memory.GetFCRAMRef(memory_offset);
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + bootloader_memory_size, 0);
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr - bootloader_memory_size,
|
||||
backing_memory, bootloader_memory_size,
|
||||
Kernel::MemoryState::Continuous);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Write bootloader
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr - bootloader_memory_size, bootloader.data(),
|
||||
std::min<size_t>(bootloader.size() * sizeof(u32), bootloader_memory_size));
|
||||
|
||||
game_instructions[0] = 0xE51FF004; // ldr pc, [pc, #-4]
|
||||
game_instructions[1] = _3GX_exe_load_addr - bootloader_memory_size;
|
||||
kernel.memory.WriteBlock(process, process.codeset->CodeSegment().addr, game_instructions,
|
||||
sizeof(u32) * 2);
|
||||
}
|
149
src/core/file_sys/plugin_3gx.h
Normal file
149
src/core/file_sys/plugin_3gx.h
Normal file
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/file_sys/archive_backend.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
}
|
||||
|
||||
namespace FileUtil {
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class FileBackend;
|
||||
|
||||
class Plugin3GXLoader {
|
||||
public:
|
||||
Loader::ResultStatus Load(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context,
|
||||
Kernel::Process& process, Kernel::KernelSystem& kernel);
|
||||
|
||||
struct PluginHeader {
|
||||
u32_le magic;
|
||||
u32_le version;
|
||||
u32_le heap_VA;
|
||||
u32_le heap_size;
|
||||
u32_le exe_size; // Include sizeof(PluginHeader) + .text + .rodata + .data + .bss (0x1000
|
||||
// aligned too)
|
||||
u32_le is_default_plugin;
|
||||
u32_le plgldr_event; ///< Used for synchronization, unused in citra
|
||||
u32_le plgldr_reply; ///< Used for synchronization, unused in citra
|
||||
u32_le reserved[24];
|
||||
u32_le config[32];
|
||||
};
|
||||
|
||||
static_assert(sizeof(PluginHeader) == 0x100, "Invalid plugin header size");
|
||||
|
||||
static constexpr const char* _3GX_magic = "3GX$0002";
|
||||
static constexpr u32 _3GX_exe_load_addr = 0x07000000;
|
||||
static constexpr u32 _3GX_heap_load_addr = 0x06000000;
|
||||
static constexpr u32 _3GX_fb_size = 0xA9000;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus Map(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context,
|
||||
Kernel::Process& process, Kernel::KernelSystem& kernel);
|
||||
|
||||
static constexpr size_t bootloader_memory_size = 0x1000;
|
||||
static void MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
u32 memory_offset, const std::vector<u32>& exe_load_func,
|
||||
const u32_le* exe_load_args, u32 checksum_size, u32 exe_checksum,
|
||||
bool no_flash);
|
||||
|
||||
struct _3gx_Infos {
|
||||
enum class Compatibility { CONSOLE = 0, CITRA = 1, CONSOLE_CITRA = 2 };
|
||||
u32_le author_len;
|
||||
u32_le author_msg_offset;
|
||||
u32_le title_len;
|
||||
u32_le title_msg_offset;
|
||||
u32_le summary_len;
|
||||
u32_le summary_msg_offset;
|
||||
u32_le description_len;
|
||||
u32_le description_msg_offset;
|
||||
union {
|
||||
u32_le raw;
|
||||
BitField<0, 1, u32_le> embedded_exe_func;
|
||||
BitField<1, 1, u32_le> embedded_swap_func;
|
||||
BitField<2, 2, u32_le> memory_region_size;
|
||||
BitField<4, 2, u32_le> compatibility;
|
||||
} flags;
|
||||
u32_le exe_load_checksum;
|
||||
u32_le builtin_load_exe_args[4];
|
||||
u32_le builtin_swap_load_args[4];
|
||||
};
|
||||
|
||||
struct _3gx_Targets {
|
||||
u32_le count;
|
||||
u32_le title_offsets;
|
||||
};
|
||||
|
||||
struct _3gx_Symtable {
|
||||
u32_le nb_symbols;
|
||||
u32_le symbols_offset;
|
||||
u32_le name_table_offset;
|
||||
};
|
||||
|
||||
struct _3gx_Executable {
|
||||
u32_le code_offset;
|
||||
u32_le rodata_offset;
|
||||
u32_le data_offset;
|
||||
u32_le code_size;
|
||||
u32_le rodata_size;
|
||||
u32_le data_size;
|
||||
u32_le bss_size;
|
||||
u32_le exe_load_func_offset; // NOP terminated
|
||||
u32_le swap_save_func_offset; // NOP terminated
|
||||
u32_le swap_load_func_offset; // NOP terminated
|
||||
};
|
||||
|
||||
struct _3gx_Header {
|
||||
u64_le magic;
|
||||
u32_le version;
|
||||
u32_le reserved;
|
||||
_3gx_Infos infos;
|
||||
_3gx_Executable executable;
|
||||
_3gx_Targets targets;
|
||||
_3gx_Symtable symtable;
|
||||
};
|
||||
|
||||
_3gx_Header header;
|
||||
|
||||
std::string author;
|
||||
std::string title;
|
||||
std::string summary;
|
||||
std::string description;
|
||||
|
||||
std::vector<u32> compatible_TID;
|
||||
std::vector<u8> text_section;
|
||||
std::vector<u8> data_section;
|
||||
std::vector<u8> rodata_section;
|
||||
|
||||
std::vector<u32> exe_load_func;
|
||||
u32_le exe_load_args[4];
|
||||
};
|
||||
} // namespace FileSys
|
186
src/core/file_sys/plugin_3gx_bootloader.h
Normal file
186
src/core/file_sys/plugin_3gx_bootloader.h
Normal file
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Plugin bootloader payload
|
||||
// Compiled with https://shell-storm.org/online/Online-Assembler-and-Disassembler/
|
||||
/*
|
||||
; Backup registers
|
||||
|
||||
stmfd sp!, {r0-r12}
|
||||
mrs r0, cpsr
|
||||
stmfd sp!, {r0}
|
||||
|
||||
; Check plugin validity and exit if invalid (also set a flag)
|
||||
|
||||
adr r0, g_plgstartendptr
|
||||
ldr r1, [r0, #4]
|
||||
ldr r0, [r0]
|
||||
adr r2, g_plgloadexeargs
|
||||
mov lr, pc
|
||||
adr pc, g_loadexefunc
|
||||
adr r1, g_loadexechecksum
|
||||
ldr r1, [r1]
|
||||
cmp r0, r1
|
||||
adr r0, g_plgldrlaunchstatus
|
||||
ldr r0, [r0]
|
||||
moveq r1, #1
|
||||
movne r1, #0
|
||||
str r1, [r0]
|
||||
svcne 0x3
|
||||
|
||||
; Flash top screen light blue
|
||||
|
||||
adr r0, g_plgnoflash
|
||||
ldrb r0, [r0]
|
||||
cmp r0, #1
|
||||
beq skipflash
|
||||
ldr r4, =0x90202204
|
||||
ldr r5, =0x01FF9933
|
||||
mov r6, #64
|
||||
flashloop:
|
||||
str r5, [r4]
|
||||
ldr r0, =0xFF4B40
|
||||
mov r1, #0
|
||||
svc 0xA
|
||||
subs r6, r6, #1
|
||||
bne flashloop
|
||||
str r6, [r4]
|
||||
skipflash:
|
||||
|
||||
; Set all memory regions to RWX
|
||||
|
||||
ldr r0, =0xFFFF8001
|
||||
mov r1, #1
|
||||
svc 0xB3
|
||||
|
||||
; Restore instructions at entrypoint
|
||||
|
||||
adr r0, g_savedGameInstr
|
||||
adr r1, g_gameentrypoint
|
||||
ldr r1, [r1]
|
||||
ldr r2, [r0]
|
||||
str r2, [r1]
|
||||
ldr r2, [r0, #4]
|
||||
str r2, [r1, #4]
|
||||
svc 0x94
|
||||
|
||||
; Launch the plugin
|
||||
|
||||
adr r0, g_savedGameInstr
|
||||
push {r0}
|
||||
adr r5, g_plgentrypoint
|
||||
ldr r5, [r5]
|
||||
blx r5
|
||||
add sp, sp, #4
|
||||
|
||||
; Restore registers and return to the game
|
||||
|
||||
ldmfd sp!, {r0}
|
||||
msr cpsr, r0
|
||||
ldmfd sp!, {r0-r12}
|
||||
adr lr, g_gameentrypoint
|
||||
ldr pc, [lr]
|
||||
|
||||
.pool
|
||||
|
||||
g_savedGameInstr:
|
||||
.word 0xDEAD0000, 0xDEAD0001
|
||||
g_gameentrypoint:
|
||||
.word 0xDEAD0002
|
||||
g_plgloadexeargs:
|
||||
.word 0xDEAD0003, 0, 0, 0
|
||||
g_plgstartendptr:
|
||||
.word 0xDEAD0004, 0xDEAD0005
|
||||
g_loadexechecksum:
|
||||
.word 0xDEAD0006
|
||||
g_plgldrlaunchstatus:
|
||||
.word 0xDEAD0007
|
||||
g_plgentrypoint:
|
||||
.word 0xDEAD0008
|
||||
g_plgnoflash:
|
||||
.word 0xDEAD0009
|
||||
g_loadexefunc:
|
||||
.word 0xDEAD000A
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
bx lr
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
|
||||
constexpr std::array<u8, 412> g_plugin_loader_bootloader = {
|
||||
0xff, 0x1f, 0x2d, 0xe9, 0x00, 0x00, 0x0f, 0xe1, 0x01, 0x00, 0x2d, 0xe9, 0xf0, 0x00, 0x8f, 0xe2,
|
||||
0x04, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x90, 0xe5, 0xd4, 0x20, 0x8f, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1,
|
||||
0xf4, 0xf0, 0x8f, 0xe2, 0xe0, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x01, 0x00, 0x50, 0xe1,
|
||||
0xd8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0x90, 0xe5, 0x01, 0x10, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0x13,
|
||||
0x00, 0x10, 0x80, 0xe5, 0x03, 0x00, 0x00, 0x1f, 0xc8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0xd0, 0xe5,
|
||||
0x01, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x78, 0x40, 0x9f, 0xe5, 0x78, 0x50, 0x9f, 0xe5,
|
||||
0x40, 0x60, 0xa0, 0xe3, 0x00, 0x50, 0x84, 0xe5, 0x70, 0x00, 0x9f, 0xe5, 0x00, 0x10, 0xa0, 0xe3,
|
||||
0x0a, 0x00, 0x00, 0xef, 0x01, 0x60, 0x56, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x00, 0x60, 0x84, 0xe5,
|
||||
0x5c, 0x00, 0x9f, 0xe5, 0x01, 0x10, 0xa0, 0xe3, 0xb3, 0x00, 0x00, 0xef, 0x54, 0x00, 0x8f, 0xe2,
|
||||
0x58, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x00, 0x20, 0x90, 0xe5, 0x00, 0x20, 0x81, 0xe5,
|
||||
0x04, 0x20, 0x90, 0xe5, 0x04, 0x20, 0x81, 0xe5, 0x94, 0x00, 0x00, 0xef, 0x34, 0x00, 0x8f, 0xe2,
|
||||
0x04, 0x00, 0x2d, 0xe5, 0x58, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x35, 0xff, 0x2f, 0xe1,
|
||||
0x04, 0xd0, 0x8d, 0xe2, 0x01, 0x00, 0xbd, 0xe8, 0x00, 0xf0, 0x29, 0xe1, 0xff, 0x1f, 0xbd, 0xe8,
|
||||
0x18, 0xe0, 0x8f, 0xe2, 0x00, 0xf0, 0x9e, 0xe5, 0x04, 0x22, 0x20, 0x90, 0x33, 0x99, 0xff, 0x01,
|
||||
0x40, 0x4b, 0xff, 0x00, 0x01, 0x80, 0xff, 0xff, 0x00, 0x00, 0xad, 0xde, 0x01, 0x00, 0xad, 0xde,
|
||||
0x02, 0x00, 0xad, 0xde, 0x03, 0x00, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xad, 0xde, 0x05, 0x00, 0xad, 0xde, 0x06, 0x00, 0xad, 0xde,
|
||||
0x07, 0x00, 0xad, 0xde, 0x08, 0x00, 0xad, 0xde, 0x09, 0x00, 0xad, 0xde, 0x0a, 0x00, 0xad, 0xde,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x1e, 0xff, 0x2f, 0xe1};
|
Loading…
Add table
Add a link
Reference in a new issue