Add custom pm sysmodule

This commit is contained in:
TuxSH 2019-03-29 00:32:45 +01:00
parent e57abad52f
commit 1041d7c438
36 changed files with 2954 additions and 1 deletions

View File

@ -1,4 +1,4 @@
SUBFOLDERS := loader sm pxi rosalina
SUBFOLDERS := loader sm pm pxi rosalina
CXIS := $(foreach dir, $(SUBFOLDERS), $(dir)/$(dir).cxi)
.PHONY: all clean $(SUBFOLDERS)

21
sysmodules/pm/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 TuxSH
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.

147
sysmodules/pm/Makefile Normal file
View File

@ -0,0 +1,147 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITARM)/3ds_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
DATA := data
INCLUDES := include
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft
DEFINES := -DARM11 -D_3DS
CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -O2 -mword-relocations \
-fomit-frame-pointer -ffunction-sections -fdata-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE)
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lctru
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(CTRULIB)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).cxi $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).cxi
$(OUTPUT).cxi : $(OUTPUT).elf $(OUTPUT).rsf
@makerom -f ncch -rsf $(word 2,$^) -nocodepadding -o $@ -elf $<
@echo built ... $(notdir $@)
$(OUTPUT).elf : $(OFILES)
%.elf: $(OFILES)
@echo linking $(notdir $@)
@$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
@$(NM) -CSn $@ > $(notdir $*.lst)
$(OFILES_SRC) : $(HFILES_BIN)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

10
sysmodules/pm/README.md Normal file
View File

