mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Add 3GX plugin loader (#6172)
* Initial plugin loader support * More plugin loader progress * Organize code and more plugin features * Fix clang-format * Fix compilation and add android gui * Fix clang-format * Fix macos build * Fix copy-paste bug and clang-format * More merge fixes * Make suggestions * Move global variable to static member * Fix typo * Apply suggestions * Proper initialization order * Allocate plugin memory from SYSTEM instead of APPLICATION * Do not mark free pages as RWX * Fix plugins in old 3DS mode. * Implement KernelSetState and notif 0x203 * Apply changes * Remove unused variable * Fix dynarmic commit * Sublicense files with MIT License * Remove non-ascii characters from license
This commit is contained in:
		
							parent
							
								
									48ee112ceb
								
							
						
					
					
						commit
						016ce6c286
					
				
					 38 changed files with 1911 additions and 42 deletions
				
			
		|  | @ -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<SettingsItem> sl) { | ||||
|  |  | |||
|  | @ -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"; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -37,6 +37,10 @@ | |||
|     <string name="init_time_description">Si el \"Tipo del reloj del sistema\" está en \"Reloj emulado\", ésto cambia la fecha y hora de inicio.</string> | ||||
|     <string name="emulated_region">Región emulada</string> | ||||
|     <string name="emulated_language">Idioma emulado</string> | ||||
|     <string name="plugin_loader">Activar \"3GX Plugin Loader\"</string> | ||||
|     <string name="plugin_loader_description">Carga \"3GX plugins\" de la SD emulada si están disponibles.</string> | ||||
|     <string name="allow_plugin_loader">Permiter que apps cambien el estado del \"plugin loader\"</string> | ||||
|     <string name="allow_plugin_loader_description">Permite a las aplicaciones homebrew activar el \"plugin loader\" incluso si está desactivado.</string> | ||||
| 
 | ||||
|     <!-- Camera settings strings --> | ||||
|     <string name="inner_camera">Cámara interior</string> | ||||
|  |  | |||
|  | @ -51,6 +51,10 @@ | |||
|     <string name="init_time_description">If the \"System clock type\" setting is set to \"Simulated clock\", this changes the fixed date and time to start at.</string> | ||||
|     <string name="emulated_region">Emulated region</string> | ||||
|     <string name="emulated_language">Emulated language</string> | ||||
|     <string name="plugin_loader">Enable 3GX Plugin Loader</string> | ||||
|     <string name="plugin_loader_description">Loads 3GX plugins from the emulated SD if they are available.</string> | ||||
|     <string name="allow_plugin_loader">Allow apps to change plugin loader state</string> | ||||
|     <string name="allow_plugin_loader_description">Allow homebrew apps to enable the plugin loader even when it is disabled.</string> | ||||
| 
 | ||||
|     <!-- Camera settings strings --> | ||||
|     <string name="inner_camera">Inner Camera</string> | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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<int>(&QComboBox::activated), this, [this](int index) { | ||||
|         ui->slider_clock_speed->setEnabled(index == 1); | ||||
|  |  | |||
|  | @ -340,6 +340,27 @@ | |||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="13" column="0"> | ||||
|           <widget class="QLabel" name="label_plugin_loader"> | ||||
|             <property name="text"> | ||||
|               <string>3GX Plugin Loader:</string> | ||||
|             </property> | ||||
|           </widget> | ||||
|         </item> | ||||
|         <item row="13" column="1"> | ||||
|           <widget class="QCheckBox" name="plugin_loader"> | ||||
|             <property name="text"> | ||||
|               <string>Enable 3GX plugin loader</string> | ||||
|             </property> | ||||
|           </widget> | ||||
|         </item> | ||||
|         <item row="14" column="1"> | ||||
|           <widget class="QCheckBox" name="allow_plugin_loader"> | ||||
|             <property name="text"> | ||||
|               <string>Allow games to change plugin loader state</string> | ||||
|             </property> | ||||
|           </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|  |  | |||
|  | @ -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)                                                                                   \ | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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()); | ||||
| } | ||||
|  |  | |||
|  | @ -432,6 +432,8 @@ struct Values { | |||
|     Setting<InitClock> init_clock{InitClock::SystemTime, "init_clock"}; | ||||
|     Setting<u64> init_time{946681277ULL, "init_time"}; | ||||
|     Setting<s64> init_time_offset{0, "init_time_offset"}; | ||||
|     Setting<bool> plugin_loader_enabled{false, "plugin_loader"}; | ||||
|     Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"}; | ||||
| 
 | ||||
|     // Renderer
 | ||||
|     Setting<bool> use_gles{false, "use_gles"}; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -473,6 +473,10 @@ const Kernel::KernelSystem& System::Kernel() const { | |||
|     return *kernel; | ||||
| } | ||||
| 
 | ||||
| bool System::KernelRunning() { | ||||
|     return kernel != nullptr; | ||||
| } | ||||
| 
 | ||||
| Timing& System::CoreTiming() { | ||||
|     return *timing; | ||||
| } | ||||
|  |  | |||
|  | @ -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(); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										364
									
								
								src/core/file_sys/plugin_3gx.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								src/core/file_sys/plugin_3gx.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,364 @@ | |||
| // Copyright 2022 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| // Copyright 2022 The Pixellizer Group
 | ||||
| //
 | ||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 | ||||
| // associated documentation files (the "Software"), to deal in the Software without restriction,
 | ||||
| // including without limitation the rights to use, copy, modify, merge, publish, distribute,
 | ||||
| // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 | ||||
| // furnished to do so, subject to the following conditions:
 | ||||
| //
 | ||||
| // The above copyright notice and this permission notice shall be included in all copies or
 | ||||
| // substantial portions of the Software.
 | ||||
| //
 | ||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 | ||||
| // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 | ||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 | ||||
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | ||||
| 
 | ||||
| #include "core/file_sys/file_backend.h" | ||||
| #include "core/file_sys/plugin_3gx.h" | ||||
| #include "core/file_sys/plugin_3gx_bootloader.h" | ||||
| #include "core/hle/kernel/vm_manager.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| static std::string ReadTextInfo(FileUtil::IOFile& file, std::size_t offset, std::size_t max_size) { | ||||
|     if (max_size > 0x400) { // Limit read string size to 0x400 bytes, just in case
 | ||||
|         return ""; | ||||
|     } | ||||
|     std::vector<char> char_data(max_size); | ||||
| 
 | ||||
|     const u64 prev_offset = file.Tell(); | ||||
|     if (!file.Seek(offset, SEEK_SET)) { | ||||
|         return ""; | ||||
|     } | ||||
|     if (file.ReadBytes(char_data.data(), max_size) != max_size) { | ||||
|         file.Seek(prev_offset, SEEK_SET); | ||||
|         return ""; | ||||
|     } | ||||
|     char_data[max_size - 1] = '\0'; | ||||
|     return std::string(char_data.data()); | ||||
| } | ||||
| 
 | ||||
