1
0
Fork 0
mirror of https://github.com/Atmosphere-NX/Atmosphere.git synced 2024-12-18 16:32:05 +00:00

fs.mitm: Implement basic passthrough framework for input commands.

This commit is contained in:
Michael Scire 2018-06-09 19:33:22 -06:00
parent 28d630a23e
commit c1c211f542
13 changed files with 855 additions and 0 deletions

View file

@ -0,0 +1,153 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_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
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
DATA := data
INCLUDES := include
EXEFS_SRC := exefs_src
DEFINES := -DDISABLE_IPC
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lstratosphere -lnx
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere
#---------------------------------------------------------------------------------
# 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 := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
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)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.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).kip $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).kip
$(OUTPUT).kip : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View file

@ -0,0 +1,77 @@
{
"name" : "fs.mitm",
"title_id" : "0x010041544D530000",
"main_thread_stack_size" : "0x8000",
"main_thread_priority": 44,
"default_cpu_id": 3,
"process_category" : 1,
"kernel_capabilities" : {
"handle_table_size" : 256,
"syscalls": {
"svcSetHeapSize": "0x01",
"svcSetMemoryPermission": "0x02",
"svcSetMemoryAttribute": "0x03",
"svcMapMemory": "0x04",
"svcUnmapMemory": "0x05",
"svcQueryMemory": "0x06",
"svcExitProcess": "0x07",
"svcCreateThread": "0x08",
"svcStartThread": "0x09",
"svcExitThread": "0x0a",
"svcSleepThread": "0x0b",
"svcGetThreadPriority": "0x0c",
"svcSetThreadPriority": "0x0d",
"svcGetThreadCoreMask": "0x0e",
"svcSetThreadCoreMask": "0x0f",
"svcGetCurrentProcessorNumber": "0x10",
"svcSignalEvent": "0x11",
"svcClearEvent": "0x12",
"svcMapSharedMemory": "0x13",
"svcUnmapSharedMemory": "0x14",
"svcCreateTransferMemory": "0x15",
"svcCloseHandle": "0x16",
"svcResetSignal": "0x17",
"svcWaitSynchronization": "0x18",
"svcCancelSynchronization": "0x19",
"svcArbitrateLock": "0x1a",
"svcArbitrateUnlock": "0x1b",
"svcWaitProcessWideKeyAtomic": "0x1c",
"svcSignalProcessWideKey": "0x1d",
"svcGetSystemTick": "0x1e",
"svcConnectToNamedPort": "0x1f",
"svcSendSyncRequestLight": "0x20",
"svcSendSyncRequest": "0x21",
"svcSendSyncRequestWithUserBuffer": "0x22",
"svcSendAsyncRequestWithUserBuffer": "0x23",
"svcGetProcessId": "0x24",
"svcGetThreadId": "0x25",
"svcBreak": "0x26",
"svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28",
"svcGetInfo": "0x29",
"svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35",
"svcCreateSession": "0x40",
"svcAcceptSession": "0x41",
"svcReplyAndReceiveLight": "0x42",
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcReadWriteRegister": "0x4E",
"svcCreateInterruptEvent": "0x53",
"svcQueryIoMapping": "0x55",
"svcCreateDeviceAddressSpace": "0x56",
"svcAttachDeviceAddressSpace": "0x57",
"svcDetachDeviceAddressSpace": "0x58",
"svcMapDeviceAddressSpaceAligned": "0x5a",
"svcUnmapDeviceAddressSpace": "0x5c",
"svcGetSystemInfo": "0x6f"
},
"map" : {
"address" : "0x40000000",
"size" : "0x40000",
"is_ro" : false,
"is_io" : true
}
}
}

View file

@ -0,0 +1,17 @@
#include <switch.h>
#include <cstring>
#include "debug.hpp"
static u64 g_num_logged = 0;
#define MAX_LOGS U64_MAX
void Reboot() {
while (1) {
/* ... */
}
}
void Log(const void *data, int size) {
/* ... */
}

View file

@ -0,0 +1,4 @@
#pragma once
void Reboot();
void Log(const void *data, int size);

View file

