Integrate 3ds_pxi and 3ds_sm

This commit is contained in:
TuxSH
2017-11-02 15:11:55 +01:00
parent 76dde0e6db
commit 8258a98647
37 changed files with 2806 additions and 1 deletions

21
sysmodules/sm/LICENSE Normal file
View File

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

52
sysmodules/sm/Makefile Normal file
View File

@@ -0,0 +1,52 @@
rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2))
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/3ds_rules
CC := arm-none-eabi-gcc
AS := arm-none-eabi-as
LD := arm-none-eabi-ld
OC := arm-none-eabi-objcopy
name := sm
dir_source := source
dir_build := build
dir_out := ../../$(dir_build)
LIBS := -lctru
LIBDIRS := $(CTRULIB)
LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
INCLUDE := $(foreach dir,$(LIBDIRS),-I$(dir)/include)
ARCH := -mcpu=mpcore -mfloat-abi=hard -mtp=soft
CFLAGS := -Wall -Wextra -MMD -MP -marm $(ARCH) -fno-builtin -std=c11 -O2 -g -ffast-math -mword-relocations \
-ffunction-sections -fdata-sections -fno-strict-aliasing $(INCLUDE) -DARM11 -D_3DS
LDFLAGS := -specs=3dsx.specs -Wl,--gc-sections $(ARCH)
objects = $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, \
$(call rwildcard, $(dir_source), *.c))
.PHONY: all
all: $(dir_out)/$(name).cxi
.PHONY: clean
clean:
@rm -rf $(dir_build)
$(dir_out)/$(name).cxi: $(dir_build)/$(name).elf
@makerom -f ncch -rsf $(name).rsf -nocodepadding -o $@ -elf $<
$(dir_build)/$(name).elf: $(objects)
$(LINK.o) $(OUTPUT_OPTION) $^ $(LIBPATHS) $(LIBS)
$(dir_build)/memory.o : CFLAGS += -O3
$(dir_build)/%.o: $(dir_source)/%.c
@mkdir -p "$(@D)"
$(COMPILE.c) $(OUTPUT_OPTION) $<
include $(call rwildcard, $(dir_build), *.d)

9
sysmodules/sm/README.md Normal file
View File

@@ -0,0 +1,9 @@
# 3ds_sm
Open source replacement of the ARM11 SM 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 sm.cxi to /luma/sysmodules/.
# Credits
Everyone that helped me fix some of stupid bugs I had been making: @fincs, @Hikari-chin, etc.

111
sysmodules/sm/sm.rsf Normal file
View File

@@ -0,0 +1,111 @@
BasicInfo:
Title : sm
CompanyCode : "00"
ProductCode : lennybuilder
ContentType : Application
Logo : None
TitleInfo:
UniqueId : 0x10
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:
AcceptSession: 74
ArbitrateAddress: 34
Break: 60
CancelTimer: 28
ClearEvent: 25
ClearTimer: 29
CloseHandle: 35
ConnectToPort: 45
ControlMemory: 1
CreateAddressArbiter: 33
CreateEvent: 23
CreateMemoryBlock: 30
CreateMutex: 19
CreatePort: 71
CreateSemaphore: 21
CreateSessionToPort: 72
CreateSession: 73
CreateThread: 8
CreateTimer: 26
DuplicateHandle: 39
ExitProcess: 3
ExitThread: 9
GetCurrentProcessorNumber: 17
GetHandleInfo: 41
GetProcessId: 53
GetProcessIdealProcessor: 6
GetProcessIdOfThread: 54
GetProcessInfo: 43
GetResourceLimit: 56
GetResourceLimitCurrentValues: 58
GetResourceLimitLimitValues: 57
GetSystemInfo: 42
GetSystemTick: 40
GetThreadContext: 59
GetThreadId: 55
GetThreadIdealProcessor: 15
GetThreadInfo: 44
GetThreadPriority: 11
MapMemoryBlock: 31
OutputDebugString: 61
QueryMemory: 2
ReleaseMutex: 20
ReleaseSemaphore: 22
ReplyAndReceive1: 75
ReplyAndReceive2: 76
ReplyAndReceive3: 77
ReplyAndReceive4: 78
ReplyAndReceive: 79
SendSyncRequest1: 46
SendSyncRequest2: 47
SendSyncRequest3: 48
SendSyncRequest4: 49
SendSyncRequest: 50
SetThreadPriority: 12
SetTimer: 27
SignalEvent: 24
SleepThread: 10
UnmapMemoryBlock: 32
WaitSynchronization1: 36
WaitSynchronizationN: 37
InterruptNumbers:
ServiceAccessControl:
FileSystemAccess:
SystemControlInfo:
SaveDataSize: 0KB # It doesn't use any save data.
RemasterVersion: 0
StackSize: 0x1000

View File