@ -0,0 +1,10 @@
# 3ds_pm
Open source replacement of the ARM11 PM system module.
This is licensed under the MIT license.
# Usage
To run this system module, use a recent release or commit of [Luma3DS](https://github.com/AuroraWright/Luma3DS/), build this project and copy the generated CXI file to `/luma/sysmodules/pm.cxi`.
# Credits
@fincs
@Stary

125
sysmodules/pm/pm.rsf Normal file
View File

@ -0,0 +1,125 @@
BasicInfo:
Title : pm
CompanyCode : "00"
ProductCode : lennybuilder
ContentType : Application
Logo : None
TitleInfo:
UniqueId : 0x12
Category : Base
Version : 2
Option:
UseOnSD : false
FreeProductCode : true # Removes limitations on ProductCode
MediaFootPadding : false # If true CCI files are created with padding
EnableCrypt : false # Enables encryption for NCCH and CIA
EnableCompress : true # Compresses exefs code
AccessControlInfo:
IdealProcessor : 1
AffinityMask : 3
Priority : 20
DisableDebug : false
EnableForceDebug : false
CanWriteSharedPage : false
CanUsePrivilegedPriority : false
CanUseNonAlphabetAndNumber : false
PermitMainFunctionArgument : false
CanShareDeviceMemory : false
RunnableOnSleep : true
SpecialMemoryArrange : false
ResourceLimitCategory : Other
CoreVersion : 2
DescVersion : 2
MemoryType : Base # Application / System / Base
HandleTableSize: 0x200
IORegisterMapping:
SystemCallAccess:
ControlMemory: 1
QueryMemory: 2
ExitProcess: 3
SetProcessAffinityMask: 5
GetProcessIdealProcessor: 6
SetProcessIdealProcessor: 7
CreateThread: 8
ExitThread: 9
SleepThread: 10
GetThreadPriority: 11
SetThreadPriority: 12
GetThreadIdealProcessor: 15
GetCurrentProcessorNumber: 17
Run: 18
CreateMutex: 19
ReleaseMutex: 20
CreateSemaphore: 21
ReleaseSemaphore: 22
CreateEvent: 23
SignalEvent: 24
ClearEvent: 25
CreateTimer: 26
SetTimer: 27
CancelTimer: 28
ClearTimer: 29
CreateMemoryBlock: 30
MapMemoryBlock: 31
UnmapMemoryBlock: 32
CreateAddressArbiter: 33
ArbitrateAddress: 34
CloseHandle: 35
WaitSynchronization1: 36
WaitSynchronizationN: 37
DuplicateHandle: 39
GetSystemTick: 40
GetHandleInfo: 41
GetSystemInfo: 42
GetProcessInfo: 43
GetThreadInfo: 44
ConnectToPort: 45
SendSyncRequest1: 46
SendSyncRequest2: 47
SendSyncRequest3: 48
SendSyncRequest4: 49
SendSyncRequest: 50
OpenProcess: 51
OpenThread: 52
GetProcessId: 53
GetProcessIdOfThread: 54
GetThreadId: 55
GetResourceLimit: 56
GetResourceLimitLimitValues: 57
GetResourceLimitCurrentValues: 58
GetThreadContext: 59
Break: 60
OutputDebugString: 61
CreatePort: 71
CreateSessionToPort: 72
AcceptSession: 74
ReplyAndReceive1: 75
ReplyAndReceive2: 76
ReplyAndReceive3: 77
ReplyAndReceive4: 78
ReplyAndReceive: 79
DebugActiveProcess: 96
TerminateDebugProcess: 98
GetThreadList: 102
TerminateProcess: 118
SetProcessResourceLimits: 119
CreateResourceLimit: 120
SetResourceLimitValues: 121
KernelSetState: 124
InterruptNumbers:
ServiceAccessControl:
# Note: pm also uses srv:pm and Loader but doesn't list them here.
- fs:REG
FileSystemAccess:
SystemControlInfo:
SaveDataSize: 0KB # It doesn't use any save data.
RemasterVersion: 0
StackSize: 0x1000

View File

@ -0,0 +1,51 @@
#include <3ds.h>
#include <string.h>
#include <stdatomic.h>
typedef union Node {
union Node *next;
ExHeader_Info info;
} Node;
static Node *g_headNode = NULL;
void ExHeaderInfoHeap_Init(void *buf, size_t num)
{
Node *node = (Node *)buf;
g_headNode = node;
for (size_t i = 0; i < num; i++) {
node->next = &node[1];
node = node->next;
}
}
ExHeader_Info *ExHeaderInfoHeap_New(void)
{
Node *newHead;
Node *oldHead = atomic_load(&g_headNode);
if (oldHead == NULL) {
return NULL;
} else {
do {
newHead = oldHead == NULL ? oldHead : oldHead->next;
} while(!atomic_compare_exchange_weak(&g_headNode, &oldHead, newHead));
if (oldHead == NULL) {
return NULL;
}
else {
memset(&oldHead->info, 0, sizeof(ExHeader_Info));
return &oldHead->info;
}
}
}
void ExHeaderInfoHeap_Delete(ExHeader_Info *data)
{
Node *newHead = (Node *)data;
Node *oldHead = atomic_load(&g_headNode);
do {
newHead->next = oldHead;
} while(!atomic_compare_exchange_weak(&g_headNode, &oldHead, newHead));
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <3ds/exheader.h>
// Official PM uses an overly complicated allocator with semaphores
void ExHeaderInfoHeap_Init(void *buf, size_t num);
ExHeader_Info *ExHeaderInfoHeap_New(void);
void ExHeaderInfoHeap_Delete(ExHeader_Info *data);

View File

@ -0,0 +1,56 @@
#include <3ds.h>
#include <string.h>
#include "firmlaunch.h"
#include "termination.h"
#include "task_runner.h"
#include "util.h"
static void *const g_firmlaunchParameters = (void *)0x12000000;
void mapFirmlaunchParameters(void)
{
assertSuccess(svcKernelSetState(3, 0, g_firmlaunchParameters));
}
Result GetFirmlaunchParams(void *outParams, size_t size)
{
size = size >= 0x1000 ? 0x1000 : size;
memcpy(outParams, g_firmlaunchParameters, size);
return 0;
}
Result SetFirmlaunchParams(const void *params, size_t size)
{
size = size >= 0x1000 ? 0x1000 : size;
memcpy(g_firmlaunchParameters, params, size);
if (size < 0x1000) {
memset(g_firmlaunchParameters + size, 0, 0x1000 - size);
}
return 0;
}
static void LaunchFirmAsync(void *argdata)
{
struct {
u32 firmTidLow;
} *args = argdata;
terminateAllProcesses((u32)-1, 4 * 1000 * 1000 * 1000LL);
// pm has dead code there (notification 0x179, but there's no 'caller process'... (-1))
u64 firmTid = args->firmTidLow; // note: tidHigh doesn't matter
firmTid = IS_N3DS ? (firmTid & ~N3DS_TID_MASK) | N3DS_TID_BIT : firmTid;
assertSuccess(svcKernelSetState(0, firmTid));
}
Result LaunchFirm(u32 firmTidLow, const void *params, size_t size)
{
struct {
u32 firmTidLow;
} args = { firmTidLow };
SetFirmlaunchParams(params, size);
TaskRunner_RunTask(LaunchFirmAsync, &args, sizeof(args));
return 0;
}

View File

@ -0,0 +1,8 @@
#pragma once
#include <3ds/types.h>
void mapFirmlaunchParameters(void);
Result GetFirmlaunchParams(void *outParams, size_t size);
Result SetFirmlaunchParams(const void *params, size_t size);
Result LaunchFirm(u32 firmTidLow, const void *params, size_t size);

127
sysmodules/pm/source/info.c Normal file
View File

@ -0,0 +1,127 @@
#include <3ds.h>
#include <string.h>
#include "exheader_info_heap.h"
#include "manager.h"
#include "info.h"
#include "util.h"
Result registerProgram(u64 *programHandle, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate)
{
FS_ProgramInfo pi = *programInfo, piu = *programInfoUpdate;
Result res = 0;
if (IS_N3DS) {
pi.programId = (pi.programId & ~N3DS_TID_MASK) | N3DS_TID_BIT;
piu.programId = (piu.programId & ~N3DS_TID_MASK) | N3DS_TID_BIT;
res = LOADER_RegisterProgram(programHandle, &pi, &piu);
if (R_FAILED(res)) {
pi.programId &= ~N3DS_TID_MASK;
piu.programId &= ~N3DS_TID_MASK;
res = LOADER_RegisterProgram(programHandle, &pi, &piu);
}
} else {
res = LOADER_RegisterProgram(programHandle, &pi, &piu);
}
return res;
}
Result getAndListDependencies(u64 *dependencies, u32 *numDeps, ProcessData *process, ExHeader_Info *exheaderInfo)
{
Result res = 0;
TRY(LOADER_GetProgramInfo(exheaderInfo, process->programHandle));
return listDependencies(dependencies, numDeps, exheaderInfo);
}
Result listDependencies(u64 *dependencies, u32 *numDeps, const ExHeader_Info *exheaderInfo)
{
Result res = 0;
u32 num = 0;
for (u32 i = 0; i < 48 && exheaderInfo->sci.dependencies[i] != 0; i++) {
u64 titleId = exheaderInfo->sci.dependencies[i];
if (IS_N3DS || (titleId & N3DS_TID_MASK) == 0) {
// On O3DS, ignore N3DS titles.
// Then (on both) remove the N3DS titleId bits
dependencies[num++] = titleId & ~N3DS_TID_MASK;
}
}
*numDeps = num;
return res;
}
Result listMergeUniqueDependencies(ProcessData **procs, u64 *dependencies, u32 *remrefcounts, u32 *numDeps, const ExHeader_Info *exheaderInfo)
{
Result res = 0;
u32 numDepsUnique = *numDeps;
u32 num2;
u64 deps[48];
u32 newrefcounts[48] = {0};
TRY(listDependencies(deps, &num2, exheaderInfo));
ProcessList_Lock(&g_manager.processList);
for (u32 i = 0; i < num2; i++) {
// Filter duplicate results
u32 j;
for (j = 0; j < numDepsUnique && deps[i] != dependencies[j]; j++);
if (j >= numDepsUnique) {
if (numDepsUnique >= 48) {
panic(2);
}
dependencies[numDepsUnique] = deps[i];
newrefcounts[numDepsUnique] = 1;
procs[numDepsUnique] = ProcessList_FindProcessByTitleId(&g_manager.processList, deps[i]);
numDepsUnique++;
} else {
++newrefcounts[j];
}
}
// Apply refcounts
for (u32 i = 0; i < numDepsUnique; i++) {
if (procs[i] != NULL) {
ProcessData_Incref(procs[i], newrefcounts[i]);
} else {
remrefcounts[i] += newrefcounts[i];
}
}
ProcessList_Unlock(&g_manager.processList);
*numDeps = numDepsUnique;
return res;
}
Result GetTitleExHeaderFlags(ExHeader_Arm11CoreInfo *outCoreInfo, ExHeader_SystemInfoFlags *outSiFlags, const FS_ProgramInfo *programInfo)
{
Result res = 0;
u64 programHandle = 0;
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
res = registerProgram(&programHandle, programInfo, programInfo);
if (R_SUCCEEDED(res))
{
res = LOADER_GetProgramInfo(exheaderInfo, programHandle);
if (R_SUCCEEDED(res)) {
*outCoreInfo = exheaderInfo->aci.local_caps.core_info;
*outSiFlags = exheaderInfo->sci.codeset_info.flags;
}
LOADER_UnregisterProgram(programHandle);
}
ExHeaderInfoHeap_Delete(exheaderInfo);
return res;
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <3ds/exheader.h>
#include <3ds/services/fs.h>
#include "process_data.h"
Result registerProgram(u64 *programHandle, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate);
Result getAndListDependencies(u64 *dependencies, u32 *numDeps, ProcessData *process, ExHeader_Info *exheaderInfo);
Result listDependencies(u64 *dependencies, u32 *numDeps, const ExHeader_Info *exheaderInfo);
Result listMergeUniqueDependencies(ProcessData **procs, u64 *dependencies, u32 *remrefcounts, u32 *numDeps, const ExHeader_Info *exheaderInfo);
Result GetTitleExHeaderFlags(ExHeader_Arm11CoreInfo *outCoreInfo, ExHeader_SystemInfoFlags *outSiFlags, const FS_ProgramInfo *programInfo);

View File

@ -0,0 +1,86 @@
#pragma once
#include <stddef.h>
#include <stdbool.h>
/// Intrusive node structure definition.
typedef struct IntrusiveNode {
struct IntrusiveNode *prev; ///< Pointer to the previous node.
struct IntrusiveNode *next; ///< Pointer to the next node.
} IntrusiveNode;
/// Intrusive list structure definition.
typedef union IntrusiveList {
IntrusiveNode node;
struct {
IntrusiveNode *last; ///< Pointer to the last element.
IntrusiveNode *first; ///< Pointer to the first element.
};
} IntrusiveList;
/**
* @brief Initializes the intrusive linked list.
*
* @param ll The intrusive list to initialize.
*/
static inline void IntrusiveList_Init(IntrusiveList *ll)
{
ll->node.prev = &ll->node;
ll->node.next = &ll->node;
}
/**
* @brief Tests if a node is past the end of the list containing it (if any).
*
* @param nd The node to test.
* @return bool true iff node is past the end of the list.
*/
static inline bool IntrusiveList_TestEnd(const IntrusiveList *ll, const IntrusiveNode *nd)
{
return nd == &ll->node;
}
/**
* @brief Inserts a node after another one list.
*
* @param pos The node to insert the new node after.
* @param nd The new node.
*/
static inline void IntrusiveList_InsertAfter(IntrusiveNode *pos, IntrusiveNode *nd)
{
// if pos is last & list is empty, ->next writes to first, etc.
pos->next->prev = nd;
nd->prev = pos;
nd->next = pos->next;
pos->next = nd;
}
/**
* @brief Erases a node.
*
* @param nd The node to erase.
*/
static inline void IntrusiveList_Erase(const IntrusiveNode *nd)
{
nd->prev->next = nd->next;
nd->next->prev = nd->prev;
}
/**
* @brief Makes a list storage from a buffer, using each element's intrusive node field at offset 0.
*
* @param ll The intrusive list to create.
* @param buf The buffer to use.
* @param elem_size The size of each element.
* @param total_size The total size of the buffer.
*
* @pre @ref ll has not been initialized yet.
* @pre Each element must contain a @ref IntrusiveNode instance at offset 0, which is then used.
*/
static inline void IntrusiveList_CreateFromBuffer(IntrusiveList *ll, void *buf, size_t elemSize, size_t totalSize)
{
IntrusiveList_Init(ll);
for (size_t pos = 0; pos < totalSize; pos += elemSize) {
IntrusiveList_InsertAfter(ll->last, (IntrusiveNode *)((char *)buf + pos));
}
}

View File

@ -0,0 +1,483 @@
#include <3ds.h>
#include <string.h>
#include "launch.h"
#include "info.h"
#include "manager.h"
#include "reslimit.h"
#include "exheader_info_heap.h"
#include "task_runner.h"
#include "util.h"
static inline void removeAccessToService(const char *service, char (*serviceAccessList)[8])
{
char ALIGN(8) name[8+1];
strncpy(name, service, 8);
name[8] = 0;
u32 j = 0;
for (u32 i = 0; i < 34 && *(u64 *)serviceAccessList[i] != 0; i++) {
if (*(u64 *)serviceAccessList[i] != *(u64 *)name) {
*(u64 *)serviceAccessList[j++] = *(u64 *)serviceAccessList[i];
}
}
if (j < 34) {
memset(&serviceAccessList[j], 0, 8 * (34 - j));
}
}
static void blacklistServices(u64 titleId, char (*serviceAccessList)[8])
{
if (osGetFirmVersion() < SYSTEM_VERSION(2, 51, 0) || (titleId >> 46) != 0x10) {
return;
}
u32 titleUid = ((u32)titleId >> 8) & 0xFFFFF;
switch (titleUid) {
// Cubic Ninja
case 0x343:
case 0x465:
case 0x4B3:
removeAccessToService("http:C", serviceAccessList);
removeAccessToService("soc:U", serviceAccessList);
break;
default:
break;
}
}
// Note: official PM has two distinct functions for sysmodule vs. regular app. We refactor that into a single function.
static Result launchTitleImpl(Handle *debug, ProcessData **outProcessData, const FS_ProgramInfo *programInfo,
const FS_ProgramInfo *programInfoUpdate, u32 launchFlags, ExHeader_Info *exheaderInfo);
// Note: official PM doesn't include svcDebugActiveProcess in this function, but rather in the caller handling dependencies
static Result loadWithoutDependencies(Handle *outDebug, ProcessData **outProcessData, u64 programHandle, const FS_ProgramInfo *programInfo,
u32 launchFlags, const ExHeader_Info *exheaderInfo)
{
Result res = 0;
Handle processHandle = 0;
u32 pid;
ProcessData *process;
const ExHeader_Arm11SystemLocalCapabilities *localcaps = &exheaderInfo->aci.local_caps;
if (outProcessData != NULL) {
*outProcessData = NULL;
}
if (programInfo->programId & (1ULL << 35)) {
// RequireBatchUpdate?
return 0xD8E05803;
}
TRY(LOADER_LoadProcess(&processHandle, programHandle));
TRY(svcGetProcessId(&pid, processHandle));
// Note: bug in official PM: it seems not to panic/cleanup properly if the function calls below fail,
// svcTerminateProcess won't be called, it's possible to trigger NULL derefs if you crash fs/sm/whatever,
// leaks dependencies, and so on...
// This can be solved by interesting the new process in the list earlier, etc. etc., allowing us to simplify the logic greatly.
ProcessList_Lock(&g_manager.processList);
process = ProcessList_New(&g_manager.processList);
if (process == NULL) {
panic(1);
}
process->handle = processHandle;
process->pid = pid;
process->titleId = exheaderInfo->aci.local_caps.title_id;;
process->programHandle = programHandle;
process->flags = 0; // will be filled later
process->terminatedNotificationVariation = (launchFlags & 0xF0) >> 4;
process->terminationStatus = TERMSTATUS_RUNNING;
process->refcount = 1;
ProcessList_Unlock(&g_manager.processList);
svcSignalEvent(g_manager.newProcessEvent);
if (outProcessData != NULL) {
*outProcessData = process;
}
u32 serviceCount;
for(serviceCount = 0; serviceCount < 34 && *(u64 *)localcaps->service_access[serviceCount] != 0; serviceCount++);
TRY(FSREG_Register(pid, programHandle, programInfo, &localcaps->storage_info));
TRY(SRVPM_RegisterProcess(pid, serviceCount, localcaps->service_access));
if (localcaps->reslimit_category <= RESLIMIT_CATEGORY_OTHER) {
TRY(svcSetProcessResourceLimits(processHandle, g_manager.reslimits[localcaps->reslimit_category]));
}
// Yes, even numberOfCores=2 on N3DS. On the 3DS, the affinity mask doesn't play the role of an access limiter,
// it's only useful for cpuId < 0. thread->affinityMask = process->affinityMask | (cpuId >= 0 ? 1 << cpuId : 0)
u8 affinityMask = localcaps->core_info.affinity_mask;
TRY(svcSetProcessAffinityMask(processHandle, &affinityMask, 2));
TRY(svcSetProcessIdealProcessor(processHandle, localcaps->core_info.ideal_processor));
if (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) {
setAppCpuTimeLimitAndSchedModeFromDescriptor(localcaps->title_id, localcaps->reslimits[0]);
(*outProcessData)->flags |= PROCESSFLAG_NORMAL_APPLICATION; // not in official PM
}
if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) {
TRY(svcDebugActiveProcess(outDebug, pid));
}
return res;
}
static Result loadWithDependencies(Handle *outDebug, ProcessData **outProcessData, u64 programHandle, const FS_ProgramInfo *programInfo,
u32 launchFlags, const ExHeader_Info *exheaderInfo)
{
Result res = 0;
u64 dependencies[48];
u32 remrefcounts[48] = {0};
ProcessData *depProcs[48] = {NULL};
u32 numUnique = 0;
FS_ProgramInfo depProgramInfo;
res = loadWithoutDependencies(outDebug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo);
ProcessData *process = *outProcessData;
if (R_FAILED(res)) {
if (process != NULL) {
svcTerminateProcess(process->handle);
}
return res;
}
ExHeader_Info *depExheaderInfo = ExHeaderInfoHeap_New();
if (depExheaderInfo == NULL) {
panic(0);
}
listMergeUniqueDependencies(depProcs, dependencies, remrefcounts, &numUnique, exheaderInfo);
if (numUnique > 0) {
process->flags |= PROCESSFLAG_DEPENDENCIES_LOADED;
}
/*
Official pm does this:
for each dependency:
if dep already loaded: if autoloaded increase refcount // note: not autoloaded = not autoterminated
else: load new sysmodule w/o its deps (then process its deps), set flag "autoloaded" return early from entire function if it fails
Naturally, it forgets to incref all subsequent dependencies here & also when it factors the duplicate entries in,
and has a few other bugs (actually I'm not entirely sure... I think it doesn't clear dependencies on termination if it fails)
It also has a buffer overflow bug if the flattened dep tree has more than 48 elements (but this can never happen in practice)
*/
for (u32 i = 0; i < numUnique; i++) {
if (depProcs[i] != NULL) {
continue;
}
// Note: numUnique is changed within the loop
depProgramInfo.programId = dependencies[i];
depProgramInfo.mediaType = MEDIATYPE_NAND;
res = launchTitleImpl(NULL, &process, &depProgramInfo, NULL, 0, depExheaderInfo);
depProcs[i] = process;
if (R_SUCCEEDED(res)) {
process->flags |= PROCESSFLAG_AUTOLOADED | PROCESSFLAG_DEPENDENCIES_LOADED;
ProcessData_Incref(process, remrefcounts[i] - 1);
remrefcounts[i] = 0;
listMergeUniqueDependencies(depProcs, dependencies, remrefcounts, &numUnique, depExheaderInfo); // does some incref too
} else if (process != NULL) {
svcTerminateProcess(process->handle);
ExHeaderInfoHeap_Delete(depExheaderInfo);
return res;
}
}
ExHeaderInfoHeap_Delete(depExheaderInfo);
return res;
}
// Note: official PM has two distinct functions for sysmodule vs. regular app. We refactor that into a single function.
static Result launchTitleImpl(Handle *debug, ProcessData **outProcessData, const FS_ProgramInfo *programInfo,
const FS_ProgramInfo *programInfoUpdate, u32 launchFlags, ExHeader_Info *exheaderInfo)
{
if (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) {
launchFlags |= PMLAUNCHFLAG_LOAD_DEPENDENCIES;
} else {
launchFlags &= ~(PMLAUNCHFLAG_USE_UPDATE_TITLE | PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION);
launchFlags &= ~(PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM | PMLAUNCHFLAG_FORCE_USE_O3DS_MAX_APP_MEM);
}
Result res = 0;
u64 programHandle;
StartupInfo si = {0};
programInfoUpdate = (launchFlags & PMLAUNCHFLAG_USE_UPDATE_TITLE) ? programInfoUpdate : programInfo;
TRY(registerProgram(&programHandle, programInfo, programInfoUpdate));
res = LOADER_GetProgramInfo(exheaderInfo, programHandle);
res = R_SUCCEEDED(res) && exheaderInfo->aci.local_caps.core_info.core_version != SYSCOREVER ? (Result)0xC8A05800 : res;
if (R_FAILED(res)) {
LOADER_UnregisterProgram(programHandle);
return res;
}
// Change APPMEMALLOC if needed
if (IS_N3DS && APPMEMTYPE == 6 && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) {
u32 limitMb;
SystemMode n3dsSystemMode = exheaderInfo->aci.local_caps.core_info.n3ds_system_mode;
if ((launchFlags & PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM) || n3dsSystemMode == SYSMODE_O3DS_PROD) {
if ((launchFlags & PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM) & PMLAUNCHFLAG_FORCE_USE_O3DS_MAX_APP_MEM) {
limitMb = 96;
} else {
switch (exheaderInfo->aci.local_caps.core_info.o3ds_system_mode) {
case SYSMODE_O3DS_PROD: limitMb = 64; break;
case SYSMODE_DEV1: limitMb = 96; break;
case SYSMODE_DEV2: limitMb = 80; break;
default: limitMb = 0; break;
}
}
setAppMemLimit(limitMb << 20);
}
}
blacklistServices(exheaderInfo->aci.local_caps.title_id, exheaderInfo->aci.local_caps.service_access);
if (launchFlags & PMLAUNCHFLAG_LOAD_DEPENDENCIES) {
TRYG(loadWithDependencies(debug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo), cleanup);
// note: official pm doesn't terminate the process if this fails (dependency loading)...
// This may be intentional, but I believe this is a bug since the 0xD8A05805 and svcRun failure codepaths terminate the process...
// It also forgets to clear PROCESSFLAG_NOTIFY_TERMINATION in the process...
} else {
TRYG(loadWithoutDependencies(debug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo), cleanup);
// note: official pm doesn't terminate the proc. if it fails here either, but will because of the svcCloseHandle and the svcRun codepath
}
ProcessData *process = *outProcessData;
if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) {
// saved field is different in official pm
// this also means official pm can't launch a title with a debug flag and an application
if (g_manager.debugData == NULL) {
g_manager.debugData = process;
} else {
res = 0xD8A05805;
}
} else {
si.priority = exheaderInfo->aci.local_caps.core_info.priority;
si.stack_size = exheaderInfo->sci.codeset_info.stack_size;
res = svcRun(process->handle, &si);
if (R_SUCCEEDED(res) && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) {
g_manager.runningApplicationData = process;
notifySubscribers(0x10C);
}
}
cleanup:
process = *outProcessData;
if (process != NULL && R_FAILED(res)) {
svcTerminateProcess(process->handle);
} else if (process != NULL) {
// official PM sets it but forgets to clear it on failure...
process->flags |= (launchFlags & PMLAUNCHFLAG_NOTIFY_TERMINATION) ? PROCESSFLAG_NOTIFY_TERMINATION : 0;
}
return res;
}
static Result launchTitleImplWrapper(Handle *outDebug, u32 *outPid, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags)
{
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
ProcessData *process;
Result res = launchTitleImpl(outDebug, &process, programInfo, programInfoUpdate, launchFlags, exheaderInfo);
if (outPid != NULL) {
*outPid = process->pid;
}
ExHeaderInfoHeap_Delete(exheaderInfo);
return res;
}
static void LaunchTitleAsync(void *argdata)
{
struct {
FS_ProgramInfo programInfo, programInfoUpdate;
u32 launchFlags;
} *args = argdata;
launchTitleImplWrapper(NULL, NULL, &args->programInfo, &args->programInfoUpdate, args->launchFlags);
}
Result LaunchTitle(u32 *outPid, const FS_ProgramInfo *programInfo, u32 launchFlags)
{
ProcessData *process, *foundProcess = NULL;
launchFlags &= ~PMLAUNCHFLAG_USE_UPDATE_TITLE;
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
u32 tidh = (u32)(programInfo->programId >> 32);
u32 tidl = (u32)programInfo->programId;
if ((tidh == 0x00040030 || tidh == 0x00040130) && (tidl & 0xFF) != SYSCOREVER) {
// Panic if launching SAFE_MODE sysmodules or applets (note: exheader syscorever check above only done for applications in official PM)
// Official PM also hardcodes SYSCOREVER = 2 here.
panic(4);
}
if ((g_manager.runningApplicationData != NULL || g_manager.debugData != NULL) && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) {
return 0xC8A05BF0;
}
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
if ((process->titleId & ~0xFFULL) == (programInfo->programId & ~0xFFULL)) {
foundProcess = process;
break;
}
}
ProcessList_Unlock(&g_manager.processList);
if (foundProcess != NULL) {
foundProcess->flags &= ~PROCESSFLAG_AUTOLOADED;
if (outPid != NULL) {
*outPid = foundProcess->pid;
}
return 0;
} else {
if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION || !(launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION)) {
return launchTitleImplWrapper(NULL, outPid, programInfo, programInfo, launchFlags);
} else {
struct {
FS_ProgramInfo programInfo, programInfoUpdate;
u32 launchFlags;
} args = { *programInfo, *programInfo, launchFlags };
if (outPid != NULL) {
*outPid = (u32)-1; // PM doesn't do that lol
}
TaskRunner_RunTask(LaunchTitleAsync, &args, sizeof(args));
return 0;
}
}
}
Result LaunchTitleUpdate(const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags)
{
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
if (g_manager.runningApplicationData != NULL || g_manager.debugData != NULL) {
return 0xC8A05BF0;
}
if (!(launchFlags & ~PMLAUNCHFLAG_NORMAL_APPLICATION)) {
return 0xD8E05802;
}
launchFlags |= PMLAUNCHFLAG_USE_UPDATE_TITLE;
if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) {
return launchTitleImplWrapper(NULL, NULL, programInfo, programInfoUpdate, launchFlags);
} else {
struct {
FS_ProgramInfo programInfo, programInfoUpdate;
u32 launchFlags;
} args = { *programInfo, *programInfoUpdate, launchFlags };
TaskRunner_RunTask(LaunchTitleAsync, &args, sizeof(args));
return 0;
}
}
Result LaunchApp(const FS_ProgramInfo *programInfo, u32 launchFlags)
{
if (g_manager.runningApplicationData != NULL || g_manager.debugData != NULL) {
return 0xC8A05BF0;
}
assertSuccess(setAppCpuTimeLimit(0));
return LaunchTitle(NULL, programInfo, launchFlags | PMLAUNCHFLAG_LOAD_DEPENDENCIES | PMLAUNCHFLAG_NORMAL_APPLICATION);
}
Result RunQueuedProcess(Handle *outDebug)
{
Result res = 0;
StartupInfo si = {0};
if (g_manager.debugData == NULL) {
return 0xD8A05804;
} else if ((g_manager.debugData->flags & PROCESSFLAG_NORMAL_APPLICATION) && g_manager.runningApplicationData != NULL) {
// Not in official PM
return 0xC8A05BF0;
}
ProcessData *process = g_manager.debugData;
g_manager.debugData = NULL;
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
TRYG(LOADER_GetProgramInfo(exheaderInfo, process->programHandle), cleanup);
TRYG(svcDebugActiveProcess(outDebug, process->pid), cleanup);
si.priority = exheaderInfo->aci.local_caps.core_info.priority;
si.stack_size = exheaderInfo->sci.codeset_info.stack_size;
res = svcRun(process->handle, &si);
if (R_SUCCEEDED(res) && process->flags & PROCESSFLAG_NORMAL_APPLICATION) {
// Second operand not in official PM
g_manager.runningApplicationData = process;
notifySubscribers(0x10C);
}
cleanup:
if (R_FAILED(res)) {
process->flags &= ~PROCESSFLAG_NOTIFY_TERMINATION;
svcTerminateProcess(process->handle);
}
ExHeaderInfoHeap_Delete(exheaderInfo);
return res;
}
Result LaunchAppDebug(Handle *outDebug, const FS_ProgramInfo *programInfo, u32 launchFlags)
{
if (g_manager.debugData != NULL) {
return RunQueuedProcess(outDebug);
}
if (g_manager.runningApplicationData != NULL) {
return 0xC8A05BF0;
}
assertSuccess(setAppCpuTimeLimit(0));
return launchTitleImplWrapper(outDebug, NULL, programInfo, programInfo,
(launchFlags & ~PMLAUNCHFLAG_USE_UPDATE_TITLE) | PMLAUNCHFLAG_NORMAL_APPLICATION);
}
Result autolaunchSysmodules(void)
{
Result res = 0;
FS_ProgramInfo programInfo = { .mediaType = MEDIATYPE_NAND };
// Launch NS
if (NSTID != 0) {
programInfo.programId = NSTID;
TRY(launchTitleImplWrapper(NULL, NULL, &programInfo, &programInfo, PMLAUNCHFLAG_LOAD_DEPENDENCIES));
}
return res;
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <3ds/exheader.h>
#include <3ds/services/fs.h>
#include "process_data.h"
Result LaunchTitle(u32 *outPid, const FS_ProgramInfo *programInfo, u32 launchFlags);
Result LaunchTitleUpdate(const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags);
Result LaunchApp(const FS_ProgramInfo *programInfo, u32 launchFlags);
Result RunQueuedProcess(Handle *outDebug);
Result LaunchAppDebug(Handle *outDebug, const FS_ProgramInfo *programInfo, u32 launchFlags);
Result autolaunchSysmodules(void);

100
sysmodules/pm/source/main.c Normal file
View File

@ -0,0 +1,100 @@
#include <3ds.h>
#include "manager.h"
#include "reslimit.h"
#include "launch.h"
#include "firmlaunch.h"
#include "exheader_info_heap.h"
#include "task_runner.h"
#include "process_monitor.h"
#include "pmapp.h"
#include "pmdbg.h"
#include "util.h"
#include "my_thread.h"
#include "service_manager.h"
static MyThread processMonitorThread, taskRunnerThread;
// this is called before main
void __appInit()
{
// Wait for sm
for(Result res = 0xD88007FA; res == (Result)0xD88007FA; svcSleepThread(500 * 1000LL)) {
res = srvPmInit();
if(R_FAILED(res) && res != (Result)0xD88007FA)
panic(res);
}
loaderInit();
fsRegInit();
static u8 ALIGN(8) processDataBuffer[0x40 * sizeof(ProcessData)] = {0};
static u8 ALIGN(8) exheaderInfoBuffer[6 * sizeof(ExHeader_Info)] = {0};
static u8 ALIGN(8) threadStacks[2][THREAD_STACK_SIZE] = {0};
// Init objects
Manager_Init(processDataBuffer, 0x40);
ExHeaderInfoHeap_Init(exheaderInfoBuffer, 6);
TaskRunner_Init();
// Init the reslimits, register the KIPs and map the firmlaunch parameters
initializeReslimits();
Manager_RegisterKips();
mapFirmlaunchParameters();
// Create the threads
assertSuccess(MyThread_Create(&processMonitorThread, processMonitor, NULL, threadStacks[0], THREAD_STACK_SIZE, 0x17, -2));
assertSuccess(MyThread_Create(&taskRunnerThread, TaskRunner_HandleTasks, NULL, threadStacks[1], THREAD_STACK_SIZE, 0x17, -2));
// Launch NS, etc.
autolaunchSysmodules();
}
// this is called after main exits
void __appExit()
{
// We don't clean up g_manager's handles because it could hang the process monitor thread, etc.
fsRegExit();
loaderExit();
srvPmExit();
}
Result __sync_init(void);
Result __sync_fini(void);
void __libc_init_array(void);
void __libc_fini_array(void);
void __ctru_exit()
{
__libc_fini_array();
__appExit();
__sync_fini();
svcExitProcess();
}
void initSystem()
{
__sync_init();
__appInit();
__libc_init_array();
}
static const ServiceManagerServiceEntry services[] = {
{ "pm:app", 3, pmAppHandleCommands, false },
{ "pm:dbg", 1, pmDbgHandleCommands, false },
{ NULL },
};
static const ServiceManagerNotificationEntry notifications[] = {
{ 0x000, NULL },
};
int main(void)
{
Result res = 0;
if (R_FAILED(res = ServiceManager_Run(services, notifications, NULL))) {
panic(res);
}
return 0;
}

View File

@ -0,0 +1,67 @@
#include <3ds.h>
#include <string.h>
#include "manager.h"
#include "reslimit.h"
#include "util.h"
Manager g_manager;
void Manager_Init(void *procBuf, size_t numProc)
{
memset(&g_manager, 0, sizeof(Manager));
ProcessList_Init(&g_manager.processList, procBuf, numProc);
assertSuccess(svcCreateEvent(&g_manager.newProcessEvent, RESET_ONESHOT));
assertSuccess(svcCreateEvent(&g_manager.allNotifiedTerminationEvent , RESET_ONESHOT));
}
void Manager_RegisterKips(void)
{
s64 numKips = 0;
ProcessData *process;
Handle processHandle;
svcGetSystemInfo(&numKips, 26, 0);
ProcessList_Lock(&g_manager.processList);
for (u32 i = 0; i < (u32)numKips; i++) {
process = ProcessList_New(&g_manager.processList);
if (process == NULL) {
panic(1);
}
assertSuccess(svcOpenProcess(&processHandle, i));
process->handle = processHandle;
process->pid = i;
process->refcount = 1;
process->titleId = 0x0004000100001000ULL; // note: same TID for all builtins
process->flags = PROCESSFLAG_KIP;
process->terminationStatus = TERMSTATUS_RUNNING;
assertSuccess(svcSetProcessResourceLimits(processHandle, g_manager.reslimits[RESLIMIT_CATEGORY_OTHER]));
}
ProcessList_Unlock(&g_manager.processList);
}
Result UnregisterProcess(u64 titleId)
{
ProcessData *foundProcess = NULL;
ProcessList_Lock(&g_manager.processList);
foundProcess = ProcessList_FindProcessByTitleId(&g_manager.processList, titleId);
if (foundProcess != NULL) {
if (foundProcess == g_manager.runningApplicationData) {
g_manager.runningApplicationData = NULL;
}
if (foundProcess == g_manager.debugData) {
g_manager.debugData = NULL;
}
svcCloseHandle(foundProcess->handle);
ProcessList_Delete(&g_manager.processList, foundProcess);
}
ProcessList_Unlock(&g_manager.processList);
return 0;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <3ds/types.h>
#include "process_data.h"
typedef struct Manager {
ProcessList processList;
ProcessData *runningApplicationData;
ProcessData *debugData; // note: official PM uses runningApplicationData for both, and has queuedApplicationProcessHandle
Handle reslimits[4];
Handle newProcessEvent;
Handle allNotifiedTerminationEvent;
bool waitingForTermination;
bool preparingForReboot;
u8 maxAppCpuTime;
s8 cpuTimeBase;
} Manager;
extern Manager g_manager;
void Manager_Init(void *procBuf, size_t numProc);
void Manager_RegisterKips(void);
Result UnregisterProcess(u64 titleId);

View File

@ -0,0 +1,35 @@
#include <3ds.h>
#include "my_thread.h"
static void _thread_begin(void* arg)
{
MyThread *t = (MyThread *)arg;
t->ep(t->p);
MyThread_Exit();
}
Result MyThread_Create(MyThread *t, void (*entrypoint)(void *p), void *p, void *stack, u32 stackSize, int prio, int affinity)
{
t->ep = entrypoint;
t->p = p;
t->stacktop = (u8 *)stack + stackSize;
return svcCreateThread(&t->handle, _thread_begin, (u32)t, (u32*)t->stacktop, prio, affinity);
}
Result MyThread_Join(MyThread *thread, s64 timeout_ns)
{
if (thread == NULL) return 0;
Result res = svcWaitSynchronization(thread->handle, timeout_ns);
if(R_FAILED(res)) return res;
svcCloseHandle(thread->handle);
thread->handle = (Handle)0;
return res;
}
void MyThread_Exit(void)
{
svcExitThread();
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <3ds/types.h>
#include <3ds/result.h>
#include <3ds/svc.h>
#include <3ds/synchronization.h>
#define THREAD_STACK_SIZE 0x1000
typedef struct MyThread {
Handle handle;
void *p;
void (*ep)(void *p);
bool finished;
void* stacktop;
} MyThread;
Result MyThread_Create(MyThread *t, void (*entrypoint)(void *p), void *p, void *stack, u32 stackSize, int prio, int affinity);
Result MyThread_Join(MyThread *thread, s64 timeout_ns);
void MyThread_Exit(void);

View File

@ -0,0 +1,129 @@
#include <3ds.h>
#include <string.h>
#include "launch.h"
#include "firmlaunch.h"
#include "termination.h"
#include "info.h"
#include "reslimit.h"
#include "manager.h"
#include "util.h"
void pmAppHandleCommands(void *ctx)
{
(void)ctx;
u32 *cmdbuf = getThreadCommandBuffer();
u32 cmdhdr = cmdbuf[0];
FS_ProgramInfo programInfo, programInfoUpdate;
ExHeader_Arm11CoreInfo coreInfo;
ExHeader_SystemInfoFlags siFlags;
u32 pid;
u64 titleId, mbz;
s64 timeout, limit;
void *buf;
size_t size;
switch (cmdhdr >> 16) {
case 1:
memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo));
cmdbuf[1] = LaunchTitle(&pid, &programInfo, cmdbuf[5]);
cmdbuf[2] = pid;
cmdbuf[0] = IPC_MakeHeader(1, 2, 0);
break;
case 2:
if (cmdhdr != IPC_MakeHeader(2, 2, 2) || (cmdbuf[3] & 0xF) != 0xA) {
goto invalid_command;
}
size = cmdbuf[3] >> 4;
buf = (void *)cmdbuf[4];
cmdbuf[1] = LaunchFirm(cmdbuf[1], buf, size);
cmdbuf[0] = IPC_MakeHeader(2, 1, 2);
cmdbuf[2] = IPC_Desc_Buffer(size, IPC_BUFFER_R);
cmdbuf[3] = (u32)buf;
break;
case 3:
memcpy(&timeout, cmdbuf + 1, 8);
cmdbuf[1] = TerminateApplication(timeout);
cmdbuf[0] = IPC_MakeHeader(3, 1, 0);
break;
case 4:
memcpy(&titleId, cmdbuf + 1, 8);
memcpy(&timeout, cmdbuf + 3, 8);
cmdbuf[1] = TerminateTitle(titleId, timeout);
cmdbuf[0] = IPC_MakeHeader(4, 1, 0);
break;
case 5:
memcpy(&timeout, cmdbuf + 2, 8);
cmdbuf[1] = TerminateProcess(cmdbuf[1], timeout);
cmdbuf[0] = IPC_MakeHeader(5, 1, 0);
break;
case 6:
if (cmdhdr != IPC_MakeHeader(6, 2, 2) || cmdbuf[3] != 0x20) {
goto invalid_command;
}
memcpy(&timeout, cmdbuf + 1, 8);
cmdbuf[1] = PrepareForReboot(cmdbuf[4], timeout);
cmdbuf[0] = IPC_MakeHeader(6, 1, 0);
break;
case 7:
if (cmdhdr != IPC_MakeHeader(7, 1, 2) || (cmdbuf[2] & 0xF) != 0xC) {
goto invalid_command;
}
size = cmdbuf[2] >> 4;
buf = (void *)cmdbuf[3];
cmdbuf[1] = GetFirmlaunchParams(buf, size);
cmdbuf[0] = IPC_MakeHeader(7, 1, 2);
cmdbuf[2] = IPC_Desc_Buffer(size, IPC_BUFFER_W);
cmdbuf[3] = (u32)buf;
break;
case 8:
memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo));
cmdbuf[1] = GetTitleExHeaderFlags(&coreInfo, &siFlags, &programInfo);
cmdbuf[0] = IPC_MakeHeader(8, 5, 0);
memcpy(cmdbuf + 2, &coreInfo, sizeof(ExHeader_Arm11CoreInfo));
memcpy(cmdbuf + 4, &siFlags, sizeof(ExHeader_SystemInfoFlags));
break;
case 9:
if (cmdhdr != IPC_MakeHeader(9, 1, 2) || (cmdbuf[2] & 0xF) != 0xA) {
goto invalid_command;
}
size = cmdbuf[2] >> 4;
buf = (void *)cmdbuf[3];
cmdbuf[1] = SetFirmlaunchParams(buf, size);
cmdbuf[0] = IPC_MakeHeader(9, 1, 2);
cmdbuf[2] = IPC_Desc_Buffer(size, IPC_BUFFER_R);
cmdbuf[3] = (u32)buf;
break;
case 10:
memcpy(&mbz, cmdbuf + 4, 8);
cmdbuf[1] = SetAppResourceLimit(cmdbuf[1], (ResourceLimitType)cmdbuf[2], cmdbuf[3], mbz);
cmdbuf[0] = IPC_MakeHeader(10, 1, 0);
break;
case 11:
memcpy(&mbz, cmdbuf + 4, 8);
cmdbuf[1] = GetAppResourceLimit(&limit, cmdbuf[1], (ResourceLimitType)cmdbuf[2], cmdbuf[3], mbz);
cmdbuf[0] = IPC_MakeHeader(11, 3, 0);
memcpy(cmdbuf + 2, &limit, 8);
break;
case 12:
memcpy(&titleId, cmdbuf + 1, 8);
cmdbuf[1] = UnregisterProcess(titleId);
cmdbuf[0] = IPC_MakeHeader(12, 1, 0);
break;
case 13:
memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo));
memcpy(&programInfoUpdate, cmdbuf + 5, sizeof(FS_ProgramInfo));
cmdbuf[1] = LaunchTitleUpdate(&programInfo, &programInfoUpdate, cmdbuf[9]);
break;
default:
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
cmdbuf[1] = 0xD900182F;
break;
}
return;
invalid_command:
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
cmdbuf[1] = 0xD9001830;
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <3ds/types.h>
void pmAppHandleCommands(void *ctx);