| static bool ReadSection(std::vector<u8>& data_out, FileUtil::IOFile& file, std::size_t offset, | ||||
|                         std::size_t size) { | ||||
|     if (size > 0x5000000) { // Limit read section size to 5MiB, just in case
 | ||||
|         return false; | ||||
|     } | ||||
|     data_out.resize(size); | ||||
| 
 | ||||
|     const u64 prev_offset = file.Tell(); | ||||
| 
 | ||||
|     if (!file.Seek(offset, SEEK_SET)) { | ||||
|         return false; | ||||
|     } | ||||
|     if (file.ReadBytes(data_out.data(), size) != size) { | ||||
|         file.Seek(prev_offset, SEEK_SET); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus FileSys::Plugin3GXLoader::Load( | ||||
|     Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process, | ||||
|     Kernel::KernelSystem& kernel) { | ||||
|     FileUtil::IOFile file(plg_context.plugin_path, "rb"); | ||||
|     if (!file.IsOpen()) { | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not found: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::Error; | ||||
|     } | ||||
| 
 | ||||
|     // Load CIA Header
 | ||||
|     std::vector<u8> header_data(sizeof(_3gx_Header)); | ||||
|     if (file.ReadBytes(header_data.data(), sizeof(_3gx_Header)) != sizeof(_3gx_Header)) { | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::Error; | ||||
|     } | ||||
| 
 | ||||
|     std::memcpy(&header, header_data.data(), sizeof(_3gx_Header)); | ||||
| 
 | ||||
|     // Check magic value
 | ||||
|     if (std::memcmp(&header.magic, _3GX_magic, 8) != 0) { | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Outdated or invalid 3GX plugin: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::Error; | ||||
|     } | ||||
| 
 | ||||
|     if (header.infos.flags.compatibility == static_cast<u32>(_3gx_Infos::Compatibility::CONSOLE)) { | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not compatible with Citra: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::Error; | ||||
|     } | ||||
| 
 | ||||
|     // Load strings
 | ||||
|     author = ReadTextInfo(file, header.infos.author_msg_offset, header.infos.author_len); | ||||
|     title = ReadTextInfo(file, header.infos.title_msg_offset, header.infos.title_len); | ||||
|     description = | ||||
|         ReadTextInfo(file, header.infos.description_msg_offset, header.infos.description_len); | ||||
|     summary = ReadTextInfo(file, header.infos.summary_msg_offset, header.infos.summary_len); | ||||
| 
 | ||||
|     LOG_INFO(Service_PLGLDR, "Trying to load plugin - Title: {} - Author: {}", title, author); | ||||
| 
 | ||||
|     // Load compatible TIDs
 | ||||
|     { | ||||
|         std::vector<u8> raw_TID_data; | ||||
|         if (!ReadSection(raw_TID_data, file, header.targets.title_offsets, | ||||
|                          header.targets.count * sizeof(u32))) { | ||||
|             return Loader::ResultStatus::Error; | ||||
|         } | ||||
|         for (u32 i = 0; i < u32(header.targets.count); i++) { | ||||
|             compatible_TID.push_back( | ||||
|                 u32_le(*reinterpret_cast<u32*>(raw_TID_data.data() + i * sizeof(u32)))); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (!compatible_TID.empty() && | ||||
|         std::find(compatible_TID.begin(), compatible_TID.end(), | ||||
|                   static_cast<u32>(process.codeset->program_id)) == compatible_TID.end()) { | ||||
|         LOG_ERROR(Service_PLGLDR, | ||||
|                   "Failed to load 3GX plugin. Not compatible with loaded process: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::Error; | ||||
|     } | ||||
| 
 | ||||
|     // Load exe load func and args
 | ||||
|     if (header.infos.flags.embedded_exe_func.Value() && | ||||
|         header.executable.exe_load_func_offset != 0) { | ||||
|         exe_load_func.clear(); | ||||
|         std::vector<u8> out; | ||||
|         for (int i = 0; i < 32; i++) { | ||||
|             ReadSection(out, file, header.executable.exe_load_func_offset + i * sizeof(u32), | ||||
|                         sizeof(u32)); | ||||
|             u32 instruction = *reinterpret_cast<u32_le*>(out.data()); | ||||
|             if (instruction == 0xE320F000) { | ||||
|                 break; | ||||
|             } | ||||
|             exe_load_func.push_back(instruction); | ||||
|         } | ||||
|         memcpy(exe_load_args, header.infos.builtin_load_exe_args, | ||||
|                sizeof(_3gx_Infos::builtin_load_exe_args)); | ||||
|     } | ||||
| 
 | ||||
|     // Load code sections
 | ||||
|     if (!ReadSection(text_section, file, header.executable.code_offset, | ||||
|                      header.executable.code_size) || | ||||
|         !ReadSection(rodata_section, file, header.executable.rodata_offset, | ||||
|                      header.executable.rodata_size) || | ||||
|         !ReadSection(data_section, file, header.executable.data_offset, | ||||
|                      header.executable.data_size)) { | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::Error; | ||||
|     } | ||||
| 
 | ||||
|     return Map(plg_context, process, kernel); | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus FileSys::Plugin3GXLoader::Map( | ||||
|     Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process, | ||||
|     Kernel::KernelSystem& kernel) { | ||||
| 
 | ||||
|     // Verify exe load checksum function is available
 | ||||
|     if (exe_load_func.empty() && plg_context.load_exe_func.empty()) { | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Missing checksum function: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::Error; | ||||
|     } | ||||
| 
 | ||||
|     const std::array<u32, 4> mem_region_sizes = { | ||||
|         5 * 1024 * 1024, // 5 MiB
 | ||||
|         2 * 1024 * 1024, // 2 MiB
 | ||||
|         3 * 1024 * 1024, // 3 MiB
 | ||||
|         4 * 1024 * 1024  // 4 MiB
 | ||||
|     }; | ||||
| 
 | ||||
|     // Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
 | ||||
|     // Calculate the sizes of the different memory regions
 | ||||
|     const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()]; | ||||
|     const u32 exe_size = (sizeof(PluginHeader) + text_section.size() + rodata_section.size() + | ||||
|                           data_section.size() + header.executable.bss_size + 0x1000) & | ||||
|                          ~0xFFF; | ||||
| 
 | ||||
|     // Allocate the framebuffer block so that is in the highest FCRAM position possible
 | ||||
|     auto offset_fb = | ||||
|         kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->RLinearAllocate(_3GX_fb_size); | ||||
|     if (!offset_fb) { | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::ErrorMemoryAllocationFailed; | ||||
|     } | ||||
|     auto backing_memory_fb = kernel.memory.GetFCRAMRef(*offset_fb); | ||||
|     Service::PLGLDR::PLG_LDR::SetPluginFBAddr(Memory::FCRAM_PADDR + *offset_fb); | ||||
|     std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0); | ||||
| 
 | ||||
|     auto vma_heap_fb = process.vm_manager.MapBackingMemory( | ||||
|         _3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size, Kernel::MemoryState::Continuous); | ||||
|     ASSERT(vma_heap_fb.Succeeded()); | ||||
|     process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite); | ||||
| 
 | ||||
|     // Allocate a block from the end of FCRAM and clear it
 | ||||
|     auto offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM) | ||||
|                       ->RLinearAllocate(block_size - _3GX_fb_size); | ||||
|     if (!offset) { | ||||
|         kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size); | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::ErrorMemoryAllocationFailed; | ||||
|     } | ||||
|     auto backing_memory = kernel.memory.GetFCRAMRef(*offset); | ||||
|     std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + block_size - _3GX_fb_size, 0); | ||||
| 
 | ||||
|     // Then we map part of the memory, which contains the executable
 | ||||
|     auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size, | ||||
|                                                    Kernel::MemoryState::Continuous); | ||||
|     ASSERT(vma.Succeeded()); | ||||
|     process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute); | ||||
| 
 | ||||
