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:
PabloMK7 2022-12-11 09:08:58 +01:00 committed by GitHub
parent 48ee112ceb
commit 016ce6c286
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1911 additions and 42 deletions

View file

@ -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) {

View file

@ -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";

View file

@ -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;

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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();

View file

@ -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);

View file

@ -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>

View file

@ -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) \

View file

@ -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

View file

@ -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());
}

View file

@ -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"};

View file

@ -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

View file

@ -473,6 +473,10 @@ const Kernel::KernelSystem& System::Kernel() const {
return *kernel;
}
bool System::KernelRunning() {
return kernel != nullptr;
}
Timing& System::CoreTiming() {
return *timing;
}

View file

@ -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();

View file

@ -0,0 +1,364 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "core/file_sys/file_backend.h"
#include "core/file_sys/plugin_3gx.h"
#include "core/file_sys/plugin_3gx_bootloader.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/loader.h"
static std::string ReadTextInfo(FileUtil::IOFile& file, std::size_t offset, std::size_t max_size) {
if (max_size > 0x400) { // Limit read string size to 0x400 bytes, just in case
return "";
}
std::vector<char> char_data(max_size);
const u64 prev_offset = file.Tell();
if (!file.Seek(offset, SEEK_SET)) {
return "";
}
if (file.ReadBytes(char_data.data(), max_size) != max_size) {
file.Seek(prev_offset, SEEK_SET);
return "";
}
char_data[max_size - 1] = '\0';
return std::string(char_data.data());
}
static bool ReadSection(std::vector<u8>& data_out, FileUtil::IOFile& file, std::size_t offset,
std::size_t size) {
if (size > 0x5000000) { // Limit read section size to 5MiB, just in case
return false;
}
data_out.resize(size);
const u64 prev_offset = file.Tell();
if (!file.Seek(offset, SEEK_SET)) {
return false;
}
if (file.ReadBytes(data_out.data(), size) != size) {
file.Seek(prev_offset, SEEK_SET);
return false;
}
return true;
}
Loader::ResultStatus FileSys::Plugin3GXLoader::Load(
Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process,
Kernel::KernelSystem& kernel) {
FileUtil::IOFile file(plg_context.plugin_path, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not found: {}",
plg_context.plugin_path);
return Loader::ResultStatus::Error;
}
// Load CIA Header
std::vector<u8> header_data(sizeof(_3gx_Header));
if (file.ReadBytes(header_data.data(), sizeof(_3gx_Header)) != sizeof(_3gx_Header)) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}",
plg_context.plugin_path);
return Loader::ResultStatus::Error;
}
std::memcpy(&header, header_data.data(), sizeof(_3gx_Header));
// Check magic value
if (std::memcmp(&header.magic, _3GX_magic, 8) != 0) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Outdated or invalid 3GX plugin: {}",
plg_context.plugin_path);
return Loader::ResultStatus::Error;
}
if (header.infos.flags.compatibility == static_cast<u32>(_3gx_Infos::Compatibility::CONSOLE)) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not compatible with Citra: {}",
plg_context.plugin_path);
return Loader::ResultStatus::Error;
}
// Load strings
author = ReadTextInfo(file, header.infos.author_msg_offset, header.infos.author_len);
title = ReadTextInfo(file, header.infos.title_msg_offset, header.infos.title_len);
description =
ReadTextInfo(file, header.infos.description_msg_offset, header.infos.description_len);
summary = ReadTextInfo(file, header.infos.summary_msg_offset, header.infos.summary_len);
LOG_INFO(Service_PLGLDR, "Trying to load plugin - Title: {} - Author: {}", title, author);
// Load compatible TIDs
{
std::vector<u8> raw_TID_data;
if (!ReadSection(raw_TID_data, file, header.targets.title_offsets,
header.targets.count * sizeof(u32))) {
return Loader::ResultStatus::Error;
}
for (u32 i = 0; i < u32(header.targets.count); i++) {
compatible_TID.push_back(
u32_le(*reinterpret_cast<u32*>(raw_TID_data.data() + i * sizeof(u32))));
}
}
if (!compatible_TID.empty() &&
std::find(compatible_TID.begin(), compatible_TID.end(),
static_cast<u32>(process.codeset->program_id)) == compatible_TID.end()) {
LOG_ERROR(Service_PLGLDR,
"Failed to load 3GX plugin. Not compatible with loaded process: {}",
plg_context.plugin_path);
return Loader::ResultStatus::Error;
}
// Load exe load func and args
if (header.infos.flags.embedded_exe_func.Value() &&
header.executable.exe_load_func_offset != 0) {
exe_load_func.clear();
std::vector<u8> out;
for (int i = 0; i < 32; i++) {
ReadSection(out, file, header.executable.exe_load_func_offset + i * sizeof(u32),
sizeof(u32));
u32 instruction = *reinterpret_cast<u32_le*>(out.data());
if (instruction == 0xE320F000) {
break;
}
exe_load_func.push_back(instruction);
}
memcpy(exe_load_args, header.infos.builtin_load_exe_args,
sizeof(_3gx_Infos::builtin_load_exe_args));
}
// Load code sections
if (!ReadSection(text_section, file, header.executable.code_offset,
header.executable.code_size) ||
!ReadSection(rodata_section, file, header.executable.rodata_offset,
header.executable.rodata_size) ||
!ReadSection(data_section, file, header.executable.data_offset,
header.executable.data_size)) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}",
plg_context.plugin_path);
return Loader::ResultStatus::Error;
}
return Map(plg_context, process, kernel);
}
Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process,
Kernel::KernelSystem& kernel) {
// Verify exe load checksum function is available
if (exe_load_func.empty() && plg_context.load_exe_func.empty()) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Missing checksum function: {}",
plg_context.plugin_path);
return Loader::ResultStatus::Error;
}
const std::array<u32, 4> mem_region_sizes = {
5 * 1024 * 1024, // 5 MiB
2 * 1024 * 1024, // 2 MiB
3 * 1024 * 1024, // 3 MiB
4 * 1024 * 1024 // 4 MiB
};
// Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
// Calculate the sizes of the different memory regions
const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()];
const u32 exe_size = (sizeof(PluginHeader) + text_section.size() + rodata_section.size() +
data_section.size() + header.executable.bss_size + 0x1000) &
~0xFFF;
// Allocate the framebuffer block so that is in the highest FCRAM position possible
auto offset_fb =
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->RLinearAllocate(_3GX_fb_size);
if (!offset_fb) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
plg_context.plugin_path);
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
}
auto backing_memory_fb = kernel.memory.GetFCRAMRef(*offset_fb);
Service::PLGLDR::PLG_LDR::SetPluginFBAddr(Memory::FCRAM_PADDR + *offset_fb);
std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0);
auto vma_heap_fb = process.vm_manager.MapBackingMemory(
_3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size, Kernel::MemoryState::Continuous);
ASSERT(vma_heap_fb.Succeeded());
process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite);
// Allocate a block from the end of FCRAM and clear it
auto offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
->RLinearAllocate(block_size - _3GX_fb_size);
if (!offset) {
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
plg_context.plugin_path);
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
}
auto backing_memory = kernel.memory.GetFCRAMRef(*offset);
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + block_size - _3GX_fb_size, 0);
// Then we map part of the memory, which contains the executable
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size,
Kernel::MemoryState::Continuous);
ASSERT(vma.Succeeded());
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
// Write text section
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
text_section.data(), header.executable.code_size);
// Write rodata section
kernel.memory.WriteBlock(
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
rodata_section.data(), header.executable.rodata_size);
// Write data section
kernel.memory.WriteBlock(process,
_3GX_exe_load_addr + sizeof(PluginHeader) +
header.executable.code_size + header.executable.rodata_size,
data_section.data(), header.executable.data_size);
// Prepare plugin header and write it
PluginHeader plugin_header = {0};
plugin_header.version = header.version;
plugin_header.exe_size = exe_size;
plugin_header.heap_VA = _3GX_heap_load_addr;
plugin_header.heap_size = block_size - exe_size;
plg_context.plg_event = _3GX_exe_load_addr - 0x4;
plg_context.plg_reply = _3GX_exe_load_addr - 0x8;
plugin_header.plgldr_event = plg_context.plg_event;
plugin_header.plgldr_reply = plg_context.plg_reply;
plugin_header.is_default_plugin = plg_context.is_default_path;
if (plg_context.use_user_load_parameters) {
memcpy(plugin_header.config, plg_context.user_load_parameters.config,
sizeof(PluginHeader::config));
}
kernel.memory.WriteBlock(process, _3GX_exe_load_addr, &plugin_header, sizeof(PluginHeader));
// Map plugin heap
auto backing_memory_heap = kernel.memory.GetFCRAMRef(*offset + exe_size);
// Map the rest of the memory at the heap location
auto vma_heap = process.vm_manager.MapBackingMemory(
_3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap,
block_size - exe_size - _3GX_fb_size, Kernel::MemoryState::Continuous);
ASSERT(vma_heap.Succeeded());
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
// Allocate a block from the end of FCRAM and clear it
auto bootloader_offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
->RLinearAllocate(bootloader_memory_size);
if (!bootloader_offset) {
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
->Free(*offset, block_size - _3GX_fb_size);
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
plg_context.plugin_path);
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
}
const bool use_internal = plg_context.load_exe_func.empty();
MapBootloader(
process, kernel, *bootloader_offset,
(use_internal) ? exe_load_func : plg_context.load_exe_func,
(use_internal) ? exe_load_args : plg_context.load_exe_args,
header.executable.code_size + header.executable.rodata_size + header.executable.data_size,
header.infos.exe_load_checksum,
plg_context.use_user_load_parameters ? plg_context.user_load_parameters.no_flash : 0);
plg_context.plugin_loaded = true;
plg_context.use_user_load_parameters = false;
return Loader::ResultStatus::Success;
}
void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel,
u32 memory_offset,
const std::vector<u32>& exe_load_func,
const u32_le* exe_load_args, u32 checksum_size,
u32 exe_checksum, bool no_flash) {
u32_le game_instructions[2];
kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr, game_instructions,
sizeof(u32) * 2);
std::array<u32_le, g_plugin_loader_bootloader.size() / sizeof(u32)> bootloader;
memcpy(bootloader.data(), g_plugin_loader_bootloader.data(), g_plugin_loader_bootloader.size());
for (auto it = bootloader.begin(); it < bootloader.end(); it++) {
switch (static_cast<u32>(*it)) {
case 0xDEAD0000: {
*it = game_instructions[0];
} break;
case 0xDEAD0001: {
*it = game_instructions[1];
} break;
case 0xDEAD0002: {
*it = process.codeset->CodeSegment().addr;
} break;
case 0xDEAD0003: {
for (u32 i = 0;
i <
sizeof(Service::PLGLDR::PLG_LDR::PluginLoaderContext::load_exe_args) / sizeof(u32);
i++) {
bootloader[i + (it - bootloader.begin())] = exe_load_args[i];
}
} break;
case 0xDEAD0004: {
*it = _3GX_exe_load_addr + sizeof(PluginHeader);
} break;
case 0xDEAD0005: {
*it = _3GX_exe_load_addr + sizeof(PluginHeader) + checksum_size;
} break;
case 0xDEAD0006: {
*it = exe_checksum;
} break;
case 0xDEAD0007: {
*it = _3GX_exe_load_addr - 0xC;
} break;
case 0xDEAD0008: {
*it = _3GX_exe_load_addr + sizeof(PluginHeader);
} break;
case 0xDEAD0009: {
*it = no_flash ? 1 : 0;
} break;
case 0xDEAD000A: {
for (u32 i = 0; i < exe_load_func.size(); i++) {
bootloader[i + (it - bootloader.begin())] = exe_load_func[i];
}
} break;
default:
break;
}
}
// Map bootloader to the offset provided
auto backing_memory = kernel.memory.GetFCRAMRef(memory_offset);
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + bootloader_memory_size, 0);
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr - bootloader_memory_size,
backing_memory, bootloader_memory_size,
Kernel::MemoryState::Continuous);
ASSERT(vma.Succeeded());
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
// Write bootloader
kernel.memory.WriteBlock(
process, _3GX_exe_load_addr - bootloader_memory_size, bootloader.data(),
std::min<size_t>(bootloader.size() * sizeof(u32), bootloader_memory_size));
game_instructions[0] = 0xE51FF004; // ldr pc, [pc, #-4]
game_instructions[1] = _3GX_exe_load_addr - bootloader_memory_size;
kernel.memory.WriteBlock(process, process.codeset->CodeSegment().addr, game_instructions,
sizeof(u32) * 2);
}