View File

@ -0,0 +1,41 @@
#include <3ds.h>
#include <string.h>
#include "launch.h"
#include "util.h"
void pmDbgHandleCommands(void *ctx)
{
(void)ctx;
u32 *cmdbuf = getThreadCommandBuffer();
u32 cmdhdr = cmdbuf[0];
FS_ProgramInfo programInfo;
Handle debug;
switch (cmdhdr >> 16) {
case 1:
debug = 0;
memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo));
cmdbuf[1] = LaunchAppDebug(&debug, &programInfo, cmdbuf[5]);
cmdbuf[0] = IPC_MakeHeader(1, 1, 2);
cmdbuf[2] = IPC_Desc_MoveHandles(1);
cmdbuf[3] = debug;
break;
case 2:
memcpy(&programInfo, cmdbuf + 1, sizeof(FS_ProgramInfo));
cmdbuf[1] = LaunchApp(&programInfo, cmdbuf[5]);
cmdbuf[0] = IPC_MakeHeader(2, 1, 0);
break;
case 3:
debug = 0;
cmdbuf[1] = RunQueuedProcess(&debug);
cmdbuf[0] = IPC_MakeHeader(3, 1, 2);
cmdbuf[2] = IPC_Desc_MoveHandles(1);
cmdbuf[3] = debug;
break;
default:
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
cmdbuf[1] = 0xD900182F;
break;
}
}

