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

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