View file

@ -0,0 +1,149 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <core/file_sys/archive_backend.h>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/plgldr/plgldr.h"
namespace Loader {
enum class ResultStatus;
}
namespace FileUtil {
class IOFile;
}
namespace FileSys {
class FileBackend;
class Plugin3GXLoader {
public:
Loader::ResultStatus Load(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context,
Kernel::Process& process, Kernel::KernelSystem& kernel);
struct PluginHeader {
u32_le magic;
u32_le version;
u32_le heap_VA;
u32_le heap_size;
u32_le exe_size; // Include sizeof(PluginHeader) + .text + .rodata + .data + .bss (0x1000
// aligned too)
u32_le is_default_plugin;
u32_le plgldr_event; ///< Used for synchronization, unused in citra
u32_le plgldr_reply; ///< Used for synchronization, unused in citra
u32_le reserved[24];
u32_le config[32];
};
static_assert(sizeof(PluginHeader) == 0x100, "Invalid plugin header size");
static constexpr const char* _3GX_magic = "3GX$0002";
static constexpr u32 _3GX_exe_load_addr = 0x07000000;
static constexpr u32 _3GX_heap_load_addr = 0x06000000;
static constexpr u32 _3GX_fb_size = 0xA9000;
private:
Loader::ResultStatus Map(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context,
Kernel::Process& process, Kernel::KernelSystem& kernel);
static constexpr size_t bootloader_memory_size = 0x1000;
static void MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel,
u32 memory_offset, const std::vector<u32>& exe_load_func,
const u32_le* exe_load_args, u32 checksum_size, u32 exe_checksum,
bool no_flash);
struct _3gx_Infos {
enum class Compatibility { CONSOLE = 0, CITRA = 1, CONSOLE_CITRA = 2 };
u32_le author_len;
u32_le author_msg_offset;
u32_le title_len;
u32_le title_msg_offset;
u32_le summary_len;
u32_le summary_msg_offset;
u32_le description_len;
u32_le description_msg_offset;
union {
u32_le raw;
BitField<0, 1, u32_le> embedded_exe_func;
BitField<1, 1, u32_le> embedded_swap_func;
BitField<2, 2, u32_le> memory_region_size;
BitField<4, 2, u32_le> compatibility;
} flags;
u32_le exe_load_checksum;
u32_le builtin_load_exe_args[4];
u32_le builtin_swap_load_args[4];
};
struct _3gx_Targets {
u32_le count;
u32_le title_offsets;
};
struct _3gx_Symtable {
u32_le nb_symbols;
u32_le symbols_offset;
u32_le name_table_offset;
};
struct _3gx_Executable {
u32_le code_offset;
u32_le rodata_offset;
u32_le data_offset;
u32_le code_size;
u32_le rodata_size;
u32_le data_size;
u32_le bss_size;
u32_le exe_load_func_offset; // NOP terminated
u32_le swap_save_func_offset; // NOP terminated
u32_le swap_load_func_offset; // NOP terminated
};
struct _3gx_Header {
u64_le magic;
u32_le version;
u32_le reserved;
_3gx_Infos infos;
_3gx_Executable executable;
_3gx_Targets targets;
_3gx_Symtable symtable;
};
_3gx_Header header;
std::string author;
std::string title;
std::string summary;
std::string description;
std::vector<u32> compatible_TID;
std::vector<u8> text_section;
std::vector<u8> data_section;
std::vector<u8> rodata_section;
std::vector<u32> exe_load_func;
u32_le exe_load_args[4];
};
} // namespace FileSys

