mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	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