diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java index 6107dd59b..d3bf9e5e1 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java @@ -192,11 +192,15 @@ public final class SettingsFragmentPresenter { Setting language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE); Setting systemClock = systemSection.getSetting(SettingsFile.KEY_INIT_CLOCK); Setting dateTime = systemSection.getSetting(SettingsFile.KEY_INIT_TIME); + Setting pluginLoader = systemSection.getSetting(SettingsFile.KEY_PLUGIN_LOADER); + Setting allowPluginLoader = systemSection.getSetting(SettingsFile.KEY_ALLOW_PLUGIN_LOADER); sl.add(new SingleChoiceSetting(SettingsFile.KEY_REGION_VALUE, Settings.SECTION_SYSTEM, R.string.emulated_region, 0, R.array.regionNames, R.array.regionValues, -1, region)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_LANGUAGE, Settings.SECTION_SYSTEM, R.string.emulated_language, 0, R.array.languageNames, R.array.languageValues, 1, language)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_INIT_CLOCK, Settings.SECTION_SYSTEM, R.string.init_clock, R.string.init_clock_description, R.array.systemClockNames, R.array.systemClockValues, 0, systemClock)); sl.add(new DateTimeSetting(SettingsFile.KEY_INIT_TIME, Settings.SECTION_SYSTEM, R.string.init_time, R.string.init_time_description, "2000-01-01 00:00:01", dateTime)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_PLUGIN_LOADER, Settings.SECTION_SYSTEM, R.string.plugin_loader, R.string.plugin_loader_description, false, pluginLoader)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_ALLOW_PLUGIN_LOADER, Settings.SECTION_SYSTEM, R.string.allow_plugin_loader, R.string.allow_plugin_loader_description, true, allowPluginLoader)); } private void addCameraSettings(ArrayList sl) { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java index 8ae6b70d7..c73f45d2e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java @@ -78,6 +78,8 @@ public final class SettingsFile { public static final String KEY_IS_NEW_3DS = "is_new_3ds"; public static final String KEY_REGION_VALUE = "region_value"; public static final String KEY_LANGUAGE = "language"; + public static final String KEY_PLUGIN_LOADER = "plugin_loader"; + public static final String KEY_ALLOW_PLUGIN_LOADER = "allow_plugin_loader"; public static final String KEY_INIT_CLOCK = "init_clock"; public static final String KEY_INIT_TIME = "init_time"; diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index e511b438e..768152c77 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -229,6 +229,10 @@ void Config::ReadValues() { std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch()) .count(); } + Settings::values.plugin_loader_enabled = + sdl2_config->GetBoolean("System", "plugin_loader", false); + Settings::values.allow_plugin_loader = + sdl2_config->GetBoolean("System", "allow_plugin_loader", true); // Camera using namespace Service::CAM; diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index b07e13a39..2fcbe9292 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -281,6 +281,11 @@ init_clock = # Note: 3DS can only handle times later then Jan 1 2000 init_time = +# Plugin loader state, if enabled plugins will be loaded from the SD card. +# You can also set if homebrew apps are allowed to enable the plugin loader +plugin_loader = +allow_plugin_loader = + [Camera] # Which camera engine to use for the right outer camera # blank: a dummy camera that always returns black image diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 448d90e87..cd64b9d6c 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -37,6 +37,10 @@ Si el \"Tipo del reloj del sistema\" está en \"Reloj emulado\", ésto cambia la fecha y hora de inicio. Región emulada Idioma emulado + Activar \"3GX Plugin Loader\" + Carga \"3GX plugins\" de la SD emulada si están disponibles. + Permiter que apps cambien el estado del \"plugin loader\" + Permite a las aplicaciones homebrew activar el \"plugin loader\" incluso si está desactivado. Cámara interior diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 8f741a354..b72357eb0 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -51,6 +51,10 @@ If the \"System clock type\" setting is set to \"Simulated clock\", this changes the fixed date and time to start at. Emulated region Emulated language + Enable 3GX Plugin Loader + Loads 3GX plugins from the emulated SD if they are available. + Allow apps to change plugin loader state + Allow homebrew apps to enable the plugin loader even when it is disabled. Inner Camera diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 7b8323593..212bc351f 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -657,6 +657,8 @@ void Config::ReadSystemValues() { ReadBasicSetting(Settings::values.init_clock); ReadBasicSetting(Settings::values.init_time); ReadBasicSetting(Settings::values.init_time_offset); + ReadBasicSetting(Settings::values.plugin_loader_enabled); + ReadBasicSetting(Settings::values.allow_plugin_loader); } qt_config->endGroup(); @@ -1131,6 +1133,8 @@ void Config::SaveSystemValues() { WriteBasicSetting(Settings::values.init_clock); WriteBasicSetting(Settings::values.init_time); WriteBasicSetting(Settings::values.init_time_offset); + WriteBasicSetting(Settings::values.plugin_loader_enabled); + WriteBasicSetting(Settings::values.allow_plugin_loader); } qt_config->endGroup(); diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index cfde68c2d..e47b0db96 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -308,6 +308,8 @@ void ConfigureSystem::SetConfiguration() { ui->clock_display_label->setText( QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue())); ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.GetValue()); + ui->plugin_loader->setChecked(Settings::values.plugin_loader_enabled.GetValue()); + ui->allow_plugin_loader->setChecked(Settings::values.allow_plugin_loader.GetValue()); } void ConfigureSystem::ReadSystemSettings() { @@ -411,6 +413,10 @@ void ConfigureSystem::ApplyConfiguration() { } Settings::values.init_time_offset = time_offset_days + time_offset_time; + Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked(); + + Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked()); + Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked()); } ConfigurationShared::ApplyPerGameSetting( @@ -520,6 +526,13 @@ void ConfigureSystem::SetupPerGameUI() { ui->edit_init_time_offset_days->setVisible(false); ui->edit_init_time_offset_time->setVisible(false); ui->button_regenerate_console_id->setVisible(false); + // Apps can change the state of the plugin loader, so plugins load + // to a chainloaded app with specific parameters. Don't allow + // the plugin loader state to be configured per-game as it may + // mess things up. + ui->label_plugin_loader->setVisible(false); + ui->plugin_loader->setVisible(false); + ui->allow_plugin_loader->setVisible(false); connect(ui->clock_speed_combo, qOverload(&QComboBox::activated), this, [this](int index) { ui->slider_clock_speed->setEnabled(index == 1); diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index b2bb618b3..0dae06b70 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -340,6 +340,27 @@ + + + + 3GX Plugin Loader: + + + + + + + Enable 3GX plugin loader + + + + + + + Allow games to change plugin loader state + + + diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index fdc702521..c01c8a376 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -226,6 +226,7 @@ void DebuggerBackend::Write(const Entry& entry) { SUB(Service, IR) \ SUB(Service, Y2R) \ SUB(Service, PS) \ + SUB(Service, PLGLDR) \ CLS(HW) \ SUB(HW, Memory) \ SUB(HW, LCD) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 59862537b..1109ce7c3 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -93,6 +93,7 @@ enum class Class : ClassType { Service_IR, ///< The IR service Service_Y2R, ///< The Y2R (YUV to RGB conversion) service Service_PS, ///< The PS (Process) service + Service_PLGLDR, ///< The PLGLDR (plugin loader) service HW, ///< Low-level hardware emulation HW_Memory, ///< Memory-map and address translation HW_LCD, ///< LCD register emulation diff --git a/src/common/settings.cpp b/src/common/settings.cpp index f6688e1e5..15eda8ab8 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -14,6 +14,7 @@ #include "core/hle/service/ir/ir_rst.h" #include "core/hle/service/ir/ir_user.h" #include "core/hle/service/mic_u.h" +#include "core/hle/service/plgldr/plgldr.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -70,6 +71,9 @@ void Apply() { Service::MIC::ReloadMic(system); } + + Service::PLGLDR::PLG_LDR::SetEnabled(values.plugin_loader_enabled.GetValue()); + Service::PLGLDR::PLG_LDR::SetAllowGameChangeState(values.allow_plugin_loader.GetValue()); } void LogSettings() { @@ -136,6 +140,8 @@ void LogSettings() { } log_setting("System_IsNew3ds", values.is_new_3ds.GetValue()); log_setting("System_RegionValue", values.region_value.GetValue()); + log_setting("System_PluginLoader", values.plugin_loader_enabled.GetValue()); + log_setting("System_PluginLoaderAllowed", values.allow_plugin_loader.GetValue()); log_setting("Debugging_UseGdbstub", values.use_gdbstub.GetValue()); log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue()); } diff --git a/src/common/settings.h b/src/common/settings.h index 38f69a97e..e96779fea 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -432,6 +432,8 @@ struct Values { Setting init_clock{InitClock::SystemTime, "init_clock"}; Setting init_time{946681277ULL, "init_time"}; Setting init_time_offset{0, "init_time_offset"}; + Setting plugin_loader_enabled{false, "plugin_loader"}; + Setting allow_plugin_loader{true, "allow_plugin_loader"}; // Renderer Setting use_gles{false, "use_gles"}; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fb9b78600..3575b048e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -80,6 +80,9 @@ add_library(core STATIC file_sys/patch.h file_sys/path_parser.cpp file_sys/path_parser.h + file_sys/plugin_3gx.cpp + file_sys/plugin_3gx.h + file_sys/plugin_3gx_bootloader.h file_sys/romfs_reader.cpp file_sys/romfs_reader.h file_sys/savedata_archive.cpp @@ -365,6 +368,8 @@ add_library(core STATIC hle/service/nwm/uds_connection.h hle/service/nwm/uds_data.cpp hle/service/nwm/uds_data.h + hle/service/plgldr/plgldr.cpp + hle/service/plgldr/plgldr.h hle/service/pm/pm.cpp hle/service/pm/pm.h hle/service/pm/pm_app.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 6d2bd6682..2249a41aa 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -473,6 +473,10 @@ const Kernel::KernelSystem& System::Kernel() const { return *kernel; } +bool System::KernelRunning() { + return kernel != nullptr; +} + Timing& System::CoreTiming() { return *timing; } diff --git a/src/core/core.h b/src/core/core.h index bd430a66f..79cbc3f85 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -234,6 +234,9 @@ public: /// Gets a const reference to the kernel [[nodiscard]] const Kernel::KernelSystem& Kernel() const; + /// Get kernel is running + [[nodiscard]] bool KernelRunning(); + /// Gets a reference to the timing system [[nodiscard]] Timing& CoreTiming(); diff --git a/src/core/file_sys/plugin_3gx.cpp b/src/core/file_sys/plugin_3gx.cpp new file mode 100644 index 000000000..e59d39090 --- /dev/null +++ b/src/core/file_sys/plugin_3gx.cpp @@ -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_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& 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 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(_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 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(raw_TID_data.data() + i * sizeof(u32)))); + } + } + + if (!compatible_TID.empty() && + std::find(compatible_TID.begin(), compatible_TID.end(), + static_cast(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 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(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 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& 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 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(*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(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); +} diff --git a/src/core/file_sys/plugin_3gx.h b/src/core/file_sys/plugin_3gx.h new file mode 100644 index 000000000..763a0395a --- /dev/null +++ b/src/core/file_sys/plugin_3gx.h @@ -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 +#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& 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 compatible_TID; + std::vector text_section; + std::vector data_section; + std::vector rodata_section; + + std::vector exe_load_func; + u32_le exe_load_args[4]; +}; +} // namespace FileSys diff --git a/src/core/file_sys/plugin_3gx_bootloader.h b/src/core/file_sys/plugin_3gx_bootloader.h new file mode 100644 index 000000000..7094c5b12 --- /dev/null +++ b/src/core/file_sys/plugin_3gx_bootloader.h @@ -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 +#include "common/common_types.h" + +constexpr std::array 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}; diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 5d595f674..d37363795 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -82,6 +82,20 @@ enum class MemoryRegion : u16 { BASE = 3, }; +union CoreVersion { + CoreVersion(u32 version) : raw(version) {} + CoreVersion(u32 major_ver, u32 minor_ver, u32 revision_ver) { + revision.Assign(revision_ver); + minor.Assign(minor_ver); + major.Assign(major_ver); + } + + u32 raw; + BitField<8, 8, u32> revision; + BitField<16, 8, u32> minor; + BitField<24, 8, u32> major; +}; + class KernelSystem { public: explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index cdef9d996..5ef9de3d4 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -245,6 +245,25 @@ std::optional MemoryRegionInfo::LinearAllocate(u32 size) { return std::nullopt; } +std::optional MemoryRegionInfo::RLinearAllocate(u32 size) { + ASSERT(!is_locked); + + // Find the first sufficient continuous block from the upper address + for (auto iter = free_blocks.rbegin(); iter != free_blocks.rend(); ++iter) { + auto interval = *iter; + ASSERT(interval.bounds() == boost::icl::interval_bounds::right_open()); + if (interval.upper() - interval.lower() >= size) { + Interval allocated(interval.upper() - size, interval.upper()); + free_blocks -= allocated; + used += size; + return allocated.lower(); + } + } + + // No sufficient block found + return std::nullopt; +} + void MemoryRegionInfo::Free(u32 offset, u32 size) { if (is_locked) { return; diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h index 9fc5f0a1f..0a7ddd166 100644 --- a/src/core/hle/kernel/memory.h +++ b/src/core/hle/kernel/memory.h @@ -60,6 +60,14 @@ struct MemoryRegionInfo { */ std::optional LinearAllocate(u32 size); + /** + * Allocates memory from the linear heap with only size specified. + * @param size size of the memory to allocate. + * @returns the address offset to the found block, searching from the end of FCRAM; null if + * there is no enough space + */ + std::optional RLinearAllocate(u32 size); + /** * Frees one segment of memory. The memory must have been allocated as heap or linear heap. * @param offset the region address offset to the beginning of FCRAM. diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index a268a0dc2..e2b905e8a 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -12,12 +12,15 @@ #include "common/common_funcs.h" #include "common/logging/log.h" #include "common/serialization/boost_vector.hpp" +#include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/vm_manager.h" +#include "core/hle/service/plgldr/plgldr.h" +#include "core/loader/loader.h" #include "core/memory.h" SERIALIZE_EXPORT_IMPL(Kernel::Process) @@ -36,6 +39,7 @@ void Process::serialize(Archive& ar, const unsigned int file_version) { ar&(boost::container::vector>&)address_mappings; ar& flags.raw; + ar& no_thread_restrictions; ar& kernel_version; ar& ideal_processor; ar& status; @@ -186,12 +190,24 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { kernel.HandleSpecialMapping(vm_manager, mapping); } + auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance()); + if (plgldr) { + plgldr->OnProcessRun(*this, kernel); + } + status = ProcessStatus::Running; vm_manager.LogLayout(Log::Level::Debug); Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this)); } +void Process::Exit() { + auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance()); + if (plgldr) { + plgldr->OnProcessExit(*this, kernel); + } +} + VAddr Process::GetLinearHeapAreaAddress() const { // Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of // the extra RAM in the n3DS. @@ -449,7 +465,7 @@ ResultCode Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission pe } Kernel::Process::Process(KernelSystem& kernel) - : Object(kernel), handle_table(kernel), vm_manager(kernel.memory), kernel(kernel) { + : Object(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) { kernel.memory.RegisterPageTable(vm_manager.page_table); } Kernel::Process::~Process() { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 132570cc8..bb4fe6c12 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -174,6 +174,7 @@ public: /// processes access to specific I/O regions and device memory. boost::container::static_vector address_mappings; ProcessFlags flags; + bool no_thread_restrictions = false; /// Kernel compatibility version for this process u16 kernel_version = 0; /// The default CPU for this process, threads are scheduled on this cpu by default. @@ -200,6 +201,11 @@ public: */ void Run(s32 main_thread_priority, u32 stack_size); + /** + * Called when the process exits by svc + */ + void Exit(); + /////////////////////////////////////////////////////////////////////////////////////////////// // Memory Management diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index ed86f2e49..608d95fea 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -38,6 +38,8 @@ #include "core/hle/kernel/wait_object.h" #include "core/hle/lock.h" #include "core/hle/result.h" +#include "core/hle/service/plgldr/plgldr.h" +#include "core/hle/service/service.h" namespace Kernel { @@ -65,6 +67,15 @@ struct MemoryInfo { u32 state; }; +/// Values accepted by svcKernelSetState, only the known values are listed +/// (the behaviour of other values are known, but their purpose is unclear and irrelevant). +enum class KernelState { + /** + * Reboots the console + */ + KERNEL_STATE_REBOOT = 7, +}; + struct PageInfo { u32 flags; }; @@ -85,6 +96,11 @@ enum class SystemInfoType { * For the ARM11 NATIVE_FIRM kernel, this is 5, for processes sm, fs, pm, loader, and pxi." */ KERNEL_SPAWNED_PIDS = 26, + /** + * Check if the current system is a new 3DS. This parameter is not available on real systems, + * but can be used by homebrew applications. + */ + NEW_3DS_INFO = 0x10001, /** * Gets citra related information. This parameter is not available on real systems, * but can be used by homebrew applications to get some emulator info. @@ -92,6 +108,134 @@ enum class SystemInfoType { CITRA_INFORMATION = 0x20000, }; +enum class ProcessInfoType { + /** + * Returns the amount of private (code, data, regular heap) and shared memory used by the + * process + total supervisor-mode stack size + page-rounded size of the external handle table. + * This is the amount of physical memory the process is using, minus TLS, main thread stack and + * linear memory. + */ + PRIVATE_AND_SHARED_USED_MEMORY = 0, + + /** + * Returns the amount of + total supervisor-mode stack size + + * page-rounded size of the external handle table. + */ + SUPERVISOR_AND_HANDLE_USED_MEMORY = 1, + + /** + * Returns the amount of private (code, data, heap) memory used by the process + total + * supervisor-mode stack size + page-rounded size of the external handle table. + */ + PRIVATE_SHARED_SUPERVISOR_HANDLE_USED_MEMORY = 2, + + /** + * Returns the amount of + total supervisor-mode stack size + + * page-rounded size of the external handle table. + */ + SUPERVISOR_AND_HANDLE_USED_MEMORY2 = 3, + + /** + * Returns the amount of handles in use by the process. + */ + USED_HANDLE_COUNT = 4, + + /** + * Returns the highest count of handles that have been open at once by the process. + */ + HIGHEST_HANDLE_COUNT = 5, + + /** + * Returns *(u32*)(KProcess+0x234) which is always 0. + */ + KPROCESS_0X234 = 6, + + /** + * Returns the number of threads of the process. + */ + THREAD_COUNT = 7, + + /** + * Returns the maximum number of threads which can be opened by this process (always 0). + */ + MAX_THREAD_AMOUNT = 8, + + /** + * Originally this only returned 0xD8E007ED. Now with v11.3 this returns the memregion for the + * process: out low u32 = KProcess "Kernel flags from the exheader kernel descriptors" & 0xF00 + * (memory region flag). High out u32 = 0. + */ + MEMORY_REGION_FLAGS = 19, + + /** + * Low u32 = (0x20000000 - ). That is, the output + * value is the value which can be added to LINEAR memory vaddrs for converting to + * physical-memory addrs. + */ + LINEAR_BASE_ADDR_OFFSET = 20, + + /** + * Returns the VA -> PA conversion offset for the QTM static mem block reserved in the exheader + * (0x800000), otherwise 0 (+ error 0xE0E01BF4) if it doesn't exist. + */ + QTM_MEMORY_BLOCK_CONVERSION_OFFSET = 21, + + /** + * Returns the base VA of the QTM static mem block reserved in the exheader, otherwise 0 (+ + * error 0xE0E01BF4) if it doesn't exist. + */ + QTM_MEMORY_ADDRESS = 22, + + /** + * Returns the size of the QTM static mem block reserved in the exheader, otherwise 0 (+ error + * 0xE0E01BF4) if it doesn't exist. + */ + QTM_MEMORY_SIZE = 23, + + // Custom values used by Luma3DS and 3GX plugins + + /** + * Returns the process name. + */ + LUMA_CUSTOM_PROCESS_NAME = 0x10000, + + /** + * Returns the process title ID. + */ + LUMA_CUSTOM_PROCESS_TITLE_ID = 0x10001, + + /** + * Returns the codeset text size. + */ + LUMA_CUSTOM_TEXT_SIZE = 0x10002, + + /** + * Returns the codeset rodata size. + */ + LUMA_CUSTOM_RODATA_SIZE = 0x10003, + + /** + * Returns the codeset data size. + */ + LUMA_CUSTOM_DATA_SIZE = 0x10004, + + /** + * Returns the codeset text vaddr. + */ + LUMA_CUSTOM_TEXT_ADDR = 0x10005, + + /** + * Returns the codeset rodata vaddr. + */ + LUMA_CUSTOM_RODATA_ADDR = 0x10006, + + /** + * Returns the codeset data vaddr. + */ + LUMA_CUSTOM_DATA_ADDR = 0x10007, + +}; + /** * Accepted by svcGetSystemInfo param with REGION_MEMORY_USAGE type. Selects a region to query * memory usage of. @@ -121,6 +265,73 @@ enum class SystemInfoCitraInformation { BUILD_GIT_DESCRIPTION_PART2 = 41, // Git description (commit) last 7 characters. }; +/** + * Accepted by the custom svcControlProcess. + */ +enum class ControlProcessOP { + /** + * List all handles of the process, varg3 can be either 0 to fetch + * all handles, or token of the type to fetch s32 count = + * svcControlProcess(handle, PROCESSOP_GET_ALL_HANDLES, + * (u32)&outBuf, 0) Returns how many handles were found + */ + PROCESSOP_GET_ALL_HANDLES = 0, + + /** + * Set the whole memory of the process with rwx access (in the mmu + * table only) svcControlProcess(handle, PROCESSOP_SET_MMU_TO_RWX, + * 0, 0) + */ + PROCESSOP_SET_MMU_TO_RWX, + + /** + * Get the handle of an event which will be signaled + * each time the memory layout of this process changes + * svcControlProcess(handle, + * PROCESSOP_GET_ON_MEMORY_CHANGE_EVENT, + * &eventHandleOut, 0) + */ + PROCESSOP_GET_ON_MEMORY_CHANGE_EVENT, + + /** + * Set a flag to be signaled when the process will be exited + * svcControlProcess(handle, PROCESSOP_SIGNAL_ON_EXIT, 0, 0) + */ + PROCESSOP_SIGNAL_ON_EXIT, + + /** + * Get the physical address of the VAddr within the process + * svcControlProcess(handle, PROCESSOP_GET_PA_FROM_VA, (u32)&PAOut, + * VAddr) + */ + PROCESSOP_GET_PA_FROM_VA, + + /* + * Lock / Unlock the process's threads + * svcControlProcess(handle, PROCESSOP_SCHEDULE_THREADS, lock, + * threadPredicate) lock: 0 to unlock threads, any other value to + * lock threads threadPredicate: can be NULL or a funcptr to a + * predicate (typedef bool (*ThreadPredicate)(KThread *thread);) + * The predicate must return true to operate on the thread + */ + PROCESSOP_SCHEDULE_THREADS, + + /* + * Lock / Unlock the process's threads + * svcControlProcess(handle, PROCESSOP_SCHEDULE_THREADS, lock, + * tlsmagicexclude) lock: 0 to unlock threads, any other value to + * lock threads tlsmagicexclude: do not lock threads with this tls magic + * value + */ + PROCESSOP_SCHEDULE_THREADS_WITHOUT_TLS_MAGIC, + + /** + * Disable any thread creation restrictions, such as priority value + * or allowed cores + */ + PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS, +}; + class SVC : public SVCWrapper { public: SVC(Core::System& system); @@ -174,6 +385,7 @@ private: ResultCode GetThreadId(u32* thread_id, Handle handle); ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count); ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count); + ResultCode KernelSetState(u32 kernel_state, u32 varg1, u32 varg2); ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, Handle process_handle, u32 addr); ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 addr); @@ -196,6 +408,14 @@ private: ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle); ResultCode GetSystemInfo(s64* out, u32 type, s32 param); ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type); + ResultCode GetThreadInfo(s64* out, Handle thread_handle, u32 type); + ResultCode InvalidateInstructionCacheRange(u32 addr, u32 size); + ResultCode InvalidateEntireInstructionCache(); + u32 ConvertVaToPa(u32 addr); + ResultCode MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address, + Handle src_process_handle, u32 src_address, u32 size); + ResultCode UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size); + ResultCode ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 varg3); struct FunctionDef { using Func = void (SVC::*)(); @@ -205,7 +425,7 @@ private: const char* name; }; - static const std::array SVC_Table; + static const std::array SVC_Table; static const FunctionDef* GetSVCInfo(u32 func_num); }; @@ -319,6 +539,8 @@ void SVC::ExitProcess() { thread->Stop(); } + current_process->Exit(); + // Kill the current thread kernel.GetCurrentThreadManager().GetCurrentThread()->Stop(); @@ -920,7 +1142,8 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr std::shared_ptr current_process = kernel.GetCurrentProcess(); std::shared_ptr& resource_limit = current_process->resource_limit; - if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) { + if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority && + !current_process->no_thread_restrictions) { return ERR_NOT_AUTHORIZED; } @@ -945,7 +1168,7 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr // process, exheader kernel-flags bitmask 0x2000 must be set (otherwise error 0xD9001BEA is // returned). When processorid==0x3 and the process is not a BASE mem-region process, error // 0xD9001BEA is returned. These are the only restriction checks done by the kernel for - // processorid. + // processorid. If this is implemented, make sure to check process->no_thread_restrictions. break; default: ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id); @@ -1110,6 +1333,22 @@ ResultCode SVC::ReleaseSemaphore(s32* count, Handle handle, s32 release_count) { return RESULT_SUCCESS; } +/// Sets the kernel state +ResultCode SVC::KernelSetState(u32 kernel_state, u32 varg1, u32 varg2) { + switch (static_cast(kernel_state)) { + + // This triggers a hardware reboot on real console, since this doesn't make sense + // on emulator, we shutdown instead. + case KernelState::KERNEL_STATE_REBOOT: + system.RequestShutdown(); + break; + default: + LOG_ERROR(Kernel_SVC, "Unknown KernelSetState state={} varg1={} varg2={}", kernel_state, + varg1, varg2); + } + return RESULT_SUCCESS; +} + /// Query process memory ResultCode SVC::QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, Handle process_handle, u32 addr) { @@ -1434,6 +1673,12 @@ ResultCode SVC::GetSystemInfo(s64* out, u32 type, s32 param) { case SystemInfoType::KERNEL_SPAWNED_PIDS: *out = 5; break; + case SystemInfoType::NEW_3DS_INFO: + // The actual subtypes are not implemented, homebrew just check + // this doesn't return an error in n3ds to know the system type + LOG_ERROR(Kernel_SVC, "unimplemented GetSystemInfo type=65537 param={}", param); + *out = 0; + return (system.GetNumCores() == 4) ? RESULT_SUCCESS : ERR_INVALID_ENUM_VALUE; case SystemInfoType::CITRA_INFORMATION: switch ((SystemInfoCitraInformation)param) { case SystemInfoCitraInformation::IS_CITRA: @@ -1501,9 +1746,9 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { if (process == nullptr) return ERR_INVALID_HANDLE; - switch (type) { - case 0: - case 2: + switch (static_cast(type)) { + case ProcessInfoType::PRIVATE_AND_SHARED_USED_MEMORY: + case ProcessInfoType::PRIVATE_SHARED_SUPERVISOR_HANDLE_USED_MEMORY: // TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure // what's the difference between them. *out = process->memory_used; @@ -1512,25 +1757,53 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { return ERR_MISALIGNED_SIZE; } break; - case 1: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: + case ProcessInfoType::SUPERVISOR_AND_HANDLE_USED_MEMORY: + case ProcessInfoType::SUPERVISOR_AND_HANDLE_USED_MEMORY2: + case ProcessInfoType::USED_HANDLE_COUNT: + case ProcessInfoType::HIGHEST_HANDLE_COUNT: + case ProcessInfoType::KPROCESS_0X234: + case ProcessInfoType::THREAD_COUNT: + case ProcessInfoType::MAX_THREAD_AMOUNT: // These are valid, but not implemented yet LOG_ERROR(Kernel_SVC, "unimplemented GetProcessInfo type={}", type); break; - case 20: + case ProcessInfoType::LINEAR_BASE_ADDR_OFFSET: *out = Memory::FCRAM_PADDR - process->GetLinearHeapAreaAddress(); break; - case 21: - case 22: - case 23: + case ProcessInfoType::QTM_MEMORY_BLOCK_CONVERSION_OFFSET: + case ProcessInfoType::QTM_MEMORY_ADDRESS: + case ProcessInfoType::QTM_MEMORY_SIZE: // These return a different error value than higher invalid values LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); return ERR_NOT_IMPLEMENTED; + // Here start the custom ones, taken from Luma3DS for 3GX support + case ProcessInfoType::LUMA_CUSTOM_PROCESS_NAME: + // Get process name + strncpy(reinterpret_cast(out), process->codeset->GetName().c_str(), 8); + break; + case ProcessInfoType::LUMA_CUSTOM_PROCESS_TITLE_ID: + // Get process TID + *out = process->codeset->program_id; + break; + case ProcessInfoType::LUMA_CUSTOM_TEXT_SIZE: + *out = process->codeset->CodeSegment().size; + break; + case ProcessInfoType::LUMA_CUSTOM_RODATA_SIZE: + *out = process->codeset->RODataSegment().size; + break; + case ProcessInfoType::LUMA_CUSTOM_DATA_SIZE: + *out = process->codeset->DataSegment().size; + break; + case ProcessInfoType::LUMA_CUSTOM_TEXT_ADDR: + *out = process->codeset->CodeSegment().addr; + break; + case ProcessInfoType::LUMA_CUSTOM_RODATA_ADDR: + *out = process->codeset->RODataSegment().addr; + break; + case ProcessInfoType::LUMA_CUSTOM_DATA_ADDR: + *out = process->codeset->DataSegment().addr; + break; + default: LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); return ERR_INVALID_ENUM_VALUE; @@ -1539,7 +1812,179 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { return RESULT_SUCCESS; } -const std::array SVC::SVC_Table{{ +ResultCode SVC::GetThreadInfo(s64* out, Handle thread_handle, u32 type) { + LOG_TRACE(Kernel_SVC, "called thread=0x{:08X} type={}", thread_handle, type); + + std::shared_ptr thread = + kernel.GetCurrentProcess()->handle_table.Get(thread_handle); + if (thread == nullptr) { + return ERR_INVALID_HANDLE; + } + + switch (type) { + case 0x10000: + *out = static_cast(thread->GetTLSAddress()); + break; + default: + LOG_ERROR(Kernel_SVC, "unknown GetThreadInfo type={}", type); + return ERR_INVALID_ENUM_VALUE; + } + + return RESULT_SUCCESS; +} + +ResultCode SVC::InvalidateInstructionCacheRange(u32 addr, u32 size) { + Core::GetRunningCore().InvalidateCacheRange(addr, size); + return RESULT_SUCCESS; +} + +ResultCode SVC::InvalidateEntireInstructionCache() { + Core::GetRunningCore().ClearInstructionCache(); + return RESULT_SUCCESS; +} + +u32 SVC::ConvertVaToPa(u32 addr) { + auto vma = kernel.GetCurrentProcess()->vm_manager.FindVMA(addr); + if (vma == kernel.GetCurrentProcess()->vm_manager.vma_map.end() || + vma->second.type != VMAType::BackingMemory) { + return 0; + } + return kernel.memory.GetFCRAMOffset(vma->second.backing_memory.GetPtr() + addr - + vma->second.base) + + Memory::FCRAM_PADDR; +} + +ResultCode SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address, + Handle src_process_handle, u32 src_address, u32 size) { + std::shared_ptr dst_process = + kernel.GetCurrentProcess()->handle_table.Get(dst_process_handle); + std::shared_ptr src_process = + kernel.GetCurrentProcess()->handle_table.Get(src_process_handle); + + if (dst_process == nullptr || src_process == nullptr) { + return ERR_INVALID_HANDLE; + } + + if (size & 0xFFF) { + size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE; + } + + // Only linear memory supported + auto vma = src_process->vm_manager.FindVMA(src_address); + if (vma == src_process->vm_manager.vma_map.end() || + vma->second.type != VMAType::BackingMemory || + vma->second.meminfo_state != MemoryState::Continuous) { + return ERR_INVALID_ADDRESS; + } + + u32 offset = src_address - vma->second.base; + if (offset + size > vma->second.size) { + return ERR_INVALID_ADDRESS; + } + + auto vma_res = dst_process->vm_manager.MapBackingMemory( + dst_address, + memory.GetFCRAMRef(vma->second.backing_memory.GetPtr() + offset - + kernel.memory.GetFCRAMPointer(0)), + size, Kernel::MemoryState::Continuous); + + if (!vma_res.Succeeded()) { + return ERR_INVALID_ADDRESS_STATE; + } + dst_process->vm_manager.Reprotect(vma_res.Unwrap(), Kernel::VMAPermission::ReadWriteExecute); + + return RESULT_SUCCESS; +} + +ResultCode SVC::UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size) { + std::shared_ptr dst_process = + kernel.GetCurrentProcess()->handle_table.Get(process); + + if (dst_process == nullptr) { + return ERR_INVALID_HANDLE; + } + + if (size & 0xFFF) { + size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE; + } + + // Only linear memory supported + auto vma = dst_process->vm_manager.FindVMA(dst_address); + if (vma == dst_process->vm_manager.vma_map.end() || + vma->second.type != VMAType::BackingMemory || + vma->second.meminfo_state != MemoryState::Continuous) { + return ERR_INVALID_ADDRESS; + } + + dst_process->vm_manager.UnmapRange(dst_address, size); + return RESULT_SUCCESS; +} + +ResultCode SVC::ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 varg3) { + std::shared_ptr process = + kernel.GetCurrentProcess()->handle_table.Get(process_handle); + + if (process == nullptr) { + return ERR_INVALID_HANDLE; + } + + switch (static_cast(process_OP)) { + case ControlProcessOP::PROCESSOP_SET_MMU_TO_RWX: { + for (auto it = process->vm_manager.vma_map.cbegin(); + it != process->vm_manager.vma_map.cend(); it++) { + if (it->second.meminfo_state != MemoryState::Free) + process->vm_manager.Reprotect(it, Kernel::VMAPermission::ReadWriteExecute); + } + return RESULT_SUCCESS; + } + case ControlProcessOP::PROCESSOP_GET_ON_MEMORY_CHANGE_EVENT: { + auto plgldr = Service::PLGLDR::GetService(system); + if (!plgldr) { + return ERR_NOT_FOUND; + } + + ResultVal out = plgldr->GetMemoryChangedHandle(kernel); + if (out.Failed()) { + return out.Code(); + } + + memory.Write32(varg2, out.Unwrap()); + return RESULT_SUCCESS; + } + case ControlProcessOP::PROCESSOP_SCHEDULE_THREADS_WITHOUT_TLS_MAGIC: { + for (u32 i = 0; i < system.GetNumCores(); i++) { + auto& thread_list = kernel.GetThreadManager(i).GetThreadList(); + for (auto& thread : thread_list) { + if (thread->owner_process.lock() != process) { + continue; + } + if (memory.Read32(thread.get()->GetTLSAddress()) == varg3) { + continue; + } + if (thread.get()->thread_id == + kernel.GetCurrentThreadManager().GetCurrentThread()->thread_id) { + continue; + } + thread.get()->can_schedule = !varg2; + } + } + return RESULT_SUCCESS; + } + case ControlProcessOP::PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS: { + process->no_thread_restrictions = varg2 == 1; + return RESULT_SUCCESS; + } + case ControlProcessOP::PROCESSOP_GET_ALL_HANDLES: + case ControlProcessOP::PROCESSOP_GET_PA_FROM_VA: + case ControlProcessOP::PROCESSOP_SIGNAL_ON_EXIT: + case ControlProcessOP::PROCESSOP_SCHEDULE_THREADS: + default: + LOG_ERROR(Kernel_SVC, "Unknown ControlProcessOp type={}", process_OP); + return ERR_NOT_IMPLEMENTED; + } +} + +const std::array SVC::SVC_Table{{ {0x00, nullptr, "Unknown"}, {0x01, &SVC::Wrap<&SVC::ControlMemory>, "ControlMemory"}, {0x02, &SVC::Wrap<&SVC::QueryMemory>, "QueryMemory"}, @@ -1584,7 +2029,7 @@ const std::array SVC::SVC_Table{{ {0x29, nullptr, "GetHandleInfo"}, {0x2A, &SVC::Wrap<&SVC::GetSystemInfo>, "GetSystemInfo"}, {0x2B, &SVC::Wrap<&SVC::GetProcessInfo>, "GetProcessInfo"}, - {0x2C, nullptr, "GetThreadInfo"}, + {0x2C, &SVC::Wrap<&SVC::GetThreadInfo>, "GetThreadInfo"}, {0x2D, &SVC::Wrap<&SVC::ConnectToPort>, "ConnectToPort"}, {0x2E, nullptr, "SendSyncRequest1"}, {0x2F, nullptr, "SendSyncRequest2"}, @@ -1664,8 +2109,63 @@ const std::array SVC::SVC_Table{{ {0x79, nullptr, "SetResourceLimitValues"}, {0x7A, nullptr, "AddCodeSegment"}, {0x7B, nullptr, "Backdoor"}, - {0x7C, nullptr, "KernelSetState"}, + {0x7C, &SVC::Wrap<&SVC::KernelSetState>, "KernelSetState"}, {0x7D, &SVC::Wrap<&SVC::QueryProcessMemory>, "QueryProcessMemory"}, + // Custom SVCs + {0x7E, nullptr, "Unused"}, + {0x7F, nullptr, "Unused"}, + {0x80, nullptr, "CustomBackdoor"}, + {0x81, nullptr, "Unused"}, + {0x82, nullptr, "Unused"}, + {0x83, nullptr, "Unused"}, + {0x84, nullptr, "Unused"}, + {0x85, nullptr, "Unused"}, + {0x86, nullptr, "Unused"}, + {0x87, nullptr, "Unused"}, + {0x88, nullptr, "Unused"}, + {0x89, nullptr, "Unused"}, + {0x8A, nullptr, "Unused"}, + {0x8B, nullptr, "Unused"}, + {0x8C, nullptr, "Unused"}, + {0x8D, nullptr, "Unused"}, + {0x8E, nullptr, "Unused"}, + {0x8F, nullptr, "Unused"}, + {0x90, &SVC::Wrap<&SVC::ConvertVaToPa>, "ConvertVaToPa"}, + {0x91, nullptr, "FlushDataCacheRange"}, + {0x92, nullptr, "FlushEntireDataCache"}, + {0x93, &SVC::Wrap<&SVC::InvalidateInstructionCacheRange>, "InvalidateInstructionCacheRange"}, + {0x94, &SVC::Wrap<&SVC::InvalidateEntireInstructionCache>, "InvalidateEntireInstructionCache"}, + {0x95, nullptr, "Unused"}, + {0x96, nullptr, "Unused"}, + {0x97, nullptr, "Unused"}, + {0x98, nullptr, "Unused"}, + {0x99, nullptr, "Unused"}, + {0x9A, nullptr, "Unused"}, + {0x9B, nullptr, "Unused"}, + {0x9C, nullptr, "Unused"}, + {0x9D, nullptr, "Unused"}, + {0x9E, nullptr, "Unused"}, + {0x9F, nullptr, "Unused"}, + {0xA0, &SVC::Wrap<&SVC::MapProcessMemoryEx>, "MapProcessMemoryEx"}, + {0xA1, &SVC::Wrap<&SVC::UnmapProcessMemoryEx>, "UnmapProcessMemoryEx"}, + {0xA2, nullptr, "ControlMemoryEx"}, + {0xA3, nullptr, "ControlMemoryUnsafe"}, + {0xA4, nullptr, "Unused"}, + {0xA5, nullptr, "Unused"}, + {0xA6, nullptr, "Unused"}, + {0xA7, nullptr, "Unused"}, + {0xA8, nullptr, "Unused"}, + {0xA9, nullptr, "Unused"}, + {0xAA, nullptr, "Unused"}, + {0xAB, nullptr, "Unused"}, + {0xAC, nullptr, "Unused"}, + {0xAD, nullptr, "Unused"}, + {0xAE, nullptr, "Unused"}, + {0xAF, nullptr, "Unused"}, + {0xB0, nullptr, "ControlService"}, + {0xB1, nullptr, "CopyHandle"}, + {0xB2, nullptr, "TranslateHandle"}, + {0xB3, &SVC::Wrap<&SVC::ControlProcess>, "ControlProcess"}, }}; const SVC::FunctionDef* SVC::GetSVCInfo(u32 func_num) { diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index e470a3687..67bcdd4be 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -72,8 +72,8 @@ void Thread::Acquire(Thread* thread) { } Thread::Thread(KernelSystem& kernel, u32 core_id) - : WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()), core_id(core_id), - thread_manager(kernel.GetThreadManager(core_id)) {} + : WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()), + can_schedule(true), core_id(core_id), thread_manager(kernel.GetThreadManager(core_id)) {} Thread::~Thread() {} Thread* ThreadManager::GetCurrentThread() const { @@ -164,15 +164,29 @@ Thread* ThreadManager::PopNextReadyThread() { Thread* thread = GetCurrentThread(); if (thread && thread->status == ThreadStatus::Running) { - // We have to do better than the current thread. - // This call returns null when that's not possible. - next = ready_queue.pop_first_better(thread->current_priority); - if (!next) { - // Otherwise just keep going with the current thread - next = thread; - } + do { + // We have to do better than the current thread. + // This call returns null when that's not possible. + next = ready_queue.pop_first_better(thread->current_priority); + if (!next) { + // Otherwise just keep going with the current thread + next = thread; + break; + } else if (!next->can_schedule) + unscheduled_ready_queue.push_back(next); + } while (!next->can_schedule); } else { - next = ready_queue.pop_first(); + do { + next = ready_queue.pop_first(); + if (next && !next->can_schedule) + unscheduled_ready_queue.push_back(next); + } while (next && !next->can_schedule); + } + + while (!unscheduled_ready_queue.empty()) { + auto t = std::move(unscheduled_ready_queue.back()); + ready_queue.push_back(t->current_priority, t); + unscheduled_ready_queue.pop_back(); } return next; diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 184318702..8eda6cab7 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -148,6 +148,7 @@ private: std::shared_ptr current_thread; Common::ThreadQueueList ready_queue; + std::deque unscheduled_ready_queue; std::unordered_map wakeup_callback_table; /// Event type for the thread wake up event @@ -289,6 +290,7 @@ public: u32 thread_id; + bool can_schedule; ThreadStatus status; VAddr entry_point; VAddr stack_top; diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 7ecc22870..49533bb7e 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -5,8 +5,10 @@ #include #include #include "common/assert.h" +#include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/vm_manager.h" +#include "core/hle/service/plgldr/plgldr.h" #include "core/memory.h" #include "core/mmio.h" @@ -37,8 +39,8 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { return true; } -VMManager::VMManager(Memory::MemorySystem& memory) - : page_table(std::make_shared()), memory(memory) { +VMManager::VMManager(Memory::MemorySystem& memory, Kernel::Process& proc) + : page_table(std::make_shared()), memory(memory), process(proc) { Reset(); } @@ -383,6 +385,10 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { memory.MapIoRegion(*page_table, vma.base, vma.size, vma.mmio_handler); break; } + + auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance()); + if (plgldr) + plgldr->OnMemoryChanged(process, Core::System::GetInstance().Kernel()); } ResultVal>> VMManager::GetBackingBlocksForRange(VAddr address, diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index a07cc3604..97c601fd6 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -131,7 +131,7 @@ public: std::map vma_map; using VMAHandle = decltype(vma_map)::const_iterator; - explicit VMManager(Memory::MemorySystem& memory); + explicit VMManager(Memory::MemorySystem& memory, Kernel::Process& proc); ~VMManager(); /// Clears the address space map, re-initializing with a single free area. @@ -254,6 +254,7 @@ private: void UpdatePageTableForVMA(const VirtualMemoryArea& vma); Memory::MemorySystem& memory; + Kernel::Process& process; // When locked, ChangeMemoryState calls will be ignored, other modification calls will hit an // assert. VMManager locks itself after deserialization. diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index 7cdafa342..f1ae11615 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -8,6 +8,8 @@ #include "common/microprofile.h" #include "common/swap.h" #include "core/core.h" +#include "core/file_sys/plugin_3gx.h" +#include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_page.h" @@ -69,6 +71,9 @@ static PAddr VirtualToPhysicalAddress(VAddr addr) { if (addr >= Memory::NEW_LINEAR_HEAP_VADDR && addr <= Memory::NEW_LINEAR_HEAP_VADDR_END) { return addr - Memory::NEW_LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR; } + if (addr >= Memory::PLUGIN_3GX_FB_VADDR && addr <= Memory::PLUGIN_3GX_FB_VADDR_END) { + return addr - Memory::PLUGIN_3GX_FB_VADDR + Service::PLGLDR::PLG_LDR::GetPluginFBAddr(); + } LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:08X}", addr); // To help with debugging, set bit on address so that it's obviously invalid. diff --git a/src/core/hle/service/plgldr/plgldr.cpp b/src/core/hle/service/plgldr/plgldr.cpp new file mode 100644 index 000000000..497ef32f6 --- /dev/null +++ b/src/core/hle/service/plgldr/plgldr.cpp @@ -0,0 +1,278 @@ +// 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 +#include +#include "common/archives.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/file_sys/plugin_3gx.h" +#include "core/frontend/mic.h" +#include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/plgldr/plgldr.h" +#include "core/loader/loader.h" + +SERIALIZE_EXPORT_IMPL(Service::PLGLDR::PLG_LDR) + +namespace Service::PLGLDR { + +const Kernel::CoreVersion PLG_LDR::plgldr_version = Kernel::CoreVersion(1, 0, 0); +PLG_LDR::PluginLoaderContext PLG_LDR::plgldr_context; +bool PLG_LDR::allow_game_change = true; +PAddr PLG_LDR::plugin_fb_addr = 0; + +PLG_LDR::PLG_LDR() : ServiceFramework{"plg:ldr", 1} { + static const FunctionInfo functions[] = { + {IPC::MakeHeader(1, 0, 2), nullptr, "LoadPlugin"}, + {IPC::MakeHeader(2, 0, 0), &PLG_LDR::IsEnabled, "IsEnabled"}, + {IPC::MakeHeader(3, 1, 0), &PLG_LDR::SetEnabled, "SetEnabled"}, + {IPC::MakeHeader(4, 2, 4), &PLG_LDR::SetLoadSettings, "SetLoadSettings"}, + {IPC::MakeHeader(5, 1, 8), nullptr, "DisplayMenu"}, + {IPC::MakeHeader(6, 0, 4), nullptr, "DisplayMessage"}, + {IPC::MakeHeader(7, 1, 4), &PLG_LDR::DisplayErrorMessage, "DisplayErrorMessage"}, + {IPC::MakeHeader(8, 0, 0), &PLG_LDR::GetPLGLDRVersion, "GetPLGLDRVersion"}, + {IPC::MakeHeader(9, 0, 0), &PLG_LDR::GetArbiter, "GetArbiter"}, + {IPC::MakeHeader(10, 0, 2), &PLG_LDR::GetPluginPath, "GetPluginPath"}, + {IPC::MakeHeader(11, 1, 0), nullptr, "SetRosalinaMenuBlock"}, + {IPC::MakeHeader(12, 2, 4), nullptr, "SetSwapParam"}, + {IPC::MakeHeader(13, 1, 2), nullptr, "SetLoadExeParam"}, + }; + RegisterHandlers(functions); + plgldr_context.memory_changed_handle = 0; + plgldr_context.plugin_loaded = false; +} + +void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kernel) { + if (!plgldr_context.is_enabled || plgldr_context.plugin_loaded) { + return; + } + { + // Same check as original plugin loader, plugins are not supported in homebrew apps + u32 value1, value2; + kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr, &value1, 4); + kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr + 32, &value2, 4); + // Check for "B #0x20" and "MOV R4, LR" instructions + bool is_homebrew = u32_le(value1) == 0xEA000006 && u32_le(value2) == 0xE1A0400E; + if (is_homebrew) { + return; + } + } + FileSys::Plugin3GXLoader plugin_loader; + if (plgldr_context.use_user_load_parameters && + plgldr_context.user_load_parameters.low_title_Id == + static_cast(process.codeset->program_id) && + plgldr_context.user_load_parameters.path[0]) { + std::string plugin_file = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + + std::string(plgldr_context.user_load_parameters.path + 1); + plgldr_context.is_default_path = false; + plgldr_context.plugin_path = plugin_file; + plugin_loader.Load(plgldr_context, process, kernel); + } else { + const std::string plugin_root = + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "luma/plugins/"; + const std::string plugin_tid = + plugin_root + fmt::format("{:016X}", process.codeset->program_id); + FileUtil::FSTEntry entry; + FileUtil::ScanDirectoryTree(plugin_tid, entry); + for (const auto child : entry.children) { + if (!child.isDirectory && child.physicalName.ends_with(".3gx")) { + plgldr_context.is_default_path = false; + plgldr_context.plugin_path = child.physicalName; + if (plugin_loader.Load(plgldr_context, process, kernel) == + Loader::ResultStatus::Success) { + return; + } + } + } + + const std::string default_path = plugin_root + "default.3gx"; + if (FileUtil::Exists(default_path)) { + plgldr_context.is_default_path = true; + plgldr_context.plugin_path = default_path; + plugin_loader.Load(plgldr_context, process, kernel); + } + } +} + +void PLG_LDR::OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel) { + if (plgldr_context.plugin_loaded) { + u32 status = kernel.memory.Read32(FileSys::Plugin3GXLoader::_3GX_exe_load_addr - 0xC); + if (status == 0) { + LOG_CRITICAL(Service_PLGLDR, "Failed to launch {}: Checksum failed", + plgldr_context.plugin_path); + } + } +} + +ResultVal PLG_LDR::GetMemoryChangedHandle(Kernel::KernelSystem& kernel) { + if (plgldr_context.memory_changed_handle) + return MakeResult(plgldr_context.memory_changed_handle); + + std::shared_ptr evt = kernel.CreateEvent( + Kernel::ResetType::OneShot, + fmt::format("event-{:08x}", Core::System::GetInstance().GetRunningCore().GetReg(14))); + CASCADE_RESULT(plgldr_context.memory_changed_handle, + kernel.GetCurrentProcess()->handle_table.Create(std::move(evt))); + + return MakeResult(plgldr_context.memory_changed_handle); +} + +void PLG_LDR::OnMemoryChanged(Kernel::Process& process, Kernel::KernelSystem& kernel) { + if (!plgldr_context.plugin_loaded || !plgldr_context.memory_changed_handle) + return; + + std::shared_ptr evt = + kernel.GetCurrentProcess()->handle_table.Get( + plgldr_context.memory_changed_handle); + if (evt == nullptr) + return; + + evt->Signal(); +} + +void PLG_LDR::IsEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 2, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(plgldr_context.is_enabled); +} + +void PLG_LDR::SetEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 3, 1, 0); + bool enabled = rp.Pop() == 1; + + bool can_change = enabled == plgldr_context.is_enabled || allow_game_change; + if (can_change) { + plgldr_context.is_enabled = enabled; + Settings::values.plugin_loader_enabled.SetValue(enabled); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push((can_change) ? RESULT_SUCCESS : Kernel::ERR_NOT_AUTHORIZED); +} + +void PLG_LDR::SetLoadSettings(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 4, 2, 4); + + plgldr_context.use_user_load_parameters = true; + plgldr_context.user_load_parameters.no_flash = rp.Pop() == 1; + plgldr_context.user_load_parameters.low_title_Id = rp.Pop(); + + auto path = rp.PopMappedBuffer(); + + path.Read( + plgldr_context.user_load_parameters.path, 0, + std::min(sizeof(PluginLoaderContext::PluginLoadParameters::path) - 1, path.GetSize())); + plgldr_context.user_load_parameters.path[std::min( + sizeof(PluginLoaderContext::PluginLoadParameters::path) - 1, path.GetSize())] = '\0'; + + auto config = rp.PopMappedBuffer(); + config.Read( + plgldr_context.user_load_parameters.config, 0, + std::min(sizeof(PluginLoaderContext::PluginLoadParameters::config), config.GetSize())); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void PLG_LDR::DisplayErrorMessage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 7, 1, 4); + u32 error_code = rp.Pop(); + auto title = rp.PopMappedBuffer(); + auto desc = rp.PopMappedBuffer(); + + std::vector title_data(title.GetSize() + 1); + std::vector desc_data(desc.GetSize() + 1); + + title.Read(title_data.data(), 0, title.GetSize()); + title_data[title.GetSize()] = '\0'; + + desc.Read(desc_data.data(), 0, desc.GetSize()); + desc_data[desc.GetSize()] = '\0'; + + LOG_ERROR(Service_PLGLDR, "Plugin error - Code: {} - Title: {} - Description: {}", error_code, + std::string(title_data.data()), std::string(desc_data.data())); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void PLG_LDR::GetPLGLDRVersion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 8, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(plgldr_version.raw); +} + +void PLG_LDR::GetArbiter(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 9, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + // NOTE: It doesn't make sense to send an arbiter in HLE, as it's used to + // signal the plgldr service thread when a event is ready. Instead we just send + // an error and the 3GX plugin will take care of it. + // (We never send any events anyways) + rb.Push(Kernel::ERR_NOT_IMPLEMENTED); +} + +void PLG_LDR::GetPluginPath(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 10, 0, 2); + auto path = rp.PopMappedBuffer(); + + // Same behaviour as strncpy + std::string root_sd = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); + std::string plugin_path = plgldr_context.plugin_path; + auto it = plugin_path.find(root_sd); + if (it != plugin_path.npos) + plugin_path.erase(it, root_sd.size()); + + std::replace(plugin_path.begin(), plugin_path.end(), '\\', '/'); + if (plugin_path.empty() || plugin_path[0] != '/') + plugin_path = "/" + plugin_path; + + path.Write(plugin_path.c_str(), 0, std::min(path.GetSize(), plugin_path.length() + 1)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(path); +} + +std::shared_ptr GetService(Core::System& system) { + if (!system.KernelRunning()) + return nullptr; + auto it = system.Kernel().named_ports.find("plg:ldr"); + if (it != system.Kernel().named_ports.end()) + return std::static_pointer_cast(it->second->GetServerPort()->hle_handler); + return nullptr; +} + +void InstallInterfaces(Core::System& system) { + std::make_shared()->InstallAsNamedPort(system.Kernel()); +} + +} // namespace Service::PLGLDR diff --git a/src/core/hle/service/plgldr/plgldr.h b/src/core/hle/service/plgldr/plgldr.h new file mode 100644 index 000000000..0780db564 --- /dev/null +++ b/src/core/hle/service/plgldr/plgldr.h @@ -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 +#include +#include "core/hle/service/service.h" + +namespace Core { +class System; +} + +namespace Service::PLGLDR { + +class PLG_LDR final : public ServiceFramework { +public: + struct PluginLoaderContext { + struct PluginLoadParameters { + u8 no_flash = 0; + u8 no_IR_Patch = 0; + u32_le low_title_Id = 0; + char path[256] = {0}; + u32_le config[32] = {0}; + + template + void serialize(Archive& ar, const unsigned int) { + ar& no_flash; + ar& no_IR_Patch; + ar& low_title_Id; + ar& path; + ar& config; + } + friend class boost::serialization::access; + }; + bool is_enabled = true; + bool plugin_loaded = false; + bool is_default_path = false; + std::string plugin_path = ""; + + bool use_user_load_parameters = false; + PluginLoadParameters user_load_parameters; + + VAddr plg_event = 0; + VAddr plg_reply = 0; + Kernel::Handle memory_changed_handle = 0; + + bool is_exe_load_function_set = false; + u32 exe_load_checksum = 0; + + std::vector load_exe_func; + u32_le load_exe_args[4] = {0}; + + template + void serialize(Archive& ar, const unsigned int) { + ar& is_enabled; + ar& plugin_loaded; + ar& is_default_path; + ar& plugin_path; + ar& use_user_load_parameters; + ar& user_load_parameters; + ar& plg_event; + ar& plg_reply; + ar& memory_changed_handle; + ar& is_exe_load_function_set; + ar& exe_load_checksum; + ar& load_exe_func; + ar& load_exe_args; + } + friend class boost::serialization::access; + }; + + PLG_LDR(); + ~PLG_LDR() {} + + void OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kernel); + void OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel); + ResultVal GetMemoryChangedHandle(Kernel::KernelSystem& kernel); + void OnMemoryChanged(Kernel::Process& process, Kernel::KernelSystem& kernel); + + static void SetEnabled(bool enabled) { + plgldr_context.is_enabled = enabled; + } + static bool GetEnabled() { + return plgldr_context.is_enabled; + } + static void SetAllowGameChangeState(bool allow) { + allow_game_change = allow; + } + static bool GetAllowGameChangeState() { + return allow_game_change; + } + static void SetPluginFBAddr(PAddr addr) { + plugin_fb_addr = addr; + } + static PAddr GetPluginFBAddr() { + return plugin_fb_addr; + } + +private: + static const Kernel::CoreVersion plgldr_version; + + static PluginLoaderContext plgldr_context; + static PAddr plugin_fb_addr; + static bool allow_game_change; + + void IsEnabled(Kernel::HLERequestContext& ctx); + void SetEnabled(Kernel::HLERequestContext& ctx); + void SetLoadSettings(Kernel::HLERequestContext& ctx); + void DisplayErrorMessage(Kernel::HLERequestContext& ctx); + void GetPLGLDRVersion(Kernel::HLERequestContext& ctx); + void GetArbiter(Kernel::HLERequestContext& ctx); + void GetPluginPath(Kernel::HLERequestContext& ctx); + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar& plgldr_context; + ar& plugin_fb_addr; + ar& allow_game_change; + } + friend class boost::serialization::access; +}; + +std::shared_ptr GetService(Core::System& system); + +void InstallInterfaces(Core::System& system); + +} // namespace Service::PLGLDR + +BOOST_CLASS_EXPORT_KEY(Service::PLGLDR::PLG_LDR) diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 94170453f..6edf5083a 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -41,6 +41,7 @@ #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nim/nim.h" #include "core/hle/service/nwm/nwm.h" +#include "core/hle/service/plgldr/plgldr.h" #include "core/hle/service/pm/pm.h" #include "core/hle/service/ps/ps_ps.h" #include "core/hle/service/ptm/ptm.h" @@ -55,7 +56,7 @@ namespace Service { -const std::array service_module_map{ +const std::array service_module_map{ {{"FS", 0x00040130'00001102, FS::InstallInterfaces}, {"PM", 0x00040130'00001202, PM::InstallInterfaces}, {"LDR", 0x00040130'00003702, LDR::InstallInterfaces}, @@ -94,6 +95,7 @@ const std::array service_module_map{ {"SOC", 0x00040130'00002E02, SOC::InstallInterfaces}, {"SSL", 0x00040130'00002F02, SSL::InstallInterfaces}, {"PS", 0x00040130'00003102, PS::InstallInterfaces}, + {"PLGLDR", 0x00040130'00006902, PLGLDR::InstallInterfaces}, // no HLE implementation {"CDC", 0x00040130'00001802, nullptr}, {"GPIO", 0x00040130'00001B02, nullptr}, diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 83b0a5fb1..fd783cefc 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -194,7 +194,7 @@ struct ServiceModuleInfo { std::function init_function; }; -extern const std::array service_module_map; +extern const std::array service_module_map; } // namespace Service diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp index 87a084dbb..aa3a1fbf4 100644 --- a/src/core/hle/service/sm/srv.cpp +++ b/src/core/hle/service/sm/srv.cpp @@ -243,10 +243,18 @@ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) { u32 notification_id = rp.Pop(); u8 flags = rp.Pop(); + // Handle notification 0x203 in HLE, as this one may be used by homebrew to power off the + // console. Normally, this is handled by NS. If notification handling is properly implemented, + // this piece of code should be removed, and handled by subscribing from NS instead. + if (notification_id == 0x203) { + Core::System::GetInstance().RequestShutdown(); + } else { + LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x{:X}, flags={}", + notification_id, flags); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x{:X}, flags={}", notification_id, - flags); } void SRV::RegisterService(Kernel::HLERequestContext& ctx) { diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 8e98ee99c..3b1c6ba93 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -20,6 +20,8 @@ #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" #include "core/hle/lock.h" +#include "core/hle/service/plgldr/plgldr.h" +#include "core/hw/hw.h" #include "core/memory.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -63,12 +65,16 @@ private: if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { return &new_linear_heap[(addr - NEW_LINEAR_HEAP_VADDR) / CITRA_PAGE_SIZE]; } + if (addr >= PLUGIN_3GX_FB_VADDR && addr < PLUGIN_3GX_FB_VADDR_END) { + return &plugin_fb[(addr - PLUGIN_3GX_FB_VADDR) / CITRA_PAGE_SIZE]; + } return nullptr; } std::array vram{}; std::array linear_heap{}; std::array new_linear_heap{}; + std::array plugin_fb{}; static_assert(sizeof(bool) == 1); friend class boost::serialization::access; @@ -77,6 +83,7 @@ private: ar& vram; ar& linear_heap; ar& new_linear_heap; + ar& plugin_fb; } }; @@ -280,6 +287,10 @@ public: if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) { return {vram_mem, addr - VRAM_VADDR}; } + if (addr >= PLUGIN_3GX_FB_VADDR && addr < PLUGIN_3GX_FB_VADDR_END) { + return {fcram_mem, addr - PLUGIN_3GX_FB_VADDR + + Service::PLGLDR::PLG_LDR::GetPluginFBAddr() - FCRAM_PADDR}; + } UNREACHABLE(); return MemoryRef{}; @@ -436,6 +447,22 @@ T MemorySystem::Read(const VAddr vaddr) { return value; } + // Custom Luma3ds mapping + // Is there a more efficient way to do this? + if (vaddr & (1 << 31)) { + PAddr paddr = (vaddr & ~(1 << 31)); + if ((paddr & 0xF0000000) == Memory::FCRAM_PADDR) { // Check FCRAM region + T value; + std::memcpy(&value, GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), sizeof(T)); + return value; + } else if ((paddr & 0xF0000000) == 0x10000000 && + paddr >= Memory::IO_AREA_PADDR) { // Check MMIO region + T ret; + HW::Read(ret, static_cast(paddr) - Memory::IO_AREA_PADDR + 0x1EC00000); + return ret; + } + } + PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS]; switch (type) { case PageType::Unmapped: @@ -473,6 +500,20 @@ void MemorySystem::Write(const VAddr vaddr, const T data) { return; } + // Custom Luma3ds mapping + // Is there a more efficient way to do this? + if (vaddr & (1 << 31)) { + PAddr paddr = (vaddr & ~(1 << 31)); + if ((paddr & 0xF0000000) == Memory::FCRAM_PADDR) { // Check FCRAM region + std::memcpy(GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), &data, sizeof(T)); + return; + } else if ((paddr & 0xF0000000) == 0x10000000 && + paddr >= Memory::IO_AREA_PADDR) { // Check MMIO region + HW::Write(static_cast(paddr) - Memory::IO_AREA_PADDR + 0x1EC00000, data); + return; + } + } + PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS]; switch (type) { case PageType::Unmapped: @@ -657,6 +698,10 @@ static std::vector PhysicalToVirtualAddressForRasterizer(PAddr addr) { if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) { return {addr - VRAM_PADDR + VRAM_VADDR}; } + if (addr >= Service::PLGLDR::PLG_LDR::GetPluginFBAddr() && + addr < Service::PLGLDR::PLG_LDR::GetPluginFBAddr() + PLUGIN_3GX_FB_SIZE) { + return {addr - Service::PLGLDR::PLG_LDR::GetPluginFBAddr() + PLUGIN_3GX_FB_VADDR}; + } if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) { return {addr - FCRAM_PADDR + LINEAR_HEAP_VADDR, addr - FCRAM_PADDR + NEW_LINEAR_HEAP_VADDR}; } @@ -796,6 +841,9 @@ void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) { CheckRegion(LINEAR_HEAP_VADDR, LINEAR_HEAP_VADDR_END, FCRAM_PADDR); CheckRegion(NEW_LINEAR_HEAP_VADDR, NEW_LINEAR_HEAP_VADDR_END, FCRAM_PADDR); CheckRegion(VRAM_VADDR, VRAM_VADDR_END, VRAM_PADDR); + if (Service::PLGLDR::PLG_LDR::GetPluginFBAddr()) + CheckRegion(PLUGIN_3GX_FB_VADDR, PLUGIN_3GX_FB_VADDR_END, + Service::PLGLDR::PLG_LDR::GetPluginFBAddr()); } u8 MemorySystem::Read8(const VAddr addr) { diff --git a/src/core/memory.h b/src/core/memory.h index d77281bcd..eac9fb374 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -242,6 +242,11 @@ enum : VAddr { NEW_LINEAR_HEAP_VADDR = 0x30000000, NEW_LINEAR_HEAP_SIZE = 0x10000000, NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, + + /// Area where 3GX plugin framebuffers are stored + PLUGIN_3GX_FB_VADDR = 0x06000000, + PLUGIN_3GX_FB_SIZE = 0x000A9000, + PLUGIN_3GX_FB_VADDR_END = PLUGIN_3GX_FB_VADDR + PLUGIN_3GX_FB_SIZE }; /** diff --git a/src/tests/core/memory/vm_manager.cpp b/src/tests/core/memory/vm_manager.cpp index f8244b357..7553626af 100644 --- a/src/tests/core/memory/vm_manager.cpp +++ b/src/tests/core/memory/vm_manager.cpp @@ -4,18 +4,24 @@ #include #include +#include "core/core_timing.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/memory.h" TEST_CASE("Memory Basics", "[kernel][memory]") { auto mem = std::make_shared(Memory::CITRA_PAGE_SIZE); MemoryRef block{mem}; + Core::Timing timing(1, 100); Memory::MemorySystem memory; + Kernel::KernelSystem kernel( + memory, timing, [] {}, 0, 1, 0); + Kernel::Process process(kernel); SECTION("mapping memory") { // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. - auto manager = std::make_unique(memory); + auto manager = std::make_unique(memory, process); auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast(block.GetSize()), Kernel::MemoryState::Private); @@ -31,7 +37,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { SECTION("unmapping memory") { // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. - auto manager = std::make_unique(memory); + auto manager = std::make_unique(memory, process); auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast(block.GetSize()), Kernel::MemoryState::Private); @@ -49,7 +55,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { SECTION("changing memory permissions") { // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. - auto manager = std::make_unique(memory); + auto manager = std::make_unique(memory, process); auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast(block.GetSize()), Kernel::MemoryState::Private); @@ -69,7 +75,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { SECTION("changing memory state") { // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. - auto manager = std::make_unique(memory); + auto manager = std::make_unique(memory, process); auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast(block.GetSize()), Kernel::MemoryState::Private);