@@ -0,0 +1,51 @@
/*
common.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include <3ds.h>
#include "memory.h"
#define IS_PRE_7X (osGetFirmVersion() < SYSTEM_VERSION(2, 39, 4))
#define IS_PRE_93 (osGetFirmVersion() < SYSTEM_VERSION(2, 48, 3))
extern u32 nbSection0Modules;
extern Handle resumeGetServiceHandleOrPortRegisteredSemaphore;
struct SessionDataList;
typedef struct SessionData
{
struct SessionData *prev, *next;
struct SessionDataList *parent;
u32 pid;
u32 replayCmdbuf[4];
Handle busyClientPortHandle;
Handle handle;
bool isSrvPm;
} SessionData;
typedef struct SessionDataList
{
SessionData *first, *last;
} SessionDataList;
extern SessionDataList sessionDataInUseList, freeSessionDataList;
extern SessionDataList sessionDataWaitingForServiceOrPortRegisterList, sessionDataToWakeUpAfterServiceOrPortRegisterList;
extern SessionDataList sessionDataWaitingPortReadyList;
static inline void panic(void)
{
svcBreak(USERBREAK_PANIC);
for(;;) svcSleepThread(0);
}
static inline void assertSuccess(Result res)
{
if(R_FAILED(res))
panic();
}

106
sysmodules/sm/source/list.c Normal file
View File

@@ -0,0 +1,106 @@
/*
list.c
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "list.h"
#include "memory.h"
#include "common.h"
struct ListBase;
typedef struct ListNodeBase
{
struct ListNodeBase *prev, *next;
struct ListBase *parent;
} ListNodeBase;
typedef struct ListBase
{
ListNodeBase *first, *last;
} ListBase;
void buildList(void *list, void *pool, u32 nb, u32 elementSize)
{
ListBase *listB = (ListBase *)list;
for(u32 i = 0; i < nb; i++)
{
ListNodeBase *node = (ListNodeBase *)((u8 *)pool + i * elementSize);
node->prev = i == 0 ? NULL : (ListNodeBase *)((u8 *)pool + (i - 1) * elementSize);
node->next = i == (nb - 1) ? NULL : (ListNodeBase *)((u8 *)pool + (i + 1) * elementSize);
node->parent = list;
}
listB->first = (ListNodeBase *)pool;
listB->last = (ListNodeBase *)((u8 *)pool + (nb - 1) * elementSize);
}
void moveNode(void *node, void *dst, bool back)
{
ListNodeBase *nodeB = (ListNodeBase *)node;
ListBase *dstB = (ListBase *)dst;
ListBase *srcB = nodeB->parent;
if(dstB == srcB)
return;
// Remove the node in the source list
if(nodeB->prev != NULL)
nodeB->prev->next = nodeB->next;
if(nodeB->next != NULL)
nodeB->next->prev = nodeB->prev;
// Update the source list if needed
if(nodeB == srcB->first)
srcB->first = nodeB->next;
if(nodeB == srcB->last)
srcB->last = nodeB->prev;
// Insert the node in the destination list
if(back)
{
if(dstB->last != NULL)
dstB->last->next = nodeB;
nodeB->prev = dstB->last;
nodeB->next = NULL;
dstB->last = nodeB;
}
else
{
if(dstB->first != NULL)
dstB->first->prev = nodeB;
nodeB->next = dstB->first;
nodeB->prev = NULL;
dstB->first = nodeB;
}
// Normalize the destination list
if(dstB->first != NULL && dstB->last == NULL)
dstB->last = dstB->first;
else if(dstB->first == NULL && dstB->last != NULL)
dstB->first = dstB->last;
nodeB->parent = dstB;
}
void *allocateNode(void *inUseList, void *freeList, u32 elementSize, bool back)
{
ListBase *freeListB = (ListBase *)freeList;
if(freeListB->first == NULL)
panic();
ListNodeBase *node = freeListB->first;
ListNodeBase nodeBk = *node;
memset(node, 0, elementSize);
*node = nodeBk;
moveNode(node, inUseList, back);
return node;
}

View File

@@ -0,0 +1,14 @@
/*
list.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include <3ds/types.h>
void buildList(void *list, void *pool, u32 nb, u32 elementSize);
void moveNode(void *node, void *dst, bool back);
void *allocateNode(void *inUseList, void *freeList, u32 elementSize, bool back);

299
sysmodules/sm/source/main.c Normal file
View File

@@ -0,0 +1,299 @@
/*
main.c
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "common.h"
#include "memory.h"
#include "services.h"
#include "processes.h"
#include "srv.h"
#include "srv_pm.h"
#include "list.h"
extern u32 __ctru_heap;
extern u32 __ctru_linear_heap;
extern char (*serviceAccessListBuffers)[34][8];
u32 __ctru_heap_size = 0x4000;
u32 __ctru_linear_heap_size = 0;
u32 nbSection0Modules;
Handle resumeGetServiceHandleOrPortRegisteredSemaphore;
SessionDataList sessionDataInUseList = {NULL, NULL}, freeSessionDataList = {NULL, NULL};
SessionDataList sessionDataWaitingForServiceOrPortRegisterList = {NULL, NULL}, sessionDataToWakeUpAfterServiceOrPortRegisterList = {NULL, NULL};
SessionDataList sessionDataWaitingPortReadyList = {NULL, NULL};
static SessionData sessionDataPool[76];
static ProcessData processDataPool[64];
static u8 ALIGN(4) serviceAccessListStaticBuffer[0x110];
void __appInit(void)
{
s64 out;
u32 *staticBuffers = getThreadStaticBuffers();
staticBuffers[0] = IPC_Desc_StaticBuffer(0x110, 0);
staticBuffers[1] = (u32)serviceAccessListStaticBuffer;
svcGetSystemInfo(&out, 26, 0);
nbSection0Modules = out;
assertSuccess(svcCreateSemaphore(&resumeGetServiceHandleOrPortRegisteredSemaphore, 0, 64));
serviceAccessListBuffers = (char (*)[34][8])__ctru_heap;
buildList(&freeSessionDataList, sessionDataPool, sizeof(sessionDataPool) / sizeof(SessionData), sizeof(SessionData));
buildList(&freeProcessDataList, processDataPool, sizeof(processDataPool) / sizeof(ProcessData), sizeof(ProcessData));
}
// this is called after main exits
void __appExit(void){}
void __system_allocateHeaps(void)
{
u32 tmp = 0;
// Allocate the application heap
__ctru_heap = 0x08000000;
svcControlMemory(&tmp, __ctru_heap, 0x0, __ctru_heap_size, MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE);
__ctru_linear_heap = 0;
}
void __system_initSyscalls(void){}
Result __sync_init(void);
Result __sync_fini(void);
void __ctru_exit(void){}
void initSystem(void)
{
__sync_init();
__system_allocateHeaps();
__appInit();
}
int main(void)
{
Result res;
u32 *cmdbuf = getThreadCommandBuffer();
u32 nbHandles = 3, nbSessions = 0, nbSrvPmSessions = 0;
Handle clientPortDummy;
Handle srvPort, srvPmPort;
Handle handles[0xE3] = { 0 };
Handle replyTarget = 0;
u32 smPid;
SessionData *sessionData;
assertSuccess(svcGetProcessId(&smPid, CUR_PROCESS_HANDLE));
assertSuccess(svcCreatePort(&srvPort, &clientPortDummy, "srv:", 64));
if(IS_PRE_7X)
assertSuccess(svcCreatePort(&srvPmPort, &clientPortDummy, "srv:pm", 64));
else
assertSuccess(doRegisterService(smPid, &srvPmPort, "srv:pm", 6, 64));
handles[0] = resumeGetServiceHandleOrPortRegisteredSemaphore;
handles[1] = srvPort;
handles[2] = srvPmPort;
for(;;)
{
s32 id;
if(replyTarget == 0)
cmdbuf[0] = 0xFFFF0000; // Kernel11
res = svcReplyAndReceive(&id, handles, nbHandles, replyTarget);
if(res == (Result)0xC920181A) // unreachable remote
{
// Note: if a process has ended, pm will call UnregisterProcess on it
if(id < 0)
{
for(id = 3; (u32)id < nbHandles && handles[id] != replyTarget; id++);
if((u32)id >= nbHandles)
panic();
}
if(id < 3)
panic();
else if((u32)id < 3 + nbSessions) // Session closed
{
sessionData = NULL;
for(u32 i = 0; i < sizeof(sessionDataPool) / sizeof(SessionData); i++)
{
if(sessionDataPool[i].handle == handles[id])
sessionData = &sessionDataPool[i];
}
if(sessionData != NULL)
{
if(sessionData->busyClientPortHandle != 0) // remove unreferenced client port handles from array
{
u32 refCount = 0;
for(u32 i = 0; i < sizeof(sessionDataPool) / sizeof(SessionData); i++)
{
if(sessionDataPool[i].busyClientPortHandle == sessionData->busyClientPortHandle)
++refCount;
}
if(refCount <= 1)
{
u32 i;
for(i = 3 + nbSessions; i < nbHandles && handles[i] == sessionData->busyClientPortHandle; i++);
if(i < nbHandles)
handles[i] = handles[--nbHandles];
}
}
--nbHandles;
for(u32 i = (u32)id; i < nbHandles; i++)
handles[i] = handles[i + 1];
--nbSessions;
svcCloseHandle(sessionData->handle);
if(sessionData->isSrvPm)
--nbSrvPmSessions;
moveNode(sessionData, &freeSessionDataList, false);
}
else
panic();
}
else // Port closed
{
SessionData *nextSessionData = NULL;
// Update the command postponing reason accordingly
for(sessionData = sessionDataWaitingPortReadyList.first; sessionData != NULL; sessionData = nextSessionData)
{
nextSessionData = sessionData->next;
if(sessionData->busyClientPortHandle == handles[id])
{
sessionData->replayCmdbuf[1] = 0xD0406401; // unregistered service or named port
moveNode(sessionData, &sessionDataWaitingForServiceOrPortRegisterList, true);
svcCloseHandle(handles[id]);
handles[id] = handles[--nbHandles];
sessionData->busyClientPortHandle = 0;
}
}
}
replyTarget = 0;
}
else if(R_FAILED(res))
panic();
else
{
replyTarget = 0;
if(id == 1) // New srv: session
{
Handle session;
assertSuccess(svcAcceptSession(&session, srvPort));
sessionData = (SessionData *)allocateNode(&sessionDataInUseList, &freeSessionDataList, sizeof(SessionData), false);
sessionData->pid = (u32)-1;
sessionData->handle = session;
for(u32 i = nbHandles; i > 3 + nbSessions; i--)
handles[i] = handles[i - 1];
handles[3 + nbSessions++] = session;
++nbHandles;
}
else if(id == 2) // New srv:pm session
{
Handle session;
if(!IS_PRE_7X && nbSrvPmSessions >= 1)
panic();
assertSuccess(svcAcceptSession(&session, srvPmPort));
sessionData = (SessionData *)allocateNode(&sessionDataInUseList, &freeSessionDataList, sizeof(SessionData), false);
sessionData->pid = (u32)-1;
sessionData->handle = session;
sessionData->isSrvPm = true;
for(u32 i = nbHandles; i > 3 + nbSessions; i--)
handles[i] = handles[i - 1];
handles[3 + nbSessions++] = session;
++nbHandles;
++nbSrvPmSessions;
}
else
{
if(id == 0) // Resume SRV:GetServiceHandle or GetPort due to service or named port not registered
{
if(sessionDataToWakeUpAfterServiceOrPortRegisterList.first == NULL)
panic();
sessionData = sessionDataToWakeUpAfterServiceOrPortRegisterList.first;
moveNode(sessionData, &sessionDataInUseList, false);
for(u32 i = nbHandles; i > 3 + nbSessions; i--)
handles[i] = handles[i - 1];
handles[3 + nbSessions++] = sessionData->handle;
++nbHandles;
if(sessionData->isSrvPm)
++nbSrvPmSessions;
memcpy(cmdbuf, sessionData->replayCmdbuf, 16);
}
else if((u32)id >= 3 + nbSessions) // Resume SRV:GetServiceHandle if service was full
{
SessionData *sTmp;
for(sessionData = sessionDataWaitingPortReadyList.first; sessionData != NULL && sessionData->busyClientPortHandle != handles[id];
sessionData = sessionData->next);
if(sessionData == NULL)
panic();
moveNode(sessionData, &sessionDataInUseList, false);
for(sTmp = sessionDataWaitingPortReadyList.first; sTmp != NULL && sTmp->busyClientPortHandle != handles[id]; sTmp = sTmp->next);
if(sTmp == NULL)
handles[id] = handles[--nbHandles];
for(u32 i = nbHandles + 1; i > 3 + nbSessions; i--)
handles[i] = handles[i - 1];
handles[3 + nbSessions++] = sessionData->handle;
++nbHandles;
if(sessionData->isSrvPm)
++nbSrvPmSessions;
memcpy(cmdbuf, sessionData->replayCmdbuf, 16);
sessionData->busyClientPortHandle = 0;
}
else
{
for(sessionData = sessionDataInUseList.first; sessionData != NULL && sessionData->handle != handles[id]; sessionData = sessionData->next);
if(sessionData == NULL)
panic();
}
for(id = 3; (u32)id < 3 + nbSessions && handles[id] != sessionData->handle; id++);
if((u32)id >= 3 + nbSessions)
panic();
res = sessionData->isSrvPm ? srvPmHandleCommands(sessionData) : srvHandleCommands(sessionData);
if(R_MODULE(res) == RM_SRV && R_SUMMARY(res) == RS_WOULDBLOCK)
{
SessionDataList *dstList = NULL;
if(res == (Result)0xD0406401) // service or named port not registered yet
dstList = &sessionDataWaitingForServiceOrPortRegisterList;
else if(res == (Result)0xD0406402) // service full
{
u32 i;
dstList = &sessionDataWaitingPortReadyList;
for(i = 3 + nbSessions; i < nbHandles && handles[i] != sessionData->busyClientPortHandle; i++);
if(i >= nbHandles)
handles[nbHandles++] = sessionData->busyClientPortHandle;
}
else
panic();
--nbHandles;
for(u32 i = (u32)id; i < nbHandles; i++)
handles[i] = handles[i + 1];
--nbSessions;
if(sessionData->isSrvPm)
--nbSrvPmSessions;
moveNode(sessionData, dstList, true);
}
else
replyTarget = sessionData->handle;
}
}
}
return 0;
}

View File

@@ -0,0 +1,27 @@
/*
memory.c
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "memory.h"
/*
//Adpated from CakesFW
void memcpy(void *dest, const void *src, u32 size)
{
u8 *destc = (u8 *)dest;
const u8 *srcc = (const u8 *)src;
for(u32 i = 0; i < size; i++)
destc[i] = srcc[i];
}*/
s32 strnlen(const char *string, s32 maxlen)
{
s32 size;
for(size = 0; size < maxlen && *string; string++, size++);
return size;
}