|     // Write text section
 | ||||
|     kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader), | ||||
|                              text_section.data(), header.executable.code_size); | ||||
|     // Write rodata section
 | ||||
|     kernel.memory.WriteBlock( | ||||
|         process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size, | ||||
|         rodata_section.data(), header.executable.rodata_size); | ||||
|     // Write data section
 | ||||
|     kernel.memory.WriteBlock(process, | ||||
|                              _3GX_exe_load_addr + sizeof(PluginHeader) + | ||||
|                                  header.executable.code_size + header.executable.rodata_size, | ||||
|                              data_section.data(), header.executable.data_size); | ||||
|     // Prepare plugin header and write it
 | ||||
|     PluginHeader plugin_header = {0}; | ||||
|     plugin_header.version = header.version; | ||||
|     plugin_header.exe_size = exe_size; | ||||
|     plugin_header.heap_VA = _3GX_heap_load_addr; | ||||
|     plugin_header.heap_size = block_size - exe_size; | ||||
|     plg_context.plg_event = _3GX_exe_load_addr - 0x4; | ||||
|     plg_context.plg_reply = _3GX_exe_load_addr - 0x8; | ||||
|     plugin_header.plgldr_event = plg_context.plg_event; | ||||
|     plugin_header.plgldr_reply = plg_context.plg_reply; | ||||
|     plugin_header.is_default_plugin = plg_context.is_default_path; | ||||
|     if (plg_context.use_user_load_parameters) { | ||||
|         memcpy(plugin_header.config, plg_context.user_load_parameters.config, | ||||
|                sizeof(PluginHeader::config)); | ||||
|     } | ||||
|     kernel.memory.WriteBlock(process, _3GX_exe_load_addr, &plugin_header, sizeof(PluginHeader)); | ||||
| 
 | ||||
|     // Map plugin heap
 | ||||
|     auto backing_memory_heap = kernel.memory.GetFCRAMRef(*offset + exe_size); | ||||
| 
 | ||||
|     // Map the rest of the memory at the heap location
 | ||||
|     auto vma_heap = process.vm_manager.MapBackingMemory( | ||||
|         _3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap, | ||||
|         block_size - exe_size - _3GX_fb_size, Kernel::MemoryState::Continuous); | ||||
|     ASSERT(vma_heap.Succeeded()); | ||||
|     process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute); | ||||
| 
 | ||||
