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:
		| @@ -192,11 +192,15 @@ public final class SettingsFragmentPresenter { | |||||||
|         Setting language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE); |         Setting language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE); | ||||||
|         Setting systemClock = systemSection.getSetting(SettingsFile.KEY_INIT_CLOCK); |         Setting systemClock = systemSection.getSetting(SettingsFile.KEY_INIT_CLOCK); | ||||||
|         Setting dateTime = systemSection.getSetting(SettingsFile.KEY_INIT_TIME); |         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_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_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 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 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) { |     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_IS_NEW_3DS = "is_new_3ds"; | ||||||
|     public static final String KEY_REGION_VALUE = "region_value"; |     public static final String KEY_REGION_VALUE = "region_value"; | ||||||
|     public static final String KEY_LANGUAGE = "language"; |     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_CLOCK = "init_clock"; | ||||||
|     public static final String KEY_INIT_TIME = "init_time"; |     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()) |                 std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch()) | ||||||
|                 .count(); |                 .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 |     // Camera | ||||||
|     using namespace Service::CAM; |     using namespace Service::CAM; | ||||||
|   | |||||||
| @@ -281,6 +281,11 @@ init_clock = | |||||||
| # Note: 3DS can only handle times later then Jan 1 2000 | # Note: 3DS can only handle times later then Jan 1 2000 | ||||||
| init_time = | 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] | [Camera] | ||||||
| # Which camera engine to use for the right outer camera | # Which camera engine to use for the right outer camera | ||||||
| # blank: a dummy camera that always returns black image | # 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="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_region">Región emulada</string> | ||||||
|     <string name="emulated_language">Idioma emulado</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 --> |     <!-- Camera settings strings --> | ||||||
|     <string name="inner_camera">Cámara interior</string> |     <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="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_region">Emulated region</string> | ||||||
|     <string name="emulated_language">Emulated language</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 --> |     <!-- Camera settings strings --> | ||||||
|     <string name="inner_camera">Inner Camera</string> |     <string name="inner_camera">Inner Camera</string> | ||||||
|   | |||||||
| @@ -657,6 +657,8 @@ void Config::ReadSystemValues() { | |||||||
|         ReadBasicSetting(Settings::values.init_clock); |         ReadBasicSetting(Settings::values.init_clock); | ||||||
|         ReadBasicSetting(Settings::values.init_time); |         ReadBasicSetting(Settings::values.init_time); | ||||||
|         ReadBasicSetting(Settings::values.init_time_offset); |         ReadBasicSetting(Settings::values.init_time_offset); | ||||||
|  |         ReadBasicSetting(Settings::values.plugin_loader_enabled); | ||||||
|  |         ReadBasicSetting(Settings::values.allow_plugin_loader); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| @@ -1131,6 +1133,8 @@ void Config::SaveSystemValues() { | |||||||
|         WriteBasicSetting(Settings::values.init_clock); |         WriteBasicSetting(Settings::values.init_clock); | ||||||
|         WriteBasicSetting(Settings::values.init_time); |         WriteBasicSetting(Settings::values.init_time); | ||||||
|         WriteBasicSetting(Settings::values.init_time_offset); |         WriteBasicSetting(Settings::values.init_time_offset); | ||||||
|  |         WriteBasicSetting(Settings::values.plugin_loader_enabled); | ||||||
|  |         WriteBasicSetting(Settings::values.allow_plugin_loader); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
|   | |||||||
| @@ -308,6 +308,8 @@ void ConfigureSystem::SetConfiguration() { | |||||||
|     ui->clock_display_label->setText( |     ui->clock_display_label->setText( | ||||||
|         QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue())); |         QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue())); | ||||||
|     ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.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() { | void ConfigureSystem::ReadSystemSettings() { | ||||||
| @@ -411,6 +413,10 @@ void ConfigureSystem::ApplyConfiguration() { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         Settings::values.init_time_offset = time_offset_days + time_offset_time; |         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( |     ConfigurationShared::ApplyPerGameSetting( | ||||||
| @@ -520,6 +526,13 @@ void ConfigureSystem::SetupPerGameUI() { | |||||||
|     ui->edit_init_time_offset_days->setVisible(false); |     ui->edit_init_time_offset_days->setVisible(false); | ||||||
|     ui->edit_init_time_offset_time->setVisible(false); |     ui->edit_init_time_offset_time->setVisible(false); | ||||||
|     ui->button_regenerate_console_id->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) { |     connect(ui->clock_speed_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) { | ||||||
|         ui->slider_clock_speed->setEnabled(index == 1); |         ui->slider_clock_speed->setEnabled(index == 1); | ||||||
|   | |||||||
| @@ -340,6 +340,27 @@ | |||||||
|           </property> |           </property> | ||||||
|          </widget> |          </widget> | ||||||
|         </item> |         </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> |        </layout> | ||||||
|       </widget> |       </widget> | ||||||
|      </item> |      </item> | ||||||
|   | |||||||
| @@ -226,6 +226,7 @@ void DebuggerBackend::Write(const Entry& entry) { | |||||||
|     SUB(Service, IR)                                                                               \ |     SUB(Service, IR)                                                                               \ | ||||||
|     SUB(Service, Y2R)                                                                              \ |     SUB(Service, Y2R)                                                                              \ | ||||||
|     SUB(Service, PS)                                                                               \ |     SUB(Service, PS)                                                                               \ | ||||||
|  |     SUB(Service, PLGLDR)                                                                           \ | ||||||
|     CLS(HW)                                                                                        \ |     CLS(HW)                                                                                        \ | ||||||
|     SUB(HW, Memory)                                                                                \ |     SUB(HW, Memory)                                                                                \ | ||||||
|     SUB(HW, LCD)                                                                                   \ |     SUB(HW, LCD)                                                                                   \ | ||||||
|   | |||||||
| @@ -93,6 +93,7 @@ enum class Class : ClassType { | |||||||
|     Service_IR,        ///< The IR service |     Service_IR,        ///< The IR service | ||||||
|     Service_Y2R,       ///< The Y2R (YUV to RGB conversion) service |     Service_Y2R,       ///< The Y2R (YUV to RGB conversion) service | ||||||
|     Service_PS,        ///< The PS (Process) service |     Service_PS,        ///< The PS (Process) service | ||||||
|  |     Service_PLGLDR,    ///< The PLGLDR (plugin loader) service | ||||||
|     HW,                ///< Low-level hardware emulation |     HW,                ///< Low-level hardware emulation | ||||||
|     HW_Memory,         ///< Memory-map and address translation |     HW_Memory,         ///< Memory-map and address translation | ||||||
|     HW_LCD,            ///< LCD register emulation |     HW_LCD,            ///< LCD register emulation | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ | |||||||
| #include "core/hle/service/ir/ir_rst.h" | #include "core/hle/service/ir/ir_rst.h" | ||||||
| #include "core/hle/service/ir/ir_user.h" | #include "core/hle/service/ir/ir_user.h" | ||||||
| #include "core/hle/service/mic_u.h" | #include "core/hle/service/mic_u.h" | ||||||
|  | #include "core/hle/service/plgldr/plgldr.h" | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
|  |  | ||||||
| @@ -70,6 +71,9 @@ void Apply() { | |||||||
|  |  | ||||||
|         Service::MIC::ReloadMic(system); |         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() { | void LogSettings() { | ||||||
| @@ -136,6 +140,8 @@ void LogSettings() { | |||||||
|     } |     } | ||||||
|     log_setting("System_IsNew3ds", values.is_new_3ds.GetValue()); |     log_setting("System_IsNew3ds", values.is_new_3ds.GetValue()); | ||||||
|     log_setting("System_RegionValue", values.region_value.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_UseGdbstub", values.use_gdbstub.GetValue()); | ||||||
|     log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue()); |     log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue()); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -432,6 +432,8 @@ struct Values { | |||||||
|     Setting<InitClock> init_clock{InitClock::SystemTime, "init_clock"}; |     Setting<InitClock> init_clock{InitClock::SystemTime, "init_clock"}; | ||||||
|     Setting<u64> init_time{946681277ULL, "init_time"}; |     Setting<u64> init_time{946681277ULL, "init_time"}; | ||||||
|     Setting<s64> init_time_offset{0, "init_time_offset"}; |     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 |     // Renderer | ||||||
|     Setting<bool> use_gles{false, "use_gles"}; |     Setting<bool> use_gles{false, "use_gles"}; | ||||||
|   | |||||||
| @@ -80,6 +80,9 @@ add_library(core STATIC | |||||||
|     file_sys/patch.h |     file_sys/patch.h | ||||||
|     file_sys/path_parser.cpp |     file_sys/path_parser.cpp | ||||||
|     file_sys/path_parser.h |     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.cpp | ||||||
|     file_sys/romfs_reader.h |     file_sys/romfs_reader.h | ||||||
|     file_sys/savedata_archive.cpp |     file_sys/savedata_archive.cpp | ||||||
| @@ -365,6 +368,8 @@ add_library(core STATIC | |||||||
|     hle/service/nwm/uds_connection.h |     hle/service/nwm/uds_connection.h | ||||||
|     hle/service/nwm/uds_data.cpp |     hle/service/nwm/uds_data.cpp | ||||||
|     hle/service/nwm/uds_data.h |     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.cpp | ||||||
|     hle/service/pm/pm.h |     hle/service/pm/pm.h | ||||||
|     hle/service/pm/pm_app.cpp |     hle/service/pm/pm_app.cpp | ||||||
|   | |||||||
| @@ -473,6 +473,10 @@ const Kernel::KernelSystem& System::Kernel() const { | |||||||
|     return *kernel; |     return *kernel; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool System::KernelRunning() { | ||||||
|  |     return kernel != nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
| Timing& System::CoreTiming() { | Timing& System::CoreTiming() { | ||||||
|     return *timing; |     return *timing; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -234,6 +234,9 @@ public: | |||||||
|     /// Gets a const reference to the kernel |     /// Gets a const reference to the kernel | ||||||
|     [[nodiscard]] const Kernel::KernelSystem& Kernel() const; |     [[nodiscard]] const Kernel::KernelSystem& Kernel() const; | ||||||
|  |  | ||||||
|  |     /// Get kernel is running | ||||||
|  |     [[nodiscard]] bool KernelRunning(); | ||||||
|  |  | ||||||
|     /// Gets a reference to the timing system |     /// Gets a reference to the timing system | ||||||
|     [[nodiscard]] Timing& CoreTiming(); |     [[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, |     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 { | class KernelSystem { | ||||||
| public: | public: | ||||||
|     explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, |     explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, | ||||||
|   | |||||||
| @@ -245,6 +245,25 @@ std::optional<u32> MemoryRegionInfo::LinearAllocate(u32 size) { | |||||||
|     return std::nullopt; |     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) { | void MemoryRegionInfo::Free(u32 offset, u32 size) { | ||||||
|     if (is_locked) { |     if (is_locked) { | ||||||
|         return; |         return; | ||||||
|   | |||||||
| @@ -60,6 +60,14 @@ struct MemoryRegionInfo { | |||||||
|      */ |      */ | ||||||
|     std::optional<u32> LinearAllocate(u32 size); |     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. |      * 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. |      * @param offset the region address offset to the beginning of FCRAM. | ||||||
|   | |||||||
| @@ -12,12 +12,15 @@ | |||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/serialization/boost_vector.hpp" | #include "common/serialization/boost_vector.hpp" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/hle/kernel/errors.h" | #include "core/hle/kernel/errors.h" | ||||||
| #include "core/hle/kernel/memory.h" | #include "core/hle/kernel/memory.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/kernel/resource_limit.h" | #include "core/hle/kernel/resource_limit.h" | ||||||
| #include "core/hle/kernel/thread.h" | #include "core/hle/kernel/thread.h" | ||||||
| #include "core/hle/kernel/vm_manager.h" | #include "core/hle/kernel/vm_manager.h" | ||||||
|  | #include "core/hle/service/plgldr/plgldr.h" | ||||||
|  | #include "core/loader/loader.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
|  |  | ||||||
| SERIALIZE_EXPORT_IMPL(Kernel::Process) | 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< |     ar&(boost::container::vector<AddressMapping, boost::container::dtl::static_storage_allocator< | ||||||
|                                                      AddressMapping, 8, 0, true>>&)address_mappings; |                                                      AddressMapping, 8, 0, true>>&)address_mappings; | ||||||
|     ar& flags.raw; |     ar& flags.raw; | ||||||
|  |     ar& no_thread_restrictions; | ||||||
|     ar& kernel_version; |     ar& kernel_version; | ||||||
|     ar& ideal_processor; |     ar& ideal_processor; | ||||||
|     ar& status; |     ar& status; | ||||||
| @@ -186,12 +190,24 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { | |||||||
|         kernel.HandleSpecialMapping(vm_manager, mapping); |         kernel.HandleSpecialMapping(vm_manager, mapping); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance()); | ||||||
|  |     if (plgldr) { | ||||||
|  |         plgldr->OnProcessRun(*this, kernel); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     status = ProcessStatus::Running; |     status = ProcessStatus::Running; | ||||||
|  |  | ||||||
|     vm_manager.LogLayout(Log::Level::Debug); |     vm_manager.LogLayout(Log::Level::Debug); | ||||||
|     Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this)); |     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 { | VAddr Process::GetLinearHeapAreaAddress() const { | ||||||
|     // Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of |     // Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of | ||||||
|     // the extra RAM in the n3DS. |     // 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) | 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.memory.RegisterPageTable(vm_manager.page_table); | ||||||
| } | } | ||||||
| Kernel::Process::~Process() { | Kernel::Process::~Process() { | ||||||
|   | |||||||
| @@ -174,6 +174,7 @@ public: | |||||||
|     /// processes access to specific I/O regions and device memory. |     /// processes access to specific I/O regions and device memory. | ||||||
|     boost::container::static_vector<AddressMapping, 8> address_mappings; |     boost::container::static_vector<AddressMapping, 8> address_mappings; | ||||||
|     ProcessFlags flags; |     ProcessFlags flags; | ||||||
|  |     bool no_thread_restrictions = false; | ||||||
|     /// Kernel compatibility version for this process |     /// Kernel compatibility version for this process | ||||||
|     u16 kernel_version = 0; |     u16 kernel_version = 0; | ||||||
|     /// The default CPU for this process, threads are scheduled on this cpu by default. |     /// 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); |     void Run(s32 main_thread_priority, u32 stack_size); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Called when the process exits by svc | ||||||
|  |      */ | ||||||
|  |     void Exit(); | ||||||
|  |  | ||||||
|     /////////////////////////////////////////////////////////////////////////////////////////////// |     /////////////////////////////////////////////////////////////////////////////////////////////// | ||||||
|     // Memory Management |     // Memory Management | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,6 +38,8 @@ | |||||||
| #include "core/hle/kernel/wait_object.h" | #include "core/hle/kernel/wait_object.h" | ||||||
| #include "core/hle/lock.h" | #include "core/hle/lock.h" | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
|  | #include "core/hle/service/plgldr/plgldr.h" | ||||||
|  | #include "core/hle/service/service.h" | ||||||
|  |  | ||||||
| namespace Kernel { | namespace Kernel { | ||||||
|  |  | ||||||
| @@ -65,6 +67,15 @@ struct MemoryInfo { | |||||||
|     u32 state; |     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 { | struct PageInfo { | ||||||
|     u32 flags; |     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." |      * For the ARM11 NATIVE_FIRM kernel, this is 5, for processes sm, fs, pm, loader, and pxi." | ||||||
|      */ |      */ | ||||||
|     KERNEL_SPAWNED_PIDS = 26, |     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, |      * Gets citra related information. This parameter is not available on real systems, | ||||||
|      * but can be used by homebrew applications to get some emulator info. |      * but can be used by homebrew applications to get some emulator info. | ||||||
| @@ -92,6 +108,134 @@ enum class SystemInfoType { | |||||||
|     CITRA_INFORMATION = 0x20000, |     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 |  * Accepted by svcGetSystemInfo param with REGION_MEMORY_USAGE type. Selects a region to query | ||||||
|  * memory usage of. |  * memory usage of. | ||||||
| @@ -121,6 +265,73 @@ enum class SystemInfoCitraInformation { | |||||||
|     BUILD_GIT_DESCRIPTION_PART2 = 41, // Git description (commit) last 7 characters. |     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> { | class SVC : public SVCWrapper<SVC> { | ||||||
| public: | public: | ||||||
|     SVC(Core::System& system); |     SVC(Core::System& system); | ||||||
| @@ -174,6 +385,7 @@ private: | |||||||
|     ResultCode GetThreadId(u32* thread_id, Handle handle); |     ResultCode GetThreadId(u32* thread_id, Handle handle); | ||||||
|     ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count); |     ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count); | ||||||
|     ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_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, |     ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, | ||||||
|                                   Handle process_handle, u32 addr); |                                   Handle process_handle, u32 addr); | ||||||
|     ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, 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 AcceptSession(Handle* out_server_session, Handle server_port_handle); | ||||||
|     ResultCode GetSystemInfo(s64* out, u32 type, s32 param); |     ResultCode GetSystemInfo(s64* out, u32 type, s32 param); | ||||||
|     ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type); |     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 { |     struct FunctionDef { | ||||||
|         using Func = void (SVC::*)(); |         using Func = void (SVC::*)(); | ||||||
| @@ -205,7 +425,7 @@ private: | |||||||
|         const char* name; |         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); |     static const FunctionDef* GetSVCInfo(u32 func_num); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -319,6 +539,8 @@ void SVC::ExitProcess() { | |||||||
|         thread->Stop(); |         thread->Stop(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     current_process->Exit(); | ||||||
|  |  | ||||||
|     // Kill the current thread |     // Kill the current thread | ||||||
|     kernel.GetCurrentThreadManager().GetCurrentThread()->Stop(); |     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<Process> current_process = kernel.GetCurrentProcess(); | ||||||
|  |  | ||||||
|     std::shared_ptr<ResourceLimit>& resource_limit = current_process->resource_limit; |     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; |         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 |         // 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 |         // 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 |         // 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; |         break; | ||||||
|     default: |     default: | ||||||
|         ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id); |         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; |     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 | /// Query process memory | ||||||
| ResultCode SVC::QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, | ResultCode SVC::QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, | ||||||
|                                    Handle process_handle, u32 addr) { |                                    Handle process_handle, u32 addr) { | ||||||
| @@ -1434,6 +1673,12 @@ ResultCode SVC::GetSystemInfo(s64* out, u32 type, s32 param) { | |||||||
|     case SystemInfoType::KERNEL_SPAWNED_PIDS: |     case SystemInfoType::KERNEL_SPAWNED_PIDS: | ||||||
|         *out = 5; |         *out = 5; | ||||||
|         break; |         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: |     case SystemInfoType::CITRA_INFORMATION: | ||||||
|         switch ((SystemInfoCitraInformation)param) { |         switch ((SystemInfoCitraInformation)param) { | ||||||
|         case SystemInfoCitraInformation::IS_CITRA: |         case SystemInfoCitraInformation::IS_CITRA: | ||||||
| @@ -1501,9 +1746,9 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { | |||||||
|     if (process == nullptr) |     if (process == nullptr) | ||||||
|         return ERR_INVALID_HANDLE; |         return ERR_INVALID_HANDLE; | ||||||
|  |  | ||||||
|     switch (type) { |     switch (static_cast<ProcessInfoType>(type)) { | ||||||
|     case 0: |     case ProcessInfoType::PRIVATE_AND_SHARED_USED_MEMORY: | ||||||
|     case 2: |     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 |         // TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure | ||||||
|         // what's the difference between them. |         // what's the difference between them. | ||||||
|         *out = process->memory_used; |         *out = process->memory_used; | ||||||
| @@ -1512,25 +1757,53 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { | |||||||
|             return ERR_MISALIGNED_SIZE; |             return ERR_MISALIGNED_SIZE; | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     case 1: |     case ProcessInfoType::SUPERVISOR_AND_HANDLE_USED_MEMORY: | ||||||
|     case 3: |     case ProcessInfoType::SUPERVISOR_AND_HANDLE_USED_MEMORY2: | ||||||
|     case 4: |     case ProcessInfoType::USED_HANDLE_COUNT: | ||||||
|     case 5: |     case ProcessInfoType::HIGHEST_HANDLE_COUNT: | ||||||
|     case 6: |     case ProcessInfoType::KPROCESS_0X234: | ||||||
|     case 7: |     case ProcessInfoType::THREAD_COUNT: | ||||||
|     case 8: |     case ProcessInfoType::MAX_THREAD_AMOUNT: | ||||||
|         // These are valid, but not implemented yet |         // These are valid, but not implemented yet | ||||||
|         LOG_ERROR(Kernel_SVC, "unimplemented GetProcessInfo type={}", type); |         LOG_ERROR(Kernel_SVC, "unimplemented GetProcessInfo type={}", type); | ||||||
|         break; |         break; | ||||||
|     case 20: |     case ProcessInfoType::LINEAR_BASE_ADDR_OFFSET: | ||||||
|         *out = Memory::FCRAM_PADDR - process->GetLinearHeapAreaAddress(); |         *out = Memory::FCRAM_PADDR - process->GetLinearHeapAreaAddress(); | ||||||
|         break; |         break; | ||||||
|     case 21: |     case ProcessInfoType::QTM_MEMORY_BLOCK_CONVERSION_OFFSET: | ||||||
|     case 22: |     case ProcessInfoType::QTM_MEMORY_ADDRESS: | ||||||
|     case 23: |     case ProcessInfoType::QTM_MEMORY_SIZE: | ||||||
|         // These return a different error value than higher invalid values |         // These return a different error value than higher invalid values | ||||||
|         LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); |         LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); | ||||||
|         return ERR_NOT_IMPLEMENTED; |         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: |     default: | ||||||
|         LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); |         LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type); | ||||||
|         return ERR_INVALID_ENUM_VALUE; |         return ERR_INVALID_ENUM_VALUE; | ||||||
| @@ -1539,7 +1812,179 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) { | |||||||
|     return RESULT_SUCCESS; |     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"}, |     {0x00, nullptr, "Unknown"}, | ||||||
|     {0x01, &SVC::Wrap<&SVC::ControlMemory>, "ControlMemory"}, |     {0x01, &SVC::Wrap<&SVC::ControlMemory>, "ControlMemory"}, | ||||||
|     {0x02, &SVC::Wrap<&SVC::QueryMemory>, "QueryMemory"}, |     {0x02, &SVC::Wrap<&SVC::QueryMemory>, "QueryMemory"}, | ||||||
| @@ -1584,7 +2029,7 @@ const std::array<SVC::FunctionDef, 126> SVC::SVC_Table{{ | |||||||
|     {0x29, nullptr, "GetHandleInfo"}, |     {0x29, nullptr, "GetHandleInfo"}, | ||||||
|     {0x2A, &SVC::Wrap<&SVC::GetSystemInfo>, "GetSystemInfo"}, |     {0x2A, &SVC::Wrap<&SVC::GetSystemInfo>, "GetSystemInfo"}, | ||||||
|     {0x2B, &SVC::Wrap<&SVC::GetProcessInfo>, "GetProcessInfo"}, |     {0x2B, &SVC::Wrap<&SVC::GetProcessInfo>, "GetProcessInfo"}, | ||||||
|     {0x2C, nullptr, "GetThreadInfo"}, |     {0x2C, &SVC::Wrap<&SVC::GetThreadInfo>, "GetThreadInfo"}, | ||||||
|     {0x2D, &SVC::Wrap<&SVC::ConnectToPort>, "ConnectToPort"}, |     {0x2D, &SVC::Wrap<&SVC::ConnectToPort>, "ConnectToPort"}, | ||||||
|     {0x2E, nullptr, "SendSyncRequest1"}, |     {0x2E, nullptr, "SendSyncRequest1"}, | ||||||
|     {0x2F, nullptr, "SendSyncRequest2"}, |     {0x2F, nullptr, "SendSyncRequest2"}, | ||||||
| @@ -1664,8 +2109,63 @@ const std::array<SVC::FunctionDef, 126> SVC::SVC_Table{{ | |||||||
|     {0x79, nullptr, "SetResourceLimitValues"}, |     {0x79, nullptr, "SetResourceLimitValues"}, | ||||||
|     {0x7A, nullptr, "AddCodeSegment"}, |     {0x7A, nullptr, "AddCodeSegment"}, | ||||||
|     {0x7B, nullptr, "Backdoor"}, |     {0x7B, nullptr, "Backdoor"}, | ||||||
|     {0x7C, nullptr, "KernelSetState"}, |     {0x7C, &SVC::Wrap<&SVC::KernelSetState>, "KernelSetState"}, | ||||||
|     {0x7D, &SVC::Wrap<&SVC::QueryProcessMemory>, "QueryProcessMemory"}, |     {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) { | const SVC::FunctionDef* SVC::GetSVCInfo(u32 func_num) { | ||||||
|   | |||||||
| @@ -72,8 +72,8 @@ void Thread::Acquire(Thread* thread) { | |||||||
| } | } | ||||||
|  |  | ||||||
| Thread::Thread(KernelSystem& kernel, u32 core_id) | Thread::Thread(KernelSystem& kernel, u32 core_id) | ||||||
|     : WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()), core_id(core_id), |     : WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()), | ||||||
|       thread_manager(kernel.GetThreadManager(core_id)) {} |       can_schedule(true), core_id(core_id), thread_manager(kernel.GetThreadManager(core_id)) {} | ||||||
| Thread::~Thread() {} | Thread::~Thread() {} | ||||||
|  |  | ||||||
| Thread* ThreadManager::GetCurrentThread() const { | Thread* ThreadManager::GetCurrentThread() const { | ||||||
| @@ -164,15 +164,29 @@ Thread* ThreadManager::PopNextReadyThread() { | |||||||
|     Thread* thread = GetCurrentThread(); |     Thread* thread = GetCurrentThread(); | ||||||
|  |  | ||||||
|     if (thread && thread->status == ThreadStatus::Running) { |     if (thread && thread->status == ThreadStatus::Running) { | ||||||
|         // We have to do better than the current thread. |         do { | ||||||
|         // This call returns null when that's not possible. |             // We have to do better than the current thread. | ||||||
|         next = ready_queue.pop_first_better(thread->current_priority); |             // This call returns null when that's not possible. | ||||||
|         if (!next) { |             next = ready_queue.pop_first_better(thread->current_priority); | ||||||
|             // Otherwise just keep going with the current thread |             if (!next) { | ||||||
|             next = thread; |                 // 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 { |     } 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; |     return next; | ||||||
|   | |||||||
| @@ -148,6 +148,7 @@ private: | |||||||
|  |  | ||||||
|     std::shared_ptr<Thread> current_thread; |     std::shared_ptr<Thread> current_thread; | ||||||
|     Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue; |     Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue; | ||||||
|  |     std::deque<Thread*> unscheduled_ready_queue; | ||||||
|     std::unordered_map<u64, Thread*> wakeup_callback_table; |     std::unordered_map<u64, Thread*> wakeup_callback_table; | ||||||
|  |  | ||||||
|     /// Event type for the thread wake up event |     /// Event type for the thread wake up event | ||||||
| @@ -289,6 +290,7 @@ public: | |||||||
|  |  | ||||||
|     u32 thread_id; |     u32 thread_id; | ||||||
|  |  | ||||||
|  |     bool can_schedule; | ||||||
|     ThreadStatus status; |     ThreadStatus status; | ||||||
|     VAddr entry_point; |     VAddr entry_point; | ||||||
|     VAddr stack_top; |     VAddr stack_top; | ||||||
|   | |||||||
| @@ -5,8 +5,10 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <iterator> | #include <iterator> | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/hle/kernel/errors.h" | #include "core/hle/kernel/errors.h" | ||||||
| #include "core/hle/kernel/vm_manager.h" | #include "core/hle/kernel/vm_manager.h" | ||||||
|  | #include "core/hle/service/plgldr/plgldr.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| #include "core/mmio.h" | #include "core/mmio.h" | ||||||
|  |  | ||||||
| @@ -37,8 +39,8 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { | |||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| VMManager::VMManager(Memory::MemorySystem& memory) | VMManager::VMManager(Memory::MemorySystem& memory, Kernel::Process& proc) | ||||||
|     : page_table(std::make_shared<Memory::PageTable>()), memory(memory) { |     : page_table(std::make_shared<Memory::PageTable>()), memory(memory), process(proc) { | ||||||
|     Reset(); |     Reset(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -383,6 +385,10 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { | |||||||
|         memory.MapIoRegion(*page_table, vma.base, vma.size, vma.mmio_handler); |         memory.MapIoRegion(*page_table, vma.base, vma.size, vma.mmio_handler); | ||||||
|         break; |         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, | ResultVal<std::vector<std::pair<MemoryRef, u32>>> VMManager::GetBackingBlocksForRange(VAddr address, | ||||||
|   | |||||||
| @@ -131,7 +131,7 @@ public: | |||||||
|     std::map<VAddr, VirtualMemoryArea> vma_map; |     std::map<VAddr, VirtualMemoryArea> vma_map; | ||||||
|     using VMAHandle = decltype(vma_map)::const_iterator; |     using VMAHandle = decltype(vma_map)::const_iterator; | ||||||
|  |  | ||||||
|     explicit VMManager(Memory::MemorySystem& memory); |     explicit VMManager(Memory::MemorySystem& memory, Kernel::Process& proc); | ||||||
|     ~VMManager(); |     ~VMManager(); | ||||||
|  |  | ||||||
|     /// Clears the address space map, re-initializing with a single free area. |     /// Clears the address space map, re-initializing with a single free area. | ||||||
| @@ -254,6 +254,7 @@ private: | |||||||
|     void UpdatePageTableForVMA(const VirtualMemoryArea& vma); |     void UpdatePageTableForVMA(const VirtualMemoryArea& vma); | ||||||
|  |  | ||||||
|     Memory::MemorySystem& memory; |     Memory::MemorySystem& memory; | ||||||
|  |     Kernel::Process& process; | ||||||
|  |  | ||||||
|     // When locked, ChangeMemoryState calls will be ignored, other modification calls will hit an |     // When locked, ChangeMemoryState calls will be ignored, other modification calls will hit an | ||||||
|     // assert. VMManager locks itself after deserialization. |     // assert. VMManager locks itself after deserialization. | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| #include "common/microprofile.h" | #include "common/microprofile.h" | ||||||
| #include "common/swap.h" | #include "common/swap.h" | ||||||
| #include "core/core.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/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/shared_memory.h" | #include "core/hle/kernel/shared_memory.h" | ||||||
| #include "core/hle/kernel/shared_page.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) { |     if (addr >= Memory::NEW_LINEAR_HEAP_VADDR && addr <= Memory::NEW_LINEAR_HEAP_VADDR_END) { | ||||||
|         return addr - Memory::NEW_LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR; |         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); |     LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:08X}", addr); | ||||||
|     // To help with debugging, set bit on address so that it's obviously invalid. |     // 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/nfc/nfc.h" | ||||||
| #include "core/hle/service/nim/nim.h" | #include "core/hle/service/nim/nim.h" | ||||||
| #include "core/hle/service/nwm/nwm.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/pm/pm.h" | ||||||
| #include "core/hle/service/ps/ps_ps.h" | #include "core/hle/service/ps/ps_ps.h" | ||||||
| #include "core/hle/service/ptm/ptm.h" | #include "core/hle/service/ptm/ptm.h" | ||||||
| @@ -55,7 +56,7 @@ | |||||||
|  |  | ||||||
| namespace Service { | namespace Service { | ||||||
|  |  | ||||||
| const std::array<ServiceModuleInfo, 40> service_module_map{ | const std::array<ServiceModuleInfo, 41> service_module_map{ | ||||||
|     {{"FS", 0x00040130'00001102, FS::InstallInterfaces}, |     {{"FS", 0x00040130'00001102, FS::InstallInterfaces}, | ||||||
|      {"PM", 0x00040130'00001202, PM::InstallInterfaces}, |      {"PM", 0x00040130'00001202, PM::InstallInterfaces}, | ||||||
|      {"LDR", 0x00040130'00003702, LDR::InstallInterfaces}, |      {"LDR", 0x00040130'00003702, LDR::InstallInterfaces}, | ||||||
| @@ -94,6 +95,7 @@ const std::array<ServiceModuleInfo, 40> service_module_map{ | |||||||
|      {"SOC", 0x00040130'00002E02, SOC::InstallInterfaces}, |      {"SOC", 0x00040130'00002E02, SOC::InstallInterfaces}, | ||||||
|      {"SSL", 0x00040130'00002F02, SSL::InstallInterfaces}, |      {"SSL", 0x00040130'00002F02, SSL::InstallInterfaces}, | ||||||
|      {"PS", 0x00040130'00003102, PS::InstallInterfaces}, |      {"PS", 0x00040130'00003102, PS::InstallInterfaces}, | ||||||
|  |      {"PLGLDR", 0x00040130'00006902, PLGLDR::InstallInterfaces}, | ||||||
|      // no HLE implementation |      // no HLE implementation | ||||||
|      {"CDC", 0x00040130'00001802, nullptr}, |      {"CDC", 0x00040130'00001802, nullptr}, | ||||||
|      {"GPIO", 0x00040130'00001B02, nullptr}, |      {"GPIO", 0x00040130'00001B02, nullptr}, | ||||||
|   | |||||||
| @@ -194,7 +194,7 @@ struct ServiceModuleInfo { | |||||||
|     std::function<void(Core::System&)> init_function; |     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 | } // namespace Service | ||||||
|  |  | ||||||
|   | |||||||
| @@ -243,10 +243,18 @@ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) { | |||||||
|     u32 notification_id = rp.Pop<u32>(); |     u32 notification_id = rp.Pop<u32>(); | ||||||
|     u8 flags = rp.Pop<u8>(); |     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); |     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
|     LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x{:X}, flags={}", notification_id, |  | ||||||
|                 flags); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void SRV::RegisterService(Kernel::HLERequestContext& ctx) { | void SRV::RegisterService(Kernel::HLERequestContext& ctx) { | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ | |||||||
| #include "core/hle/kernel/memory.h" | #include "core/hle/kernel/memory.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/lock.h" | #include "core/hle/lock.h" | ||||||
|  | #include "core/hle/service/plgldr/plgldr.h" | ||||||
|  | #include "core/hw/hw.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
| @@ -63,12 +65,16 @@ private: | |||||||
|         if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { |         if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) { | ||||||
|             return &new_linear_heap[(addr - NEW_LINEAR_HEAP_VADDR) / CITRA_PAGE_SIZE]; |             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; |         return nullptr; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     std::array<bool, VRAM_SIZE / CITRA_PAGE_SIZE> vram{}; |     std::array<bool, VRAM_SIZE / CITRA_PAGE_SIZE> vram{}; | ||||||
|     std::array<bool, LINEAR_HEAP_SIZE / CITRA_PAGE_SIZE> linear_heap{}; |     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, 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); |     static_assert(sizeof(bool) == 1); | ||||||
|     friend class boost::serialization::access; |     friend class boost::serialization::access; | ||||||
| @@ -77,6 +83,7 @@ private: | |||||||
|         ar& vram; |         ar& vram; | ||||||
|         ar& linear_heap; |         ar& linear_heap; | ||||||
|         ar& new_linear_heap; |         ar& new_linear_heap; | ||||||
|  |         ar& plugin_fb; | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -280,6 +287,10 @@ public: | |||||||
|         if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) { |         if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) { | ||||||
|             return {vram_mem, addr - VRAM_VADDR}; |             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(); |         UNREACHABLE(); | ||||||
|         return MemoryRef{}; |         return MemoryRef{}; | ||||||
| @@ -436,6 +447,22 @@ T MemorySystem::Read(const VAddr vaddr) { | |||||||
|         return value; |         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]; |     PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS]; | ||||||
|     switch (type) { |     switch (type) { | ||||||
|     case PageType::Unmapped: |     case PageType::Unmapped: | ||||||
| @@ -473,6 +500,20 @@ void MemorySystem::Write(const VAddr vaddr, const T data) { | |||||||
|         return; |         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]; |     PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS]; | ||||||
|     switch (type) { |     switch (type) { | ||||||
|     case PageType::Unmapped: |     case PageType::Unmapped: | ||||||
| @@ -657,6 +698,10 @@ static std::vector<VAddr> PhysicalToVirtualAddressForRasterizer(PAddr addr) { | |||||||
|     if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) { |     if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) { | ||||||
|         return {addr - VRAM_PADDR + VRAM_VADDR}; |         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) { |     if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) { | ||||||
|         return {addr - FCRAM_PADDR + LINEAR_HEAP_VADDR, addr - FCRAM_PADDR + NEW_LINEAR_HEAP_VADDR}; |         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(LINEAR_HEAP_VADDR, LINEAR_HEAP_VADDR_END, FCRAM_PADDR); | ||||||
|     CheckRegion(NEW_LINEAR_HEAP_VADDR, NEW_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); |     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) { | u8 MemorySystem::Read8(const VAddr addr) { | ||||||
|   | |||||||
| @@ -242,6 +242,11 @@ enum : VAddr { | |||||||
|     NEW_LINEAR_HEAP_VADDR = 0x30000000, |     NEW_LINEAR_HEAP_VADDR = 0x30000000, | ||||||
|     NEW_LINEAR_HEAP_SIZE = 0x10000000, |     NEW_LINEAR_HEAP_SIZE = 0x10000000, | ||||||
|     NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, |     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 <vector> | ||||||
| #include <catch2/catch_test_macros.hpp> | #include <catch2/catch_test_macros.hpp> | ||||||
|  | #include "core/core_timing.h" | ||||||
| #include "core/hle/kernel/errors.h" | #include "core/hle/kernel/errors.h" | ||||||
| #include "core/hle/kernel/memory.h" | #include "core/hle/kernel/memory.h" | ||||||
|  | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/kernel/vm_manager.h" | #include "core/hle/kernel/vm_manager.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
|  |  | ||||||
| TEST_CASE("Memory Basics", "[kernel][memory]") { | TEST_CASE("Memory Basics", "[kernel][memory]") { | ||||||
|     auto mem = std::make_shared<BufferMem>(Memory::CITRA_PAGE_SIZE); |     auto mem = std::make_shared<BufferMem>(Memory::CITRA_PAGE_SIZE); | ||||||
|     MemoryRef block{mem}; |     MemoryRef block{mem}; | ||||||
|  |     Core::Timing timing(1, 100); | ||||||
|     Memory::MemorySystem memory; |     Memory::MemorySystem memory; | ||||||
|  |     Kernel::KernelSystem kernel( | ||||||
|  |         memory, timing, [] {}, 0, 1, 0); | ||||||
|  |     Kernel::Process process(kernel); | ||||||
|     SECTION("mapping memory") { |     SECTION("mapping memory") { | ||||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. |         // 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 = |         auto result = | ||||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), |             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), | ||||||
|                                       Kernel::MemoryState::Private); |                                       Kernel::MemoryState::Private); | ||||||
| @@ -31,7 +37,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||||||
|  |  | ||||||
|     SECTION("unmapping memory") { |     SECTION("unmapping memory") { | ||||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. |         // 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 = |         auto result = | ||||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), |             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), | ||||||
|                                       Kernel::MemoryState::Private); |                                       Kernel::MemoryState::Private); | ||||||
| @@ -49,7 +55,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||||||
|  |  | ||||||
|     SECTION("changing memory permissions") { |     SECTION("changing memory permissions") { | ||||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. |         // 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 = |         auto result = | ||||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), |             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), | ||||||
|                                       Kernel::MemoryState::Private); |                                       Kernel::MemoryState::Private); | ||||||
| @@ -69,7 +75,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||||||
|  |  | ||||||
|     SECTION("changing memory state") { |     SECTION("changing memory state") { | ||||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack. |         // 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 = |         auto result = | ||||||
|             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), |             manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()), | ||||||
|                                       Kernel::MemoryState::Private); |                                       Kernel::MemoryState::Private); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user