View File

@ -0,0 +1,5 @@
#pragma once
#include <3ds/types.h>
void pmDbgHandleCommands(void *ctx);

View File

@ -0,0 +1,90 @@
#include <3ds.h>
#include <string.h>
#include "process_data.h"
#include "util.h"
ProcessData *ProcessList_FindProcessById(const ProcessList *list, u32 pid)
{
ProcessData *process;
FOREACH_PROCESS(list, process) {
if (process->pid == pid) {
return process;
}
}
return NULL;
}
ProcessData *ProcessList_FindProcessByHandle(const ProcessList *list, Handle handle)
{
ProcessData *process;
FOREACH_PROCESS(list, process) {
if (process->handle == handle) {
return process;
}
}
return NULL;
}
ProcessData *ProcessList_FindProcessByTitleId(const ProcessList *list, u64 titleId)
{
ProcessData *process;
FOREACH_PROCESS(list, process) {
if ((process->titleId & ~0xFFULL) == (titleId & ~0xFFULL)) {
return process;
}
}
return NULL;
}
Result ProcessData_Notify(const ProcessData *process, u32 notificationId)
{
Result res = SRVPM_PublishToProcess(notificationId, process->handle);
if (res == (Result)0xD8606408) {
panic(res);
}
return res;
}
Result ProcessData_SendTerminationNotification(ProcessData *process)
{
Result res = ProcessData_Notify(process, 0x100);
process->terminationStatus = R_SUCCEEDED(res) ? TERMSTATUS_NOTIFICATION_SENT : TERMSTATUS_NOTIFICATION_FAILED;
return res;
}
void ProcessData_Incref(ProcessData *process, u32 amount)
{
if (process->flags & PROCESSFLAG_AUTOLOADED) {
if (process->refcount + amount > 0xFF) {
panic(3);
}
process->refcount += amount;
}
}
ProcessData *ProcessList_New(ProcessList *list)
{
if (IntrusiveList_TestEnd(&list->freeList, list->freeList.first)) {
return NULL;
}
IntrusiveNode *nd = list->freeList.first;
IntrusiveList_Erase(nd);
memset(nd, 0, sizeof(ProcessData));
IntrusiveList_InsertAfter(list->list.last, nd);
return (ProcessData *)nd;
}
void ProcessList_Delete(ProcessList *list, ProcessData *process)
{
IntrusiveList_Erase(&process->node);
IntrusiveList_InsertAfter(list->freeList.first, &process->node);
}

