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:
PabloMK7 2022-12-11 09:08:58 +01:00 committed by GitHub
parent 48ee112ceb
commit 016ce6c286
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1911 additions and 42 deletions

View 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);
}

View 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

View 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};