|     // Allocate a block from the end of FCRAM and clear it
 | ||||
|     auto bootloader_offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM) | ||||
|                                  ->RLinearAllocate(bootloader_memory_size); | ||||
|     if (!bootloader_offset) { | ||||
|         kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size); | ||||
|         kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM) | ||||
|             ->Free(*offset, block_size - _3GX_fb_size); | ||||
|         LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}", | ||||
|                   plg_context.plugin_path); | ||||
|         return Loader::ResultStatus::ErrorMemoryAllocationFailed; | ||||
|     } | ||||
|     const bool use_internal = plg_context.load_exe_func.empty(); | ||||
|     MapBootloader( | ||||
|         process, kernel, *bootloader_offset, | ||||
|         (use_internal) ? exe_load_func : plg_context.load_exe_func, | ||||
|         (use_internal) ? exe_load_args : plg_context.load_exe_args, | ||||
|         header.executable.code_size + header.executable.rodata_size + header.executable.data_size, | ||||
|         header.infos.exe_load_checksum, | ||||
|         plg_context.use_user_load_parameters ? plg_context.user_load_parameters.no_flash : 0); | ||||
| 
 | ||||
|     plg_context.plugin_loaded = true; | ||||
|     plg_context.use_user_load_parameters = false; | ||||
|     return Loader::ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel, | ||||
|                                              u32 memory_offset, | ||||
|                                              const std::vector<u32>& exe_load_func, | ||||
|                                              const u32_le* exe_load_args, u32 checksum_size, | ||||
|                                              u32 exe_checksum, bool no_flash) { | ||||
| 
 | ||||
|     u32_le game_instructions[2]; | ||||
|     kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr, game_instructions, | ||||
|                             sizeof(u32) * 2); | ||||
| 
 | ||||
|     std::array<u32_le, g_plugin_loader_bootloader.size() / sizeof(u32)> bootloader; | ||||
|     memcpy(bootloader.data(), g_plugin_loader_bootloader.data(), g_plugin_loader_bootloader.size()); | ||||
| 
 | ||||
|     for (auto it = bootloader.begin(); it < bootloader.end(); it++) { | ||||
|         switch (static_cast<u32>(*it)) { | ||||
|         case 0xDEAD0000: { | ||||
|             *it = game_instructions[0]; | ||||
|         } break; | ||||
|         case 0xDEAD0001: { | ||||
|             *it = game_instructions[1]; | ||||
|         } break; | ||||
|         case 0xDEAD0002: { | ||||
|             *it = process.codeset->CodeSegment().addr; | ||||
|         } break; | ||||
|         case 0xDEAD0003: { | ||||
|             for (u32 i = 0; | ||||
|                  i < | ||||
|                  sizeof(Service::PLGLDR::PLG_LDR::PluginLoaderContext::load_exe_args) / sizeof(u32); | ||||
|                  i++) { | ||||
|                 bootloader[i + (it - bootloader.begin())] = exe_load_args[i]; | ||||
|             } | ||||
|         } break; | ||||
|         case 0xDEAD0004: { | ||||
|             *it = _3GX_exe_load_addr + sizeof(PluginHeader); | ||||
|         } break; | ||||
|         case 0xDEAD0005: { | ||||
|             *it = _3GX_exe_load_addr + sizeof(PluginHeader) + checksum_size; | ||||
|         } break; | ||||
|         case 0xDEAD0006: { | ||||
|             *it = exe_checksum; | ||||
|         } break; | ||||
|         case 0xDEAD0007: { | ||||
|             *it = _3GX_exe_load_addr - 0xC; | ||||
|         } break; | ||||
|         case 0xDEAD0008: { | ||||
|             *it = _3GX_exe_load_addr + sizeof(PluginHeader); | ||||
|         } break; | ||||
|         case 0xDEAD0009: { | ||||
|             *it = no_flash ? 1 : 0; | ||||
|         } break; | ||||
|         case 0xDEAD000A: { | ||||
|             for (u32 i = 0; i < exe_load_func.size(); i++) { | ||||
|                 bootloader[i + (it - bootloader.begin())] = exe_load_func[i]; | ||||
|             } | ||||
|         } break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Map bootloader to the offset provided
 | ||||
|     auto backing_memory = kernel.memory.GetFCRAMRef(memory_offset); | ||||
|     std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + bootloader_memory_size, 0); | ||||
|     auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr - bootloader_memory_size, | ||||
|                                                    backing_memory, bootloader_memory_size, | ||||
|                                                    Kernel::MemoryState::Continuous); | ||||
|     ASSERT(vma.Succeeded()); | ||||
|     process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute); | ||||
| 
 | ||||
|     // Write bootloader
 | ||||
|     kernel.memory.WriteBlock( | ||||
|         process, _3GX_exe_load_addr - bootloader_memory_size, bootloader.data(), | ||||
|         std::min<size_t>(bootloader.size() * sizeof(u32), bootloader_memory_size)); | ||||
| 
 | ||||
|     game_instructions[0] = 0xE51FF004; // ldr pc, [pc, #-4]
 | ||||
|     game_instructions[1] = _3GX_exe_load_addr - bootloader_memory_size; | ||||
|     kernel.memory.WriteBlock(process, process.codeset->CodeSegment().addr, game_instructions, | ||||
|                              sizeof(u32) * 2); | ||||
| } | ||||
							
								
								
									
										149
									
								
								src/core/file_sys/plugin_3gx.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/core/file_sys/plugin_3gx.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,149 @@ | |||
| // Copyright 2022 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| // Copyright 2022 The Pixellizer Group
 | ||||
| //
 | ||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 | ||||
| // associated documentation files (the "Software"), to deal in the Software without restriction,
 | ||||