View File

@ -0,0 +1,95 @@
#pragma once
#include <3ds/types.h>
#include <3ds/synchronization.h>
#include "intrusive_list.h"
#define FOREACH_PROCESS(list, process) \
for (process = ProcessList_GetFirst(list); !ProcessList_TestEnd(list, process); process = ProcessList_GetNext(process))
enum {
PROCESSFLAG_NOTIFY_TERMINATION = BIT(0),
PROCESSFLAG_KIP = BIT(1),
PROCESSFLAG_DEPENDENCIES_LOADED = BIT(2),
PROCESSFLAG_AUTOLOADED = BIT(3),
PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED = BIT(4),
PROCESSFLAG_NORMAL_APPLICATION = BIT(5), // Official PM doesn't have this
};
typedef enum TerminationStatus {
TERMSTATUS_RUNNING = 0,
TERMSTATUS_NOTIFICATION_SENT = 1,
TERMSTATUS_NOTIFICATION_FAILED = 2,
TERMSTATUS_TERMINATED = 3,
} TerminationStatus;
typedef struct ProcessData {
IntrusiveNode node;
Handle handle;
u32 pid;
u64 titleId;
u64 programHandle;
u8 flags;
u8 terminatedNotificationVariation;
TerminationStatus terminationStatus;
u8 refcount;
} ProcessData;
typedef struct ProcessList {
RecursiveLock lock;
IntrusiveList list;
IntrusiveList freeList;
} ProcessList;
static inline void ProcessList_Init(ProcessList *list, void *buf, size_t num)
{
IntrusiveList_Init(&list->list);
IntrusiveList_CreateFromBuffer(&list->freeList, buf, sizeof(ProcessData), sizeof(ProcessData) * num);
RecursiveLock_Init(&list->lock);
}
static inline void ProcessList_Lock(ProcessList *list)
{
RecursiveLock_Lock(&list->lock);
}
static inline void ProcessList_Unlock(ProcessList *list)
{
RecursiveLock_Unlock(&list->lock);
}
static inline ProcessData *ProcessList_GetNext(const ProcessData *process)
{
return (ProcessData *)process->node.next;
}
static inline ProcessData *ProcessList_GetPrev(const ProcessData *process)
{
return (ProcessData *)process->node.next;
}
static inline ProcessData *ProcessList_GetFirst(const ProcessList *list)
{
return (ProcessData *)list->list.first;
}
static inline ProcessData *ProcessList_GetLast(const ProcessList *list)
{
return (ProcessData *)list->list.last;
}
static inline bool ProcessList_TestEnd(const ProcessList *list, const ProcessData *process)
{
return IntrusiveList_TestEnd(&list->list, &process->node);
}
ProcessData *ProcessList_New(ProcessList *list);
void ProcessList_Delete(ProcessList *list, ProcessData *process);
ProcessData *ProcessList_FindProcessById(const ProcessList *list, u32 pid);
ProcessData *ProcessList_FindProcessByTitleId(const ProcessList *list, u64 titleId);
ProcessData *ProcessList_FindProcessByHandle(const ProcessList *list, Handle handle);
void ProcessData_Incref(ProcessData *process, u32 amount);
Result ProcessData_Notify(const ProcessData *process, u32 notificationId);
Result ProcessData_SendTerminationNotification(ProcessData *process);

View File

@ -0,0 +1,109 @@
#include <3ds.h>
#include <string.h>
#include "process_monitor.h"
#include "exheader_info_heap.h"
#include "termination.h"
#include "reslimit.h"
#include "manager.h"
#include "util.h"
static void cleanupProcess(ProcessData *process)
{
if (process->flags & PROCESSFLAG_DEPENDENCIES_LOADED) {
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
listAndTerminateDependencies(process, exheaderInfo);
ExHeaderInfoHeap_Delete(exheaderInfo);
}
if (!(process->flags & PROCESSFLAG_KIP)) {
SRVPM_UnregisterProcess(process->pid);
FSREG_Unregister(process->pid);
LOADER_UnregisterProgram(process->programHandle);
}
if (process == g_manager.runningApplicationData) {
if (IS_N3DS && APPMEMTYPE == 6) {
assertSuccess(resetAppMemLimit());
}
g_manager.runningApplicationData = NULL;
}
if (process == g_manager.debugData) {
g_manager.debugData = NULL;
}
if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) {
notifySubscribers(0x110 + process->terminatedNotificationVariation);
}
}
void processMonitor(void *p)
{
(void)p;
Handle handles[0x41] = { g_manager.newProcessEvent };
for (;;) {
u32 numProcesses = 0;
bool atLeastOneTerminating = false;
ProcessData *process;
ProcessData processBackup;
s32 id = -1;
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
// Rebuild the handle array
if (process->terminationStatus != TERMSTATUS_TERMINATED) {
handles[1 + numProcesses++] = process->handle;
if (process->terminationStatus == TERMSTATUS_NOTIFICATION_SENT) {
atLeastOneTerminating = true;
}
}
}
ProcessList_Unlock(&g_manager.processList);
// If no more processes are terminating, signal the event
if (g_manager.waitingForTermination && !atLeastOneTerminating) {
assertSuccess(svcSignalEvent(g_manager.allNotifiedTerminationEvent));
}
// Note: lack of assertSuccess is intentional.
svcWaitSynchronizationN(&id, handles, 1 + numProcesses, false, -1LL);
if (id > 0) {
// Note: official PM conditionally erases the process from the list, cleans up, then conditionally frees the process data
// Bug in official PM (?): it unlocks the list before setting termstatus = TERMSTATUS_TERMINATED
ProcessList_Lock(&g_manager.processList);
process = ProcessList_FindProcessByHandle(&g_manager.processList, handles[id]);
if (process != NULL) {
process->terminationStatus = TERMSTATUS_TERMINATED;
if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) {
process->flags |= PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED;
}
processBackup = *process; // <-- make sure no list access is done through this node
// Note: PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED can be set by terminateProcessImpl
// APT is shit, why must an app call APT to ask to terminate itself?
if (!(process->flags & PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED)) {
ProcessList_Delete(&g_manager.processList, process);
}
}
ProcessList_Unlock(&g_manager.processList);
if (process != NULL) {
cleanupProcess(&processBackup);
if (!(processBackup.flags & PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED)) {
svcCloseHandle(processBackup.handle);
}
}
}
}
}