View File

@@ -0,0 +1,18 @@
/*
memory.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include <3ds/types.h>
//void memcpy(void *dest, const void *src, u32 size);
#define memcpy __builtin_memcpy
#define memset __builtin_memset
#define strncmp __builtin_strncmp
#define strncpy __builtin_strncpy
s32 strnlen(const char *string, s32 maxlen);

View File

@@ -0,0 +1,191 @@
/*
notifications.c
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "notifications.h"
#include "processes.h"
static bool doPublishNotification(ProcessData *processData, u32 notificationId, u32 flags)
{
if((flags & 1) && processData->nbPendingNotifications != 0) // only send if not already pending
{
for(u16 i = 0; i < processData->nbPendingNotifications; i++)
{
if(processData->pendingNotifications[(processData->pendingNotificationIndex + i) % 16] == notificationId)
return true;
}
}
if(processData->nbPendingNotifications < 0x10)
{
s32 count;
processData->pendingNotifications[processData->pendingNotificationIndex] = notificationId;
processData->pendingNotificationIndex = (processData->pendingNotificationIndex + 1) % 16;
++processData->nbPendingNotifications;
assertSuccess(svcReleaseSemaphore(&count, processData->notificationSemaphore, 1));
return true;
}
else
return (flags & 2) != 0;
}
Result EnableNotification(SessionData *sessionData, Handle *notificationSemaphore)
{
ProcessData *processData = findProcessData(sessionData->pid);
if(processData == NULL)
{
// Section 0 modules have access to all services, so we need to register them here for notifications
if(sessionData->pid < nbSection0Modules)
processData = doRegisterProcess(sessionData->pid, NULL, 0);
else
return 0xD8806404;
}
processData->notificationEnabled = true;
*notificationSemaphore = processData->notificationSemaphore;
return 0;
}
Result Subscribe(SessionData *sessionData, u32 notificationId)
{
ProcessData *processData = findProcessData(sessionData->pid);
if(processData == NULL || !processData->notificationEnabled)
return 0xD8806404;
for(u16 i = 0; i < processData->nbSubscribed; i++)
{
if(processData->subscribedNotifications[i] == notificationId)
return 0xD9006403;
}
if(processData->nbSubscribed < 0x11)
{
processData->subscribedNotifications[processData->nbSubscribed++] = notificationId;
return 0;
}
else
return 0xD9006405;
}
Result Unsubscribe(SessionData *sessionData, u32 notificationId)
{
ProcessData *processData = findProcessData(sessionData->pid);
if(processData == NULL || !processData->notificationEnabled)
return 0xD8806404;
u16 i;
for(i = 0; i < processData->nbSubscribed && processData->subscribedNotifications[i] != notificationId; i++);
if(i == processData->nbSubscribed)
return 0xD8806404;
else
{
processData->subscribedNotifications[i] = processData->subscribedNotifications[--processData->nbSubscribed];
return 0;
}
}
Result ReceiveNotification(SessionData *sessionData, u32 *notificationId)
{
ProcessData *processData = findProcessData(sessionData->pid);
if(processData == NULL || !processData->notificationEnabled || processData->nbPendingNotifications == 0)
{
if(processData->nbPendingNotifications)
*notificationId = 0;
return 0xD8806404;
}
else
{
--processData->nbPendingNotifications;
*notificationId = processData->pendingNotifications[processData->receivedNotificationIndex];
processData->receivedNotificationIndex = (processData->receivedNotificationIndex + 1) % 16;
return 0;
}
}
Result PublishToSubscriber(u32 notificationId, u32 flags)
{
for(ProcessData *node = processDataInUseList.first; node != NULL; node = node->next)
{
if(!node->notificationEnabled)
continue;
u16 i;
for(i = 0; i < node->nbSubscribed && node->subscribedNotifications[i] != notificationId; i++);
if(i >= node->nbSubscribed)
continue;
if(!doPublishNotification(node, notificationId, flags))
return 0xD8606408;
}
return 0;
}
Result PublishAndGetSubscriber(u32 *pidCount, u32 *pidList, u32 notificationId, u32 flags)
{
u32 nb = 0;
for(ProcessData *node = processDataInUseList.first; node != NULL; node = node->next)
{
if(!node->notificationEnabled)
continue;
u16 i;
for(i = 0; i < node->nbSubscribed && node->subscribedNotifications[i] != notificationId; i++);
if(i >= node->nbSubscribed)
continue;
if(!doPublishNotification(node, notificationId, flags))
return 0xD8606408;
else if(pidList != NULL && nb < 60)
pidList[nb++] = node->pid;
}
if(pidCount != NULL)
*pidCount = nb;
return 0;
}
Result PublishToProcess(Handle process, u32 notificationId)
{
u32 pid;
Result res = svcGetProcessId(&pid, process);
if(R_FAILED(res))
return res;
ProcessData *processData = findProcessData(pid);
if(processData == NULL || !processData->notificationEnabled)
res = 0xD8806404;
else if(!doPublishNotification(processData, notificationId, 0))
res = 0xD8606408;
else
res = 0;
svcCloseHandle(process);
return res;
}
Result PublishToAll(u32 notificationId)
{
for(ProcessData *node = processDataInUseList.first; node != NULL; node = node->next)
{
if(!node->notificationEnabled)
continue;
else if(!doPublishNotification(node, notificationId, 0))
return 0xD8606408;
}
return 0;
}

View File

@@ -0,0 +1,19 @@
/*
notifications.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include "common.h"
Result EnableNotification(SessionData *sessionData, Handle *notificationSemaphore);
Result Subscribe(SessionData *sessionData, u32 notificationId);
Result Unsubscribe(SessionData *sessionData, u32 notificationId);
Result ReceiveNotification(SessionData *sessionData, u32 *notificationId);
Result PublishToSubscriber(u32 notificationId, u32 flags);
Result PublishAndGetSubscriber(u32 *pidCount, u32 *pidList, u32 notificationId, u32 flags);
Result PublishToProcess(Handle process, u32 notificationId);
Result PublishToAll(u32 notificationId);

View File

@@ -0,0 +1,88 @@
/*
processes.c
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "list.h"
#include "processes.h"
#include "services.h"
ProcessDataList processDataInUseList = { NULL, NULL }, freeProcessDataList = { NULL, NULL };
char (*serviceAccessListBuffers)[34][8];
// The kernel limits the number of processes to 47 anyways...
static u64 freeServiceAccessListBuffersIds = (1ULL << 59) - 1;
ProcessData *findProcessData(u32 pid)
{
for(ProcessData *node = processDataInUseList.first; node != NULL; node = node->next)
{
if(node->pid == pid)
return node;
}
return NULL;
}
ProcessData *doRegisterProcess(u32 pid, char (*serviceAccessList)[8], u32 serviceAccessListSize)
{
ProcessData *processData = (ProcessData *)allocateNode(&processDataInUseList, &freeProcessDataList, sizeof(ProcessData), false);
if(serviceAccessListSize != 0)
{
s32 bufferId = 63 - __builtin_clzll(freeServiceAccessListBuffersIds);
if(bufferId == -1)
panic();
else
{
freeServiceAccessListBuffersIds &= ~(1ULL << bufferId);
processData->serviceAccessList = serviceAccessListBuffers[bufferId];
processData->serviceAccessListSize = serviceAccessListSize;
memcpy(processData->serviceAccessList, serviceAccessList, serviceAccessListSize);
}
}
assertSuccess(svcCreateSemaphore(&processData->notificationSemaphore, 0, 0x10));
processData->pid = pid;
return processData;
}
Result RegisterProcess(u32 pid, char (*serviceAccessList)[8], u32 serviceAccessListSize)
{
serviceAccessListSize = serviceAccessListSize - (serviceAccessListSize % 8);
if(findProcessData(pid) != NULL)
return 0xD9006403;
else if(serviceAccessListSize > 8 * (IS_PRE_93 ? 32 : 34))
return 0xD9006405;
else
{
doRegisterProcess(pid, serviceAccessList, serviceAccessListSize);
return 0;
}
}
Result UnregisterProcess(u32 pid)
{
ProcessData *processData = findProcessData(pid);
if(processData == NULL)
return 0xD8806404;
svcCloseHandle(processData->notificationSemaphore);
// Unregister the services registered by the process
for(u32 i = 0; i < nbServices; i++)
{
if(servicesInfo[i].pid == pid)
{
svcCloseHandle(servicesInfo[i].clientPort);
servicesInfo[i] = servicesInfo[--nbServices];
}
}
freeServiceAccessListBuffersIds |= 1ULL << (u32)((processData->serviceAccessList - serviceAccessListBuffers[0]) / 34);
moveNode(processData, &freeProcessDataList, false);
return 0;
}

View File

@@ -0,0 +1,48 @@
/*
processes.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include "common.h"
struct ProcessDataList;
typedef struct ProcessData
{
struct ProcessData *prev, *next;
struct ProcessDataList *parent;
u32 pid;
Handle notificationSemaphore;
char (*serviceAccessList)[8];
u32 serviceAccessListSize;
bool notificationEnabled;
// Circular buffer
u16 receivedNotificationIndex;
u16 pendingNotificationIndex;
u16 nbPendingNotifications;
u32 pendingNotifications[16];
u16 nbSubscribed;
u32 subscribedNotifications[17];
} ProcessData;
typedef struct ProcessDataList
{
ProcessData *first, *last;
} ProcessDataList;
extern ProcessDataList processDataInUseList, freeProcessDataList;
ProcessData *findProcessData(u32 pid);
ProcessData *doRegisterProcess(u32 pid, char (*serviceAccessList)[8], u32 serviceAccessListSize);
Result RegisterProcess(u32 pid, char (*serviceAccessList)[8], u32 serviceAccessListSize);
Result UnregisterProcess(u32 pid);

View File

@@ -0,0 +1,217 @@
/*
services.c
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "services.h"
#include "processes.h"
#include "memory.h"
#include "list.h"
ServiceInfo servicesInfo[0xA0] = { 0 };
u32 nbServices = 0; // including "ports" registered with getPort
static Result checkServiceName(const char *name, s32 nameSize)
{
if(nameSize <= 0 || nameSize > 8)
return 0xD9006405;
else if(strnlen(name, nameSize) < nameSize)
return 0xD9006407;
else
return 0;
}
static inline bool areServiceNamesEqual(const char *name, const char *name2, s32 nameSize)
{
return strncmp(name, name2, nameSize) == 0 && (nameSize == 8 || name[nameSize] == 0);
}
static s32 findServicePortByName(bool isNamedPort, const char *name, s32 nameSize)
{
ServiceInfo *info;
for(info = servicesInfo; info < servicesInfo + nbServices && (info->isNamedPort != isNamedPort || !areServiceNamesEqual(info->name, name, nameSize)); info++);
return info >= servicesInfo + nbServices ? -1 : info - servicesInfo;
}
static bool checkServiceAccess(SessionData *sessionData, const char *name, s32 nameSize)
{
if(sessionData->pid < nbSection0Modules)
return true;
ProcessData *processData = findProcessData(sessionData->pid);
if(processData == NULL)
return false;
for(u32 i = 0; i < processData->serviceAccessListSize; i++)
{
if(areServiceNamesEqual(processData->serviceAccessList[i], name, nameSize))
return true;
}
return false;
}
static Result doRegisterServiceOrPort(u32 pid, Handle *serverPort, Handle clientPort, const char *name, s32 nameSize, s32 maxSessions, bool isNamedPort)
{
Result res = checkServiceName(name, nameSize);
Handle portServer, portClient;
if(R_FAILED(res))
return res;
else if(findServicePortByName(isNamedPort, name, nameSize) != -1)
return 0xD9001BFC;
if(nbServices >= 0xA0)
return 0xD86067F3;
if(!isNamedPort)
{
res = svcCreatePort(&portServer, &portClient, NULL, maxSessions);
if(R_FAILED(res))
return 0xD9001BFC;
}
else
portClient = clientPort;
ServiceInfo *serviceInfo = &servicesInfo[nbServices++];
strncpy(serviceInfo->name, name, 8);
serviceInfo->pid = pid;
serviceInfo->clientPort = portClient;
serviceInfo->isNamedPort = isNamedPort;
SessionData *nextSessionData;
s32 n = 0;
for(SessionData *node = sessionDataWaitingForServiceOrPortRegisterList.first; node != NULL; node = nextSessionData)
{
nextSessionData = node->next;
if((node->replayCmdbuf[0] & 0xF0000) == (!isNamedPort ? 0x50000 : 0x80000) &&
areServiceNamesEqual((const char *)(node->replayCmdbuf + 1), name, (s32)node->replayCmdbuf[3]))
{
moveNode(node, &sessionDataToWakeUpAfterServiceOrPortRegisterList, true);
++n;
}
}
if(n > 0)
{
s32 count;
assertSuccess(svcReleaseSemaphore(&count, resumeGetServiceHandleOrPortRegisteredSemaphore, n));
}
if(!isNamedPort)
*serverPort = portServer;
return res;
}
Result doRegisterService(u32 pid, Handle *serverPort, const char *name, s32 nameSize, s32 maxSessions)
{
return doRegisterServiceOrPort(pid, serverPort, 0, name, nameSize, maxSessions, false);
}
Result RegisterService(SessionData *sessionData, Handle *serverPort, const char *name, s32 nameSize, s32 maxSessions)
{
return doRegisterService(sessionData->pid, serverPort, name, nameSize, maxSessions);
}
Result RegisterPort(SessionData *sessionData, Handle clientPort, const char *name, s32 nameSize)
{
return doRegisterServiceOrPort(sessionData->pid, NULL, clientPort, name, nameSize, -1, true);
}
static Result UnregisterServiceOrPort(SessionData *sessionData, const char *name, s32 nameSize, bool isNamedPort)
{
Result res = checkServiceName(name, nameSize);
s32 serviceId;
if(R_FAILED(res))
return res;
serviceId = findServicePortByName(isNamedPort, name, nameSize);
if(serviceId == -1)
return 0xD8801BFA;
else if(servicesInfo[serviceId].pid != sessionData->pid)
return 0xD8E06406;
else
{
svcCloseHandle(servicesInfo[serviceId].clientPort);
servicesInfo[serviceId] = servicesInfo[--nbServices];
return 0;
}
}
Result UnregisterService(SessionData *sessionData, const char *name, s32 nameSize)
{
return UnregisterServiceOrPort(sessionData, name, nameSize, false);
}
Result UnregisterPort(SessionData *sessionData, const char *name, s32 nameSize)
{
return UnregisterServiceOrPort(sessionData, name, nameSize, true);
}
Result IsServiceRegistered(SessionData *sessionData, bool *isRegistered, const char *name, s32 nameSize)
{
Result res = checkServiceName(name, nameSize);
if(R_FAILED(res))
return res;
else if(!checkServiceAccess(sessionData, name, nameSize))
return 0xD8E06406;
else
{
*isRegistered = findServicePortByName(false, name, nameSize) != -1;
return 0;
}
}
Result GetServiceHandle(SessionData *sessionData, Handle *session, const char *name, s32 nameSize, u32 flags)
{
Result res = checkServiceName(name, nameSize);
s32 serviceId;
if(R_FAILED(res))
return res;
else if(!checkServiceAccess(sessionData, name, nameSize))
return 0xD8E06406;
serviceId = findServicePortByName(false, name, nameSize);
if(serviceId == -1)
return 0xD0406401;
else
{
Handle port = servicesInfo[serviceId].clientPort;
res = svcCreateSessionToPort(session, port);
if(res == (Result)0xD0401834 && !(flags & 1))
{
sessionData->busyClientPortHandle = port;
return 0xD0406402;
}
return 0;
}
}
Result GetPort(SessionData *sessionData, Handle *port, const char *name, s32 nameSize, u8 flags)
{
Result res = checkServiceName(name, nameSize);
s32 serviceId;
if(R_FAILED(res))
return res;
else if(!checkServiceAccess(sessionData, name, nameSize))
return 0xD8E06406;
serviceId = findServicePortByName(true, name, nameSize);
if(serviceId == -1)
return flags != 0 ? 0xD0406401 : 0xD8801BFA;
else if(flags == 0)
*port = servicesInfo[serviceId].clientPort;
return 0;
}

View File

@@ -0,0 +1,32 @@
/*
services.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include <3ds.h>
#include "common.h"
typedef struct ServiceInfo
{
char name[8];
Handle clientPort;
u32 pid;
bool isNamedPort;
} ServiceInfo;
extern ServiceInfo servicesInfo[0xA0];
extern u32 nbServices;
Result doRegisterService(u32 pid, Handle *serverPort, const char *name, s32 nameSize, s32 maxSessions);
Result RegisterService(SessionData *sessionData, Handle *serverPort, const char *name, s32 nameSize, s32 maxSessions);
Result RegisterPort(SessionData *sessionData, Handle clientPort, const char *name, s32 nameSize);
Result UnregisterService(SessionData *sessionData, const char *name, s32 nameSize);
Result UnregisterPort(SessionData *sessionData, const char *name, s32 nameSize);
Result IsServiceRegistered(SessionData *SessionData, bool *isRegistered, const char *name, s32 nameSize);
Result GetServiceHandle(SessionData *sessionData, Handle *session, const char *name, s32 nameSize, u32 flags);
Result GetPort(SessionData *sessionData, Handle *port, const char *name, s32 nameSize, u8 flags);

181
sysmodules/sm/source/srv.c Normal file
View File

@@ -0,0 +1,181 @@
/*
srv.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "srv.h"
#include "services.h"
#include "notifications.h"
#include "processes.h"
Result srvHandleCommands(SessionData *sessionData)
{
Result res = 0;
u32 *cmdbuf = getThreadCommandBuffer();
switch(cmdbuf[0] >> 16)
{
case 1: // RegisterClient
{
if(cmdbuf[0] == IPC_MakeHeader(1, 0, 2) && cmdbuf[1] == IPC_Desc_CurProcessHandle())
{
sessionData->pid = cmdbuf[2];
cmdbuf[0] = IPC_MakeHeader(1, 1, 0);
cmdbuf[1] = 0;
}
else
goto invalid_command;
break;
}
case 2: // EnableNotification
{
Handle notificationSemaphore = 0;
res = EnableNotification(sessionData, &notificationSemaphore);
cmdbuf[0] = IPC_MakeHeader(2, 1, 2);
cmdbuf[1] = (u32)res;
cmdbuf[2] = IPC_Desc_SharedHandles(1);
cmdbuf[3] = notificationSemaphore;
break;
}
case 3: // RegisterService
{
Handle serverPort = 0;
res = RegisterService(sessionData, &serverPort, (const char *)(cmdbuf + 1), (s32)cmdbuf[3], (s32)cmdbuf[4]);
cmdbuf[0] = IPC_MakeHeader(3, 1, 2);
cmdbuf[1] = (u32)res;
cmdbuf[2] = IPC_Desc_MoveHandles(1);
cmdbuf[3] = serverPort;
break;
}
case 4: // UnregisterService
{
res = UnregisterService(sessionData, (const char *)(cmdbuf + 1), (s32)cmdbuf[3]);
cmdbuf[0] = IPC_MakeHeader(4, 1, 0);
cmdbuf[1] = (u32)res;
break;
}
case 5: // GetServiceHandle
{
Handle session = 0;
res = GetServiceHandle(sessionData, &session, (const char *)(cmdbuf + 1), (s32)cmdbuf[3], cmdbuf[4]);
if(R_MODULE(res) == RM_SRV && R_SUMMARY(res) == RS_WOULDBLOCK)
memcpy(sessionData->replayCmdbuf, cmdbuf, 16);
else
{
cmdbuf[0] = IPC_MakeHeader(5, 1, 2);
cmdbuf[1] = (u32)res;
cmdbuf[2] = IPC_Desc_MoveHandles(1);
cmdbuf[3] = session;
}
break;
}
case 6: // RegisterPort
{
if(cmdbuf[0] == IPC_MakeHeader(6, 3, 2) && cmdbuf[4] == IPC_Desc_SharedHandles(1))
{
res = RegisterPort(sessionData, (Handle)cmdbuf[5], (const char *)(cmdbuf + 1), (s32)cmdbuf[3]);
cmdbuf[0] = IPC_MakeHeader(6, 1, 0);
cmdbuf[1] = (u32)res;
}
else
goto invalid_command;
break;
}
case 7: // UnregisterPort
{
res = UnregisterPort(sessionData, (const char *)(cmdbuf + 1), (s32)cmdbuf[3]);
cmdbuf[0] = IPC_MakeHeader(7, 1, 0);
cmdbuf[1] = (u32)res;
break;
}
case 8: // GetPort
{
Handle port = 0;
res = GetPort(sessionData, &port, (const char *)(cmdbuf + 1), (s32)cmdbuf[3], (u8)cmdbuf[4]);
if(R_MODULE(res) == RM_SRV && R_SUMMARY(res) == RS_WOULDBLOCK)
memcpy(sessionData->replayCmdbuf, cmdbuf, 16);
else
{
cmdbuf[0] = IPC_MakeHeader(8, 1, 2);
cmdbuf[1] = (u32)res;
cmdbuf[2] = IPC_Desc_SharedHandles(1);
cmdbuf[3] = port;
}
break;
}
case 9: // Subscribe
{
res = Subscribe(sessionData, cmdbuf[1]);
cmdbuf[0] = IPC_MakeHeader(9, 1, 0);
cmdbuf[1] = (u32)res;
break;
}
case 10: // Unsubscribe
{
res = Unsubscribe(sessionData, cmdbuf[1]);
cmdbuf[0] = IPC_MakeHeader(10, 1, 0);
cmdbuf[1] = (u32)res;
break;
}
case 11: // ReceiveNotification
{
u32 notificationId;
res = ReceiveNotification(sessionData, &notificationId);
cmdbuf[0] = IPC_MakeHeader(11, 2, 0);;
cmdbuf[1] = (u32)res;
cmdbuf[2] = notificationId;
break;
}
case 12: // PublishToSubscriber
{
res = PublishToSubscriber(cmdbuf[1], cmdbuf[2]);
cmdbuf[0] = IPC_MakeHeader(12, 1, 0);;
cmdbuf[1] = (u32)res;
break;
}
case 13: // PublishAndGetSubscriber
{
u32 pidCount;
res = PublishAndGetSubscriber(&pidCount, cmdbuf + 3, cmdbuf[1], 0);
cmdbuf[0] = IPC_MakeHeader(13, 62, 0);
cmdbuf[1] = (u32)res;
cmdbuf[2] = pidCount;
break;
}
case 14: // IsServiceRegistered
{
bool isRegistered;
res = IsServiceRegistered(sessionData, &isRegistered, (const char *)(cmdbuf + 1), (s32)cmdbuf[3]);
cmdbuf[0] = IPC_MakeHeader(14, 2, 0);;
cmdbuf[1] = (u32)res;
cmdbuf[2] = isRegistered ? 1 : 0;
break;
}
default:
goto invalid_command;
break;
}
return res;
invalid_command:
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);;
cmdbuf[1] = (u32)0xD9001830;
return 0xD9001830;
}

View File

@@ -0,0 +1,12 @@
/*
srv.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include "common.h"
Result srvHandleCommands(SessionData *sessionData);

View File

@@ -0,0 +1,81 @@
/*
srv_pm.c
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#include "srv_pm.h"
#include "srv.h"
#include "services.h"
#include "notifications.h"
#include "processes.h"
Result srvPmHandleCommands(SessionData *sessionData)
{
Result res = 0;
u32 *cmdbuf = getThreadCommandBuffer();
u32 mask = (cmdbuf[0] & 0x04000000) >> 16;
if(IS_PRE_7X && mask == 0)
return srvHandleCommands(sessionData);
else if(!IS_PRE_7X && mask != 0)
goto invalid_command;
switch((cmdbuf[0] >> 16) & ~mask)
{
case 1: // PublishToProcess
{
if(cmdbuf[0] == IPC_MakeHeader(mask | 1, 1, 2) && cmdbuf[2] == IPC_Desc_SharedHandles(1))
{
res = PublishToProcess((Handle)cmdbuf[3], cmdbuf[1]);
cmdbuf[0] = IPC_MakeHeader(mask | 1, 1, 0);
cmdbuf[1] = (u32)res;
}
else
goto invalid_command;
break;
}
case 2: // PublishToAll
{
res = PublishToAll(cmdbuf[1]);
cmdbuf[0] = IPC_MakeHeader(mask | 2, 1, 0);
cmdbuf[1] = (u32)res;
break;
}
case 3: // RegisterProcess
{
if(cmdbuf[0] == IPC_MakeHeader(mask | 3, 2, 2) && (cmdbuf[3] & 0x3C0F) == (IPC_Desc_StaticBuffer(0x110, 0) & 0x3C0F))
{
res = RegisterProcess(cmdbuf[1], (char (*)[8])cmdbuf[4], cmdbuf[3] >> 14);
cmdbuf[0] = IPC_MakeHeader(mask | 3, 1, 0);
cmdbuf[1] = (u32)res;
}
else
goto invalid_command;
break;
}
case 4:
{
res = UnregisterProcess(cmdbuf[1]);
cmdbuf[0] = IPC_MakeHeader(mask | 4, 1, 0);
cmdbuf[1] = (u32)res;
break;
}
default:
goto invalid_command;
break;
}
return res;
invalid_command:
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);;
cmdbuf[1] = (u32)0xD900182F;
return 0xD900182F;
}

View File

@@ -0,0 +1,12 @@
/*
srv_pm.h
(c) TuxSH, 2017
This is part of 3ds_sm, which is licensed under the MIT license (see LICENSE for details).
*/
#pragma once
#include "common.h"
Result srvPmHandleCommands(SessionData *sessionData);