diff --git a/Common/Include/hb/hb_Target.hpp b/Common/Include/hb/hb_Target.hpp new file mode 100644 index 0000000..ee4aa87 --- /dev/null +++ b/Common/Include/hb/hb_Target.hpp @@ -0,0 +1,14 @@ + +#pragma once +#include + +namespace hb +{ + struct TargetInput + { + char nro_path[2048]; + char argv[2048]; + }; + + void Target(TargetInput input, bool once); +} \ No newline at end of file diff --git a/Common/Source/hb/hb_Target.cpp b/Common/Source/hb/hb_Target.cpp new file mode 100644 index 0000000..df17dcc --- /dev/null +++ b/Common/Source/hb/hb_Target.cpp @@ -0,0 +1,15 @@ +#include + +extern "C" +{ + void hb_hbl_Target(const char *path, const char *argv, int counter); +} + +namespace hb +{ + void Target(TargetInput input, bool once) + { + int ctr = once ? 1 : -1; + hb_hbl_Target(input.nro_path, input.argv, ctr); + } +} \ No newline at end of file diff --git a/Common/Source/hb/hb_hbl.c b/Common/Source/hb/hb_hbl.c new file mode 100644 index 0000000..4aa8bb4 --- /dev/null +++ b/Common/Source/hb/hb_hbl.c @@ -0,0 +1,462 @@ +#include +#include +#include +#include + +const char g_noticeText[] = + "HbTarget impl (thanks nx-hbloader)\0" + "Do you mean to tell me that you're thinking seriously of building that way, when and if you are an architect?"; + +static char g_argv[2048]; +static char g_nextArgv[2048]; +static char g_nextNroPath[512]; +u64 g_nroAddr = 0; +static u64 g_nroSize = 0; +static NroHeader g_nroHeader; +static bool g_isApplication = 0; + +static NsApplicationControlData g_applicationControlData; +static bool g_isAutomaticGameplayRecording = 0; +static bool g_smCloseWorkaround = false; + +static u64 g_appletHeapSize = 0; +static u64 g_appletHeapReservationSize = 0; + +static u128 g_userIdStorage; + +static u8 g_savedTls[0x100]; + +// Special HbTarget stuff +static char g_basePath[2048]; +static char g_baseArgv[2048]; +static int g_targetCounter = 0; + +// Used by trampoline.s +Result g_lastRet = 0; + +extern void* __stack_top;//Defined in libnx. +#define STACK_SIZE 0x100000 //Change this if main-thread stack size ever changes. + +void __libnx_initheap(void) +{ + static char g_innerheap[0x20000]; + + extern char* fake_heap_start; + extern char* fake_heap_end; + + fake_heap_start = &g_innerheap[0]; + fake_heap_end = &g_innerheap[sizeof g_innerheap]; +} + +void __appInit(void) +{ + Result rc; + + rc = smInitialize(); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 1)); + + rc = setsysInitialize(); + if (R_SUCCEEDED(rc)) { + SetSysFirmwareVersion fw; + rc = setsysGetFirmwareVersion(&fw); + if (R_SUCCEEDED(rc)) + hosversionSet(MAKEHOSVERSION(fw.major, fw.minor, fw.micro)); + setsysExit(); + } + + rc = fsInitialize(); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 2)); + + fsdevMountSdmc(); +} + +void __wrap_exit(void) +{ + // exit() effectively never gets called, so let's stub it out. + fatalSimple(MAKERESULT(Module_HomebrewLoader, 39)); +} + +static void* g_heapAddr; +static size_t g_heapSize; + +static u64 calculateMaxHeapSize(void) +{ + u64 size = 0; + u64 mem_available = 0, mem_used = 0; + + svcGetInfo(&mem_available, InfoType_TotalMemorySize, CUR_PROCESS_HANDLE, 0); + svcGetInfo(&mem_used, InfoType_UsedMemorySize, CUR_PROCESS_HANDLE, 0); + + if (mem_available > mem_used+0x200000) + size = (mem_available - mem_used - 0x200000) & ~0x1FFFFF; + if (size == 0) + size = 0x2000000*16; + if (size > 0x6000000 && g_isAutomaticGameplayRecording) + size -= 0x6000000; + + return size; +} + +static void setupHbHeap(void) +{ + void* addr = NULL; + u64 size = calculateMaxHeapSize(); + + if (!g_isApplication) { + if (g_appletHeapSize) { + u64 requested_size = (g_appletHeapSize + 0x1FFFFF) &~ 0x1FFFFF; + if (requested_size < size) + size = requested_size; + } + else if (g_appletHeapReservationSize) { + u64 reserved_size = (g_appletHeapReservationSize + 0x1FFFFF) &~ 0x1FFFFF; + if (reserved_size < size) + size -= reserved_size; + } + } + + Result rc = svcSetHeapSize(&addr, size); + + if (R_FAILED(rc) || addr==NULL) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 9)); + + g_heapAddr = addr; + g_heapSize = size; +} + +static Handle g_procHandle; + +static void procHandleReceiveThread(void* arg) +{ + Handle session = (Handle)(uintptr_t)arg; + Result rc; + + u32* tls = (u32*)armGetTls(); + tls[0] = 0; + tls[1] = 0; + + s32 idx = 0; + rc = svcReplyAndReceive(&idx, &session, 1, INVALID_HANDLE, UINT64_MAX); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 15)); + + IpcParsedCommand ipc; + rc = ipcParse(&ipc); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 16)); + + if (ipc.NumHandles != 1) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 17)); + + g_procHandle = ipc.Handles[0]; + svcCloseHandle(session); +} + +//Gets the PID of the process with application_type==APPLICATION in the NPDM, then sets g_isApplication if it matches the current PID. +static void getIsApplication(void) { + Result rc=0; + u64 cur_pid=0, app_pid=0; + + g_isApplication = 0; + + rc = svcGetProcessId(&cur_pid, CUR_PROCESS_HANDLE); + if (R_FAILED(rc)) return; + + rc = pmshellInitialize(); + + if (R_SUCCEEDED(rc)) { + rc = pmshellGetApplicationPid(&app_pid); + pmshellExit(); + } + + if (R_SUCCEEDED(rc) && cur_pid == app_pid) g_isApplication = 1; +} + +//Gets the control.nacp for the current title id, and then sets g_isAutomaticGameplayRecording if less memory should be allocated. +static void getIsAutomaticGameplayRecording(void) { + if (hosversionAtLeast(5,0,0) && g_isApplication) { + Result rc=0; + u64 cur_tid=0; + + rc = svcGetInfo(&cur_tid, InfoType_TitleId, CUR_PROCESS_HANDLE, 0); + if (R_FAILED(rc)) return; + + g_isAutomaticGameplayRecording = 0; + + rc = nsInitialize(); + + if (R_SUCCEEDED(rc)) { + size_t dummy; + rc = nsGetApplicationControlData(0x1, cur_tid, &g_applicationControlData, sizeof(g_applicationControlData), &dummy); + nsExit(); + } + + if (R_SUCCEEDED(rc) && g_applicationControlData.nacp.videoCaptureMode == 2) g_isAutomaticGameplayRecording = 1; + } +} + +static void getOwnProcessHandle(void) +{ + static Thread t; + Result rc; + + Handle server_handle, client_handle; + rc = svcCreateSession(&server_handle, &client_handle, 0, 0); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 12)); + + rc = threadCreate(&t, &procHandleReceiveThread, (void*)(uintptr_t)server_handle, 0x1000, 0x20, 0); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 10)); + + rc = threadStart(&t); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 13)); + + IpcCommand ipc; + ipcInitialize(&ipc); + ipcSendHandleCopy(&ipc, CUR_PROCESS_HANDLE); + ipcPrepareHeader(&ipc, 0); + + ipcDispatch(client_handle); + svcCloseHandle(client_handle); + + threadWaitForExit(&t); + threadClose(&t); +} + +void loadNro(void) +{ + if(g_targetCounter >= 0) + { + if(g_targetCounter == 0) exit(0); + g_targetCounter--; + } + + NroHeader* header = NULL; + size_t rw_size=0; + Result rc=0; + + if (g_smCloseWorkaround) { + // For old applications, wait for SM to handle closing the SM session from this process. + // If we don't do this, smInitialize will fail once eventually used later. + // This is caused by a bug in old versions of libnx that was fixed in commit 68a77ac950. + g_smCloseWorkaround = false; + svcSleepThread(1000000000); + } + + memcpy((u8*)armGetTls() + 0x100, g_savedTls, 0x100); + + if (g_nroSize > 0) + { + // Unmap previous NRO. + header = &g_nroHeader; + rw_size = header->segments[2].size + header->bss_size; + rw_size = (rw_size+0xFFF) & ~0xFFF; + + // .text + rc = svcUnmapProcessCodeMemory( + g_procHandle, g_nroAddr + header->segments[0].file_off, ((u64) g_heapAddr) + header->segments[0].file_off, header->segments[0].size); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 24)); + + // .rodata + rc = svcUnmapProcessCodeMemory( + g_procHandle, g_nroAddr + header->segments[1].file_off, ((u64) g_heapAddr) + header->segments[1].file_off, header->segments[1].size); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 25)); + + // .data + .bss + rc = svcUnmapProcessCodeMemory( + g_procHandle, g_nroAddr + header->segments[2].file_off, ((u64) g_heapAddr) + header->segments[2].file_off, rw_size); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 26)); + + g_nroAddr = g_nroSize = 0; + } + + if (strlen(g_nextNroPath) == 0) + { + strcpy(g_nextNroPath, g_basePath); + strcpy(g_nextArgv, g_baseArgv); + } + + memcpy(g_argv, g_nextArgv, sizeof g_argv); + + uint8_t *nrobuf = (uint8_t*) g_heapAddr; + + NroStart* start = (NroStart*) (nrobuf + 0); + header = (NroHeader*) (nrobuf + sizeof(NroStart)); + uint8_t* rest = (uint8_t*) (nrobuf + sizeof(NroStart) + sizeof(NroHeader)); + + FILE* f = fopen(g_nextNroPath, "rb"); + if (f == NULL) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 3)); + + // Reset NRO path to load hbmenu by default next time. + g_nextNroPath[0] = '\0'; + + if (fread(start, sizeof(*start), 1, f) != 1) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 4)); + + if (fread(header, sizeof(*header), 1, f) != 1) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 4)); + + if(header->magic != NROHEADER_MAGIC) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 5)); + + size_t rest_size = header->size - (sizeof(NroStart) + sizeof(NroHeader)); + if (fread(rest, rest_size, 1, f) != 1) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 7)); + + fclose(f); + + size_t total_size = header->size + header->bss_size; + total_size = (total_size+0xFFF) & ~0xFFF; + + rw_size = header->segments[2].size + header->bss_size; + rw_size = (rw_size+0xFFF) & ~0xFFF; + + bool has_mod0 = false; + if (start->mod_offset > 0 && start->mod_offset <= (total_size-0x24)) // Validate MOD0 offset + has_mod0 = *(uint32_t*)(nrobuf + start->mod_offset) == 0x30444F4D; // Validate MOD0 header + + int i; + for (i=0; i<3; i++) + { + if (header->segments[i].file_off >= header->size || header->segments[i].size > header->size || + (header->segments[i].file_off + header->segments[i].size) > header->size) + { + fatalSimple(MAKERESULT(Module_HomebrewLoader, 6)); + } + } + + // todo: Detect whether NRO fits into heap or not. + + // Copy header to elsewhere because we're going to unmap it next. + memcpy(&g_nroHeader, header, sizeof(g_nroHeader)); + header = &g_nroHeader; + + u64 map_addr; + + do { + map_addr = randomGet64() & 0xFFFFFF000ull; + rc = svcMapProcessCodeMemory(g_procHandle, map_addr, (u64)nrobuf, total_size); + + } while (rc == 0xDC01 || rc == 0xD401); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 18)); + + // .text + rc = svcSetProcessMemoryPermission( + g_procHandle, map_addr + header->segments[0].file_off, header->segments[0].size, Perm_R | Perm_X); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 19)); + + // .rodata + rc = svcSetProcessMemoryPermission( + g_procHandle, map_addr + header->segments[1].file_off, header->segments[1].size, Perm_R); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 20)); + + // .data + .bss + rc = svcSetProcessMemoryPermission( + g_procHandle, map_addr + header->segments[2].file_off, rw_size, Perm_Rw); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_HomebrewLoader, 21)); + + u64 nro_size = header->segments[2].file_off + rw_size; + u64 nro_heap_start = ((u64) g_heapAddr) + nro_size; + u64 nro_heap_size = g_heapSize + (u64) g_heapAddr - (u64) nro_heap_start; + + #define M EntryFlag_IsMandatory + + static ConfigEntry entries[] = { + { EntryType_MainThreadHandle, 0, {0, 0} }, + { EntryType_ProcessHandle, 0, {0, 0} }, + { EntryType_AppletType, 0, {AppletType_LibraryApplet, 0} }, + { EntryType_OverrideHeap, M, {0, 0} }, + { EntryType_Argv, 0, {0, 0} }, + { EntryType_NextLoadPath, 0, {0, 0} }, + { EntryType_LastLoadResult, 0, {0, 0} }, + { EntryType_SyscallAvailableHint, 0, {0xffffffffffffffff, 0x9fc1fff0007ffff} }, + { EntryType_RandomSeed, 0, {0, 0} }, + { EntryType_UserIdStorage, 0, {(u64)(uintptr_t)&g_userIdStorage, 0} }, + { EntryType_HosVersion, 0, {0, 0} }, + { EntryType_EndOfList, 0, {(u64)(uintptr_t)g_noticeText, sizeof(g_noticeText)} } + }; + + ConfigEntry *entry_AppletType = &entries[2]; + + if (g_isApplication) { + entry_AppletType->Value[0] = AppletType_SystemApplication; + entry_AppletType->Value[1] = EnvAppletFlags_ApplicationOverride; + } + + // MainThreadHandle + entries[0].Value[0] = envGetMainThreadHandle(); + // ProcessHandle + entries[1].Value[0] = g_procHandle; + // OverrideHeap + entries[3].Value[0] = nro_heap_start; + entries[3].Value[1] = nro_heap_size; + // Argv + entries[4].Value[1] = (u64) &g_argv[0]; + // NextLoadPath + entries[5].Value[0] = (u64) &g_nextNroPath[0]; + entries[5].Value[1] = (u64) &g_nextArgv[0]; + // LastLoadResult + entries[6].Value[0] = g_lastRet; + // RandomSeed + entries[8].Value[0] = randomGet64(); + entries[8].Value[1] = randomGet64(); + // HosVersion + entries[10].Value[0] = hosversionGet(); + + u64 entrypoint = map_addr; + + g_nroAddr = map_addr; + g_nroSize = nro_size; + + memset(__stack_top - STACK_SIZE, 0, STACK_SIZE); + + if (!has_mod0) { + // Apply sm-close workaround to NROs which do not contain a valid MOD0 header. + // This heuristic is based on the fact that MOD0 support was added very shortly after + // the fix for the sm-close bug (in fact, two commits later). + g_smCloseWorkaround = true; + } + + extern NORETURN void hbTargetImpl(u64 entries_ptr, u64 handle, u64 entrypoint); + hbTargetImpl((u64) entries, -1, entrypoint); +} + +static void InnerTarget(const char *path, const char *argv) +{ + memcpy(g_savedTls, (u8*)armGetTls() + 0x100, 0x100); + strcpy(g_basePath, path); + strcpy(g_baseArgv, argv); + + getIsApplication(); + getIsAutomaticGameplayRecording(); + smExit(); // Close SM as we don't need it anymore. + setupHbHeap(); + getOwnProcessHandle(); + + loadNro(); +} + +void hb_hbl_Target(const char *path, const char *argv, int counter) +{ + g_targetCounter = counter; + InnerTarget(path, argv); +} \ No newline at end of file diff --git a/Common/Source/hb/hb_hbl_trampoline.s b/Common/Source/hb/hb_hbl_trampoline.s new file mode 100644 index 0000000..f3a73cd --- /dev/null +++ b/Common/Source/hb/hb_hbl_trampoline.s @@ -0,0 +1,51 @@ +.section .text.hbTargetImpl, "ax", %progbits + +.global hbTargetImpl +.type hbTargetImpl, %function +.align 2 + +.global __libnx_exception_entry +.type __libnx_exception_entry, %function + +.cfi_startproc + +hbTargetImpl: + + // Reset stack pointer. + adrp x8, __stack_top //Defined in libnx. + ldr x8, [x8, #:lo12:__stack_top] + mov sp, x8 + + // Call NRO. + blr x2 + + // Save retval + adrp x1, g_lastRet + str w0, [x1, #:lo12:g_lastRet] + + // Reset stack pointer and load next NRO. + adrp x8, __stack_top + ldr x8, [x8, #:lo12:__stack_top] + mov sp, x8 + + b loadNro + +.cfi_endproc + +.section .text.__libnx_exception_entry, "ax", %progbits +.align 2 + +.cfi_startproc + +__libnx_exception_entry: + adrp x7, g_nroAddr + ldr x7, [x7, #:lo12:g_nroAddr] + cbz x7, __libnx_exception_entry_fail + br x7 + +__libnx_exception_entry_fail: + mov w0, #0xf801 + bl svcReturnFromException + b . + +.cfi_endproc \ No newline at end of file diff --git a/LibraryAppletQHbTarget/LibraryAppletQHbTarget.json b/LibraryAppletQHbTarget/LibraryAppletQHbTarget.json new file mode 100644 index 0000000..caebc06 --- /dev/null +++ b/LibraryAppletQHbTarget/LibraryAppletQHbTarget.json @@ -0,0 +1,163 @@ +{ + "name": "QHbTarget", + "title_id": "0xFFFFFFFFFFFFFFFF", + "title_id_range_min": "0xFFFFFFFFFFFFFFFF", + "title_id_range_max": "0xFFFFFFFFFFFFFFFF", + "main_thread_stack_size": "0x100000", + "main_thread_priority": 44, + "default_cpu_id": 0, + "process_category": 0, + "pool_partition": 0, + "is_64_bit": true, + "address_space_type": 1, + "is_retail": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_host": [ + "*" + ], + "service_access": [ + "*" + ], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 59, + "lowest_thread_priority": 28, + "highest_cpu_id": 2, + "lowest_cpu_id": 0 + } + }, + { + "type": "syscalls", + "value": { + "svcUnknown00": "0x00", + "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", + "svcFlushEntireDataCache": "0x2A", + "svcFlushDataCache": "0x2B", + "svcMapPhysicalMemory": "0x2C", + "svcUnmapPhysicalMemory": "0x2D", + "svcGetFutureThreadInfo": "0x2E", + "svcGetLastThreadInfo": "0x2F", + "svcGetResourceLimitLimitValue": "0x30", + "svcGetResourceLimitCurrentValue": "0x31", + "svcSetThreadActivity": "0x32", + "svcGetThreadContext3": "0x33", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcUnknown36": "0x36", + "svcUnknown37": "0x37", + "svcUnknown38": "0x38", + "svcUnknown39": "0x39", + "svcUnknown3a": "0x3A", + "svcUnknown3b": "0x3B", + "svcDumpInfo": "0x3C", + "svcDumpInfoNew": "0x3D", + "svcUnknown3e": "0x3E", + "svcUnknown3f": "0x3F", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcUnknown46": "0x46", + "svcUnknown47": "0x47", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcSetUnsafeLimit": "0x4A", + "svcCreateCodeMemory": "0x4B", + "svcControlCodeMemory": "0x4C", + "svcSleepSystem": "0x4D", + "svcReadWriteRegister": "0x4E", + "svcSetProcessActivity": "0x4F", + "svcCreateSharedMemory": "0x50", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6A", + "svcWriteDebugProcessMemory": "0x6B", + "svcSetHardwareBreakPoint": "0x6C", + "svcGetDebugThreadParam": "0x6D", + "svcConnectToPort": "0x72", + "svcSetProcessMemoryPermission": "0x73", + "svcMapProcessMemory": "0x74", + "svcUnmapProcessMemory": "0x75", + "svcQueryProcessMemory": "0x76", + "svcMapProcessCodeMemory": "0x77", + "svcUnmapProcessCodeMemory": "0x78", + "svcCallSecureMonitor": "0x7F" + } + }, + { + "type": "application_type", + "value": 2 + }, + { + "type": "min_kernel_version", + "value": "0x30" + }, + { + "type": "handle_table_size", + "value": 512 + }, + { + "type": "debug_flags", + "value": { + "allow_debug": true, + "force_debug": true + } + } + ] +} \ No newline at end of file diff --git a/LibraryAppletQHbTarget/Makefile b/LibraryAppletQHbTarget/Makefile new file mode 100644 index 0000000..fc51f28 --- /dev/null +++ b/LibraryAppletQHbTarget/Makefile @@ -0,0 +1,183 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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 := ../Common/Source ../Common/Source/am ../Common/Source/cfg ../Common/Source/db ../Common/Source/fs ../Common/Source/hb ../Common/Source/os ../Common/Source/util Source Source/ui +DATA := data +INCLUDES := ../Common/Include Include +EXEFS_SRC := exefs_src + +#--------------------------------------------------------------------------------- +# 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 := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# 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 + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @rm -rf $(CURDIR)/Out + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + @mkdir -p $(CURDIR)/Out + @cp $(OUTPUT).nsp $(CURDIR)/Out/exefs.nsp + @touch $(CURDIR)/Out/fsmitm.flag + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(CURDIR)/Out $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +all : $(OUTPUT).nsp + +ifeq ($(strip $(APP_JSON)),) +$(OUTPUT).nsp : $(OUTPUT).nso +else +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +endif + +$(OUTPUT).nso : $(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 +#--------------------------------------------------------------------------------------- diff --git a/LibraryAppletQHbTarget/Source/Main.cpp b/LibraryAppletQHbTarget/Source/Main.cpp new file mode 100644 index 0000000..542d5c2 --- /dev/null +++ b/LibraryAppletQHbTarget/Source/Main.cpp @@ -0,0 +1,48 @@ +#include +#include + +extern "C" +{ + u32 __nx_applet_type = AppletType_LibraryApplet; + u32 __nx_applet_exit_mode = 2; +} + +hb::TargetInput hb_input; +bool target_once = true; // Library applet targets once + +int main() +{ + hb_input = {}; + + hb_input.nro_path[0] = '\0'; + hb_input.argv[0] = '\0'; + + // Initialize applet, read stuff, close applet + appletInitialize(); + LibAppletArgs largs; + auto rc = am::QLibraryAppletReadStorage(&largs, sizeof(largs)); + if(R_SUCCEEDED(rc)) + { + if(largs.LaVersion == am::Magic) + { + hb::TargetInput ipt = {}; + rc = am::QLibraryAppletReadStorage(&ipt, sizeof(ipt)); + if(R_SUCCEEDED(rc)) + { + if(strlen(ipt.nro_path)) + { + strcpy(hb_input.nro_path, ipt.nro_path); + if(strlen(ipt.argv)) strcpy(hb_input.argv, ipt.argv); + } + } + } + } + appletExit(); + + if(hb_input.nro_path[0] == '\0') strcpy(hb_input.nro_path, "sdmc:/hbmenu.nro"); + if(hb_input.argv[0] == '\0') strcpy(hb_input.argv, hb_input.nro_path); + + hb::Target(hb_input, target_once); + + return 0; +} \ No newline at end of file diff --git a/LibraryAppletQMenu/RomFs/default/theme/Manifest.json b/LibraryAppletQMenu/RomFs/default/theme/Manifest.json new file mode 100644 index 0000000..c7613b0 --- /dev/null +++ b/LibraryAppletQMenu/RomFs/default/theme/Manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Default theme", + "format_version": 0, + "release": "Dev", + "description": "Default theme", + "author": "XorTroll" +} \ No newline at end of file diff --git a/LibraryAppletQMenu/RomFs/default/ui/UI.json b/LibraryAppletQMenu/RomFs/default/ui/UI.json new file mode 100644 index 0000000..2998b3d --- /dev/null +++ b/LibraryAppletQMenu/RomFs/default/ui/UI.json @@ -0,0 +1,3 @@ +{ + "suspended_final_alpha": 80 +} \ No newline at end of file diff --git a/SystemApplicationQHbTarget/Makefile b/SystemApplicationQHbTarget/Makefile new file mode 100644 index 0000000..fc51f28 --- /dev/null +++ b/SystemApplicationQHbTarget/Makefile @@ -0,0 +1,183 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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 := ../Common/Source ../Common/Source/am ../Common/Source/cfg ../Common/Source/db ../Common/Source/fs ../Common/Source/hb ../Common/Source/os ../Common/Source/util Source Source/ui +DATA := data +INCLUDES := ../Common/Include Include +EXEFS_SRC := exefs_src + +#--------------------------------------------------------------------------------- +# 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 := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# 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 + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @rm -rf $(CURDIR)/Out + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + @mkdir -p $(CURDIR)/Out + @cp $(OUTPUT).nsp $(CURDIR)/Out/exefs.nsp + @touch $(CURDIR)/Out/fsmitm.flag + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(CURDIR)/Out $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +all : $(OUTPUT).nsp + +ifeq ($(strip $(APP_JSON)),) +$(OUTPUT).nsp : $(OUTPUT).nso +else +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +endif + +$(OUTPUT).nso : $(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 +#--------------------------------------------------------------------------------------- diff --git a/SystemApplicationQHbTarget/Source/Main.cpp b/SystemApplicationQHbTarget/Source/Main.cpp new file mode 100644 index 0000000..8083aa4 --- /dev/null +++ b/SystemApplicationQHbTarget/Source/Main.cpp @@ -0,0 +1,40 @@ +#include +#include + +extern "C" +{ + u32 __nx_applet_type = AppletType_SystemApplication; + u32 __nx_applet_exit_mode = 2; +} + +hb::TargetInput hb_input; +bool target_once = false; // The application system allows classic hbl-ish system to exit to hbmenu + +int main() +{ + hb_input = {}; + + hb_input.nro_path[0] = '\0'; + hb_input.argv[0] = '\0'; + + // Initialize applet, read stuff, close applet + appletInitialize(); + hb::TargetInput ipt = {}; + auto rc = am::QApplicationReadStorage(&ipt, sizeof(ipt)); + if(R_SUCCEEDED(rc)) + { + if(strlen(ipt.nro_path)) + { + strcpy(hb_input.nro_path, ipt.nro_path); + if(strlen(ipt.argv)) strcpy(hb_input.argv, ipt.argv); + } + } + appletExit(); + + if(hb_input.nro_path[0] == '\0') strcpy(hb_input.nro_path, "sdmc:/hbmenu.nro"); + if(hb_input.argv[0] == '\0') strcpy(hb_input.argv, hb_input.nro_path); + + hb::Target(hb_input, target_once); + + return 0; +} \ No newline at end of file diff --git a/SystemApplicationQHbTarget/SystemApplicationQHbTarget.json b/SystemApplicationQHbTarget/SystemApplicationQHbTarget.json new file mode 100644 index 0000000..91958d4 --- /dev/null +++ b/SystemApplicationQHbTarget/SystemApplicationQHbTarget.json @@ -0,0 +1,163 @@ +{ + "name": "QHbTarget", + "title_id": "0xFFFFFFFFFFFFFFFF", + "title_id_range_min": "0xFFFFFFFFFFFFFFFF", + "title_id_range_max": "0xFFFFFFFFFFFFFFFF", + "main_thread_stack_size": "0x100000", + "main_thread_priority": 44, + "default_cpu_id": 0, + "process_category": 0, + "pool_partition": 0, + "is_64_bit": true, + "address_space_type": 1, + "is_retail": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_host": [ + "*" + ], + "service_access": [ + "*" + ], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 59, + "lowest_thread_priority": 28, + "highest_cpu_id": 2, + "lowest_cpu_id": 0 + } + }, + { + "type": "syscalls", + "value": { + "svcUnknown00": "0x00", + "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", + "svcFlushEntireDataCache": "0x2A", + "svcFlushDataCache": "0x2B", + "svcMapPhysicalMemory": "0x2C", + "svcUnmapPhysicalMemory": "0x2D", + "svcGetFutureThreadInfo": "0x2E", + "svcGetLastThreadInfo": "0x2F", + "svcGetResourceLimitLimitValue": "0x30", + "svcGetResourceLimitCurrentValue": "0x31", + "svcSetThreadActivity": "0x32", + "svcGetThreadContext3": "0x33", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcUnknown36": "0x36", + "svcUnknown37": "0x37", + "svcUnknown38": "0x38", + "svcUnknown39": "0x39", + "svcUnknown3a": "0x3A", + "svcUnknown3b": "0x3B", + "svcDumpInfo": "0x3C", + "svcDumpInfoNew": "0x3D", + "svcUnknown3e": "0x3E", + "svcUnknown3f": "0x3F", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcUnknown46": "0x46", + "svcUnknown47": "0x47", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcSetUnsafeLimit": "0x4A", + "svcCreateCodeMemory": "0x4B", + "svcControlCodeMemory": "0x4C", + "svcSleepSystem": "0x4D", + "svcReadWriteRegister": "0x4E", + "svcSetProcessActivity": "0x4F", + "svcCreateSharedMemory": "0x50", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6A", + "svcWriteDebugProcessMemory": "0x6B", + "svcSetHardwareBreakPoint": "0x6C", + "svcGetDebugThreadParam": "0x6D", + "svcConnectToPort": "0x72", + "svcSetProcessMemoryPermission": "0x73", + "svcMapProcessMemory": "0x74", + "svcUnmapProcessMemory": "0x75", + "svcQueryProcessMemory": "0x76", + "svcMapProcessCodeMemory": "0x77", + "svcUnmapProcessCodeMemory": "0x78", + "svcCallSecureMonitor": "0x7F" + } + }, + { + "type": "application_type", + "value": 1 + }, + { + "type": "min_kernel_version", + "value": "0x30" + }, + { + "type": "handle_table_size", + "value": 512 + }, + { + "type": "debug_flags", + "value": { + "allow_debug": true, + "force_debug": true + } + } + ] +} \ No newline at end of file