@ -0,0 +1,104 @@
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <stratosphere.hpp>
#include "sm_mitm.h"
#include "mitm_server.hpp"
#include "fsmitm_service.hpp"
extern "C" {
extern u32 __start__;
u32 __nx_applet_type = AppletType_None;
#define INNER_HEAP_SIZE 0x1000000
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void);
void __appInit(void);
void __appExit(void);
}
void __libnx_initheap(void) {
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void) {
Result rc;
rc = smInitialize();
if (R_FAILED(rc)) {
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
}
rc = smMitMInitialize();
if (R_FAILED(rc)) {
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
}
rc = fsInitialize();
if (R_FAILED(rc)) {
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_FS));
}
rc = splInitialize();
if (R_FAILED(rc)) {
fatalSimple(0xCAFE << 4 | 3);
}
/* Check for exosphere API compatibility. */
u64 exosphere_cfg;
if (R_SUCCEEDED(splGetConfig((SplConfigItem)65000, &exosphere_cfg))) {
/* MitM requires Atmosphere API 0.1. */
u16 api_version = (exosphere_cfg >> 16) & 0xFFFF;
if (api_version < 0x0001) {
fatalSimple(0xCAFE << 4 | 0xFE);
}
} else {
fatalSimple(0xCAFE << 4 | 0xFF);
}
splExit();
}
void __appExit(void) {
/* Cleanup services. */
fsExit();
smMitMExit();
smExit();
}
int main(int argc, char **argv)
{
consoleDebugInit(debugDevice_SVC);
/* TODO: What's a good timeout value to use here? */
WaitableManager *server_manager = new WaitableManager(U64_MAX);
/* Create fsp-srv mitm. */
server_manager->add_waitable(new MitMServer<FsMitMService>("fsp-srv", 61));
/* Loop forever, servicing our services. */
server_manager->process();
/* Cleanup. */
delete server_manager;
return 0;
}

View file

@ -0,0 +1,12 @@
#include <switch.h>
#include "fsmitm_service.hpp"
Result FsMitMService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) {
Result rc = 0xF601;
return rc;
}
Result FsMitMService::handle_deferred() {
/* This service is never deferrable. */
return 0;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <switch.h>
#include <stratosphere/iserviceobject.hpp>
class FsMitMService : IServiceObject {
public:
virtual Result dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size);
virtual Result handle_deferred();
};

View file

@ -0,0 +1,127 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "sm_mitm.h"
#include "mitm_session.hpp"
#include "debug.hpp"
template <typename T>
class MitMSession;
template <typename T>
class MitMServer final : public IWaitable {
static_assert(std::is_base_of<IServiceObject, T>::value, "Service Objects must derive from IServiceObject");
protected:
Handle port_handle;
unsigned int max_sessions;
unsigned int num_sessions;
MitMSession<T> **sessions;
char mitm_name[9];
public:
MitMServer(const char *service_name, unsigned int max_s) : max_sessions(max_s) {
this->sessions = new MitMSession<T> *[this->max_sessions];
for (unsigned int i = 0; i < this->max_sessions; i++) {
this->sessions[i] = NULL;
}
this->num_sessions = 0;
strncpy(mitm_name, service_name, 8);
mitm_name[8] = '\x00';
Result rc;
if (R_FAILED((rc = smMitMInstall(&this->port_handle, mitm_name)))) {
/* TODO: Panic. */
}
}
virtual ~MitMServer() {
for (unsigned int i = 0; i < this->max_sessions; i++) {
if (this->sessions[i]) {
delete this->sessions[i];
}
delete this->sessions;
}
if (port_handle) {
if (R_FAILED(smMitMUninstall(mitm_name))) {
/* TODO: Panic. */
}
svcCloseHandle(port_handle);
}
}
/* IWaitable */
virtual unsigned int get_num_waitables() {
unsigned int n = 1;
for (unsigned int i = 0; i < this->max_sessions; i++) {
if (this->sessions[i]) {
n += this->sessions[i]->get_num_waitables();
}
}
return n;
}
virtual void get_waitables(IWaitable **dst) {
dst[0] = this;
unsigned int n = 0;
for (unsigned int i = 0; i < this->max_sessions; i++) {
if (this->sessions[i]) {
this->sessions[i]->get_waitables(&dst[1 + n]);
n += this->sessions[i]->get_num_waitables();
}
}
}
virtual void delete_child(IWaitable *child) {
unsigned int i;
for (i = 0; i < this->max_sessions; i++) {
if (this->sessions[i] == child) {
break;
}
}
if (i == this->max_sessions) {
/* TODO: Panic, because this isn't our child. */
} else {
delete this->sessions[i];
this->sessions[i] = NULL;
this->num_sessions--;
}
}
virtual Handle get_handle() {
return this->port_handle;
}
virtual void handle_deferred() {
/* TODO: Panic, because we can never defer a server. */
}
virtual Result handle_signaled(u64 timeout) {
/* If this server's port was signaled, accept a new session. */
Handle session_h;
svcAcceptSession(&session_h, this->port_handle);
if (this->num_sessions >= this->max_sessions) {
svcCloseHandle(session_h);
return 0x10601;
}
unsigned int i;
for (i = 0; i < this->max_sessions; i++) {
if (this->sessions[i] == NULL) {
break;
}
}
this->sessions[i] = new MitMSession<T>(this, session_h, 0, mitm_name);
this->sessions[i]->set_parent(this);
this->num_sessions++;
return 0;
}
};

