Add 3GX plugin loader (#6172)
* Initial plugin loader support * More plugin loader progress * Organize code and more plugin features * Fix clang-format * Fix compilation and add android gui * Fix clang-format * Fix macos build * Fix copy-paste bug and clang-format * More merge fixes * Make suggestions * Move global variable to static member * Fix typo * Apply suggestions * Proper initialization order * Allocate plugin memory from SYSTEM instead of APPLICATION * Do not mark free pages as RWX * Fix plugins in old 3DS mode. * Implement KernelSetState and notif 0x203 * Apply changes * Remove unused variable * Fix dynarmic commit * Sublicense files with MIT License * Remove non-ascii characters from license
This commit is contained in:
parent
48ee112ceb
commit
016ce6c286
|
@ -192,11 +192,15 @@ public final class SettingsFragmentPresenter {
|
|||
Setting language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE);
|
||||
Setting systemClock = systemSection.getSetting(SettingsFile.KEY_INIT_CLOCK);
|
||||
Setting dateTime = systemSection.getSetting(SettingsFile.KEY_INIT_TIME);
|
||||
Setting pluginLoader = systemSection.getSetting(SettingsFile.KEY_PLUGIN_LOADER);
|
||||
Setting allowPluginLoader = systemSection.getSetting(SettingsFile.KEY_ALLOW_PLUGIN_LOADER);
|
||||
|
||||
sl.add(new SingleChoiceSetting(SettingsFile.KEY_REGION_VALUE, Settings.SECTION_SYSTEM, R.string.emulated_region, 0, R.array.regionNames, R.array.regionValues, -1, region));
|
||||
sl.add(new SingleChoiceSetting(SettingsFile.KEY_LANGUAGE, Settings.SECTION_SYSTEM, R.string.emulated_language, 0, R.array.languageNames, R.array.languageValues, 1, language));
|
||||
sl.add(new SingleChoiceSetting(SettingsFile.KEY_INIT_CLOCK, Settings.SECTION_SYSTEM, R.string.init_clock, R.string.init_clock_description, R.array.systemClockNames, R.array.systemClockValues, 0, systemClock));
|
||||
sl.add(new DateTimeSetting(SettingsFile.KEY_INIT_TIME, Settings.SECTION_SYSTEM, R.string.init_time, R.string.init_time_description, "2000-01-01 00:00:01", dateTime));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_PLUGIN_LOADER, Settings.SECTION_SYSTEM, R.string.plugin_loader, R.string.plugin_loader_description, false, pluginLoader));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_ALLOW_PLUGIN_LOADER, Settings.SECTION_SYSTEM, R.string.allow_plugin_loader, R.string.allow_plugin_loader_description, true, allowPluginLoader));
|
||||
}
|
||||
|
||||
private void addCameraSettings(ArrayList<SettingsItem> sl) {
|
||||
|
|
|
@ -78,6 +78,8 @@ public final class SettingsFile {
|
|||
public static final String KEY_IS_NEW_3DS = "is_new_3ds";
|
||||
public static final String KEY_REGION_VALUE = "region_value";
|
||||
public static final String KEY_LANGUAGE = "language";
|
||||
public static final String KEY_PLUGIN_LOADER = "plugin_loader";
|
||||
public static final String KEY_ALLOW_PLUGIN_LOADER = "allow_plugin_loader";
|
||||
|
||||
public static final String KEY_INIT_CLOCK = "init_clock";
|
||||
public static final String KEY_INIT_TIME = "init_time";
|
||||
|
|
|
@ -229,6 +229,10 @@ void Config::ReadValues() {
|
|||
std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
Settings::values.plugin_loader_enabled =
|
||||
sdl2_config->GetBoolean("System", "plugin_loader", false);
|
||||
Settings::values.allow_plugin_loader =
|
||||
sdl2_config->GetBoolean("System", "allow_plugin_loader", true);
|
||||
|
||||
// Camera
|
||||
using namespace Service::CAM;
|
||||
|
|
|
@ -281,6 +281,11 @@ init_clock =
|
|||
# Note: 3DS can only handle times later then Jan 1 2000
|
||||
init_time =
|
||||
|
||||
# Plugin loader state, if enabled plugins will be loaded from the SD card.
|
||||
# You can also set if homebrew apps are allowed to enable the plugin loader
|
||||
plugin_loader =
|
||||
allow_plugin_loader =
|
||||
|
||||
[Camera]
|
||||
# Which camera engine to use for the right outer camera
|
||||
# blank: a dummy camera that always returns black image
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
<string name="init_time_description">Si el \"Tipo del reloj del sistema\" está en \"Reloj emulado\", ésto cambia la fecha y hora de inicio.</string>
|
||||
<string name="emulated_region">Región emulada</string>
|
||||
<string name="emulated_language">Idioma emulado</string>
|
||||
<string name="plugin_loader">Activar \"3GX Plugin Loader\"</string>
|
||||
<string name="plugin_loader_description">Carga \"3GX plugins\" de la SD emulada si están disponibles.</string>
|
||||
<string name="allow_plugin_loader">Permiter que apps cambien el estado del \"plugin loader\"</string>
|
||||
<string name="allow_plugin_loader_description">Permite a las aplicaciones homebrew activar el \"plugin loader\" incluso si está desactivado.</string>
|
||||
|
||||
<!-- Camera settings strings -->
|
||||
<string name="inner_camera">Cámara interior</string>
|
||||
|
|
|
@ -51,6 +51,10 @@
|
|||
<string name="init_time_description">If the \"System clock type\" setting is set to \"Simulated clock\", this changes the fixed date and time to start at.</string>
|
||||
<string name="emulated_region">Emulated region</string>
|
||||
<string name="emulated_language">Emulated language</string>
|
||||
<string name="plugin_loader">Enable 3GX Plugin Loader</string>
|
||||
<string name="plugin_loader_description">Loads 3GX plugins from the emulated SD if they are available.</string>
|
||||
<string name="allow_plugin_loader">Allow apps to change plugin loader state</string>
|
||||
<string name="allow_plugin_loader_description">Allow homebrew apps to enable the plugin loader even when it is disabled.</string>
|
||||
|
||||
<!-- Camera settings strings -->
|
||||
<string name="inner_camera">Inner Camera</string>
|
||||
|
|
|
@ -657,6 +657,8 @@ void Config::ReadSystemValues() {
|
|||
ReadBasicSetting(Settings::values.init_clock);
|
||||
ReadBasicSetting(Settings::values.init_time);
|
||||
ReadBasicSetting(Settings::values.init_time_offset);
|
||||
ReadBasicSetting(Settings::values.plugin_loader_enabled);
|
||||
ReadBasicSetting(Settings::values.allow_plugin_loader);
|
||||
}
|
||||
|
||||
qt_config->endGroup();
|
||||
|
@ -1131,6 +1133,8 @@ void Config::SaveSystemValues() {
|
|||
WriteBasicSetting(Settings::values.init_clock);
|
||||
WriteBasicSetting(Settings::values.init_time);
|
||||
WriteBasicSetting(Settings::values.init_time_offset);
|
||||
WriteBasicSetting(Settings::values.plugin_loader_enabled);
|
||||
WriteBasicSetting(Settings::values.allow_plugin_loader);
|
||||
}
|
||||
|
||||
qt_config->endGroup();
|
||||
|
|
|
@ -308,6 +308,8 @@ void ConfigureSystem::SetConfiguration() {
|
|||
ui->clock_display_label->setText(
|
||||
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue()));
|
||||
ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.GetValue());
|
||||
ui->plugin_loader->setChecked(Settings::values.plugin_loader_enabled.GetValue());
|
||||
ui->allow_plugin_loader->setChecked(Settings::values.allow_plugin_loader.GetValue());
|
||||
}
|
||||
|
||||
void ConfigureSystem::ReadSystemSettings() {
|
||||
|
@ -411,6 +413,10 @@ void ConfigureSystem::ApplyConfiguration() {
|
|||
}
|
||||
|
||||
Settings::values.init_time_offset = time_offset_days + time_offset_time;
|
||||
Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked();
|
||||
|
||||
Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked());
|
||||
Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked());
|
||||
}
|
||||
|
||||
ConfigurationShared::ApplyPerGameSetting(
|
||||
|
@ -520,6 +526,13 @@ void ConfigureSystem::SetupPerGameUI() {
|
|||
ui->edit_init_time_offset_days->setVisible(false);
|
||||
ui->edit_init_time_offset_time->setVisible(false);
|
||||
ui->button_regenerate_console_id->setVisible(false);
|
||||
// Apps can change the state of the plugin loader, so plugins load
|
||||
// to a chainloaded app with specific parameters. Don't allow
|
||||
// the plugin loader state to be configured per-game as it may
|
||||
// mess things up.
|
||||
ui->label_plugin_loader->setVisible(false);
|
||||
ui->plugin_loader->setVisible(false);
|
||||
ui->allow_plugin_loader->setVisible(false);
|
||||
|
||||
connect(ui->clock_speed_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) {
|
||||
ui->slider_clock_speed->setEnabled(index == 1);
|
||||
|
|
|
@ -340,6 +340,27 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label_plugin_loader">
|
||||
<property name="text">
|
||||
<string>3GX Plugin Loader:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QCheckBox" name="plugin_loader">
|
||||
<property name="text">
|
||||
<string>Enable 3GX plugin loader</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="QCheckBox" name="allow_plugin_loader">
|
||||
<property name="text">
|
||||
<string>Allow games to change plugin loader state</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -226,6 +226,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
|||
SUB(Service, IR) \
|
||||
SUB(Service, Y2R) \
|
||||
SUB(Service, PS) \
|
||||
SUB(Service, PLGLDR) \
|
||||
CLS(HW) \
|
||||
SUB(HW, Memory) \
|
||||
SUB(HW, LCD) \
|
||||
|
|
|
@ -93,6 +93,7 @@ enum class Class : ClassType {
|
|||
Service_IR, ///< The IR service
|
||||
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
|
||||
Service_PS, ///< The PS (Process) service
|
||||
Service_PLGLDR, ///< The PLGLDR (plugin loader) service
|
||||
HW, ///< Low-level hardware emulation
|
||||
HW_Memory, ///< Memory-map and address translation
|
||||
HW_LCD, ///< LCD register emulation
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "core/hle/service/ir/ir_rst.h"
|
||||
#include "core/hle/service/ir/ir_user.h"
|
||||
#include "core/hle/service/mic_u.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
|
@ -70,6 +71,9 @@ void Apply() {
|
|||
|
||||
Service::MIC::ReloadMic(system);
|
||||
}
|
||||
|
||||
Service::PLGLDR::PLG_LDR::SetEnabled(values.plugin_loader_enabled.GetValue());
|
||||
Service::PLGLDR::PLG_LDR::SetAllowGameChangeState(values.allow_plugin_loader.GetValue());
|
||||
}
|
||||
|
||||
void LogSettings() {
|
||||
|
@ -136,6 +140,8 @@ void LogSettings() {
|
|||
}
|
||||
log_setting("System_IsNew3ds", values.is_new_3ds.GetValue());
|
||||
log_setting("System_RegionValue", values.region_value.GetValue());
|
||||
log_setting("System_PluginLoader", values.plugin_loader_enabled.GetValue());
|
||||
log_setting("System_PluginLoaderAllowed", values.allow_plugin_loader.GetValue());
|
||||
log_setting("Debugging_UseGdbstub", values.use_gdbstub.GetValue());
|
||||
log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue());
|
||||
}
|
||||
|
|
|
@ -432,6 +432,8 @@ struct Values {
|
|||
Setting<InitClock> init_clock{InitClock::SystemTime, "init_clock"};
|
||||
Setting<u64> init_time{946681277ULL, "init_time"};
|
||||
Setting<s64> init_time_offset{0, "init_time_offset"};
|
||||
Setting<bool> plugin_loader_enabled{false, "plugin_loader"};
|
||||
Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
|
||||
|
||||
// Renderer
|
||||
Setting<bool> use_gles{false, "use_gles"};
|
||||
|
|
|
@ -80,6 +80,9 @@ add_library(core STATIC
|
|||
file_sys/patch.h
|
||||
file_sys/path_parser.cpp
|
||||
file_sys/path_parser.h
|
||||
file_sys/plugin_3gx.cpp
|
||||
file_sys/plugin_3gx.h
|
||||
file_sys/plugin_3gx_bootloader.h
|
||||
file_sys/romfs_reader.cpp
|
||||
file_sys/romfs_reader.h
|
||||
file_sys/savedata_archive.cpp
|
||||
|
@ -365,6 +368,8 @@ add_library(core STATIC
|
|||
hle/service/nwm/uds_connection.h
|
||||
hle/service/nwm/uds_data.cpp
|
||||
hle/service/nwm/uds_data.h
|
||||
hle/service/plgldr/plgldr.cpp
|
||||
hle/service/plgldr/plgldr.h
|
||||
hle/service/pm/pm.cpp
|
||||
hle/service/pm/pm.h
|
||||
hle/service/pm/pm_app.cpp
|
||||
|
|
|
@ -473,6 +473,10 @@ const Kernel::KernelSystem& System::Kernel() const {
|
|||
return *kernel;
|
||||
}
|
||||
|
||||
bool System::KernelRunning() {
|
||||
return kernel != nullptr;
|
||||
}
|
||||
|
||||
Timing& System::CoreTiming() {
|
||||
return *timing;
|
||||
}
|
||||
|
|
|
@ -234,6 +234,9 @@ public:
|
|||
/// Gets a const reference to the kernel
|
||||
[[nodiscard]] const Kernel::KernelSystem& Kernel() const;
|
||||
|
||||
/// Get kernel is running
|
||||
[[nodiscard]] bool KernelRunning();
|
||||
|
||||
/// Gets a reference to the timing system
|
||||
[[nodiscard]] Timing& CoreTiming();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Plugin bootloader payload
|
||||
// Compiled with https://shell-storm.org/online/Online-Assembler-and-Disassembler/
|
||||
/*
|
||||
; Backup registers
|
||||
|
||||
stmfd sp!, {r0-r12}
|
||||
mrs r0, cpsr
|
||||
stmfd sp!, {r0}
|
||||
|
||||
; Check plugin validity and exit if invalid (also set a flag)
|
||||
|
||||
adr r0, g_plgstartendptr
|
||||
ldr r1, [r0, #4]
|
||||
ldr r0, [r0]
|
||||
adr r2, g_plgloadexeargs
|
||||
mov lr, pc
|
||||
adr pc, g_loadexefunc
|
||||
adr r1, g_loadexechecksum
|
||||
ldr r1, [r1]
|
||||
cmp r0, r1
|
||||
adr r0, g_plgldrlaunchstatus
|
||||
ldr r0, [r0]
|
||||
moveq r1, #1
|
||||
movne r1, #0
|
||||
str r1, [r0]
|
||||
svcne 0x3
|
||||
|
||||
; Flash top screen light blue
|
||||
|
||||
adr r0, g_plgnoflash
|
||||
ldrb r0, [r0]
|
||||
cmp r0, #1
|
||||
beq skipflash
|
||||
ldr r4, =0x90202204
|
||||
ldr r5, =0x01FF9933
|
||||
mov r6, #64
|
||||
flashloop:
|
||||
str r5, [r4]
|
||||
ldr r0, =0xFF4B40
|
||||
mov r1, #0
|
||||
svc 0xA
|
||||
subs r6, r6, #1
|
||||
bne flashloop
|
||||
str r6, [r4]
|
||||
skipflash:
|
||||
|
||||
; Set all memory regions to RWX
|
||||
|
||||
ldr r0, =0xFFFF8001
|
||||
mov r1, #1
|
||||
svc 0xB3
|
||||
|
||||
; Restore instructions at entrypoint
|
||||
|
||||
adr r0, g_savedGameInstr
|
||||
adr r1, g_gameentrypoint
|
||||
ldr r1, [r1]
|
||||
ldr r2, [r0]
|
||||
str r2, [r1]
|
||||
ldr r2, [r0, #4]
|
||||
str r2, [r1, #4]
|
||||
svc 0x94
|
||||
|
||||
; Launch the plugin
|
||||
|
||||
adr r0, g_savedGameInstr
|
||||
push {r0}
|
||||
adr r5, g_plgentrypoint
|
||||
ldr r5, [r5]
|
||||
blx r5
|
||||
add sp, sp, #4
|
||||
|
||||
; Restore registers and return to the game
|
||||
|
||||
ldmfd sp!, {r0}
|
||||
msr cpsr, r0
|
||||
ldmfd sp!, {r0-r12}
|
||||
adr lr, g_gameentrypoint
|
||||
ldr pc, [lr]
|
||||
|
||||
.pool
|
||||
|
||||
g_savedGameInstr:
|
||||
.word 0xDEAD0000, 0xDEAD0001
|
||||
g_gameentrypoint:
|
||||
.word 0xDEAD0002
|
||||
g_plgloadexeargs:
|
||||
.word 0xDEAD0003, 0, 0, 0
|
||||
g_plgstartendptr:
|
||||
.word 0xDEAD0004, 0xDEAD0005
|
||||
g_loadexechecksum:
|
||||
.word 0xDEAD0006
|
||||
g_plgldrlaunchstatus:
|
||||
.word 0xDEAD0007
|
||||
g_plgentrypoint:
|
||||
.word 0xDEAD0008
|
||||
g_plgnoflash:
|
||||
.word 0xDEAD0009
|
||||
g_loadexefunc:
|
||||
.word 0xDEAD000A
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
bx lr
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
|
||||
constexpr std::array<u8, 412> g_plugin_loader_bootloader = {
|
||||
0xff, 0x1f, 0x2d, 0xe9, 0x00, 0x00, 0x0f, 0xe1, 0x01, 0x00, 0x2d, 0xe9, 0xf0, 0x00, 0x8f, 0xe2,
|
||||
0x04, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x90, 0xe5, 0xd4, 0x20, 0x8f, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1,
|
||||
0xf4, 0xf0, 0x8f, 0xe2, 0xe0, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x01, 0x00, 0x50, 0xe1,
|
||||
0xd8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0x90, 0xe5, 0x01, 0x10, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0x13,
|
||||
0x00, 0x10, 0x80, 0xe5, 0x03, 0x00, 0x00, 0x1f, 0xc8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0xd0, 0xe5,
|
||||
0x01, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x78, 0x40, 0x9f, 0xe5, 0x78, 0x50, 0x9f, 0xe5,
|
||||
0x40, 0x60, 0xa0, 0xe3, 0x00, 0x50, 0x84, 0xe5, 0x70, 0x00, 0x9f, 0xe5, 0x00, 0x10, 0xa0, 0xe3,
|
||||
0x0a, 0x00, 0x00, 0xef, 0x01, 0x60, 0x56, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x00, 0x60, 0x84, 0xe5,
|
||||
0x5c, 0x00, 0x9f, 0xe5, 0x01, 0x10, 0xa0, 0xe3, 0xb3, 0x00, 0x00, 0xef, 0x54, 0x00, 0x8f, 0xe2,
|
||||
0x58, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x00, 0x20, 0x90, 0xe5, 0x00, 0x20, 0x81, 0xe5,
|
||||
0x04, 0x20, 0x90, 0xe5, 0x04, 0x20, 0x81, 0xe5, 0x94, 0x00, 0x00, 0xef, 0x34, 0x00, 0x8f, 0xe2,
|
||||
0x04, 0x00, 0x2d, 0xe5, 0x58, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x35, 0xff, 0x2f, 0xe1,
|
||||
0x04, 0xd0, 0x8d, 0xe2, 0x01, 0x00, 0xbd, 0xe8, 0x00, 0xf0, 0x29, 0xe1, 0xff, 0x1f, 0xbd, 0xe8,
|
||||
0x18, 0xe0, 0x8f, 0xe2, 0x00, 0xf0, 0x9e, 0xe5, 0x04, 0x22, 0x20, 0x90, 0x33, 0x99, 0xff, 0x01,
|
||||
0x40, 0x4b, 0xff, 0x00, 0x01, 0x80, 0xff, 0xff, 0x00, 0x00, 0xad, 0xde, 0x01, 0x00, 0xad, 0xde,
|
||||
0x02, 0x00, 0xad, 0xde, 0x03, 0x00, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xad, 0xde, 0x05, 0x00, 0xad, 0xde, 0x06, 0x00, 0xad, 0xde,
|
||||
0x07, 0x00, 0xad, 0xde, 0x08, 0x00, 0xad, 0xde, 0x09, 0x00, 0xad, 0xde, 0x0a, 0x00, 0xad, 0xde,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x1e, 0xff, 0x2f, 0xe1};
|
|
@ -82,6 +82,20 @@ enum class MemoryRegion : u16 {
|
|||
BASE = 3,
|
||||
};
|
||||
|
||||
union CoreVersion {
|
||||
CoreVersion(u32 version) : raw(version) {}
|
||||
CoreVersion(u32 major_ver, u32 minor_ver, u32 revision_ver) {
|
||||
revision.Assign(revision_ver);
|
||||
minor.Assign(minor_ver);
|
||||
major.Assign(major_ver);
|
||||
}
|
||||
|
||||
u32 raw;
|
||||
BitField<8, 8, u32> revision;
|
||||
BitField<16, 8, u32> minor;
|
||||
BitField<24, 8, u32> major;
|
||||
};
|
||||
|
||||
class KernelSystem {
|
||||
public:
|
||||
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
||||
|
|
|
@ -245,6 +245,25 @@ std::optional<u32> MemoryRegionInfo::LinearAllocate(u32 size) {
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<u32> MemoryRegionInfo::RLinearAllocate(u32 size) {
|
||||
ASSERT(!is_locked);
|
||||
|
||||
// Find the first sufficient continuous block from the upper address
|
||||
for (auto iter = free_blocks.rbegin(); iter != free_blocks.rend(); ++iter) {
|
||||
auto interval = *iter;
|
||||
ASSERT(interval.bounds() == boost::icl::interval_bounds::right_open());
|
||||
if (interval.upper() - interval.lower() >= size) {
|
||||
Interval allocated(interval.upper() - size, interval.upper());
|
||||
free_blocks -= allocated;
|
||||
used += size;
|
||||
return allocated.lower();
|
||||
}
|
||||
}
|
||||
|
||||
// No sufficient block found
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void MemoryRegionInfo::Free(u32 offset, u32 size) {
|
||||
if (is_locked) {
|
||||
return;
|
||||
|
|
|
@ -60,6 +60,14 @@ struct MemoryRegionInfo {
|
|||
*/
|
||||
std::optional<u32> LinearAllocate(u32 size);
|
||||
|
||||
/**
|
||||
* Allocates memory from the linear heap with only size specified.
|
||||
* @param size size of the memory to allocate.
|
||||
* @returns the address offset to the found block, searching from the end of FCRAM; null if
|
||||
* there is no enough space
|
||||
*/
|
||||
std::optional<u32> RLinearAllocate(u32 size);
|
||||
|
||||
/**
|
||||
* Frees one segment of memory. The memory must have been allocated as heap or linear heap.
|
||||
* @param offset the region address offset to the beginning of FCRAM.
|
||||
|
|
|
@ -12,12 +12,15 @@
|
|||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/serialization/boost_vector.hpp"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(Kernel::Process)
|
||||
|
@ -36,6 +39,7 @@ void Process::serialize(Archive& ar, const unsigned int file_version) {
|
|||
ar&(boost::container::vector<AddressMapping, boost::container::dtl::static_storage_allocator<
|
||||
AddressMapping, 8, 0, true>>&)address_mappings;
|
||||
ar& flags.raw;
|
||||
ar& no_thread_restrictions;
|
||||
ar& kernel_version;
|
||||
ar& ideal_processor;
|
||||
ar& status;
|
||||
|
@ -186,12 +190,24 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
|
|||
kernel.HandleSpecialMapping(vm_manager, mapping);
|
||||
}
|
||||
|
||||
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
|
||||
if (plgldr) {
|
||||
plgldr->OnProcessRun(*this, kernel);
|
||||
}
|
||||
|
||||
status = ProcessStatus::Running;
|
||||
|
||||
vm_manager.LogLayout(Log::Level::Debug);
|
||||
Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this));
|
||||
}
|
||||
|
||||
void Process::Exit() {
|
||||
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
|
||||
if (plgldr) {
|
||||
plgldr->OnProcessExit(*this, kernel);
|
||||
}
|
||||
}
|
||||
|
||||
VAddr Process::GetLinearHeapAreaAddress() const {
|
||||
// Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of
|
||||
// the extra RAM in the n3DS.
|
||||
|
@ -449,7 +465,7 @@ ResultCode Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission pe
|
|||
}
|
||||
|
||||
Kernel::Process::Process(KernelSystem& kernel)
|
||||
: Object(kernel), handle_table(kernel), vm_manager(kernel.memory), kernel(kernel) {
|
||||
: Object(kernel), handle_table(kernel), vm_manager(kernel.memory, *this), kernel(kernel) {
|
||||
kernel.memory.RegisterPageTable(vm_manager.page_table);
|
||||
}
|
||||
Kernel::Process::~Process() {
|
||||
|
|
|
@ -174,6 +174,7 @@ public:
|
|||
/// processes access to specific I/O regions and device memory.
|
||||
boost::container::static_vector<AddressMapping, 8> address_mappings;
|
||||
ProcessFlags flags;
|
||||
bool no_thread_restrictions = false;
|
||||
/// Kernel compatibility version for this process
|
||||
u16 kernel_version = 0;
|
||||
/// The default CPU for this process, threads are scheduled on this cpu by default.
|
||||
|
@ -200,6 +201,11 @@ public:
|
|||
*/
|
||||
void Run(s32 main_thread_priority, u32 stack_size);
|
||||
|
||||
/**
|
||||
* Called when the process exits by svc
|
||||
*/
|
||||
void Exit();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Memory Management
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
|
@ -65,6 +67,15 @@ struct MemoryInfo {
|
|||
u32 state;
|
||||
};
|
||||
|
||||
/// Values accepted by svcKernelSetState, only the known values are listed
|
||||
/// (the behaviour of other values are known, but their purpose is unclear and irrelevant).
|
||||
enum class KernelState {
|
||||
/**
|
||||
* Reboots the console
|
||||
*/
|
||||
KERNEL_STATE_REBOOT = 7,
|
||||
};
|
||||
|
||||
struct PageInfo {
|
||||
u32 flags;
|
||||
};
|
||||
|
@ -85,6 +96,11 @@ enum class SystemInfoType {
|
|||
* For the ARM11 NATIVE_FIRM kernel, this is 5, for processes sm, fs, pm, loader, and pxi."
|
||||
*/
|
||||
KERNEL_SPAWNED_PIDS = 26,
|
||||
/**
|
||||
* Check if the current system is a new 3DS. This parameter is not available on real systems,
|
||||
* but can be used by homebrew applications.
|
||||
*/
|
||||
NEW_3DS_INFO = 0x10001,
|
||||
/**
|
||||
* Gets citra related information. This parameter is not available on real systems,
|
||||
* but can be used by homebrew applications to get some emulator info.
|
||||
|
@ -92,6 +108,134 @@ enum class SystemInfoType {
|
|||
CITRA_INFORMATION = 0x20000,
|
||||
};
|
||||
|
||||
enum class ProcessInfoType {
|
||||
/**
|
||||
* Returns the amount of private (code, data, regular heap) and shared memory used by the
|
||||
* process + total supervisor-mode stack size + page-rounded size of the external handle table.
|
||||
* This is the amount of physical memory the process is using, minus TLS, main thread stack and
|
||||
* linear memory.
|
||||
*/
|
||||
PRIVATE_AND_SHARED_USED_MEMORY = 0,
|
||||
|
||||
/**
|
||||
* Returns the amount of <related unused field> + total supervisor-mode stack size +
|
||||
* page-rounded size of the external handle table.
|
||||
*/
|
||||
SUPERVISOR_AND_HANDLE_USED_MEMORY = 1,
|
||||
|
||||
/**
|
||||
* Returns the amount of private (code, data, heap) memory used by the process + total
|
||||
* supervisor-mode stack size + page-rounded size of the external handle table.
|
||||
*/
|
||||
PRIVATE_SHARED_SUPERVISOR_HANDLE_USED_MEMORY = 2,
|
||||
|
||||
/**
|
||||
* Returns the amount of <related unused field> + total supervisor-mode stack size +
|
||||
* page-rounded size of the external handle table.
|
||||
*/
|
||||
SUPERVISOR_AND_HANDLE_USED_MEMORY2 = 3,
|
||||
|
||||
/**
|
||||
* Returns the amount of handles in use by the process.
|
||||
*/
|
||||
USED_HANDLE_COUNT = 4,
|
||||
|
||||
/**
|
||||
* Returns the highest count of handles that have been open at once by the process.
|
||||
*/
|
||||
HIGHEST_HANDLE_COUNT = 5,
|
||||
|
||||
/**
|
||||
* Returns *(u32*)(KProcess+0x234) which is always 0.
|
||||
*/
|
||||
KPROCESS_0X234 = 6,
|
||||
|
||||
/**
|
||||
* Returns the number of threads of the process.
|
||||
*/
|
||||
THREAD_COUNT = 7,
|
||||
|
||||
/**
|
||||
* Returns the maximum number of threads which can be opened by this process (always 0).
|
||||
*/
|
||||
MAX_THREAD_AMOUNT = 8,
|
||||
|
||||
/**
|
||||
* Originally this only returned 0xD8E007ED. Now with v11.3 this returns the memregion for the
|
||||
* process: out low u32 = KProcess "Kernel flags from the exheader kernel descriptors" & 0xF00
|
||||
* (memory region flag). High out u32 = 0.
|
||||
*/
|
||||
MEMORY_REGION_FLAGS = 19,
|
||||
|
||||
/**
|
||||
* Low u32 = (0x20000000 - <LINEAR virtual-memory base for this process>). That is, the output
|
||||
* value is the value which can be added to LINEAR memory vaddrs for converting to
|
||||
* physical-memory addrs.
|
||||
*/
|
||||
LINEAR_BASE_ADDR_OFFSET = 20,
|
||||
|
||||
/**
|
||||
* Returns the VA -> PA conversion offset for the QTM static mem block reserved in the exheader
|
||||
* (0x800000), otherwise 0 (+ error 0xE0E01BF4) if it doesn't exist.
|
||||
*/
|
||||
QTM_MEMORY_BLOCK_CONVERSION_OFFSET = 21,
|
||||
|
||||
/**
|
||||
* Returns the base VA of the QTM static mem block reserved in the exheader, otherwise 0 (+
|
||||
* error 0xE0E01BF4) if it doesn't exist.
|
||||
*/
|
||||
QTM_MEMORY_ADDRESS = 22,
|
||||
|
||||
/**
|
||||
* Returns the size of the QTM static mem block reserved in the exheader, otherwise 0 (+ error
|
||||
* 0xE0E01BF4) if it doesn't exist.
|
||||
*/
|
||||
QTM_MEMORY_SIZE = 23,
|
||||
|
||||
// Custom values used by Luma3DS and 3GX plugins
|
||||
|
||||
/**
|
||||
* Returns the process name.
|
||||
*/
|
||||
LUMA_CUSTOM_PROCESS_NAME = 0x10000,
|
||||
|
||||
/**
|
||||
* Returns the process title ID.
|
||||
*/
|
||||
LUMA_CUSTOM_PROCESS_TITLE_ID = 0x10001,
|
||||
|
||||
/**
|
||||
* Returns the codeset text size.
|
||||
*/
|
||||
LUMA_CUSTOM_TEXT_SIZE = 0x10002,
|
||||
|
||||
/**
|
||||
* Returns the codeset rodata size.
|
||||
*/
|
||||
LUMA_CUSTOM_RODATA_SIZE = 0x10003,
|
||||
|
||||
/**
|
||||
* Returns the codeset data size.
|
||||
*/
|
||||
LUMA_CUSTOM_DATA_SIZE = 0x10004,
|
||||
|
||||
/**
|
||||
* Returns the codeset text vaddr.
|
||||
*/
|
||||
LUMA_CUSTOM_TEXT_ADDR = 0x10005,
|
||||
|
||||
/**
|
||||
* Returns the codeset rodata vaddr.
|
||||
*/
|
||||
LUMA_CUSTOM_RODATA_ADDR = 0x10006,
|
||||
|
||||
/**
|
||||
* Returns the codeset data vaddr.
|
||||
*/
|
||||
LUMA_CUSTOM_DATA_ADDR = 0x10007,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepted by svcGetSystemInfo param with REGION_MEMORY_USAGE type. Selects a region to query
|
||||
* memory usage of.
|
||||
|
@ -121,6 +265,73 @@ enum class SystemInfoCitraInformation {
|
|||
BUILD_GIT_DESCRIPTION_PART2 = 41, // Git description (commit) last 7 characters.
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepted by the custom svcControlProcess.
|
||||
*/
|
||||
enum class ControlProcessOP {
|
||||
/**
|
||||
* List all handles of the process, varg3 can be either 0 to fetch
|
||||
* all handles, or token of the type to fetch s32 count =
|
||||
* svcControlProcess(handle, PROCESSOP_GET_ALL_HANDLES,
|
||||
* (u32)&outBuf, 0) Returns how many handles were found
|
||||
*/
|
||||
PROCESSOP_GET_ALL_HANDLES = 0,
|
||||
|
||||
/**
|
||||
* Set the whole memory of the process with rwx access (in the mmu
|
||||
* table only) svcControlProcess(handle, PROCESSOP_SET_MMU_TO_RWX,
|
||||
* 0, 0)
|
||||
*/
|
||||
PROCESSOP_SET_MMU_TO_RWX,
|
||||
|
||||
/**
|
||||
* Get the handle of an event which will be signaled
|
||||
* each time the memory layout of this process changes
|
||||
* svcControlProcess(handle,
|
||||
* PROCESSOP_GET_ON_MEMORY_CHANGE_EVENT,
|
||||
* &eventHandleOut, 0)
|
||||
*/
|
||||
PROCESSOP_GET_ON_MEMORY_CHANGE_EVENT,
|
||||
|
||||
/**
|
||||
* Set a flag to be signaled when the process will be exited
|
||||
* svcControlProcess(handle, PROCESSOP_SIGNAL_ON_EXIT, 0, 0)
|
||||
*/
|
||||
PROCESSOP_SIGNAL_ON_EXIT,
|
||||
|
||||
/**
|
||||
* Get the physical address of the VAddr within the process
|
||||
* svcControlProcess(handle, PROCESSOP_GET_PA_FROM_VA, (u32)&PAOut,
|
||||
* VAddr)
|
||||
*/
|
||||
PROCESSOP_GET_PA_FROM_VA,
|
||||
|
||||
/*
|
||||
* Lock / Unlock the process's threads
|
||||
* svcControlProcess(handle, PROCESSOP_SCHEDULE_THREADS, lock,
|
||||
* threadPredicate) lock: 0 to unlock threads, any other value to
|
||||
* lock threads threadPredicate: can be NULL or a funcptr to a
|
||||
* predicate (typedef bool (*ThreadPredicate)(KThread *thread);)
|
||||
* The predicate must return true to operate on the thread
|
||||
*/
|
||||
PROCESSOP_SCHEDULE_THREADS,
|
||||
|
||||
/*
|
||||
* Lock / Unlock the process's threads
|
||||
* svcControlProcess(handle, PROCESSOP_SCHEDULE_THREADS, lock,
|
||||
* tlsmagicexclude) lock: 0 to unlock threads, any other value to
|
||||
* lock threads tlsmagicexclude: do not lock threads with this tls magic
|
||||
* value
|
||||
*/
|
||||
PROCESSOP_SCHEDULE_THREADS_WITHOUT_TLS_MAGIC,
|
||||
|
||||
/**
|
||||
* Disable any thread creation restrictions, such as priority value
|
||||
* or allowed cores
|
||||
*/
|
||||
PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS,
|
||||
};
|
||||
|
||||
class SVC : public SVCWrapper<SVC> {
|
||||
public:
|
||||
SVC(Core::System& system);
|
||||
|
@ -174,6 +385,7 @@ private:
|
|||
ResultCode GetThreadId(u32* thread_id, Handle handle);
|
||||
ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count);
|
||||
ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count);
|
||||
ResultCode KernelSetState(u32 kernel_state, u32 varg1, u32 varg2);
|
||||
ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info,
|
||||
Handle process_handle, u32 addr);
|
||||
ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 addr);
|
||||
|
@ -196,6 +408,14 @@ private:
|
|||
ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle);
|
||||
ResultCode GetSystemInfo(s64* out, u32 type, s32 param);
|
||||
ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type);
|
||||
ResultCode GetThreadInfo(s64* out, Handle thread_handle, u32 type);
|
||||
ResultCode InvalidateInstructionCacheRange(u32 addr, u32 size);
|
||||
ResultCode InvalidateEntireInstructionCache();
|
||||
u32 ConvertVaToPa(u32 addr);
|
||||
ResultCode MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address,
|
||||
Handle src_process_handle, u32 src_address, u32 size);
|
||||
ResultCode UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size);
|
||||
ResultCode ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 varg3);
|
||||
|
||||
struct FunctionDef {
|
||||
using Func = void (SVC::*)();
|
||||
|
@ -205,7 +425,7 @@ private:
|
|||
const char* name;
|
||||
};
|
||||
|
||||
static const std::array<FunctionDef, 126> SVC_Table;
|
||||
static const std::array<FunctionDef, 180> SVC_Table;
|
||||
static const FunctionDef* GetSVCInfo(u32 func_num);
|
||||
};
|
||||
|
||||
|
@ -319,6 +539,8 @@ void SVC::ExitProcess() {
|
|||
thread->Stop();
|
||||
}
|
||||
|
||||
current_process->Exit();
|
||||
|
||||
// Kill the current thread
|
||||
kernel.GetCurrentThreadManager().GetCurrentThread()->Stop();
|
||||
|
||||
|
@ -920,7 +1142,8 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
|
|||
std::shared_ptr<Process> current_process = kernel.GetCurrentProcess();
|
||||
|
||||
std::shared_ptr<ResourceLimit>& resource_limit = current_process->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority &&
|
||||
!current_process->no_thread_restrictions) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
|
@ -945,7 +1168,7 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
|
|||
// process, exheader kernel-flags bitmask 0x2000 must be set (otherwise error 0xD9001BEA is
|
||||
// returned). When processorid==0x3 and the process is not a BASE mem-region process, error
|
||||
// 0xD9001BEA is returned. These are the only restriction checks done by the kernel for
|
||||
// processorid.
|
||||
// processorid. If this is implemented, make sure to check process->no_thread_restrictions.
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id);
|
||||
|
@ -1110,6 +1333,22 @@ ResultCode SVC::ReleaseSemaphore(s32* count, Handle handle, s32 release_count) {
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Sets the kernel state
|
||||
ResultCode SVC::KernelSetState(u32 kernel_state, u32 varg1, u32 varg2) {
|
||||
switch (static_cast<KernelState>(kernel_state)) {
|
||||
|
||||
// This triggers a hardware reboot on real console, since this doesn't make sense
|
||||
// on emulator, we shutdown instead.
|
||||
case KernelState::KERNEL_STATE_REBOOT:
|
||||
system.RequestShutdown();
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "Unknown KernelSetState state={} varg1={} varg2={}", kernel_state,
|
||||
varg1, varg2);
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Query process memory
|
||||
ResultCode SVC::QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info,
|
||||
Handle process_handle, u32 addr) {
|
||||
|
@ -1434,6 +1673,12 @@ ResultCode SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
|
|||
case SystemInfoType::KERNEL_SPAWNED_PIDS:
|
||||
*out = 5;
|
||||
break;
|
||||
case SystemInfoType::NEW_3DS_INFO:
|
||||
// The actual subtypes are not implemented, homebrew just check
|
||||
// this doesn't return an error in n3ds to know the system type
|
||||
LOG_ERROR(Kernel_SVC, "unimplemented GetSystemInfo type=65537 param={}", param);
|
||||
*out = 0;
|
||||
return (system.GetNumCores() == 4) ? RESULT_SUCCESS : ERR_INVALID_ENUM_VALUE;
|
||||
case SystemInfoType::CITRA_INFORMATION:
|
||||
switch ((SystemInfoCitraInformation)param) {
|
||||
case SystemInfoCitraInformation::IS_CITRA:
|
||||
|
@ -1501,9 +1746,9 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) {
|
|||
if (process == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
switch (type) {
|
||||
case 0:
|
||||
case 2:
|
||||
switch (static_cast<ProcessInfoType>(type)) {
|
||||
case ProcessInfoType::PRIVATE_AND_SHARED_USED_MEMORY:
|
||||
case ProcessInfoType::PRIVATE_SHARED_SUPERVISOR_HANDLE_USED_MEMORY:
|
||||
// TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure
|
||||
// what's the difference between them.
|
||||
*out = process->memory_used;
|
||||
|
@ -1512,25 +1757,53 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) {
|
|||
return ERR_MISALIGNED_SIZE;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case ProcessInfoType::SUPERVISOR_AND_HANDLE_USED_MEMORY:
|
||||
case ProcessInfoType::SUPERVISOR_AND_HANDLE_USED_MEMORY2:
|
||||
case ProcessInfoType::USED_HANDLE_COUNT:
|
||||
case ProcessInfoType::HIGHEST_HANDLE_COUNT:
|
||||
case ProcessInfoType::KPROCESS_0X234:
|
||||
case ProcessInfoType::THREAD_COUNT:
|
||||
case ProcessInfoType::MAX_THREAD_AMOUNT:
|
||||
// These are valid, but not implemented yet
|
||||
LOG_ERROR(Kernel_SVC, "unimplemented GetProcessInfo type={}", type);
|
||||
break;
|
||||
case 20:
|
||||
case ProcessInfoType::LINEAR_BASE_ADDR_OFFSET:
|
||||
*out = Memory::FCRAM_PADDR - process->GetLinearHeapAreaAddress();
|
||||
break;
|
||||
case 21:
|
||||
case 22:
|
||||
case 23:
|
||||
case ProcessInfoType::QTM_MEMORY_BLOCK_CONVERSION_OFFSET:
|
||||
case ProcessInfoType::QTM_MEMORY_ADDRESS:
|
||||
case ProcessInfoType::QTM_MEMORY_SIZE:
|
||||
// These return a different error value than higher invalid values
|
||||
LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type);
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
// Here start the custom ones, taken from Luma3DS for 3GX support
|
||||
case ProcessInfoType::LUMA_CUSTOM_PROCESS_NAME:
|
||||
// Get process name
|
||||
strncpy(reinterpret_cast<char*>(out), process->codeset->GetName().c_str(), 8);
|
||||
break;
|
||||
case ProcessInfoType::LUMA_CUSTOM_PROCESS_TITLE_ID:
|
||||
// Get process TID
|
||||
*out = process->codeset->program_id;
|
||||
break;
|
||||
case ProcessInfoType::LUMA_CUSTOM_TEXT_SIZE:
|
||||
*out = process->codeset->CodeSegment().size;
|
||||
break;
|
||||
case ProcessInfoType::LUMA_CUSTOM_RODATA_SIZE:
|
||||
*out = process->codeset->RODataSegment().size;
|
||||
break;
|
||||
case ProcessInfoType::LUMA_CUSTOM_DATA_SIZE:
|
||||
*out = process->codeset->DataSegment().size;
|
||||
break;
|
||||
case ProcessInfoType::LUMA_CUSTOM_TEXT_ADDR:
|
||||
*out = process->codeset->CodeSegment().addr;
|
||||
break;
|
||||
case ProcessInfoType::LUMA_CUSTOM_RODATA_ADDR:
|
||||
*out = process->codeset->RODataSegment().addr;
|
||||
break;
|
||||
case ProcessInfoType::LUMA_CUSTOM_DATA_ADDR:
|
||||
*out = process->codeset->DataSegment().addr;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type={}", type);
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
|
@ -1539,7 +1812,179 @@ ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) {
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
const std::array<SVC::FunctionDef, 126> SVC::SVC_Table{{
|
||||
ResultCode SVC::GetThreadInfo(s64* out, Handle thread_handle, u32 type) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X} type={}", thread_handle, type);
|
||||
|
||||
std::shared_ptr<Thread> thread =
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Thread>(thread_handle);
|
||||
if (thread == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 0x10000:
|
||||
*out = static_cast<s64>(thread->GetTLSAddress());
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "unknown GetThreadInfo type={}", type);
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode SVC::InvalidateInstructionCacheRange(u32 addr, u32 size) {
|
||||
Core::GetRunningCore().InvalidateCacheRange(addr, size);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode SVC::InvalidateEntireInstructionCache() {
|
||||
Core::GetRunningCore().ClearInstructionCache();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
u32 SVC::ConvertVaToPa(u32 addr) {
|
||||
auto vma = kernel.GetCurrentProcess()->vm_manager.FindVMA(addr);
|
||||
if (vma == kernel.GetCurrentProcess()->vm_manager.vma_map.end() ||
|
||||
vma->second.type != VMAType::BackingMemory) {
|
||||
return 0;
|
||||
}
|
||||
return kernel.memory.GetFCRAMOffset(vma->second.backing_memory.GetPtr() + addr -
|
||||
vma->second.base) +
|
||||
Memory::FCRAM_PADDR;
|
||||
}
|
||||
|
||||
ResultCode SVC::MapProcessMemoryEx(Handle dst_process_handle, u32 dst_address,
|
||||
Handle src_process_handle, u32 src_address, u32 size) {
|
||||
std::shared_ptr<Process> dst_process =
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Process>(dst_process_handle);
|
||||
std::shared_ptr<Process> src_process =
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Process>(src_process_handle);
|
||||
|
||||
if (dst_process == nullptr || src_process == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (size & 0xFFF) {
|
||||
size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE;
|
||||
}
|
||||
|
||||
// Only linear memory supported
|
||||
auto vma = src_process->vm_manager.FindVMA(src_address);
|
||||
if (vma == src_process->vm_manager.vma_map.end() ||
|
||||
vma->second.type != VMAType::BackingMemory ||
|
||||
vma->second.meminfo_state != MemoryState::Continuous) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
u32 offset = src_address - vma->second.base;
|
||||
if (offset + size > vma->second.size) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto vma_res = dst_process->vm_manager.MapBackingMemory(
|
||||
dst_address,
|
||||
memory.GetFCRAMRef(vma->second.backing_memory.GetPtr() + offset -
|
||||
kernel.memory.GetFCRAMPointer(0)),
|
||||
size, Kernel::MemoryState::Continuous);
|
||||
|
||||
if (!vma_res.Succeeded()) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
dst_process->vm_manager.Reprotect(vma_res.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode SVC::UnmapProcessMemoryEx(Handle process, u32 dst_address, u32 size) {
|
||||
std::shared_ptr<Process> dst_process =
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Process>(process);
|
||||
|
||||
if (dst_process == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (size & 0xFFF) {
|
||||
size = (size & ~0xFFF) + Memory::CITRA_PAGE_SIZE;
|
||||
}
|
||||
|
||||
// Only linear memory supported
|
||||
auto vma = dst_process->vm_manager.FindVMA(dst_address);
|
||||
if (vma == dst_process->vm_manager.vma_map.end() ||
|
||||
vma->second.type != VMAType::BackingMemory ||
|
||||
vma->second.meminfo_state != MemoryState::Continuous) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
dst_process->vm_manager.UnmapRange(dst_address, size);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode SVC::ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 varg3) {
|
||||
std::shared_ptr<Process> process =
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Process>(process_handle);
|
||||
|
||||
if (process == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
switch (static_cast<ControlProcessOP>(process_OP)) {
|
||||
case ControlProcessOP::PROCESSOP_SET_MMU_TO_RWX: {
|
||||
for (auto it = process->vm_manager.vma_map.cbegin();
|
||||
it != process->vm_manager.vma_map.cend(); it++) {
|
||||
if (it->second.meminfo_state != MemoryState::Free)
|
||||
process->vm_manager.Reprotect(it, Kernel::VMAPermission::ReadWriteExecute);
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
case ControlProcessOP::PROCESSOP_GET_ON_MEMORY_CHANGE_EVENT: {
|
||||
auto plgldr = Service::PLGLDR::GetService(system);
|
||||
if (!plgldr) {
|
||||
return ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
ResultVal<Handle> out = plgldr->GetMemoryChangedHandle(kernel);
|
||||
if (out.Failed()) {
|
||||
return out.Code();
|
||||
}
|
||||
|
||||
memory.Write32(varg2, out.Unwrap());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
case ControlProcessOP::PROCESSOP_SCHEDULE_THREADS_WITHOUT_TLS_MAGIC: {
|
||||
for (u32 i = 0; i < system.GetNumCores(); i++) {
|
||||
auto& thread_list = kernel.GetThreadManager(i).GetThreadList();
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->owner_process.lock() != process) {
|
||||
continue;
|
||||
}
|
||||
if (memory.Read32(thread.get()->GetTLSAddress()) == varg3) {
|
||||
continue;
|
||||
}
|
||||
if (thread.get()->thread_id ==
|
||||
kernel.GetCurrentThreadManager().GetCurrentThread()->thread_id) {
|
||||
continue;
|
||||
}
|
||||
thread.get()->can_schedule = !varg2;
|
||||
}
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
case ControlProcessOP::PROCESSOP_DISABLE_CREATE_THREAD_RESTRICTIONS: {
|
||||
process->no_thread_restrictions = varg2 == 1;
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
case ControlProcessOP::PROCESSOP_GET_ALL_HANDLES:
|
||||
case ControlProcessOP::PROCESSOP_GET_PA_FROM_VA:
|
||||
case ControlProcessOP::PROCESSOP_SIGNAL_ON_EXIT:
|
||||
case ControlProcessOP::PROCESSOP_SCHEDULE_THREADS:
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "Unknown ControlProcessOp type={}", process_OP);
|
||||
return ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<SVC::FunctionDef, 180> SVC::SVC_Table{{
|
||||
{0x00, nullptr, "Unknown"},
|
||||
{0x01, &SVC::Wrap<&SVC::ControlMemory>, "ControlMemory"},
|
||||
{0x02, &SVC::Wrap<&SVC::QueryMemory>, "QueryMemory"},
|
||||
|
@ -1584,7 +2029,7 @@ const std::array<SVC::FunctionDef, 126> SVC::SVC_Table{{
|
|||
{0x29, nullptr, "GetHandleInfo"},
|
||||
{0x2A, &SVC::Wrap<&SVC::GetSystemInfo>, "GetSystemInfo"},
|
||||
{0x2B, &SVC::Wrap<&SVC::GetProcessInfo>, "GetProcessInfo"},
|
||||
{0x2C, nullptr, "GetThreadInfo"},
|
||||
{0x2C, &SVC::Wrap<&SVC::GetThreadInfo>, "GetThreadInfo"},
|
||||
{0x2D, &SVC::Wrap<&SVC::ConnectToPort>, "ConnectToPort"},
|
||||
{0x2E, nullptr, "SendSyncRequest1"},
|
||||
{0x2F, nullptr, "SendSyncRequest2"},
|
||||
|
@ -1664,8 +2109,63 @@ const std::array<SVC::FunctionDef, 126> SVC::SVC_Table{{
|
|||
{0x79, nullptr, "SetResourceLimitValues"},
|
||||
{0x7A, nullptr, "AddCodeSegment"},
|
||||
{0x7B, nullptr, "Backdoor"},
|
||||
{0x7C, nullptr, "KernelSetState"},
|
||||
{0x7C, &SVC::Wrap<&SVC::KernelSetState>, "KernelSetState"},
|
||||
{0x7D, &SVC::Wrap<&SVC::QueryProcessMemory>, "QueryProcessMemory"},
|
||||
// Custom SVCs
|
||||
{0x7E, nullptr, "Unused"},
|
||||
{0x7F, nullptr, "Unused"},
|
||||
{0x80, nullptr, "CustomBackdoor"},
|
||||
{0x81, nullptr, "Unused"},
|
||||
{0x82, nullptr, "Unused"},
|
||||
{0x83, nullptr, "Unused"},
|
||||
{0x84, nullptr, "Unused"},
|
||||
{0x85, nullptr, "Unused"},
|
||||
{0x86, nullptr, "Unused"},
|
||||
{0x87, nullptr, "Unused"},
|
||||
{0x88, nullptr, "Unused"},
|
||||
{0x89, nullptr, "Unused"},
|
||||
{0x8A, nullptr, "Unused"},
|
||||
{0x8B, nullptr, "Unused"},
|
||||
{0x8C, nullptr, "Unused"},
|
||||
{0x8D, nullptr, "Unused"},
|
||||
{0x8E, nullptr, "Unused"},
|
||||
{0x8F, nullptr, "Unused"},
|
||||
{0x90, &SVC::Wrap<&SVC::ConvertVaToPa>, "ConvertVaToPa"},
|
||||
{0x91, nullptr, "FlushDataCacheRange"},
|
||||
{0x92, nullptr, "FlushEntireDataCache"},
|
||||
{0x93, &SVC::Wrap<&SVC::InvalidateInstructionCacheRange>, "InvalidateInstructionCacheRange"},
|
||||
{0x94, &SVC::Wrap<&SVC::InvalidateEntireInstructionCache>, "InvalidateEntireInstructionCache"},
|
||||
{0x95, nullptr, "Unused"},
|
||||
{0x96, nullptr, "Unused"},
|
||||
{0x97, nullptr, "Unused"},
|
||||
{0x98, nullptr, "Unused"},
|
||||
{0x99, nullptr, "Unused"},
|
||||
{0x9A, nullptr, "Unused"},
|
||||
{0x9B, nullptr, "Unused"},
|
||||
{0x9C, nullptr, "Unused"},
|
||||
{0x9D, nullptr, "Unused"},
|
||||
{0x9E, nullptr, "Unused"},
|
||||
{0x9F, nullptr, "Unused"},
|
||||
{0xA0, &SVC::Wrap<&SVC::MapProcessMemoryEx>, "MapProcessMemoryEx"},
|
||||
{0xA1, &SVC::Wrap<&SVC::UnmapProcessMemoryEx>, "UnmapProcessMemoryEx"},
|
||||
{0xA2, nullptr, "ControlMemoryEx"},
|
||||
{0xA3, nullptr, "ControlMemoryUnsafe"},
|
||||
{0xA4, nullptr, "Unused"},
|
||||
{0xA5, nullptr, "Unused"},
|
||||
{0xA6, nullptr, "Unused"},
|
||||
{0xA7, nullptr, "Unused"},
|
||||
{0xA8, nullptr, "Unused"},
|
||||
{0xA9, nullptr, "Unused"},
|
||||
{0xAA, nullptr, "Unused"},
|
||||
{0xAB, nullptr, "Unused"},
|
||||
{0xAC, nullptr, "Unused"},
|
||||
{0xAD, nullptr, "Unused"},
|
||||
{0xAE, nullptr, "Unused"},
|
||||
{0xAF, nullptr, "Unused"},
|
||||
{0xB0, nullptr, "ControlService"},
|
||||
{0xB1, nullptr, "CopyHandle"},
|
||||
{0xB2, nullptr, "TranslateHandle"},
|
||||
{0xB3, &SVC::Wrap<&SVC::ControlProcess>, "ControlProcess"},
|
||||
}};
|
||||
|
||||
const SVC::FunctionDef* SVC::GetSVCInfo(u32 func_num) {
|
||||
|
|
|
@ -72,8 +72,8 @@ void Thread::Acquire(Thread* thread) {
|
|||
}
|
||||
|
||||
Thread::Thread(KernelSystem& kernel, u32 core_id)
|
||||
: WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()), core_id(core_id),
|
||||
thread_manager(kernel.GetThreadManager(core_id)) {}
|
||||
: WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()),
|
||||
can_schedule(true), core_id(core_id), thread_manager(kernel.GetThreadManager(core_id)) {}
|
||||
Thread::~Thread() {}
|
||||
|
||||
Thread* ThreadManager::GetCurrentThread() const {
|
||||
|
@ -164,15 +164,29 @@ Thread* ThreadManager::PopNextReadyThread() {
|
|||
Thread* thread = GetCurrentThread();
|
||||
|
||||
if (thread && thread->status == ThreadStatus::Running) {
|
||||
// We have to do better than the current thread.
|
||||
// This call returns null when that's not possible.
|
||||
next = ready_queue.pop_first_better(thread->current_priority);
|
||||
if (!next) {
|
||||
// Otherwise just keep going with the current thread
|
||||
next = thread;
|
||||
}
|
||||
do {
|
||||
// We have to do better than the current thread.
|
||||
// This call returns null when that's not possible.
|
||||
next = ready_queue.pop_first_better(thread->current_priority);
|
||||
if (!next) {
|
||||
// Otherwise just keep going with the current thread
|
||||
next = thread;
|
||||
break;
|
||||
} else if (!next->can_schedule)
|
||||
unscheduled_ready_queue.push_back(next);
|
||||
} while (!next->can_schedule);
|
||||
} else {
|
||||
next = ready_queue.pop_first();
|
||||
do {
|
||||
next = ready_queue.pop_first();
|
||||
if (next && !next->can_schedule)
|
||||
unscheduled_ready_queue.push_back(next);
|
||||
} while (next && !next->can_schedule);
|
||||
}
|
||||
|
||||
while (!unscheduled_ready_queue.empty()) {
|
||||
auto t = std::move(unscheduled_ready_queue.back());
|
||||
ready_queue.push_back(t->current_priority, t);
|
||||
unscheduled_ready_queue.pop_back();
|
||||
}
|
||||
|
||||
return next;
|
||||
|
|
|
@ -148,6 +148,7 @@ private:
|
|||
|
||||
std::shared_ptr<Thread> current_thread;
|
||||
Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue;
|
||||
std::deque<Thread*> unscheduled_ready_queue;
|
||||
std::unordered_map<u64, Thread*> wakeup_callback_table;
|
||||
|
||||
/// Event type for the thread wake up event
|
||||
|
@ -289,6 +290,7 @@ public:
|
|||
|
||||
u32 thread_id;
|
||||
|
||||
bool can_schedule;
|
||||
ThreadStatus status;
|
||||
VAddr entry_point;
|
||||
VAddr stack_top;
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/mmio.h"
|
||||
|
||||
|
@ -37,8 +39,8 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
VMManager::VMManager(Memory::MemorySystem& memory)
|
||||
: page_table(std::make_shared<Memory::PageTable>()), memory(memory) {
|
||||
VMManager::VMManager(Memory::MemorySystem& memory, Kernel::Process& proc)
|
||||
: page_table(std::make_shared<Memory::PageTable>()), memory(memory), process(proc) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
@ -383,6 +385,10 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
|
|||
memory.MapIoRegion(*page_table, vma.base, vma.size, vma.mmio_handler);
|
||||
break;
|
||||
}
|
||||
|
||||
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
|
||||
if (plgldr)
|
||||
plgldr->OnMemoryChanged(process, Core::System::GetInstance().Kernel());
|
||||
}
|
||||
|
||||
ResultVal<std::vector<std::pair<MemoryRef, u32>>> VMManager::GetBackingBlocksForRange(VAddr address,
|
||||
|
|
|
@ -131,7 +131,7 @@ public:
|
|||
std::map<VAddr, VirtualMemoryArea> vma_map;
|
||||
using VMAHandle = decltype(vma_map)::const_iterator;
|
||||
|
||||
explicit VMManager(Memory::MemorySystem& memory);
|
||||
explicit VMManager(Memory::MemorySystem& memory, Kernel::Process& proc);
|
||||
~VMManager();
|
||||
|
||||
/// Clears the address space map, re-initializing with a single free area.
|
||||
|
@ -254,6 +254,7 @@ private:
|
|||
void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
|
||||
|
||||
Memory::MemorySystem& memory;
|
||||
Kernel::Process& process;
|
||||
|
||||
// When locked, ChangeMemoryState calls will be ignored, other modification calls will hit an
|
||||
// assert. VMManager locks itself after deserialization.
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "common/microprofile.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/plugin_3gx.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/kernel/shared_page.h"
|
||||
|
@ -69,6 +71,9 @@ static PAddr VirtualToPhysicalAddress(VAddr addr) {
|
|||
if (addr >= Memory::NEW_LINEAR_HEAP_VADDR && addr <= Memory::NEW_LINEAR_HEAP_VADDR_END) {
|
||||
return addr - Memory::NEW_LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR;
|
||||
}
|
||||
if (addr >= Memory::PLUGIN_3GX_FB_VADDR && addr <= Memory::PLUGIN_3GX_FB_VADDR_END) {
|
||||
return addr - Memory::PLUGIN_3GX_FB_VADDR + Service::PLGLDR::PLG_LDR::GetPluginFBAddr();
|
||||
}
|
||||
|
||||
LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:08X}", addr);
|
||||
// To help with debugging, set bit on address so that it's obviously invalid.
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <boost/serialization/version.hpp>
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::PLGLDR {
|
||||
|
||||
class PLG_LDR final : public ServiceFramework<PLG_LDR> {
|
||||
public:
|
||||
struct PluginLoaderContext {
|
||||
struct PluginLoadParameters {
|
||||
u8 no_flash = 0;
|
||||
u8 no_IR_Patch = 0;
|
||||
u32_le low_title_Id = 0;
|
||||
char path[256] = {0};
|
||||
u32_le config[32] = {0};
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& no_flash;
|
||||
ar& no_IR_Patch;
|
||||
ar& low_title_Id;
|
||||
ar& path;
|
||||
ar& config;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
bool is_enabled = true;
|
||||
bool plugin_loaded = false;
|
||||
bool is_default_path = false;
|
||||
std::string plugin_path = "";
|
||||
|
||||
bool use_user_load_parameters = false;
|
||||
PluginLoadParameters user_load_parameters;
|
||||
|
||||
VAddr plg_event = 0;
|
||||
VAddr plg_reply = 0;
|
||||
Kernel::Handle memory_changed_handle = 0;
|
||||
|
||||
bool is_exe_load_function_set = false;
|
||||
u32 exe_load_checksum = 0;
|
||||
|
||||
std::vector<u32> load_exe_func;
|
||||
u32_le load_exe_args[4] = {0};
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& is_enabled;
|
||||
ar& plugin_loaded;
|
||||
ar& is_default_path;
|
||||
ar& plugin_path;
|
||||
ar& use_user_load_parameters;
|
||||
ar& user_load_parameters;
|
||||
ar& plg_event;
|
||||
ar& plg_reply;
|
||||
ar& memory_changed_handle;
|
||||
ar& is_exe_load_function_set;
|
||||
ar& exe_load_checksum;
|
||||
ar& load_exe_func;
|
||||
ar& load_exe_args;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
PLG_LDR();
|
||||
~PLG_LDR() {}
|
||||
|
||||
void OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kernel);
|
||||
void OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel);
|
||||
ResultVal<Kernel::Handle> GetMemoryChangedHandle(Kernel::KernelSystem& kernel);
|
||||
void OnMemoryChanged(Kernel::Process& process, Kernel::KernelSystem& kernel);
|
||||
|
||||
static void SetEnabled(bool enabled) {
|
||||
plgldr_context.is_enabled = enabled;
|
||||
}
|
||||
static bool GetEnabled() {
|
||||
return plgldr_context.is_enabled;
|
||||
}
|
||||
static void SetAllowGameChangeState(bool allow) {
|
||||
allow_game_change = allow;
|
||||
}
|
||||
static bool GetAllowGameChangeState() {
|
||||
return allow_game_change;
|
||||
}
|
||||
static void SetPluginFBAddr(PAddr addr) {
|
||||
plugin_fb_addr = addr;
|
||||
}
|
||||
static PAddr GetPluginFBAddr() {
|
||||
return plugin_fb_addr;
|
||||
}
|
||||
|
||||
private:
|
||||
static const Kernel::CoreVersion plgldr_version;
|
||||
|
||||
static PluginLoaderContext plgldr_context;
|
||||
static PAddr plugin_fb_addr;
|
||||
static bool allow_game_change;
|
||||
|
||||
void IsEnabled(Kernel::HLERequestContext& ctx);
|
||||
void SetEnabled(Kernel::HLERequestContext& ctx);
|
||||
void SetLoadSettings(Kernel::HLERequestContext& ctx);
|
||||
void DisplayErrorMessage(Kernel::HLERequestContext& ctx);
|
||||
void GetPLGLDRVersion(Kernel::HLERequestContext& ctx);
|
||||
void GetArbiter(Kernel::HLERequestContext& ctx);
|
||||
void GetPluginPath(Kernel::HLERequestContext& ctx);
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
|
||||
ar& plgldr_context;
|
||||
ar& plugin_fb_addr;
|
||||
ar& allow_game_change;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
std::shared_ptr<PLG_LDR> GetService(Core::System& system);
|
||||
|
||||
void InstallInterfaces(Core::System& system);
|
||||
|
||||
} // namespace Service::PLGLDR
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(Service::PLGLDR::PLG_LDR)
|
|
@ -41,6 +41,7 @@
|
|||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/hle/service/nim/nim.h"
|
||||
#include "core/hle/service/nwm/nwm.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "core/hle/service/pm/pm.h"
|
||||
#include "core/hle/service/ps/ps_ps.h"
|
||||
#include "core/hle/service/ptm/ptm.h"
|
||||
|
@ -55,7 +56,7 @@
|
|||
|
||||
namespace Service {
|
||||
|
||||
const std::array<ServiceModuleInfo, 40> service_module_map{
|
||||
const std::array<ServiceModuleInfo, 41> service_module_map{
|
||||
{{"FS", 0x00040130'00001102, FS::InstallInterfaces},
|
||||
{"PM", 0x00040130'00001202, PM::InstallInterfaces},
|
||||
{"LDR", 0x00040130'00003702, LDR::InstallInterfaces},
|
||||
|
@ -94,6 +95,7 @@ const std::array<ServiceModuleInfo, 40> service_module_map{
|
|||
{"SOC", 0x00040130'00002E02, SOC::InstallInterfaces},
|
||||
{"SSL", 0x00040130'00002F02, SSL::InstallInterfaces},
|
||||
{"PS", 0x00040130'00003102, PS::InstallInterfaces},
|
||||
{"PLGLDR", 0x00040130'00006902, PLGLDR::InstallInterfaces},
|
||||
// no HLE implementation
|
||||
{"CDC", 0x00040130'00001802, nullptr},
|
||||
{"GPIO", 0x00040130'00001B02, nullptr},
|
||||
|
|
|
@ -194,7 +194,7 @@ struct ServiceModuleInfo {
|
|||
std::function<void(Core::System&)> init_function;
|
||||
};
|
||||
|
||||
extern const std::array<ServiceModuleInfo, 40> service_module_map;
|
||||
extern const std::array<ServiceModuleInfo, 41> service_module_map;
|
||||
|
||||
} // namespace Service
|
||||
|
||||
|
|
|
@ -243,10 +243,18 @@ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) {
|
|||
u32 notification_id = rp.Pop<u32>();
|
||||
u8 flags = rp.Pop<u8>();
|
||||
|
||||
// Handle notification 0x203 in HLE, as this one may be used by homebrew to power off the
|
||||
// console. Normally, this is handled by NS. If notification handling is properly implemented,
|
||||
// this piece of code should be removed, and handled by subscribing from NS instead.
|
||||
if (notification_id == 0x203) {
|
||||
Core::System::GetInstance().RequestShutdown();
|
||||
} else {
|
||||
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x{:X}, flags={}",
|
||||
notification_id, flags);
|
||||
}
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x{:X}, flags={}", notification_id,
|
||||
flags);
|
||||
}
|
||||
|
||||
void SRV::RegisterService(Kernel::HLERequestContext& ctx) {
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
@ -63,12 +65,16 @@ private:
|
|||
if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) {
|
||||
return &new_linear_heap[(addr - NEW_LINEAR_HEAP_VADDR) / CITRA_PAGE_SIZE];
|
||||
}
|
||||
if (addr >= PLUGIN_3GX_FB_VADDR && addr < PLUGIN_3GX_FB_VADDR_END) {
|
||||
return &plugin_fb[(addr - PLUGIN_3GX_FB_VADDR) / CITRA_PAGE_SIZE];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::array<bool, VRAM_SIZE / CITRA_PAGE_SIZE> vram{};
|
||||
std::array<bool, LINEAR_HEAP_SIZE / CITRA_PAGE_SIZE> linear_heap{};
|
||||
std::array<bool, NEW_LINEAR_HEAP_SIZE / CITRA_PAGE_SIZE> new_linear_heap{};
|
||||
std::array<bool, PLUGIN_3GX_FB_SIZE / CITRA_PAGE_SIZE> plugin_fb{};
|
||||
|
||||
static_assert(sizeof(bool) == 1);
|
||||
friend class boost::serialization::access;
|
||||
|
@ -77,6 +83,7 @@ private:
|
|||
ar& vram;
|
||||
ar& linear_heap;
|
||||
ar& new_linear_heap;
|
||||
ar& plugin_fb;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -280,6 +287,10 @@ public:
|
|||
if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) {
|
||||
return {vram_mem, addr - VRAM_VADDR};
|
||||
}
|
||||
if (addr >= PLUGIN_3GX_FB_VADDR && addr < PLUGIN_3GX_FB_VADDR_END) {
|
||||
return {fcram_mem, addr - PLUGIN_3GX_FB_VADDR +
|
||||
Service::PLGLDR::PLG_LDR::GetPluginFBAddr() - FCRAM_PADDR};
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return MemoryRef{};
|
||||
|
@ -436,6 +447,22 @@ T MemorySystem::Read(const VAddr vaddr) {
|
|||
return value;
|
||||
}
|
||||
|
||||
// Custom Luma3ds mapping
|
||||
// Is there a more efficient way to do this?
|
||||
if (vaddr & (1 << 31)) {
|
||||
PAddr paddr = (vaddr & ~(1 << 31));
|
||||
if ((paddr & 0xF0000000) == Memory::FCRAM_PADDR) { // Check FCRAM region
|
||||
T value;
|
||||
std::memcpy(&value, GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), sizeof(T));
|
||||
return value;
|
||||
} else if ((paddr & 0xF0000000) == 0x10000000 &&
|
||||
paddr >= Memory::IO_AREA_PADDR) { // Check MMIO region
|
||||
T ret;
|
||||
HW::Read<T>(ret, static_cast<VAddr>(paddr) - Memory::IO_AREA_PADDR + 0x1EC00000);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
|
@ -473,6 +500,20 @@ void MemorySystem::Write(const VAddr vaddr, const T data) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Custom Luma3ds mapping
|
||||
// Is there a more efficient way to do this?
|
||||
if (vaddr & (1 << 31)) {
|
||||
PAddr paddr = (vaddr & ~(1 << 31));
|
||||
if ((paddr & 0xF0000000) == Memory::FCRAM_PADDR) { // Check FCRAM region
|
||||
std::memcpy(GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), &data, sizeof(T));
|
||||
return;
|
||||
} else if ((paddr & 0xF0000000) == 0x10000000 &&
|
||||
paddr >= Memory::IO_AREA_PADDR) { // Check MMIO region
|
||||
HW::Write<T>(static_cast<VAddr>(paddr) - Memory::IO_AREA_PADDR + 0x1EC00000, data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
|
@ -657,6 +698,10 @@ static std::vector<VAddr> PhysicalToVirtualAddressForRasterizer(PAddr addr) {
|
|||
if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) {
|
||||
return {addr - VRAM_PADDR + VRAM_VADDR};
|
||||
}
|
||||
if (addr >= Service::PLGLDR::PLG_LDR::GetPluginFBAddr() &&
|
||||
addr < Service::PLGLDR::PLG_LDR::GetPluginFBAddr() + PLUGIN_3GX_FB_SIZE) {
|
||||
return {addr - Service::PLGLDR::PLG_LDR::GetPluginFBAddr() + PLUGIN_3GX_FB_VADDR};
|
||||
}
|
||||
if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) {
|
||||
return {addr - FCRAM_PADDR + LINEAR_HEAP_VADDR, addr - FCRAM_PADDR + NEW_LINEAR_HEAP_VADDR};
|
||||
}
|
||||
|
@ -796,6 +841,9 @@ void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) {
|
|||
CheckRegion(LINEAR_HEAP_VADDR, LINEAR_HEAP_VADDR_END, FCRAM_PADDR);
|
||||
CheckRegion(NEW_LINEAR_HEAP_VADDR, NEW_LINEAR_HEAP_VADDR_END, FCRAM_PADDR);
|
||||
CheckRegion(VRAM_VADDR, VRAM_VADDR_END, VRAM_PADDR);
|
||||
if (Service::PLGLDR::PLG_LDR::GetPluginFBAddr())
|
||||
CheckRegion(PLUGIN_3GX_FB_VADDR, PLUGIN_3GX_FB_VADDR_END,
|
||||
Service::PLGLDR::PLG_LDR::GetPluginFBAddr());
|
||||
}
|
||||
|
||||
u8 MemorySystem::Read8(const VAddr addr) {
|
||||
|
|
|
@ -242,6 +242,11 @@ enum : VAddr {
|
|||
NEW_LINEAR_HEAP_VADDR = 0x30000000,
|
||||
NEW_LINEAR_HEAP_SIZE = 0x10000000,
|
||||
NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
|
||||
|
||||
/// Area where 3GX plugin framebuffers are stored
|
||||
PLUGIN_3GX_FB_VADDR = 0x06000000,
|
||||
PLUGIN_3GX_FB_SIZE = 0x000A9000,
|
||||
PLUGIN_3GX_FB_VADDR_END = PLUGIN_3GX_FB_VADDR + PLUGIN_3GX_FB_SIZE
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,18 +4,24 @@
|
|||
|
||||
#include <vector>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
TEST_CASE("Memory Basics", "[kernel][memory]") {
|
||||
auto mem = std::make_shared<BufferMem>(Memory::CITRA_PAGE_SIZE);
|
||||
MemoryRef block{mem};
|
||||
Core::Timing timing(1, 100);
|
||||
Memory::MemorySystem memory;
|
||||
Kernel::KernelSystem kernel(
|
||||
memory, timing, [] {}, 0, 1, 0);
|
||||
Kernel::Process process(kernel);
|
||||
SECTION("mapping memory") {
|
||||
// Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory);
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory, process);
|
||||
auto result =
|
||||
manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()),
|
||||
Kernel::MemoryState::Private);
|
||||
|
@ -31,7 +37,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
|
|||
|
||||
SECTION("unmapping memory") {
|
||||
// Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory);
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory, process);
|
||||
auto result =
|
||||
manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()),
|
||||
Kernel::MemoryState::Private);
|
||||
|
@ -49,7 +55,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
|
|||
|
||||
SECTION("changing memory permissions") {
|
||||
// Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory);
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory, process);
|
||||
auto result =
|
||||
manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()),
|
||||
Kernel::MemoryState::Private);
|
||||
|
@ -69,7 +75,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
|
|||
|
||||
SECTION("changing memory state") {
|
||||
// Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory);
|
||||
auto manager = std::make_unique<Kernel::VMManager>(memory, process);
|
||||
auto result =
|
||||
manager->MapBackingMemory(Memory::HEAP_VADDR, block, static_cast<u32>(block.GetSize()),
|
||||
Kernel::MemoryState::Private);
|
||||
|
|
Reference in New Issue