View File

@ -0,0 +1,3 @@
#pragma once
void processMonitor(void *p);

View File

@ -0,0 +1,373 @@
#include <3ds.h>
#include "reslimit.h"
#include "util.h"
#include "manager.h"
typedef s64 ReslimitValues[10];
static const ResourceLimitType g_reslimitInitOrder[10] = {
RESLIMIT_COMMIT,
RESLIMIT_PRIORITY,
RESLIMIT_THREAD,
RESLIMIT_EVENT,
RESLIMIT_MUTEX,
RESLIMIT_SEMAPHORE,
RESLIMIT_TIMER,
RESLIMIT_SHAREDMEMORY,
RESLIMIT_ADDRESSARBITER,
RESLIMIT_CPUTIME,
};
static u32 g_currentAppMemLimit = 0, g_defaultAppMemLimit;
static ReslimitValues g_o3dsReslimitValues[4] = {
// APPLICATION
{
0x4000000, // Allocatable memory (dynamically calculated)
0x18, // Max. priority
32, // Threads
32, // Events
32, // Mutexes
8, // Semaphores
8, // Timers
16, // Shared memory objects
2, // Address arbiters
0, // Core1 CPU time
},
// SYS_APPLET
{
0x2606000, // Allocatable memory (dynamically calculated)
4, // Max priority
14, // Threads
8, // Events
8, // Mutexes
4, // Semaphores
4, // Timers
8, // Shared memory objects
3, // Address arbiters
10000, // Core1 CPU time
},
// LIB_APPLET
{
0x0602000, // Allocatable memory (dynamically calculated)
4, // Max priority
14, // Threads
8, // Events
8, // Mutexes
4, // Semaphores
4, // Timers
8, // Shared memory objects
1, // Address arbiters
10000, // Core1 CPU time
},
// OTHER (BASE sysmodules)
{
0x1682000, // Allocatable memory (dynamically calculated)
4, // Max priority
202, // Threads
248, // Events
35, // Mutexes
64, // Semaphores
43, // Timers
30, // Shared memory objects
43, // Address arbiters
1000, // Core1 CPU time
},
};
static ReslimitValues g_n3dsReslimitValues[4] = {
// APPLICATION
{
0x7C00000, // Allocatable memory (dynamically calculated)
0x18, // Max. priority
32, // Threads
32, // Events
32, // Mutexes
8, // Semaphores
8, // Timers
16, // Shared memory objects
2, // Address arbiters
0, // Core1 CPU time
},
// SYS_APPLET
{
0x5E06000, // Allocatable memory (dynamically calculated)
4, // Max priority
29, // Threads
11, // Events
8, // Mutexes
4, // Semaphores
4, // Timers
8, // Shared memory objects
3, // Address arbiters
10000, // Core1 CPU time
},
// LIB_APPLET
{
0x0602000, // Allocatable memory (dynamically calculated)
4, // Max priority
14, // Threads
8, // Events
8, // Mutexes
4, // Semaphores
4, // Timers
8, // Shared memory objects
1, // Address arbiters
10000, // Core1 CPU time
},
// OTHER (BASE sysmodules)
{
0x2182000, // Allocatable memory (dynamically calculated)
4, // Max priority
225, // Threads
264, // Events
37, // Mutexes
67, // Semaphores
44, // Timers
31, // Shared memory objects
45, // Address arbiters
1000, // Core1 CPU time
},
};
/*
SCHEDULER AND PREEMPTION STUFF ON THE 3DS:
Most of the basic scheduler stuff is the same between the Switch and the 3DS:
- Multi-level queue with 64 levels, 0 being highest priority.
- Interrupt handling code mostly the same, same thing for the entire irq bottom-half
object hierarchy.
- There is the same global critical section object, although not fully a spinlock,
and critsec Leave code has the same logic w/r/t the scheduler and scheduler interrupts.
Some stuff is different:
- The thread selection is done core-per-core, and not all-at-once like on the Switch.
- No load balancing. SetThreadAffinityMask is not implemented either.
- When needed (thread creation), each scheduler has a thread queue;
other schedulers enqueue threads on these queues, triggering the scheduler
SGI when transferring a thread, to make sure threads run on the right core.
- Preemption stuff is different. Preemption is only on core1 when enabled.
- Only one time source, on core1.
- On 3DS, the process affinity mask (specified in exheader) IS NOT A LIMITER.
ThreadAffinityMask = processAffinityMask | (1 << coreId). processAffinityMask is only used
for cpuId < 0 in CreateThread.
3DS CPUTIME
Seemingly implemented since 2.1 (see pm workaround and kernel workaround for 1.0 titles below),
on 1.0, there was not any restriction on core1 usage (?).
There are 2 preemption modes, controlled by svcKernelSetState(6, 3, (u64)x) (switching fails
if any of those are still active).
Both rely on segregating running thread/processes in 3 categories: "application", "sysmodules"
and "exempted" (applets).
Cputime values have different meanings:
- 0: disables preemption. Prevent applications from spawning threads in core1.
Behavior is undefined if an 1..89 reslimit is still running. Intended to be ran before app creation only.
- 1..89: core1 cputime percentage. Enables preemption. Previous threads from the same
reslimit are not affected (bug?), but *all* threads with prio >= 16 from the "sysmodule" category
(reslimit value 1000, see below) are affected and possibly immediately setup for preemption, depending on their
current reslimit value.
- 1000: Sets the "sysmodule" preemption info to point to the current reslimit's,
for when something else (application cputime reslimit being set by PM) is set to 1..89.
- 10000 and above: exempted from preemption.
- other values: buggy/possibly undefined?
For the following, time durations might be off by a factor of 2 (?), and nothing was tested.
Please check on hardware/emu code. Really really needs proofreading/prooftesting, especially mode0.
The timers are updated on scheduler thread reselection.
Both modes pause threads they don't want to run in thread selection, and unpause them when needed.
If the threads that are intended to be paused is running an SVC, the pause will happen *after* SVC return.
Mode0 (unsure)
Starting by "sysmodule" threads, alternatively allow (if preemptible) only sysmodule threads,
and then only application threads to run.
The latter has an exception; if "sysmodule" threads have run for less than 2usec, they
are unpaused an allowed to run instead.
This happens at a rate of 1ms * (cpuTime/100).
Mode1
When this mode is enabled, only one application thread is allowed to be created on core1.
This divides the core1 time into slices of 12.5ms.
The "application" thread is given cpuTime% of the slice.
The "sysmodules" threads are given a total of (90 - cpuTime)% of the slice.
10% remain for other threads.
*/
static const struct {
u32 titleUid;
u32 value;
} g_startCpuTimeOverrides[] = {
/*
Region-incoherent? CHN/KOR/TWN consistently missing.
This seems to be here to replace a 2.0+ kernel workaround for 1.0 titles (maybe?).
It seems like 1.0 kernel didn't implement cputime reslimit & you could freely access core1.
2.1+ kernel returns an error if you try to start an application thread on core1 with cputime=0,
except for 1.0 titles for which it automatically sets cputime to 25.
*/
{ 0x205, 10000 }, // 3DS sound (JPN)
{ 0x215, 10000 }, // 3DS sound (USA)
{ 0x225, 10000 }, // 3DS sound (EUR)
{ 0x304, 10000 }, // Star Fox 64 3D (JPN)
{ 0x32E, 10000 }, // Super Monkey Ball 3D (JPN)
{ 0x334, 30 }, // Zelda no Densetsu: Toki no Ocarina 3D (JPN)
{ 0x335, 30 }, // The Legend of Zelda: Ocarina of Time 3D (USA)
{ 0x336, 30 }, // The Legend of Zelda: Ocarina of Time 3D (EUR)
{ 0x348, 10000 }, // Doctor Lautrec to Boukyaku no Kishidan (JPN)
{ 0x349, 10000 }, // Pro Yakyuu Spirits 2011 (JPN)
{ 0x368, 10000 }, // Doctor Lautrec and the Forgotten Knights (USA)
{ 0x370, 10000 }, // Super Monkey Ball 3D (USA)
{ 0x389, 10000 }, // Super Monkey Ball 3D (EUR)
{ 0x490, 10000 }, // Star Fox 64 3D (USA)
{ 0x491, 10000 }, // Star Fox 64 3D (EUR)
{ 0x562, 10000 }, // Doctor Lautrec and the Forgotten Knights (EUR)
};
static ReslimitValues *fixupReslimitValues(void)
{
// In order: APPLICATION, SYS_APPLET, LIB_APPLET, OTHER
// Fixup "commit" reslimit
u32 sysmemalloc = SYSMEMALLOC;
ReslimitValues *values = !IS_N3DS ? g_o3dsReslimitValues : g_n3dsReslimitValues;
static const u32 minAppletMemAmount = 0x1200000;
u32 defaultMemAmount = !IS_N3DS ? 0x2C00000 : 0x6400000;
u32 otherMinOvercommitAmount = !IS_N3DS ? 0x280000 : 0x180000;
u32 baseRegionSize = !IS_N3DS ? 0x1400000 : 0x2000000;
if (sysmemalloc < minAppletMemAmount) {
values[1][0] = SYSMEMALLOC - minAppletMemAmount / 3;
values[2][0] = 0;
values[3][0] = baseRegionSize + otherMinOvercommitAmount;
} else {
u32 excess = sysmemalloc < defaultMemAmount ? 0 : sysmemalloc - defaultMemAmount;
values[1][0] = 3 * excess / 4 + sysmemalloc - minAppletMemAmount / 3;
values[2][0] = 1 * excess / 4 + minAppletMemAmount / 3;
values[3][0] = baseRegionSize + (otherMinOvercommitAmount + excess / 4);
}
values[0][0] = APPMEMALLOC;
g_defaultAppMemLimit = APPMEMALLOC;
return values;
}
Result initializeReslimits(void)
{
Result res = 0;
ReslimitValues *values = fixupReslimitValues();
for (u32 i = 0; i < 4; i++) {
TRY(svcCreateResourceLimit(&g_manager.reslimits[i]));
TRY(svcSetResourceLimitValues(g_manager.reslimits[i], g_reslimitInitOrder, values[i], 10));
}
return res;
}
Result setAppMemLimit(u32 limit)
{
if (g_currentAppMemLimit == g_defaultAppMemLimit) {
return 0;
}
ResourceLimitType category = RESLIMIT_COMMIT;
s64 value = limit | ((u64)limit << 32); // high u32 is the value written to APPMEMALLOC
return svcSetResourceLimitValues(g_manager.reslimits[0], &category, &value, 1);
}
Result resetAppMemLimit(void)
{
return setAppMemLimit(g_defaultAppMemLimit);
}
Result setAppCpuTimeLimit(s64 limit)
{
ResourceLimitType category = RESLIMIT_CPUTIME;
return svcSetResourceLimitValues(g_manager.reslimits[0], &category, &limit, 1);
}
static Result getAppCpuTimeLimit(s64 *limit)
{
ResourceLimitType category = RESLIMIT_CPUTIME;
return svcGetResourceLimitLimitValues(limit, g_manager.reslimits[0], &category, 1);
}
void setAppCpuTimeLimitAndSchedModeFromDescriptor(u64 titleId, u16 descriptor)
{
/*
Two main cases here:
- app has a non-0 cputime descriptor in exhdr: current core1 cputime reslimit,
and maximum, and scheduling mode are set to it. SetAppResourceLimit is *not* needed
to use core1.
- app has a 0 cputime descriptor: maximum is set to 80.
Current reslimit is set to 0, and SetAppResourceLimit *is* needed
to use core1, **EXCEPT** for an hardcoded set of titles.
*/
u8 cpuTime = (u8)descriptor;
assertSuccess(setAppCpuTimeLimit(cpuTime));
g_manager.cpuTimeBase = 0;
if (cpuTime != 0) {
// Set core1 scheduling mode
g_manager.maxAppCpuTime = cpuTime & 0x7F;
assertSuccess(svcKernelSetState(6, 3, (cpuTime & 0x80) ? 1LL : 0LL));
} else {
u32 titleUid = ((u32)titleId >> 8) & 0xFFFFF;
g_manager.maxAppCpuTime = 80;
static const u32 numOverrides = sizeof(g_startCpuTimeOverrides) / sizeof(g_startCpuTimeOverrides[0]);
if (titleUid >= g_startCpuTimeOverrides[0].titleUid && titleUid <= g_startCpuTimeOverrides[numOverrides - 1].titleUid) {
u32 i = 0;
for (u32 i = 0; i < numOverrides && titleUid < g_startCpuTimeOverrides[i].titleUid; i++);
if (i < numOverrides) {
if (g_startCpuTimeOverrides[i].value > 100 && g_startCpuTimeOverrides[i].value < 200) {
assertSuccess(svcKernelSetState(6, 3, 0LL));
assertSuccess(setAppCpuTimeLimit(g_startCpuTimeOverrides[i].value - 100));
} else {
assertSuccess(svcKernelSetState(6, 3, 1LL));
assertSuccess(setAppCpuTimeLimit(g_startCpuTimeOverrides[i].value));
}
}
}
}
}
Result SetAppResourceLimit(u32 mbz, ResourceLimitType category, u32 value, u64 mbz2)
{
if (mbz != 0 || mbz2 != 0 || category != RESLIMIT_CPUTIME || value > (u32)g_manager.maxAppCpuTime) {
return 0xD8E05BF4;
}
value += value < 5 ? 0 : g_manager.cpuTimeBase;
return setAppCpuTimeLimit(value);
}
Result GetAppResourceLimit(s64 *value, u32 mbz, ResourceLimitType category, u32 mbz2, u64 mbz3)
{
if (mbz != 0 || mbz2 != 0 || mbz3 != 0 || category != RESLIMIT_CPUTIME) {
return 0xD8E05BF4;
}
Result res = getAppCpuTimeLimit(value);
if (R_SUCCEEDED(res) && *value >= 5) {
*value = *value >= g_manager.cpuTimeBase ? *value - g_manager.cpuTimeBase : 0;
}
return res;
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <3ds/svc.h>
Result initializeReslimits(void);
Result setAppMemLimit(u32 limit);
Result resetAppMemLimit(void);
Result setAppCpuTimeLimit(s64 limit);
void setAppCpuTimeLimitAndSchedModeFromDescriptor(u64 titleId, u16 descriptor);
Result SetAppResourceLimit(u32 mbz, ResourceLimitType category, u32 value, u64 mbz2);
Result GetAppResourceLimit(s64 *value, u32 mbz, ResourceLimitType category, u32 mbz2, u64 mbz3);

View File

@ -0,0 +1,182 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "service_manager.h"
#define TRY(expr) if(R_FAILED(res = (expr))) goto cleanup;
Result ServiceManager_Run(const ServiceManagerServiceEntry *services, const ServiceManagerNotificationEntry *notifications, const ServiceManagerContextAllocator *allocator)
{
Result res = 0;
u32 numServices = 0;
u32 maxSessionsTotal = 0;
u32 numActiveSessions = 0;
bool terminationRequested = false;
for (u32 i = 0; services[i].name != NULL; i++) {
numServices++;
maxSessionsTotal += services[i].maxSessions;
}
Handle waitHandles[1 + numServices + maxSessionsTotal];
void *ctxs[maxSessionsTotal];
u8 handlerIds[maxSessionsTotal];
Handle replyTarget = 0;
s32 id = -1;
u32 *cmdbuf = getThreadCommandBuffer();
TRY(srvEnableNotification(&waitHandles[0]));
// Subscribe to notifications if needed.
for (u32 i = 0; notifications[i].handler != NULL; i++) {
// Termination & ready for reboot events send by PM using PublishToProcess don't require subscription.
if (notifications[i].id != 0x100 && notifications[i].id != 0x179) {
TRY(srvSubscribe(notifications[i].id));
}
}
for (u32 i = 0; i < numServices; i++) {
if (!services[i].isGlobalPort) {
TRY(srvRegisterService(&waitHandles[1 + i], services[i].name, (s32)services[i].maxSessions));
} else {
Handle clientPort;
TRY(svcCreatePort(&waitHandles[1 + i], &clientPort, services[i].name, (s32)services[i].maxSessions));
svcCloseHandle(clientPort);
}
}
while (!terminationRequested) {
if (replyTarget == 0) {
cmdbuf[0] = 0xFFFF0000;
}
id = -1;
res = svcReplyAndReceive(&id, waitHandles, 1 + numServices + numActiveSessions, replyTarget);
if (res == (Result)0xC920181A) {
// Session has been closed
u32 off;
if (id == -1) {
for (off = 0; off < numActiveSessions && waitHandles[1 + numServices + off] != replyTarget; off++);
if (off >= numActiveSessions) {
return res;
}
id = 1 + numServices + off;
} else if ((u32)id < 1 + numServices) {
return res;
}
off = id - 1 - numServices;
Handle h = waitHandles[id];
void *ctx = ctxs[off];
waitHandles[id] = waitHandles[1 + numServices + --numActiveSessions];
handlerIds[off] = handlerIds[numActiveSessions];
ctxs[off] = ctxs[numActiveSessions];
svcCloseHandle(h);
if (allocator != NULL) {
allocator->freeSessionContext(ctx);
}
replyTarget = 0;
res = 0;
} else if (R_FAILED(res)) {
return res;
}
else {
// Ok, no session closed and no error
replyTarget = 0;
if (id == 0) {
// Notification
u32 notificationId = 0;
TRY(srvReceiveNotification(&notificationId));
terminationRequested = notificationId == 0x100;
for (u32 i = 0; notifications[i].handler != NULL; i++) {
if (notifications[i].id == notificationId) {
notifications[i].handler(notificationId);
break;
}
}
} else if ((u32)id < 1 + numServices) {
// New session
Handle session;
void *ctx = NULL;
TRY(svcAcceptSession(&session, waitHandles[id]));
if (allocator) {
ctx = allocator->newSessionContext((u8)(id - 1));
if (ctx == NULL) {
svcCloseHandle(session);
return 0xDEAD0000;
}
}
waitHandles[1 + numServices + numActiveSessions] = session;
handlerIds[numActiveSessions] = (u8)(id - 1);
ctxs[numActiveSessions++] = ctx;
} else {
// Service command
u32 off = id - 1 - numServices;
services[handlerIds[off]].handler(ctxs[off]);
replyTarget = waitHandles[id];
}
}
}
cleanup:
for (u32 i = 0; i < 1 + numServices + numActiveSessions; i++) {
svcCloseHandle(waitHandles[i]);
}
// Subscribe to notifications if needed.
for (u32 i = 0; notifications[i].handler != NULL; i++) {
// Termination & ready for reboot events send by PM using PublishToProcess don't require subscription.
if (notifications[i].id != 0x100 && notifications[i].id != 0x179) {
TRY(srvUnsubscribe(notifications[i].id));
}
}
for (u32 i = 0; i < numServices; i++) {
if (!services[i].isGlobalPort) {
srvUnregisterService(services[i].name);
}
}
if (allocator) {
for (u32 i = 0; i < numActiveSessions; i++) {
allocator->freeSessionContext(ctxs[i]);
}
}
return res;
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <3ds/types.h>
typedef struct ServiceManagerServiceEntry {
const char *name;
u32 maxSessions;
void (*handler)(void *ctx);
bool isGlobalPort;
} ServiceManagerServiceEntry;
typedef struct ServiceManagerNotificationEntry {
u32 id;
void (*handler)(u32 id);
} ServiceManagerNotificationEntry;
typedef struct ServiceManagerContextAllocator {
void* (*newSessionContext)(u8 serviceId);
void (*freeSessionContext)(void *ctx);
} ServiceManagerContextAllocator;
Result ServiceManager_Run(const ServiceManagerServiceEntry *services, const ServiceManagerNotificationEntry *notifications, const ServiceManagerContextAllocator *allocator);

View File

@ -0,0 +1,31 @@
#include <3ds.h>
#include <string.h>
#include "task_runner.h"
TaskRunner g_taskRunner;
void TaskRunner_Init(void)
{
memset(&g_taskRunner, 0, sizeof(TaskRunner));
LightEvent_Init(&g_taskRunner.readyEvent, RESET_ONESHOT);
LightEvent_Init(&g_taskRunner.parametersSetEvent, RESET_ONESHOT);
}
void TaskRunner_RunTask(void (*task)(void *argdata), void *argdata, size_t argsize)
{
argsize = argsize > sizeof(g_taskRunner.argStorage) ? sizeof(g_taskRunner.argStorage) : argsize;
LightEvent_Wait(&g_taskRunner.readyEvent);
g_taskRunner.task = task;
memcpy(g_taskRunner.argStorage, argdata, argsize);
LightEvent_Signal(&g_taskRunner.parametersSetEvent);
}
void TaskRunner_HandleTasks(void *p)
{
(void)p;
for (;;) {
LightEvent_Signal(&g_taskRunner.readyEvent);
LightEvent_Wait(&g_taskRunner.parametersSetEvent);
g_taskRunner.task(g_taskRunner.argStorage);
}
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <3ds/types.h>
#include <3ds/synchronization.h>
typedef struct TaskRunner {
LightEvent readyEvent;
LightEvent parametersSetEvent;
void (*task)(void *argdata);
u8 argStorage[0x40];
} TaskRunner;
extern TaskRunner g_taskRunner;
void TaskRunner_Init(void);
void TaskRunner_RunTask(void (*task)(void *argdata), void *argdata, size_t argsize);
/// Thread function
void TaskRunner_HandleTasks(void *p);

View File

@ -0,0 +1,351 @@
#include <3ds.h>
#include "termination.h"
#include "info.h"
#include "manager.h"
#include "util.h"
#include "exheader_info_heap.h"
#include "task_runner.h"
static Result terminateUnusedDependencies(const u64 *dependencies, u32 numDeps)
{
ProcessData *process;
Result res = 0;
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
if (process->terminationStatus != TERMSTATUS_RUNNING || !(process->flags & PROCESSFLAG_AUTOLOADED)) {
continue;
}
u32 i;
for (i = 0; i < numDeps && dependencies[i] != process->titleId; i++);
if (i >= numDeps || --process->refcount > 0) {
// Process not a listed dependency or still used
continue;
}
res = ProcessData_SendTerminationNotification(process);
res = R_SUMMARY(res) == RS_NOTFOUND ? 0 : res;
if (R_FAILED(res)) {
assertSuccess(svcTerminateProcess(process->handle));
}
}
ProcessList_Unlock(&g_manager.processList);
return res;
}
Result listAndTerminateDependencies(ProcessData *process, ExHeader_Info *exheaderInfo)
{
Result res = 0;
u64 dependencies[48]; // note: official pm reuses exheaderInfo to save space
u32 numDeps = 0;
TRY(getAndListDependencies(dependencies, &numDeps, process, exheaderInfo));
return terminateUnusedDependencies(dependencies, numDeps);
}
static Result terminateProcessImpl(ProcessData *process, ExHeader_Info *exheaderInfo)
{
// NOTE: list dependencies BEFORE sending the notification -- race condition material
Result res = 0;
u64 dependencies[48]; // note: official pm reuses exheaderInfo to save space
u32 numDeps = 0;
if (process->flags & PROCESSFLAG_DEPENDENCIES_LOADED) {
TRY(getAndListDependencies(dependencies, &numDeps, process, exheaderInfo));
process->flags &= ~PROCESSFLAG_DEPENDENCIES_LOADED;
ProcessData_SendTerminationNotification(process);
return terminateUnusedDependencies(dependencies, numDeps);
} else {
ProcessData_SendTerminationNotification(process);
return 0;
}
}
static Result commitPendingTerminations(s64 timeout)
{
// Wait for all of the processes that have received notification 0x100 to terminate
// till the timeout, then actually terminate these processes, etc.
Result res = 0;
bool atLeastOneListener = false;
ProcessList_Lock(&g_manager.processList);
ProcessData *process;
FOREACH_PROCESS(&g_manager.processList, process) {
switch (process->terminationStatus) {
case TERMSTATUS_NOTIFICATION_SENT:
atLeastOneListener = true;
break;
case TERMSTATUS_NOTIFICATION_FAILED:
res = svcTerminateProcess(process->handle); // official pm does not panic on failure here
break;
default:
break;
}
}
ProcessList_Unlock(&g_manager.processList);
if (atLeastOneListener) {
Result res = assertSuccess(svcWaitSynchronization(g_manager.allNotifiedTerminationEvent, timeout));
if (R_DESCRIPTION(res) == RD_TIMEOUT) {
ProcessList_Lock(&g_manager.processList);
ProcessData *process;
FOREACH_PROCESS(&g_manager.processList, process) {
if (process->terminationStatus == TERMSTATUS_NOTIFICATION_SENT) {
res = svcTerminateProcess(process->handle);
}
}
ProcessList_Unlock(&g_manager.processList);
assertSuccess(svcWaitSynchronization(g_manager.allNotifiedTerminationEvent, -1LL));
}
} else {
res = 0;
}
return res;
}
static void TerminateProcessOrTitleAsync(void *argdata)
{
struct {
u64 id;
s64 timeout;
bool useTitleId;
} *args = argdata;
ProcessData *process;
bool notify = false;
u8 variation;
if (args->timeout >= 0) {
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
}
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
// It's the only place where it uses the full titleId, and doesn't break after the first result.
// Maybe it's to allow killing all the builtins at once with their dummy titleIds? Otherwise,
// two processes can't have the same titleId.
if ((args->useTitleId && process->titleId == args->id) || process->pid == args->id) {
if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) {
notify = true;
variation = process->terminatedNotificationVariation;
process->flags = (process->flags & ~PROCESSFLAG_NOTIFY_TERMINATION) | PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED;
}
terminateProcessImpl(process, exheaderInfo);
if (!args->useTitleId) {
break;
}
}
}
ProcessList_Unlock(&g_manager.processList);
ExHeaderInfoHeap_Delete(exheaderInfo);
if (args->timeout >= 0) {
commitPendingTerminations(args->timeout);
g_manager.waitingForTermination = false;
if (notify) {
notifySubscribers(0x110 + variation);
}
}
}
static Result TerminateProcessOrTitle(u64 id, s64 timeout, bool useTitleId)
{
ProcessData *process;
if (g_manager.preparingForReboot) {
if (timeout != 0) {
return 0xC8A05801;
}
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
if ((useTitleId && process->titleId == id) || process->pid == id) {
assertSuccess(svcTerminateProcess(process->handle));
if (!useTitleId) {
break;
}
}
}
ProcessList_Unlock(&g_manager.processList);
return 0;
} else {
struct {
u64 id;
s64 timeout;
bool useTitleId;
} args = { id, timeout, useTitleId };
TaskRunner_RunTask(TerminateProcessOrTitleAsync, &args, sizeof(args));
return 0;
}
}
Result TerminateApplication(s64 timeout)
{
Result res = 0;
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
if (g_manager.runningApplicationData != NULL) {
terminateProcessImpl(g_manager.runningApplicationData, exheaderInfo);
}
res = commitPendingTerminations(timeout);
ExHeaderInfoHeap_Delete(exheaderInfo);
g_manager.waitingForTermination = false;
return res;
}
Result TerminateTitle(u64 titleId, s64 timeout)
{
return TerminateProcessOrTitle(titleId, timeout, true);
}
Result TerminateProcess(u32 pid, s64 timeout)
{
return TerminateProcessOrTitle(pid, timeout, false);
}
ProcessData *terminateAllProcesses(u32 callerPid, s64 timeout)
{
u64 dstTimePoint = svcGetSystemTick() + nsToTicks(timeout);
ProcessData *process;
ProcessData *callerProcess = NULL; // note: official pm returns the caller's handle instead
u64 dependencies[48];
u32 numDeps = 0;
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
// List the dependencies of the caller
if (callerPid != (u32)-1) {
ProcessList_Lock(&g_manager.processList);
process = ProcessList_FindProcessById(&g_manager.processList, callerPid);
if (process != NULL) {
callerProcess = process;
getAndListDependencies(dependencies, &numDeps, process, exheaderInfo);
}
ProcessList_Unlock(&g_manager.processList);
}
// Send notification 0x100 to the currently running application
if (g_manager.runningApplicationData != NULL) {
g_manager.runningApplicationData->flags &= ~PROCESSFLAG_DEPENDENCIES_LOADED;
ProcessData_SendTerminationNotification(g_manager.runningApplicationData);
}
// Send notification 0x100 to anything but the caller deps or the caller; and *increase* the refcount of the latter if autoloaded
// Ignore KIPs
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
if (process->flags & PROCESSFLAG_KIP) {
continue;
} else if (process == callerProcess && (process->flags & PROCESSFLAG_AUTOLOADED) != 0) {
ProcessData_Incref(process, 1);
continue;
}
u32 i;
for (i = 0; i < numDeps && dependencies[i] != process->titleId; i++);
if (i >= numDeps) {
// Process not a listed dependency: send notification 0x100
ProcessData_SendTerminationNotification(process);
} else if (process->flags & PROCESSFLAG_AUTOLOADED){
ProcessData_Incref(process, 1);
}
}
ProcessList_Unlock(&g_manager.processList);
ExHeaderInfoHeap_Delete(exheaderInfo);
s64 timeoutTicks = dstTimePoint - svcGetSystemTick();
commitPendingTerminations(timeoutTicks >= 0 ? ticksToNs(timeoutTicks) : 0LL);
g_manager.waitingForTermination = false;
// Now, send termination notification to PXI (PID 4)
assertSuccess(svcClearEvent(g_manager.allNotifiedTerminationEvent));
g_manager.waitingForTermination = true;
ProcessList_Lock(&g_manager.processList);
process = ProcessList_FindProcessById(&g_manager.processList, 4);
if (process != NULL) {
ProcessData_SendTerminationNotification(process);
} else {
panic(0LL);
}
ProcessList_Unlock(&g_manager.processList);
// Allow 1.5 extra seconds for PXI (approx 402167783 ticks)
timeoutTicks = dstTimePoint - svcGetSystemTick();
commitPendingTerminations(1500 * 1000 * 1000LL + (timeoutTicks >= 0 ? ticksToNs(timeoutTicks) : 0LL));
g_manager.waitingForTermination = false;
return callerProcess;
}
static void PrepareForRebootAsync(void *argdata)
{
struct {
u32 pid;
s64 timeout;
} *args = argdata;
ProcessData *caller = terminateAllProcesses(args->pid, args->timeout);
if (caller != NULL) {
ProcessData_Notify(caller, 0x179);
}
}
Result PrepareForReboot(u32 pid, s64 timeout)
{
struct {
u32 pid;
s64 timeout;
} args = { pid, timeout };
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
g_manager.preparingForReboot = true;
TaskRunner_RunTask(PrepareForRebootAsync, &args, sizeof(args));
return 0;
}