| // including without limitation the rights to use, copy, modify, merge, publish, distribute,
 | ||||
| // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 | ||||
| // furnished to do so, subject to the following conditions:
 | ||||
| //
 | ||||
| // The above copyright notice and this permission notice shall be included in all copies or
 | ||||
| // substantial portions of the Software.
 | ||||
| //
 | ||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 | ||||
| // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 | ||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 | ||||
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <core/file_sys/archive_backend.h> | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/hle/service/plgldr/plgldr.h" | ||||
| 
 | ||||
| namespace Loader { | ||||
| enum class ResultStatus; | ||||
| } | ||||
| 
 | ||||
| namespace FileUtil { | ||||
| class IOFile; | ||||
| } | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| class FileBackend; | ||||
| 
 | ||||
| class Plugin3GXLoader { | ||||
| public: | ||||
|     Loader::ResultStatus Load(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, | ||||
|                               Kernel::Process& process, Kernel::KernelSystem& kernel); | ||||
| 
 | ||||
|     struct PluginHeader { | ||||
|         u32_le magic; | ||||
|         u32_le version; | ||||
|         u32_le heap_VA; | ||||
|         u32_le heap_size; | ||||
|         u32_le exe_size; // Include sizeof(PluginHeader) + .text + .rodata + .data + .bss (0x1000
 | ||||
|                          // aligned too)
 | ||||
|         u32_le is_default_plugin; | ||||
|         u32_le plgldr_event; ///< Used for synchronization, unused in citra
 | ||||
|         u32_le plgldr_reply; ///< Used for synchronization, unused in citra
 | ||||
|         u32_le reserved[24]; | ||||
|         u32_le config[32]; | ||||
|     }; | ||||
| 
 | ||||
|     static_assert(sizeof(PluginHeader) == 0x100, "Invalid plugin header size"); | ||||
| 
 | ||||
|     static constexpr const char* _3GX_magic = "3GX$0002"; | ||||
|     static constexpr u32 _3GX_exe_load_addr = 0x07000000; | ||||
|     static constexpr u32 _3GX_heap_load_addr = 0x06000000; | ||||
|     static constexpr u32 _3GX_fb_size = 0xA9000; | ||||
| 
 | ||||
| private: | ||||
|     Loader::ResultStatus Map(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, | ||||
|                              Kernel::Process& process, Kernel::KernelSystem& kernel); | ||||
| 
 | ||||
|     static constexpr size_t bootloader_memory_size = 0x1000; | ||||
|     static void MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel, | ||||
|                               u32 memory_offset, const std::vector<u32>& exe_load_func, | ||||
|                               const u32_le* exe_load_args, u32 checksum_size, u32 exe_checksum, | ||||
|                               bool no_flash); | ||||
| 
 | ||||
|     struct _3gx_Infos { | ||||
|         enum class Compatibility { CONSOLE = 0, CITRA = 1, CONSOLE_CITRA = 2 }; | ||||
|         u32_le author_len; | ||||
|         u32_le author_msg_offset; | ||||
|         u32_le title_len; | ||||
|         u32_le title_msg_offset; | ||||
|         u32_le summary_len; | ||||
|         u32_le summary_msg_offset; | ||||
|         u32_le description_len; | ||||
|         u32_le description_msg_offset; | ||||
|         union { | ||||
|             u32_le raw; | ||||
|             BitField<0, 1, u32_le> embedded_exe_func; | ||||
|             BitField<1, 1, u32_le> embedded_swap_func; | ||||
|             BitField<2, 2, u32_le> memory_region_size; | ||||
|             BitField<4, 2, u32_le> compatibility; | ||||
|         } flags; | ||||
|         u32_le exe_load_checksum; | ||||
|         u32_le builtin_load_exe_args[4]; | ||||
|         u32_le builtin_swap_load_args[4]; | ||||
|     }; | ||||
| 
 | ||||
|     struct _3gx_Targets { | ||||
|         u32_le count; | ||||
|         u32_le title_offsets; | ||||
|     }; | ||||
| 
 | ||||
|     struct _3gx_Symtable { | ||||
|         u32_le nb_symbols; | ||||
|         u32_le symbols_offset; | ||||
|         u32_le name_table_offset; | ||||
|     }; | ||||
| 
 | ||||
|     struct _3gx_Executable { | ||||
|         u32_le code_offset; | ||||
|         u32_le rodata_offset; | ||||
|         u32_le data_offset; | ||||
|         u32_le code_size; | ||||
|         u32_le rodata_size; | ||||
|         u32_le data_size; | ||||
|         u32_le bss_size; | ||||
|         u32_le exe_load_func_offset;  // NOP terminated
 | ||||
|         u32_le swap_save_func_offset; // NOP terminated
 | ||||
|         u32_le swap_load_func_offset; // NOP terminated
 | ||||
|     }; | ||||
| 
 | ||||
|     struct _3gx_Header { | ||||
|         u64_le magic; | ||||
|         u32_le version; | ||||
|         u32_le reserved; | ||||
|         _3gx_Infos infos; | ||||
|         _3gx_Executable executable; | ||||
|         _3gx_Targets targets; | ||||
|         _3gx_Symtable symtable; | ||||
|     }; | ||||
| 
 | ||||
|     _3gx_Header header; | ||||
| 
 | ||||
|     std::string author; | ||||
|     std::string title; | ||||
|     std::string summary; | ||||
|     std::string description; | ||||
| 
 | ||||
|     std::vector<u32> compatible_TID; | ||||
|     std::vector<u8> text_section; | ||||
|     std::vector<u8> data_section; | ||||
|     std::vector<u8> rodata_section; | ||||
| 
 | ||||