View file

@ -0,0 +1,153 @@
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "mitm_server.hpp"
#include "debug.hpp"
template <typename T>
class MitMServer;
template <typename T>
class MitMSession final : public IWaitable {
static_assert(std::is_base_of<IServiceObject, T>::value, "Service Objects must derive from IServiceObject");
T *service_object;
MitMServer<T> *server;
Handle server_handle;
Handle client_handle;
/* This will be for the actual session. */
Service forward_service;
char *pointer_buffer;
size_t pointer_buffer_size;
static_assert(sizeof(pointer_buffer) <= POINTER_BUFFER_SIZE_MAX, "Incorrect Size for PointerBuffer!");
public:
MitMSession<T>(MitMServer<T> *s, Handle s_h, Handle c_h, const char *srv) : server(s), server_handle(s_h), client_handle(c_h) {
this->service_object = new T();
if (R_FAILED(smMitMGetService(&forward_service, srv))) {
/* TODO: Panic. */
}
if (R_FAILED(ipcQueryPointerBufferSize(forward_service.handle, &pointer_buffer_size))) {
/* TODO: Panic. */
}
this->pointer_buffer = new char[pointer_buffer_size];
}
~MitMSession() override {
delete this->service_object;
serviceClose(&forward_service);
if (server_handle) {
svcCloseHandle(server_handle);
}
if (client_handle) {
svcCloseHandle(client_handle);
}
}
T *get_service_object() { return this->service_object; }
Handle get_server_handle() { return this->server_handle; }
Handle get_client_handle() { return this->client_handle; }
/* IWaitable */
unsigned int get_num_waitables() override {
return 1;
}
void get_waitables(IWaitable **dst) override {
dst[0] = this;
}
void delete_child(IWaitable *child) override {
/* TODO: Panic, because we can never have any children. */
}
Handle get_handle() override {
return this->server_handle;
}
void handle_deferred() override {
/* TODO: Panic, because we can never be deferred. */
}
Result handle_signaled(u64 timeout) override {
Result rc;
int handle_index;
/* Prepare pointer buffer... */
IpcCommand c_for_reply;
ipcInitialize(&c_for_reply);
ipcAddRecvStatic(&c_for_reply, this->pointer_buffer, this->pointer_buffer_size, 0);
u32 *cmdbuf = (u32 *)armGetTls();
ipcPrepareHeader(&c_for_reply, 0);
if (R_SUCCEEDED(rc = svcReplyAndReceive(&handle_index, &this->server_handle, 1, 0, timeout))) {
if (handle_index != 0) {
/* TODO: Panic? */
}
Log(armGetTls(), 0x100);
Result retval = 0;
u32 *rawdata_start = cmdbuf;
IpcParsedCommand r;
IpcCommand c;
IpcParsedCommand out_r;
out_r.NumHandles = 0;
ipcInitialize(&c);
retval = ipcParse(&r);
/* TODO: Close input copy handles that we don't need. */
if (R_SUCCEEDED(retval)) {
rawdata_start = (u32 *)r.Raw;
retval = 0xF601;
if (r.CommandType == IpcCommandType_Request || r.CommandType == IpcCommandType_RequestWithContext) {
retval = this->service_object->dispatch(r, c, rawdata_start[2], (u8 *)this->pointer_buffer, sizeof(this->pointer_buffer));
}
/* 0xF601 --> Dispatch onwards. */
if (retval == 0xF601) {
/* Patch PID Descriptor, if relevant. */
if (r.HasPid) {
/* [ctrl 0] [ctrl 1] [handle desc 0] [pid low] [pid high] */
cmdbuf[4] = 0xFFFE0000UL | (cmdbuf[4] & 0xFFFFUL);
}
Log(armGetTls(), 0x100);
retval = serviceIpcDispatch(&forward_service);
if (R_SUCCEEDED(retval)) {
ipcParse(&out_r);
struct {
u64 magic;
u64 result;
} *resp = (decltype(resp))out_r.Raw;
retval = resp->result;
}
}
}
if (retval == 0xF601) {
/* Session close. */
rc = retval;
} else {
Log(armGetTls(), 0x100);
rc = svcReplyAndReceive(&handle_index, &this->server_handle, 0, this->server_handle, 0);
/* Clean up copy handles. */
for (unsigned int i = 0; i < out_r.NumHandles; i++) {
if (out_r.WasHandleCopied[i]) {
svcCloseHandle(out_r.Handles[i]);
}
}
}
}
return rc;
}
};