View file

@ -0,0 +1,186 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
// Plugin bootloader payload
// Compiled with https://shell-storm.org/online/Online-Assembler-and-Disassembler/
/*
; Backup registers
stmfd sp!, {r0-r12}
mrs r0, cpsr
stmfd sp!, {r0}
; Check plugin validity and exit if invalid (also set a flag)
adr r0, g_plgstartendptr
ldr r1, [r0, #4]
ldr r0, [r0]
adr r2, g_plgloadexeargs
mov lr, pc
adr pc, g_loadexefunc
adr r1, g_loadexechecksum
ldr r1, [r1]
cmp r0, r1
adr r0, g_plgldrlaunchstatus
ldr r0, [r0]
moveq r1, #1
movne r1, #0
str r1, [r0]
svcne 0x3
; Flash top screen light blue
adr r0, g_plgnoflash
ldrb r0, [r0]
cmp r0, #1
beq skipflash
ldr r4, =0x90202204
ldr r5, =0x01FF9933
mov r6, #64
flashloop:
str r5, [r4]
ldr r0, =0xFF4B40
mov r1, #0
svc 0xA
subs r6, r6, #1
bne flashloop
str r6, [r4]
skipflash:
; Set all memory regions to RWX
ldr r0, =0xFFFF8001
mov r1, #1
svc 0xB3
; Restore instructions at entrypoint
adr r0, g_savedGameInstr
adr r1, g_gameentrypoint
ldr r1, [r1]
ldr r2, [r0]
str r2, [r1]
ldr r2, [r0, #4]
str r2, [r1, #4]
svc 0x94
; Launch the plugin
adr r0, g_savedGameInstr
push {r0}
adr r5, g_plgentrypoint
ldr r5, [r5]
blx r5
add sp, sp, #4
; Restore registers and return to the game
ldmfd sp!, {r0}
msr cpsr, r0
ldmfd sp!, {r0-r12}
adr lr, g_gameentrypoint
ldr pc, [lr]
.pool
g_savedGameInstr:
.word 0xDEAD0000, 0xDEAD0001
g_gameentrypoint:
.word 0xDEAD0002
g_plgloadexeargs:
.word 0xDEAD0003, 0, 0, 0
g_plgstartendptr:
.word 0xDEAD0004, 0xDEAD0005
g_loadexechecksum:
.word 0xDEAD0006
g_plgldrlaunchstatus:
.word 0xDEAD0007
g_plgentrypoint:
.word 0xDEAD0008
g_plgnoflash:
.word 0xDEAD0009
g_loadexefunc:
.word 0xDEAD000A
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
bx lr
*/
#include <array>
#include "common/common_types.h"
constexpr std::array<u8, 412> g_plugin_loader_bootloader = {
0xff, 0x1f, 0x2d, 0xe9, 0x00, 0x00, 0x0f, 0xe1, 0x01, 0x00, 0x2d, 0xe9, 0xf0, 0x00, 0x8f, 0xe2,
0x04, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x90, 0xe5, 0xd4, 0x20, 0x8f, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1,
0xf4, 0xf0, 0x8f, 0xe2, 0xe0, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x01, 0x00, 0x50, 0xe1,
0xd8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0x90, 0xe5, 0x01, 0x10, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0x13,
0x00, 0x10, 0x80, 0xe5, 0x03, 0x00, 0x00, 0x1f, 0xc8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0xd0, 0xe5,
0x01, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x78, 0x40, 0x9f, 0xe5, 0x78, 0x50, 0x9f, 0xe5,
0x40, 0x60, 0xa0, 0xe3, 0x00, 0x50, 0x84, 0xe5, 0x70, 0x00, 0x9f, 0xe5, 0x00, 0x10, 0xa0, 0xe3,
0x0a, 0x00, 0x00, 0xef, 0x01, 0x60, 0x56, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x00, 0x60, 0x84, 0xe5,
0x5c, 0x00, 0x9f, 0xe5, 0x01, 0x10, 0xa0, 0xe3, 0xb3, 0x00, 0x00, 0xef, 0x54, 0x00, 0x8f, 0xe2,
0x58, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x00, 0x20, 0x90, 0xe5, 0x00, 0x20, 0x81, 0xe5,
0x04, 0x20, 0x90, 0xe5, 0x04, 0x20, 0x81, 0xe5, 0x94, 0x00, 0x00, 0xef, 0x34, 0x00, 0x8f, 0xe2,
0x04, 0x00, 0x2d, 0xe5, 0x58, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x35, 0xff, 0x2f, 0xe1,
0x04, 0xd0, 0x8d, 0xe2, 0x01, 0x00, 0xbd, 0xe8, 0x00, 0xf0, 0x29, 0xe1, 0xff, 0x1f, 0xbd, 0xe8,
0x18, 0xe0, 0x8f, 0xe2, 0x00, 0xf0, 0x9e, 0xe5, 0x04, 0x22, 0x20, 0x90, 0x33, 0x99, 0xff, 0x01,
0x40, 0x4b, 0xff, 0x00, 0x01, 0x80, 0xff, 0xff, 0x00, 0x00, 0xad, 0xde, 0x01, 0x00, 0xad, 0xde,
0x02, 0x00, 0xad, 0xde, 0x03, 0x00, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xad, 0xde, 0x05, 0x00, 0xad, 0xde, 0x06, 0x00, 0xad, 0xde,
0x07, 0x00, 0xad, 0xde, 0x08, 0x00, 0xad, 0xde, 0x09, 0x00, 0xad, 0xde, 0x0a, 0x00, 0xad, 0xde,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x1e, 0xff, 0x2f, 0xe1};