|     std::vector<u32> exe_load_func; | ||||
|     u32_le exe_load_args[4]; | ||||
| }; | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										186
									
								
								src/core/file_sys/plugin_3gx_bootloader.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/core/file_sys/plugin_3gx_bootloader.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,186 @@ | |||
| // Copyright 2022 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| // Copyright 2022 The Pixellizer Group
 | ||||
| //
 | ||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 | ||||
| // associated documentation files (the "Software"), to deal in the Software without restriction,
 | ||||
| // including without limitation the rights to use, copy, modify, merge, publish, distribute,
 | ||||
| // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 | ||||
| // furnished to do so, subject to the following conditions:
 | ||||
| //
 | ||||
| // The above copyright notice and this permission notice shall be included in all copies or
 | ||||
| // substantial portions of the Software.
 | ||||
| //
 | ||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 | ||||
| // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 | ||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 | ||||
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| // Plugin bootloader payload
 | ||||
| // Compiled with https://shell-storm.org/online/Online-Assembler-and-Disassembler/
 | ||||
| /*
 | ||||
| ; Backup registers | ||||
| 
 | ||||
|     stmfd   sp!, {r0-r12} | ||||
|     mrs     r0, cpsr | ||||
|     stmfd   sp!, {r0} | ||||
| 
 | ||||
| ; Check plugin validity and exit if invalid (also set a flag) | ||||
| 
 | ||||
|     adr     r0, g_plgstartendptr | ||||
|     ldr     r1, [r0, #4] | ||||
|     ldr     r0, [r0] | ||||
|     adr     r2, g_plgloadexeargs | ||||
|     mov     lr, pc | ||||
|     adr     pc, g_loadexefunc | ||||
|     adr     r1, g_loadexechecksum | ||||
|     ldr     r1, [r1] | ||||
|     cmp     r0, r1 | ||||
|     adr     r0, g_plgldrlaunchstatus | ||||
|     ldr     r0, [r0] | ||||
|     moveq   r1, #1 | ||||
|     movne   r1, #0 | ||||
|     str     r1, [r0] | ||||
|     svcne   0x3 | ||||
| 
 | ||||
| ; Flash top screen light blue | ||||
| 
 | ||||
|     adr     r0, g_plgnoflash | ||||
|     ldrb    r0, [r0] | ||||
|     cmp     r0, #1 | ||||
|     beq     skipflash | ||||
|     ldr     r4, =0x90202204 | ||||
|     ldr     r5, =0x01FF9933 | ||||
|     mov     r6, #64 | ||||
|     flashloop: | ||||
|     str     r5, [r4] | ||||
|     ldr     r0, =0xFF4B40 | ||||
|     mov     r1, #0 | ||||
|     svc     0xA | ||||
|     subs    r6, r6, #1 | ||||
|     bne     flashloop | ||||
|     str     r6, [r4] | ||||
|     skipflash: | ||||
| 
 | ||||
| ; Set all memory regions to RWX | ||||
| 
 | ||||
|     ldr     r0, =0xFFFF8001 | ||||
|     mov     r1, #1 | ||||
|     svc     0xB3 | ||||
| 
 | ||||
| ; Restore instructions at entrypoint | ||||
| 
 | ||||
|     adr     r0, g_savedGameInstr | ||||
|     adr     r1, g_gameentrypoint | ||||
|     ldr     r1, [r1] | ||||
|     ldr     r2, [r0] | ||||
|     str     r2, [r1] | ||||
|     ldr     r2, [r0, #4] | ||||
|     str     r2, [r1, #4] | ||||
|     svc     0x94 | ||||
| 
 | ||||
| ; Launch the plugin | ||||
| 
 | ||||
|     adr     r0, g_savedGameInstr | ||||
|     push    {r0} | ||||
|     adr     r5, g_plgentrypoint | ||||
|     ldr     r5, [r5] | ||||
|     blx     r5 | ||||
|     add     sp, sp, #4 | ||||
| 
 | ||||
| ; Restore registers and return to the game | ||||
| 
 | ||||
|     ldmfd   sp!, {r0} | ||||
|     msr     cpsr, r0 | ||||
|     ldmfd   sp!, {r0-r12} | ||||
|     adr     lr, g_gameentrypoint | ||||
|     ldr     pc, [lr] | ||||
| 
 | ||||
| .pool | ||||
| 
 | ||||
| g_savedGameInstr: | ||||
|     .word 0xDEAD0000, 0xDEAD0001 | ||||
| g_gameentrypoint: | ||||
|     .word 0xDEAD0002 | ||||
| g_plgloadexeargs: | ||||
|     .word 0xDEAD0003, 0, 0, 0 | ||||
| g_plgstartendptr: | ||||
|     .word 0xDEAD0004, 0xDEAD0005 | ||||
| g_loadexechecksum: | ||||
|     .word 0xDEAD0006 | ||||
| g_plgldrlaunchstatus: | ||||
|     .word 0xDEAD0007 | ||||
| g_plgentrypoint: | ||||
|     .word 0xDEAD0008 | ||||
| g_plgnoflash: | ||||
|     .word 0xDEAD0009 | ||||
| g_loadexefunc: | ||||
|     .word 0xDEAD000A | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     nop | ||||
|     bx lr | ||||
| */ | ||||
| 
 | ||||