View file

@ -0,0 +1,173 @@
#include <switch.h>
#include <switch/arm/atomics.h>
#include "sm_mitm.h"
static Handle g_smMitmHandle = INVALID_HANDLE;
static u64 g_refCnt;
Result smMitMInitialize(void) {
atomicIncrement64(&g_refCnt);
if (g_smMitmHandle != INVALID_HANDLE)
return 0;
Result rc = svcConnectToNamedPort(&g_smMitmHandle, "sm:");
if (R_SUCCEEDED(rc)) {
IpcCommand c;
ipcInitialize(&c);
ipcSendPid(&c);
struct {
u64 magic;
u64 cmd_id;
u64 zero;
u64 reserved[2];
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 0;
raw->zero = 0;
rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
}
if (R_FAILED(rc))
smExit();
return rc;
}
void smMitMExit(void) {
if (atomicDecrement64(&g_refCnt) == 0) {
svcCloseHandle(g_smMitmHandle);
g_smMitmHandle = INVALID_HANDLE;
}
}
Result smMitMGetService(Service* service_out, const char *name_str)
{
u64 name = smEncodeName(name_str);
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 service_name;
u64 reserved[2];
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 1;
raw->service_name = name;
Result rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
service_out->type = ServiceType_Normal;
service_out->handle = r.Handles[0];
}
}
return rc;
}
Result smMitMInstall(Handle *handle_out, const char *name) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 service_name;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 65000;
raw->service_name = smEncodeName(name);
Result rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
*handle_out = r.Handles[0];
}
}
return rc;
}
Result smMitMUninstall(const char *name) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 service_name;
u64 reserved;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 65001;
raw->service_name = smEncodeName(name);
Result rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}

View file

@ -0,0 +1,22 @@
/**
* @file sm_mitm.h
* @brief Service manager (sm) IPC wrapper for Atmosphere extensions.
* @author SciresM
* @copyright libnx Authors
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
Result smMitMInitialize(void);
void smMitMExit(void);
Result smMitMGetService(Service* service_out, const char *name);
Result smMitMInstall(Handle *handle_out, const char *name);
Result smMitMUninstall(const char *name);
#ifdef __cplusplus
}
#endif

View file

@ -75,6 +75,8 @@ void __appInit(void) {
fatalSimple(0xCAFE << 4 | 0xFF);
/* TODO: Does Loader need to know about target firmware/master key revision? If so, extract from exosphere_cfg. */
}
splExit();
}
void __appExit(void) {

View file

@ -264,6 +264,8 @@ Result Registration::RegisterServiceForPid(u64 pid, u64 service, u64 max_session
if (R_SUCCEEDED(rc)) {
free_service->service_name = service;
free_service->owner_pid = pid;
free_service->max_sessions = max_sessions;
free_service->is_light = is_light;
}
return rc;