View file

@ -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,

View file

@ -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;

View file

@ -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.

View file

@ -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() {

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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.

View file

@ -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.

View file

@ -0,0 +1,278 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <boost/serialization/weak_ptr.hpp>
#include <fmt/format.h>
#include "common/archives.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/file_sys/plugin_3gx.h"
#include "core/frontend/mic.h"
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/plgldr/plgldr.h"
#include "core/loader/loader.h"
SERIALIZE_EXPORT_IMPL(Service::PLGLDR::PLG_LDR)
namespace Service::PLGLDR {
const Kernel::CoreVersion PLG_LDR::plgldr_version = Kernel::CoreVersion(1, 0, 0);
PLG_LDR::PluginLoaderContext PLG_LDR::plgldr_context;
bool PLG_LDR::allow_game_change = true;
PAddr PLG_LDR::plugin_fb_addr = 0;
PLG_LDR::PLG_LDR() : ServiceFramework{"plg:ldr", 1} {
static const FunctionInfo functions[] = {
{IPC::MakeHeader(1, 0, 2), nullptr, "LoadPlugin"},
{IPC::MakeHeader(2, 0, 0), &PLG_LDR::IsEnabled, "IsEnabled"},
{IPC::MakeHeader(3, 1, 0), &PLG_LDR::SetEnabled, "SetEnabled"},
{IPC::MakeHeader(4, 2, 4), &PLG_LDR::SetLoadSettings, "SetLoadSettings"},
{IPC::MakeHeader(5, 1, 8), nullptr, "DisplayMenu"},
{IPC::MakeHeader(6, 0, 4), nullptr, "DisplayMessage"},
{IPC::MakeHeader(7, 1, 4), &PLG_LDR::DisplayErrorMessage, "DisplayErrorMessage"},
{IPC::MakeHeader(8, 0, 0), &PLG_LDR::GetPLGLDRVersion, "GetPLGLDRVersion"},
{IPC::MakeHeader(9, 0, 0), &PLG_LDR::GetArbiter, "GetArbiter"},
{IPC::MakeHeader(10, 0, 2), &PLG_LDR::GetPluginPath, "GetPluginPath"},
{IPC::MakeHeader(11, 1, 0), nullptr, "SetRosalinaMenuBlock"},
{IPC::MakeHeader(12, 2, 4), nullptr, "SetSwapParam"},
{IPC::MakeHeader(13, 1, 2), nullptr, "SetLoadExeParam"},
};
RegisterHandlers(functions);
plgldr_context.memory_changed_handle = 0;
plgldr_context.plugin_loaded = false;
}
void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kernel) {
if (!plgldr_context.is_enabled || plgldr_context.plugin_loaded) {
return;
}
{
// Same check as original plugin loader, plugins are not supported in homebrew apps
u32 value1, value2;
kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr, &value1, 4);
kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr + 32, &value2, 4);
// Check for "B #0x20" and "MOV R4, LR" instructions
bool is_homebrew = u32_le(value1) == 0xEA000006 && u32_le(value2) == 0xE1A0400E;
if (is_homebrew) {
return;
}
}
FileSys::Plugin3GXLoader plugin_loader;
if (plgldr_context.use_user_load_parameters &&
plgldr_context.user_load_parameters.low_title_Id ==
static_cast<u32>(process.codeset->program_id) &&
plgldr_context.user_load_parameters.path[0]) {
std::string plugin_file = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
std::string(plgldr_context.user_load_parameters.path + 1);
plgldr_context.is_default_path = false;
plgldr_context.plugin_path = plugin_file;
plugin_loader.Load(plgldr_context, process, kernel);
} else {
const std::string plugin_root =
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "luma/plugins/";
const std::string plugin_tid =
plugin_root + fmt::format("{:016X}", process.codeset->program_id);
FileUtil::FSTEntry entry;
FileUtil::ScanDirectoryTree(plugin_tid, entry);
for (const auto child : entry.children) {
if (!child.isDirectory && child.physicalName.ends_with(".3gx")) {
plgldr_context.is_default_path = false;
plgldr_context.plugin_path = child.physicalName;
if (plugin_loader.Load(plgldr_context, process, kernel) ==
Loader::ResultStatus::Success) {
return;
}
}
}
const std::string default_path = plugin_root + "default.3gx";
if (FileUtil::Exists(default_path)) {
plgldr_context.is_default_path = true;
plgldr_context.plugin_path = default_path;
plugin_loader.Load(plgldr_context, process, kernel);
}
}
}
void PLG_LDR::OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel) {
if (plgldr_context.plugin_loaded) {
u32 status = kernel.memory.Read32(FileSys::Plugin3GXLoader::_3GX_exe_load_addr - 0xC);
if (status == 0) {
LOG_CRITICAL(Service_PLGLDR, "Failed to launch {}: Checksum failed",
plgldr_context.plugin_path);
}
}
}
ResultVal<Kernel::Handle> PLG_LDR::GetMemoryChangedHandle(Kernel::KernelSystem& kernel) {
if (plgldr_context.memory_changed_handle)
return MakeResult(plgldr_context.memory_changed_handle);
std::shared_ptr<Kernel::Event> evt = kernel.CreateEvent(
Kernel::ResetType::OneShot,
fmt::format("event-{:08x}", Core::System::GetInstance().GetRunningCore().GetReg(14)));
CASCADE_RESULT(plgldr_context.memory_changed_handle,
kernel.GetCurrentProcess()->handle_table.Create(std::move(evt)));
return MakeResult(plgldr_context.memory_changed_handle);
}
void PLG_LDR::OnMemoryChanged(Kernel::Process& process, Kernel::KernelSystem& kernel) {
if (!plgldr_context.plugin_loaded || !plgldr_context.memory_changed_handle)
return;
std::shared_ptr<Kernel::Event> evt =
kernel.GetCurrentProcess()->handle_table.Get<Kernel::Event>(
plgldr_context.memory_changed_handle);
if (evt == nullptr)
return;
evt->Signal();
}
void PLG_LDR::IsEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 2, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.Push(plgldr_context.is_enabled);
}
void PLG_LDR::SetEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 3, 1, 0);
bool enabled = rp.Pop<u32>() == 1;
bool can_change = enabled == plgldr_context.is_enabled || allow_game_change;
if (can_change) {
plgldr_context.is_enabled = enabled;
Settings::values.plugin_loader_enabled.SetValue(enabled);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push((can_change) ? RESULT_SUCCESS : Kernel::ERR_NOT_AUTHORIZED);
}
void PLG_LDR::SetLoadSettings(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 4, 2, 4);
plgldr_context.use_user_load_parameters = true;
plgldr_context.user_load_parameters.no_flash = rp.Pop<u32>() == 1;
plgldr_context.user_load_parameters.low_title_Id = rp.Pop<u32>();
auto path = rp.PopMappedBuffer();
path.Read(
plgldr_context.user_load_parameters.path, 0,
std::min(sizeof(PluginLoaderContext::PluginLoadParameters::path) - 1, path.GetSize()));
plgldr_context.user_load_parameters.path[std::min(
sizeof(PluginLoaderContext::PluginLoadParameters::path) - 1, path.GetSize())] = '\0';
auto config = rp.PopMappedBuffer();
config.Read(
plgldr_context.user_load_parameters.config, 0,
std::min(sizeof(PluginLoaderContext::PluginLoadParameters::config), config.GetSize()));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
}
void PLG_LDR::DisplayErrorMessage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 7, 1, 4);
u32 error_code = rp.Pop<u32>();
auto title = rp.PopMappedBuffer();
auto desc = rp.PopMappedBuffer();
std::vector<char> title_data(title.GetSize() + 1);
std::vector<char> desc_data(desc.GetSize() + 1);
title.Read(title_data.data(), 0, title.GetSize());
title_data[title.GetSize()] = '\0';
desc.Read(desc_data.data(), 0, desc.GetSize());
desc_data[desc.GetSize()] = '\0';
LOG_ERROR(Service_PLGLDR, "Plugin error - Code: {} - Title: {} - Description: {}", error_code,
std::string(title_data.data()), std::string(desc_data.data()));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
}
void PLG_LDR::GetPLGLDRVersion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 8, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.Push(plgldr_version.raw);
}
void PLG_LDR::GetArbiter(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 9, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
// NOTE: It doesn't make sense to send an arbiter in HLE, as it's used to
// signal the plgldr service thread when a event is ready. Instead we just send
// an error and the 3GX plugin will take care of it.
// (We never send any events anyways)
rb.Push(Kernel::ERR_NOT_IMPLEMENTED);
}
void PLG_LDR::GetPluginPath(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 10, 0, 2);
auto path = rp.PopMappedBuffer();
// Same behaviour as strncpy
std::string root_sd = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
std::string plugin_path = plgldr_context.plugin_path;
auto it = plugin_path.find(root_sd);
if (it != plugin_path.npos)
plugin_path.erase(it, root_sd.size());
std::replace(plugin_path.begin(), plugin_path.end(), '\\', '/');
if (plugin_path.empty() || plugin_path[0] != '/')
plugin_path = "/" + plugin_path;
path.Write(plugin_path.c_str(), 0, std::min(path.GetSize(), plugin_path.length() + 1));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(path);
}
std::shared_ptr<PLG_LDR> GetService(Core::System& system) {
if (!system.KernelRunning())
return nullptr;
auto it = system.Kernel().named_ports.find("plg:ldr");
if (it != system.Kernel().named_ports.end())
return std::static_pointer_cast<PLG_LDR>(it->second->GetServerPort()->hle_handler);
return nullptr;
}
void InstallInterfaces(Core::System& system) {
std::make_shared<PLG_LDR>()->InstallAsNamedPort(system.Kernel());
}
} // namespace Service::PLGLDR