| #include <array> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| constexpr std::array<u8, 412> g_plugin_loader_bootloader = { | ||||
|     0xff, 0x1f, 0x2d, 0xe9, 0x00, 0x00, 0x0f, 0xe1, 0x01, 0x00, 0x2d, 0xe9, 0xf0, 0x00, 0x8f, 0xe2, | ||||
|     0x04, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x90, 0xe5, 0xd4, 0x20, 0x8f, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1, | ||||
|     0xf4, 0xf0, 0x8f, 0xe2, 0xe0, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x01, 0x00, 0x50, 0xe1, | ||||
|     0xd8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0x90, 0xe5, 0x01, 0x10, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0x13, | ||||
|     0x00, 0x10, 0x80, 0xe5, 0x03, 0x00, 0x00, 0x1f, 0xc8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0xd0, 0xe5, | ||||
|     0x01, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x78, 0x40, 0x9f, 0xe5, 0x78, 0x50, 0x9f, 0xe5, | ||||
|     0x40, 0x60, 0xa0, 0xe3, 0x00, 0x50, 0x84, 0xe5, 0x70, 0x00, 0x9f, 0xe5, 0x00, 0x10, 0xa0, 0xe3, | ||||
|     0x0a, 0x00, 0x00, 0xef, 0x01, 0x60, 0x56, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x00, 0x60, 0x84, 0xe5, | ||||
|     0x5c, 0x00, 0x9f, 0xe5, 0x01, 0x10, 0xa0, 0xe3, 0xb3, 0x00, 0x00, 0xef, 0x54, 0x00, 0x8f, 0xe2, | ||||
|     0x58, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x00, 0x20, 0x90, 0xe5, 0x00, 0x20, 0x81, 0xe5, | ||||
|     0x04, 0x20, 0x90, 0xe5, 0x04, 0x20, 0x81, 0xe5, 0x94, 0x00, 0x00, 0xef, 0x34, 0x00, 0x8f, 0xe2, | ||||
|     0x04, 0x00, 0x2d, 0xe5, 0x58, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x35, 0xff, 0x2f, 0xe1, | ||||
|     0x04, 0xd0, 0x8d, 0xe2, 0x01, 0x00, 0xbd, 0xe8, 0x00, 0xf0, 0x29, 0xe1, 0xff, 0x1f, 0xbd, 0xe8, | ||||
|     0x18, 0xe0, 0x8f, 0xe2, 0x00, 0xf0, 0x9e, 0xe5, 0x04, 0x22, 0x20, 0x90, 0x33, 0x99, 0xff, 0x01, | ||||
|     0x40, 0x4b, 0xff, 0x00, 0x01, 0x80, 0xff, 0xff, 0x00, 0x00, 0xad, 0xde, 0x01, 0x00, 0xad, 0xde, | ||||
|     0x02, 0x00, 0xad, 0xde, 0x03, 0x00, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xad, 0xde, 0x05, 0x00, 0xad, 0xde, 0x06, 0x00, 0xad, 0xde, | ||||
|     0x07, 0x00, 0xad, 0xde, 0x08, 0x00, 0xad, 0xde, 0x09, 0x00, 0xad, 0xde, 0x0a, 0x00, 0xad, 0xde, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, | ||||
|     0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x1e, 0xff, 0x2f, 0xe1}; | ||||
|  | @ -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, | ||||
|  |  | |||
|  | @ -245,6 +245,25 @@ std::optional<u32> MemoryRegionInfo::LinearAllocate(u32 size) { | |||
|     return std::nullopt; | ||||
| } | ||||
| 
 | ||||