View File

@ -0,0 +1,12 @@
#pragma once
#include "process_data.h"
#include <3ds/exheader.h>
Result listAndTerminateDependencies(ProcessData *process, ExHeader_Info *exheaderInfo);
ProcessData *terminateAllProcesses(u32 callerPid, s64 timeout); // callerPid = -1 for firmlaunch
Result TerminateApplication(s64 timeout);
Result TerminateTitle(u64 titleId, s64 timeout);
Result TerminateProcess(u32 pid, s64 timeout);
Result PrepareForReboot(u32 pid, s64 timeout);

View File

@ -0,0 +1,81 @@
#pragma once
#include <3ds/types.h>
#include <3ds/result.h>
#include <3ds/os.h>
#define REG32(reg) (*(vu32 *)reg)
#define REG64(reg) (*(vu64 *)reg)
#define NSTID REG64(0x1FF80008)
#define SYSCOREVER REG32(0x1FF80010)
#define APPMEMTYPE REG32(0x1FF80030)
#define APPMEMALLOC REG32(0x1FF80040)
#define SYSMEMALLOC REG32(0x1FF80044)
#define IS_N3DS (*(vu32 *)0x1FF80030 >= 6) // APPMEMTYPE. Hacky but doesn't use APT
#define N3DS_TID_MASK 0xF0000000ULL
#define N3DS_TID_BIT 0x20000000ULL
#define TRY(expr) if(R_FAILED(res = (expr))) return res;
#define TRYG(expr, label) if(R_FAILED(res = (expr))) goto label;
#define XDS
#ifdef XDS
static void hexItoa(u64 number, char *out, u32 digits, bool uppercase)
{
const char hexDigits[] = "0123456789ABCDEF";
const char hexDigitsLowercase[] = "0123456789abcdef";
u32 i = 0;
while(number > 0)
{
out[digits - 1 - i++] = uppercase ? hexDigits[number & 0xF] : hexDigitsLowercase[number & 0xF];
number >>= 4;
}
while(i < digits) out[digits - 1 - i++] = '0';
}
#endif
static void __attribute__((noinline)) panic(Result res)
{
#ifndef XDS
(void)res;
__builtin_trap();
#else
char buf[32] = {0};
hexItoa(res, buf, 8, false);
svcOutputDebugString(buf, 8);
svcBreak(USERBREAK_PANIC);
#endif
}
static inline Result assertSuccess(Result res)
{
if(R_FAILED(res)) {
panic(res);
}
return res;
}
static inline Result notifySubscribers(u32 notificationId)
{
Result res = srvPublishToSubscriber(notificationId, 0);
if (res == (Result)0xD8606408) {
panic(res);
}
return res;
}
static inline s64 nsToTicks(s64 ns)
{
return ns * SYSCLOCK_ARM11 / (1000 * 1000 * 1000LL);
}
static inline s64 ticksToNs(s64 ticks)
{
return 1000 * 1000 * 1000LL * ticks / SYSCLOCK_ARM11;
}