View file

@ -0,0 +1,149 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <memory>
#include <boost/serialization/version.hpp>
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::PLGLDR {
class PLG_LDR final : public ServiceFramework<PLG_LDR> {
public:
struct PluginLoaderContext {
struct PluginLoadParameters {
u8 no_flash = 0;
u8 no_IR_Patch = 0;
u32_le low_title_Id = 0;
char path[256] = {0};
u32_le config[32] = {0};
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& no_flash;
ar& no_IR_Patch;
ar& low_title_Id;
ar& path;
ar& config;
}
friend class boost::serialization::access;
};
bool is_enabled = true;
bool plugin_loaded = false;
bool is_default_path = false;
std::string plugin_path = "";
bool use_user_load_parameters = false;
PluginLoadParameters user_load_parameters;
VAddr plg_event = 0;
VAddr plg_reply = 0;
Kernel::Handle memory_changed_handle = 0;
bool is_exe_load_function_set = false;
u32 exe_load_checksum = 0;
std::vector<u32> load_exe_func;
u32_le load_exe_args[4] = {0};
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& is_enabled;
ar& plugin_loaded;
ar& is_default_path;
ar& plugin_path;
ar& use_user_load_parameters;
ar& user_load_parameters;
ar& plg_event;
ar& plg_reply;
ar& memory_changed_handle;
ar& is_exe_load_function_set;
ar& exe_load_checksum;
ar& load_exe_func;
ar& load_exe_args;
}
friend class boost::serialization::access;
};
PLG_LDR();
~PLG_LDR() {}
void OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kernel);
void OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel);
ResultVal<Kernel::Handle> GetMemoryChangedHandle(Kernel::KernelSystem& kernel);
void OnMemoryChanged(Kernel::Process& process, Kernel::KernelSystem& kernel);
static void SetEnabled(bool enabled) {
plgldr_context.is_enabled = enabled;
}
static bool GetEnabled() {
return plgldr_context.is_enabled;
}
static void SetAllowGameChangeState(bool allow) {
allow_game_change = allow;
}
static bool GetAllowGameChangeState() {
return allow_game_change;
}
static void SetPluginFBAddr(PAddr addr) {
plugin_fb_addr = addr;
}
static PAddr GetPluginFBAddr() {
return plugin_fb_addr;
}
private:
static const Kernel::CoreVersion plgldr_version;
static PluginLoaderContext plgldr_context;
static PAddr plugin_fb_addr;
static bool allow_game_change;
void IsEnabled(Kernel::HLERequestContext& ctx);
void SetEnabled(Kernel::HLERequestContext& ctx);
void SetLoadSettings(Kernel::HLERequestContext& ctx);
void DisplayErrorMessage(Kernel::HLERequestContext& ctx);
void GetPLGLDRVersion(Kernel::HLERequestContext& ctx);
void GetArbiter(Kernel::HLERequestContext& ctx);
void GetPluginPath(Kernel::HLERequestContext& ctx);
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
ar& plgldr_context;
ar& plugin_fb_addr;
ar& allow_game_change;
}
friend class boost::serialization::access;
};
std::shared_ptr<PLG_LDR> GetService(Core::System& system);
void InstallInterfaces(Core::System& system);
} // namespace Service::PLGLDR
BOOST_CLASS_EXPORT_KEY(Service::PLGLDR::PLG_LDR)

View file

@ -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},

View file

@ -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

View file

@ -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) {

View file

@ -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) {

View file

@ -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
};
/**

View file

@ -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);