| std::optional<u32> 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; | ||||
|  |  | |||
|  | @ -60,6 +60,14 @@ struct MemoryRegionInfo { | |||
|      */ | ||||
|     std::optional<u32> 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<u32> 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. | ||||
|  |  | |||
|  | @ -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<AddressMapping, boost::container::dtl::static_storage_allocator< | ||||
|                                                      AddressMapping, 8, 0, true>>&)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() { | ||||
|  |  | |||
|  | @ -174,6 +174,7 @@ public: | |||
|     /// processes access to specific I/O regions and device memory.
 | ||||
|     boost::container::static_vector<AddressMapping, 8> 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
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 <related unused field> + 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 <related unused field> + 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 - <LINEAR virtual-memory base for this process>). 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<SVC> { | ||||
| 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<FunctionDef, 126> SVC_Table; | ||||
|     static const std::array<FunctionDef, 180> 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<Process> current_process = kernel.GetCurrentProcess(); | ||||
| 
 | ||||
|     std::shared_ptr<ResourceLimit>& 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<KernelState>(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<ProcessInfoType>(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<char*>(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::FunctionDef, 126> 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> thread = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<Thread>(thread_handle); | ||||
|     if (thread == nullptr) { | ||||
|         return ERR_INVALID_HANDLE; | ||||
|     } | ||||
| 
 | ||||
|     switch (type) { | ||||
|     case 0x10000: | ||||
|         *out = static_cast<s64>(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<Process> dst_process = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<Process>(dst_process_handle); | ||||
|     std::shared_ptr<Process> src_process = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<Process>(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<Process> dst_process = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<Process>(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> process = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<Process>(process_handle); | ||||
| 
 | ||||
|     if (process == nullptr) { | ||||
|         return ERR_INVALID_HANDLE; | ||||
|     } | ||||
| 
 | ||||
|     switch (static_cast<ControlProcessOP>(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<Handle> 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::FunctionDef, 180> 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::FunctionDef, 126> 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::FunctionDef, 126> 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) { | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -148,6 +148,7 @@ private: | |||
| 
 | ||||
|     std::shared_ptr<Thread> current_thread; | ||||
|     Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue; | ||||
|     std::deque<Thread*> unscheduled_ready_queue; | ||||
|     std::unordered_map<u64, Thread*> 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; | ||||
|  |  | |||
|  | @ -5,8 +5,10 @@ | |||
| #include <algorithm> | ||||
| #include <iterator> | ||||
| #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::PageTable>()), memory(memory) { | ||||
| VMManager::VMManager(Memory::MemorySystem& memory, Kernel::Process& proc) | ||||
|     : page_table(std::make_shared<Memory::PageTable>()), 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<std::vector<std::pair<MemoryRef, u32>>> VMManager::GetBackingBlocksForRange(VAddr address, | ||||
|  |  | |||
|  | @ -131,7 +131,7 @@ public: | |||
|     std::map<VAddr, VirtualMemoryArea> 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.
 | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
							
								
								
									
										278
									
								
								src/core/hle/service/plgldr/plgldr.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								src/core/hle/service/plgldr/plgldr.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <boost/serialization/weak_ptr.hpp> | ||||
| #include <fmt/format.h> | ||||
| #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<u32>(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<Kernel::Handle> PLG_LDR::GetMemoryChangedHandle(Kernel::KernelSystem& kernel) { | ||||
|     if (plgldr_context.memory_changed_handle) | ||||
|         return MakeResult(plgldr_context.memory_changed_handle); | ||||
| 
 | ||||
|     std::shared_ptr<Kernel::Event> 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<Kernel::Event> evt = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<Kernel::Event>( | ||||
|             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<u32>() == 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<u32>() == 1; | ||||
|     plgldr_context.user_load_parameters.low_title_Id = rp.Pop<u32>(); | ||||
| 
 | ||||
|     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<u32>(); | ||||
|     auto title = rp.PopMappedBuffer(); | ||||
|     auto desc = rp.PopMappedBuffer(); | ||||
| 
 | ||||
|     std::vector<char> title_data(title.GetSize() + 1); | ||||
|     std::vector<char> 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<PLG_LDR> 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<PLG_LDR>(it->second->GetServerPort()->hle_handler); | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| void InstallInterfaces(Core::System& system) { | ||||
|     std::make_shared<PLG_LDR>()->InstallAsNamedPort(system.Kernel()); | ||||
| } | ||||
| 
 | ||||
| } // namespace Service::PLGLDR
 | ||||
							
								
								
									
										149
									
								
								src/core/hle/service/plgldr/plgldr.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/core/hle/service/plgldr/plgldr.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,149 @@ | |||
| // Copyright 2022 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| // Copyright 2022 The Pixellizer Group
 | ||||
| //
 | ||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 | ||||
| // associated documentation files (the "Software"), to deal in the Software without restriction,
 | ||||
| // including without limitation the rights to use, copy, modify, merge, publish, distribute,
 | ||||
| // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 | ||||
| // furnished to do so, subject to the following conditions:
 | ||||
| //
 | ||||
| // The above copyright notice and this permission notice shall be included in all copies or
 | ||||
| // substantial portions of the Software.
 | ||||
| //
 | ||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 | ||||
| // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 | ||||
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 | ||||
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <boost/serialization/version.hpp> | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
| 
 | ||||
| namespace Service::PLGLDR { | ||||
| 
 | ||||
| class PLG_LDR final : public ServiceFramework<PLG_LDR> { | ||||
| 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 <class Archive> | ||||
|             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<u32> load_exe_func; | ||||
|         u32_le load_exe_args[4] = {0}; | ||||
| 
 | ||||
|         template <class Archive> | ||||
|         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<Kernel::Handle> 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 <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this); | ||||
|         ar& plgldr_context; | ||||
|         ar& plugin_fb_addr; | ||||
|         ar& allow_game_change; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
| 
 | ||||
| std::shared_ptr<PLG_LDR> GetService(Core::System& system); | ||||
| 
 | ||||
| void InstallInterfaces(Core::System& system); | ||||
| 
 | ||||
| } // namespace Service::PLGLDR
 | ||||
| 
 | ||||
| BOOST_CLASS_EXPORT_KEY(Service::PLGLDR::PLG_LDR) | ||||
|  | @ -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<ServiceModuleInfo, 40> service_module_map{ | ||||
| const std::array<ServiceModuleInfo, 41> 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<ServiceModuleInfo, 40> 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}, | ||||
|  |  | |||
|  | @ -194,7 +194,7 @@ struct ServiceModuleInfo { | |||
|     std::function<void(Core::System&)> init_function; | ||||
| }; | ||||
| 
 | ||||
| extern const std::array<ServiceModuleInfo, 40> service_module_map; | ||||
| extern const std::array<ServiceModuleInfo, 41> service_module_map; | ||||
| 
 | ||||
| } // namespace Service
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -243,10 +243,18 @@ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) { | |||
|     u32 notification_id = rp.Pop<u32>(); | ||||
|     u8 flags = rp.Pop<u8>(); | ||||
| 
 | ||||
|     // 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) { | ||||
|  |  | |||
|  | @ -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<bool, VRAM_SIZE / CITRA_PAGE_SIZE> vram{}; | ||||
|     std::array<bool, LINEAR_HEAP_SIZE / CITRA_PAGE_SIZE> linear_heap{}; | ||||
|     std::array<bool, NEW_LINEAR_HEAP_SIZE / CITRA_PAGE_SIZE> new_linear_heap{}; | ||||
|     std::array<bool, PLUGIN_3GX_FB_SIZE / CITRA_PAGE_SIZE> 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<T>(ret, static_cast<VAddr>(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<T>(static_cast<VAddr>(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<VAddr> 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) { | ||||
|  |  | |||
|  | @ -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 | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  |  | |||
|  | @ -4,18 +4,24 @@ | |||
| 
 | ||||
| #include <vector> | ||||
| #include <catch2/catch_test_macros.hpp> | ||||
| #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<BufferMem>(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<Kernel::VMManager>(memory); | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory, process); | ||||
|         auto result = | ||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(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<Kernel::VMManager>(memory); | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory, process); | ||||
|         auto result = | ||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(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<Kernel::VMManager>(memory); | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory, process); | ||||
|         auto result = | ||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(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<Kernel::VMManager>(memory); | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory, process); | ||||
|         auto result = | ||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), | ||||
|                                       Kernel::MemoryState::Private); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue