public release
This commit is contained in:
88
hbl/CMakeLists.txt
Normal file
88
hbl/CMakeLists.txt
Normal file
@@ -0,0 +1,88 @@
|
||||
project(hbl
|
||||
VERSION 3.0.0
|
||||
LANGUAGES ASM C
|
||||
)
|
||||
|
||||
add_executable(hbl
|
||||
source/main.c
|
||||
source/trampoline.s
|
||||
)
|
||||
|
||||
set_target_properties(hbl PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_EXTENSIONS ON
|
||||
)
|
||||
|
||||
target_link_options(hbl PRIVATE
|
||||
-Wl,-wrap,exit
|
||||
)
|
||||
|
||||
target_compile_definitions(hbl PRIVATE
|
||||
VERSION="${CMAKE_PROJECT_VERSION}"
|
||||
)
|
||||
|
||||
find_program(NX_ELF2NSO_EXE NAMES elf2nso HINTS "${DEVKITPRO}/tools/bin" REQUIRED)
|
||||
find_program(NX_NPDMTOOL_EXE NAMES npdmtool HINTS "${DEVKITPRO}/tools/bin" REQUIRED)
|
||||
|
||||
function(nx_create_nso target)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 NX_NSO "" "OUTPUT" "")
|
||||
|
||||
set(intarget "${target}")
|
||||
set(outtarget "${target}_nso")
|
||||
|
||||
if (DEFINED NX_NSO_OUTPUT)
|
||||
get_filename_component(NX_NSO_OUTPUT "${NX_NSO_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
else()
|
||||
set(NX_NSO_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${target}.nso")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${NX_NSO_OUTPUT}"
|
||||
COMMAND ${NX_ELF2NSO_EXE} "$<TARGET_FILE:${intarget}>" "${NX_NSO_OUTPUT}"
|
||||
DEPENDS ${intarget}
|
||||
COMMENT "Converting ${intarget} to NSO format"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(${outtarget} ALL DEPENDS "${NX_NSO_OUTPUT}")
|
||||
dkp_set_target_file(${outtarget} "${NX_NSO_OUTPUT}")
|
||||
endfunction()
|
||||
|
||||
function(nx_create_npdm target config)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 NX_NPDM "" "OUTPUT;CONFIG" "")
|
||||
|
||||
set(intarget "${target}")
|
||||
set(outtarget "${target}_npdm")
|
||||
|
||||
if (DEFINED NX_NPDM_CONFIG)
|
||||
get_filename_component(NX_NPDM_CONFIG "${NX_NPDM_CONFIG}" ABSOLUTE)
|
||||
else()
|
||||
message(FATAL_ERROR "nx_create_exefs: must provide a CONFIG file in json format")
|
||||
endif()
|
||||
|
||||
if (DEFINED NX_NPDM_OUTPUT)
|
||||
get_filename_component(NX_NPDM_OUTPUT "${NX_NPDM_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
else()
|
||||
set(NX_NPDM_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${target}.npdm")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${NX_NPDM_OUTPUT}"
|
||||
COMMAND ${NX_NPDMTOOL_EXE} "${NX_NPDM_CONFIG}" "${NX_NPDM_OUTPUT}"
|
||||
DEPENDS "${NX_NPDM_CONFIG}"
|
||||
COMMENT "Generating NPDM for ${outtarget}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(${outtarget} ALL DEPENDS "${NX_NPDM_OUTPUT}")
|
||||
dkp_set_target_file(${outtarget} "${NX_NPDM_OUTPUT}")
|
||||
endfunction()
|
||||
|
||||
nx_create_nso(hbl
|
||||
OUTPUT main
|
||||
)
|
||||
|
||||
nx_create_npdm(hbl
|
||||
OUTPUT main.npdm
|
||||
CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/hbl.json
|
||||
)
|
||||
263
hbl/hbl.json
Normal file
263
hbl/hbl.json
Normal file
@@ -0,0 +1,263 @@
|
||||
{
|
||||
"name": "Application",
|
||||
"title_id": "0x0000000000000000",
|
||||
"title_id_range_min": "0x0000000000000000",
|
||||
"title_id_range_max": "0x0000000000000000",
|
||||
"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",
|
||||
"content_owner_ids": [
|
||||
"0x0100000000001000"
|
||||
]
|
||||
},
|
||||
"service_host": [
|
||||
"*"
|
||||
],
|
||||
"service_access": [
|
||||
"*"
|
||||
],
|
||||
"kernel_capabilities": [
|
||||
{
|
||||
"type": "kernel_flags",
|
||||
"value": {
|
||||
"highest_thread_priority": 59,
|
||||
"lowest_thread_priority": 28,
|
||||
"highest_cpu_id": 3,
|
||||
"lowest_cpu_id": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "syscalls",
|
||||
"value": {
|
||||
"svcUnassigned00": "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",
|
||||
"svcGetDebugFutureThreadInfo": "0x2E",
|
||||
"svcGetLastThreadInfo": "0x2F",
|
||||
"svcGetResourceLimitLimitValue": "0x30",
|
||||
"svcGetResourceLimitCurrentValue": "0x31",
|
||||
"svcSetThreadActivity": "0x32",
|
||||
"svcGetThreadContext3": "0x33",
|
||||
"svcWaitForAddress": "0x34",
|
||||
"svcSignalToAddress": "0x35",
|
||||
"svcSynchronizePreemptionState": "0x36",
|
||||
"svcGetResourceLimitPeakValue": "0x37",
|
||||
"svcUnassigned38": "0x38",
|
||||
"svcCreateIoPool": "0x39",
|
||||
"svcCreateIoRegion": "0x3A",
|
||||
"svcUnassigned3B": "0x3B",
|
||||
"svcKernelDebug": "0x3C",
|
||||
"svcChangeKernelTraceState": "0x3D",
|
||||
"svcUnassigned3E": "0x3E",
|
||||
"svcUnassigned3F": "0x3F",
|
||||
"svcCreateSession": "0x40",
|
||||
"svcAcceptSession": "0x41",
|
||||
"svcReplyAndReceiveLight": "0x42",
|
||||
"svcReplyAndReceive": "0x43",
|
||||
"svcReplyAndReceiveWithUserBuffer": "0x44",
|
||||
"svcCreateEvent": "0x45",
|
||||
"svcMapIoRegion": "0x46",
|
||||
"svcUnmapIoRegion": "0x47",
|
||||
"svcMapPhysicalMemoryUnsafe": "0x48",
|
||||
"svcUnmapPhysicalMemoryUnsafe": "0x49",
|
||||
"svcSetUnsafeLimit": "0x4A",
|
||||
"svcCreateCodeMemory": "0x4B",
|
||||
"svcControlCodeMemory": "0x4C",
|
||||
"svcSleepSystem": "0x4D",
|
||||
"svcReadWriteRegister": "0x4E",
|
||||
"svcSetProcessActivity": "0x4F",
|
||||
"svcCreateSharedMemory": "0x50",
|
||||
"svcMapTransferMemory": "0x51",
|
||||
"svcUnmapTransferMemory": "0x52",
|
||||
"svcCreateInterruptEvent": "0x53",
|
||||
"svcQueryPhysicalAddress": "0x54",
|
||||
"svcQueryIoMapping": "0x55",
|
||||
"svcCreateDeviceAddressSpace": "0x56",
|
||||
"svcAttachDeviceAddressSpace": "0x57",
|
||||
"svcDetachDeviceAddressSpace": "0x58",
|
||||
"svcMapDeviceAddressSpaceByForce": "0x59",
|
||||
"svcMapDeviceAddressSpaceAligned": "0x5A",
|
||||
"svcMapDeviceAddressSpace": "0x5B",
|
||||
"svcUnmapDeviceAddressSpace": "0x5C",
|
||||
"svcInvalidateProcessDataCache": "0x5D",
|
||||
"svcStoreProcessDataCache": "0x5E",
|
||||
"svcFlushProcessDataCache": "0x5F",
|
||||
"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",
|
||||
"svcUnassigned6E": "0x6E",
|
||||
"svcGetSystemInfo": "0x6F",
|
||||
"svcCreatePort": "0x70",
|
||||
"svcManageNamedPort": "0x71",
|
||||
"svcConnectToPort": "0x72",
|
||||
"svcSetProcessMemoryPermission": "0x73",
|
||||
"svcMapProcessMemory": "0x74",
|
||||
"svcUnmapProcessMemory": "0x75",
|
||||
"svcQueryProcessMemory": "0x76",
|
||||
"svcMapProcessCodeMemory": "0x77",
|
||||
"svcUnmapProcessCodeMemory": "0x78",
|
||||
"svcCreateProcess": "0x79",
|
||||
"svcStartProcess": "0x7A",
|
||||
"svcTerminateProcess": "0x7B",
|
||||
"svcGetProcessInfo": "0x7C",
|
||||
"svcCreateResourceLimit": "0x7D",
|
||||
"svcSetResourceLimitLimitValue": "0x7E",
|
||||
"svcCallSecureMonitor": "0x7F",
|
||||
"svcUnassigned80": "0x80",
|
||||
"svcUnassigned81": "0x81",
|
||||
"svcUnassigned82": "0x82",
|
||||
"svcUnassigned83": "0x83",
|
||||
"svcUnassigned84": "0x84",
|
||||
"svcUnassigned85": "0x85",
|
||||
"svcUnassigned86": "0x86",
|
||||
"svcUnassigned87": "0x87",
|
||||
"svcUnassigned88": "0x88",
|
||||
"svcUnassigned89": "0x89",
|
||||
"svcUnassigned8A": "0x8A",
|
||||
"svcUnassigned8B": "0x8B",
|
||||
"svcUnassigned8C": "0x8C",
|
||||
"svcUnassigned8D": "0x8D",
|
||||
"svcUnassigned8E": "0x8E",
|
||||
"svcUnassigned8F": "0x8F",
|
||||
"svcMapInsecureMemory": "0x90",
|
||||
"svcUnmapInsecureMemory": "0x91",
|
||||
"svcUnassigned92": "0x92",
|
||||
"svcUnassigned93": "0x93",
|
||||
"svcUnassigned94": "0x94",
|
||||
"svcUnassigned95": "0x95",
|
||||
"svcUnassigned96": "0x96",
|
||||
"svcUnassigned97": "0x97",
|
||||
"svcUnassigned98": "0x98",
|
||||
"svcUnassigned99": "0x99",
|
||||
"svcUnassigned9A": "0x9A",
|
||||
"svcUnassigned9B": "0x9B",
|
||||
"svcUnassigned9C": "0x9C",
|
||||
"svcUnassigned9D": "0x9D",
|
||||
"svcUnassigned9E": "0x9E",
|
||||
"svcUnassigned9F": "0x9F",
|
||||
"svcUnassignedA0": "0xA0",
|
||||
"svcUnassignedA1": "0xA1",
|
||||
"svcUnassignedA2": "0xA2",
|
||||
"svcUnassignedA3": "0xA3",
|
||||
"svcUnassignedA4": "0xA4",
|
||||
"svcUnassignedA5": "0xA5",
|
||||
"svcUnassignedA6": "0xA6",
|
||||
"svcUnassignedA7": "0xA7",
|
||||
"svcUnassignedA8": "0xA8",
|
||||
"svcUnassignedA9": "0xA9",
|
||||
"svcUnassignedAA": "0xAA",
|
||||
"svcUnassignedAB": "0xAB",
|
||||
"svcUnassignedAC": "0xAC",
|
||||
"svcUnassignedAD": "0xAD",
|
||||
"svcUnassignedAE": "0xAE",
|
||||
"svcUnassignedAF": "0xAF",
|
||||
"svcUnassignedB0": "0xB0",
|
||||
"svcUnassignedB1": "0xB1",
|
||||
"svcUnassignedB2": "0xB2",
|
||||
"svcUnassignedB3": "0xB3",
|
||||
"svcUnassignedB4": "0xB4",
|
||||
"svcUnassignedB5": "0xB5",
|
||||
"svcUnassignedB6": "0xB6",
|
||||
"svcUnassignedB7": "0xB7",
|
||||
"svcUnassignedB8": "0xB8",
|
||||
"svcUnassignedB9": "0xB9",
|
||||
"svcUnassignedBA": "0xBA",
|
||||
"svcUnassignedBB": "0xBB",
|
||||
"svcUnassignedBC": "0xBC",
|
||||
"svcUnassignedBD": "0xBD",
|
||||
"svcUnassignedBE": "0xBE",
|
||||
"svcUnassignedBF": "0xBF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "application_type",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"type": "min_kernel_version",
|
||||
"value": "0x30"
|
||||
},
|
||||
{
|
||||
"type": "handle_table_size",
|
||||
"value": 512
|
||||
},
|
||||
{
|
||||
"type": "debug_flags",
|
||||
"value": {
|
||||
"allow_debug": false,
|
||||
"force_debug_prod": true,
|
||||
"force_debug": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "map_region",
|
||||
"value": [
|
||||
{
|
||||
"region_type": 1,
|
||||
"is_ro": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
5
hbl/nx-hbloader.LICENSE.md
Normal file
5
hbl/nx-hbloader.LICENSE.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Copyright 2017-2018 nx-hbloader Authors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
563
hbl/source/main.c
Normal file
563
hbl/source/main.c
Normal file
@@ -0,0 +1,563 @@
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define EXIT_DETECTION_STR "if this isn't replaced i will exit :)"
|
||||
|
||||
// this uses 3-4KiB more than nx-hbloader.
|
||||
static char g_argv[2048] = {0};
|
||||
// this can and will be modified by libnx if launched nro calls envSetNextLoad().
|
||||
static char g_nextArgv[2048] = {0};
|
||||
static char g_nextNroPath[FS_MAX_PATH] = {0};
|
||||
// stores first launched nro argv + path.
|
||||
static char g_defaultArgv[2048] = {0};
|
||||
static char g_defaultNroPath[FS_MAX_PATH] = {0};
|
||||
|
||||
static const char g_noticeText[] = { "sphaira " VERSION };
|
||||
|
||||
static u64 g_nroSize = 0;
|
||||
static NroHeader g_nroHeader = {0};
|
||||
|
||||
static enum {
|
||||
CodeMemoryUnavailable = 0,
|
||||
CodeMemoryForeignProcess = BIT(0),
|
||||
CodeMemorySameProcess = BIT(0) | BIT(1),
|
||||
} g_codeMemoryCapability = CodeMemoryUnavailable;
|
||||
|
||||
static void* g_heapAddr = {0};
|
||||
static size_t g_heapSize = {0};
|
||||
|
||||
static Handle g_procHandle = {0};
|
||||
static u128 g_userIdStorage = {0};
|
||||
static u8 g_savedTls[0x100] = {0};
|
||||
|
||||
// Used by trampoline.s
|
||||
u64 g_nroAddr = 0;
|
||||
Result g_lastRet = 0;
|
||||
|
||||
void NX_NORETURN nroEntrypointTrampoline(const ConfigEntry* entries, u64 handle, u64 entrypoint);
|
||||
|
||||
static void fix_nro_path(char* path) {
|
||||
// hbmenu prefixes paths with sdmc: which fsFsOpenFile won't like
|
||||
if (!strncmp(path, "sdmc:/", 6)) {
|
||||
// memmove(path, path + 5, strlen(path)-5);
|
||||
memmove(path, path + 5, FS_MAX_PATH-5);
|
||||
}
|
||||
}
|
||||
|
||||
// Credit to behemoth
|
||||
// SOURCE: https://github.com/HookedBehemoth/nx-hbloader/commit/7f8000a41bc5e8a6ad96a097ef56634cfd2fabcb
|
||||
static NX_NORETURN void selfExit(void) {
|
||||
Result rc = smInitialize();
|
||||
if (R_FAILED(rc))
|
||||
goto fail0;
|
||||
|
||||
Service applet, proxy, self;
|
||||
|
||||
rc = smGetService(&applet, "appletOE");
|
||||
if (R_FAILED(rc))
|
||||
goto fail1;
|
||||
|
||||
const u32 cmd_id = 0;
|
||||
const u64 reserved = 0;
|
||||
|
||||
// GetSessionProxy
|
||||
rc = serviceDispatchIn(&applet, cmd_id, reserved,
|
||||
.in_send_pid = true,
|
||||
.in_num_handles = 1,
|
||||
.in_handles = { g_procHandle },
|
||||
.out_num_objects = 1,
|
||||
.out_objects = &proxy,
|
||||
);
|
||||
if (R_FAILED(rc))
|
||||
goto fail2;
|
||||
|
||||
// GetSelfController
|
||||
rc = serviceDispatch(&proxy, 1,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = &self,
|
||||
);
|
||||
if (R_FAILED(rc))
|
||||
goto fail3;
|
||||
|
||||
// Exit
|
||||
rc = serviceDispatch(&self, 0);
|
||||
|
||||
serviceClose(&self);
|
||||
|
||||
fail3:
|
||||
serviceClose(&proxy);
|
||||
|
||||
fail2:
|
||||
serviceClose(&applet);
|
||||
|
||||
fail1:
|
||||
smExit();
|
||||
|
||||
fail0:
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
while(1) svcSleepThread(86400000000000ULL);
|
||||
svcExitProcess();
|
||||
__builtin_unreachable();
|
||||
} else {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
size -= 0x6000000;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void setupHbHeap(void) {
|
||||
void* addr = NULL;
|
||||
u64 size = calculateMaxHeapSize();
|
||||
Result rc = svcSetHeapSize(&addr, size);
|
||||
|
||||
if (R_FAILED(rc) || addr==NULL)
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 9));
|
||||
|
||||
g_heapAddr = addr;
|
||||
g_heapSize = size;
|
||||
}
|
||||
|
||||
static void procHandleReceiveThread(void* arg) {
|
||||
Handle session = (Handle)(uintptr_t)arg;
|
||||
Result rc;
|
||||
|
||||
void* base = armGetTls();
|
||||
hipcMakeRequestInline(base);
|
||||
|
||||
s32 idx = 0;
|
||||
rc = svcReplyAndReceive(&idx, &session, 1, INVALID_HANDLE, UINT64_MAX);
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 15));
|
||||
|
||||
HipcParsedRequest r = hipcParseRequest(base);
|
||||
if (r.meta.num_copy_handles != 1)
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 17));
|
||||
|
||||
g_procHandle = r.data.copy_handles[0];
|
||||
svcCloseHandle(session);
|
||||
}
|
||||
|
||||
static void getOwnProcessHandle(void) {
|
||||
Result rc;
|
||||
|
||||
Handle server_handle, client_handle;
|
||||
rc = svcCreateSession(&server_handle, &client_handle, 0, 0);
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 12));
|
||||
|
||||
Thread t;
|
||||
rc = threadCreate(&t, &procHandleReceiveThread, (void*)(uintptr_t)server_handle, NULL, 0x1000, 0x20, 0);
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 10));
|
||||
|
||||
rc = threadStart(&t);
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 13));
|
||||
|
||||
hipcMakeRequestInline(armGetTls(),
|
||||
.num_copy_handles = 1,
|
||||
).copy_handles[0] = CUR_PROCESS_HANDLE;
|
||||
|
||||
svcSendSyncRequest(client_handle);
|
||||
svcCloseHandle(client_handle);
|
||||
|
||||
threadWaitForExit(&t);
|
||||
threadClose(&t);
|
||||
}
|
||||
|
||||
static bool isKernel5xOrLater(void) {
|
||||
u64 dummy = 0;
|
||||
Result rc = svcGetInfo(&dummy, InfoType_UserExceptionContextAddress, INVALID_HANDLE, 0);
|
||||
return R_VALUE(rc) != KERNELRESULT(InvalidEnumValue);
|
||||
}
|
||||
|
||||
static bool isKernel4x(void) {
|
||||
u64 dummy = 0;
|
||||
Result rc = svcGetInfo(&dummy, InfoType_InitialProcessIdRange, INVALID_HANDLE, 0);
|
||||
return R_VALUE(rc) != KERNELRESULT(InvalidEnumValue);
|
||||
}
|
||||
|
||||
static void getCodeMemoryCapability(void) {
|
||||
if (detectMesosphere()) {
|
||||
// Mesosphère allows for same-process code memory usage.
|
||||
g_codeMemoryCapability = CodeMemorySameProcess;
|
||||
} else if (isKernel5xOrLater()) {
|
||||
// On [5.0.0+], the kernel does not allow the creator process of a CodeMemory object
|
||||
// to use svcControlCodeMemory on itself, thus returning InvalidMemoryState (0xD401).
|
||||
// However the kernel can be patched to support same-process usage of CodeMemory.
|
||||
// We can detect that by passing a bad operation and observe if we actually get InvalidEnumValue (0xF001).
|
||||
Handle code;
|
||||
Result rc = svcCreateCodeMemory(&code, g_heapAddr, 0x1000);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = svcControlCodeMemory(code, (CodeMapOperation)-1, 0, 0x1000, 0);
|
||||
svcCloseHandle(code);
|
||||
|
||||
if (R_VALUE(rc) == KERNELRESULT(InvalidEnumValue))
|
||||
g_codeMemoryCapability = CodeMemorySameProcess;
|
||||
else
|
||||
g_codeMemoryCapability = CodeMemoryForeignProcess;
|
||||
}
|
||||
} else if (isKernel4x()) {
|
||||
// On [4.0.0-4.1.0] there is no such restriction on same-process CodeMemory usage.
|
||||
g_codeMemoryCapability = CodeMemorySameProcess;
|
||||
} else {
|
||||
// This kernel is too old to support CodeMemory syscalls.
|
||||
g_codeMemoryCapability = CodeMemoryUnavailable;
|
||||
}
|
||||
}
|
||||
|
||||
void NX_NORETURN loadNro(void) {
|
||||
NroHeader* header = NULL;
|
||||
size_t rw_size = 0;
|
||||
Result rc = 0;
|
||||
|
||||
memcpy((u8*)armGetTls() + 0x100, g_savedTls, 0x100);
|
||||
|
||||
// check's if the homebrew replaced nro_path.
|
||||
// if so, load new nro, otherwise, exit.
|
||||
if (!strcmp(g_nextArgv, EXIT_DETECTION_STR)) {
|
||||
if (!strcmp(g_nextNroPath, g_defaultNroPath)) {
|
||||
selfExit();
|
||||
} else {
|
||||
// exited nro, now returning to default nro
|
||||
strcpy(g_nextNroPath, g_defaultNroPath);
|
||||
strcpy(g_nextArgv, g_defaultArgv);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_nroSize) {
|
||||
// checks if nro was previously mapped, if so, unmap
|
||||
header = &g_nroHeader;
|
||||
rw_size = header->segments[2].size + header->bss_size;
|
||||
rw_size = (rw_size+0xFFF) & ~0xFFF;
|
||||
|
||||
if (R_FAILED(rc = svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PreUnloadDll, g_nroAddr, g_nroSize))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
// .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))
|
||||
diagAbortWithResult(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))
|
||||
diagAbortWithResult(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))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 26));
|
||||
|
||||
if (R_FAILED(rc = svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PostUnloadDll, g_nroAddr, g_nroSize))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
g_nroAddr = g_nroSize = 0;
|
||||
} else {
|
||||
// otherwise, this is the first time launching, read path / argv from romfs
|
||||
FsStorage s;
|
||||
romfs_header romfs_header;
|
||||
|
||||
if (R_FAILED(rc = fsOpenDataStorageByCurrentProcess(&s))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsStorageRead(&s, 0, &romfs_header, sizeof(romfs_header)))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
u8* romfs_dirs = malloc(romfs_header.dirTableSize); // should be 1 entry ("/")
|
||||
u8* romfs_files = malloc(romfs_header.fileTableSize); // should be 2 entries (argv and nro)
|
||||
|
||||
if (!romfs_dirs || !romfs_files) {
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, LibnxError_OutOfMemory));
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsStorageRead(&s, romfs_header.dirTableOff, romfs_dirs, romfs_header.dirTableSize))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsStorageRead(&s, romfs_header.fileTableOff, romfs_files, romfs_header.fileTableSize))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
const romfs_dir* dir = (const romfs_dir*)romfs_dirs;
|
||||
const romfs_file* next_argv_file = (const romfs_file*)(romfs_files + dir->childFile);
|
||||
const romfs_file* next_nro_file = (const romfs_file*)(romfs_files + next_argv_file->sibling);
|
||||
|
||||
if (R_FAILED(rc = fsStorageRead(&s, romfs_header.fileDataOff + next_argv_file->dataOff, g_nextArgv, next_argv_file->dataSize))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsStorageRead(&s, romfs_header.fileDataOff + next_nro_file->dataOff, g_nextNroPath, next_nro_file->dataSize))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
free(romfs_dirs);
|
||||
free(romfs_files);
|
||||
fsStorageClose(&s);
|
||||
|
||||
strcpy(g_defaultNroPath, g_nextNroPath);
|
||||
strcpy(g_defaultArgv, g_nextArgv);
|
||||
}
|
||||
|
||||
{
|
||||
// fix paths
|
||||
char fixedNextNroPath[FS_MAX_PATH];
|
||||
strcpy(fixedNextNroPath, g_nextNroPath);
|
||||
fix_nro_path(fixedNextNroPath);
|
||||
|
||||
memcpy(g_argv, g_nextArgv, sizeof(g_argv));
|
||||
if (R_FAILED(rc = svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PreLoadDll, (uintptr_t)g_argv, sizeof(g_argv)))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
FsFileSystem fs;
|
||||
FsFile f;
|
||||
u64 bytes_read = 0;
|
||||
s64 offset = 0;
|
||||
|
||||
if (R_FAILED(rc = fsOpenSdCardFileSystem(&fs))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
// don't fatal if we don't find the nro, exit to menu
|
||||
if (R_FAILED(rc = fsFsOpenFile(&fs, fixedNextNroPath, FsOpenMode_Read, &f))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsFileRead(&f, offset, start, sizeof(*start), FsReadOption_None, &bytes_read)) ||
|
||||
bytes_read != sizeof(*start)) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
offset += sizeof(*start);
|
||||
|
||||
if (R_FAILED(rc = fsFileRead(&f, offset, header, sizeof(*header), FsReadOption_None, &bytes_read)) ||
|
||||
bytes_read != sizeof(*header) || header->magic != NROHEADER_MAGIC) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
offset += sizeof(*header);
|
||||
|
||||
const size_t rest_size = header->size - (sizeof(NroStart) + sizeof(NroHeader));
|
||||
if (R_FAILED(rc = fsFileRead(&f, offset, rest, rest_size, FsReadOption_None, &bytes_read)) ||
|
||||
bytes_read != rest_size) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
fsFileClose(&f);
|
||||
fsFsClose(&fs);
|
||||
}
|
||||
|
||||
rw_size = header->segments[2].size + header->bss_size;
|
||||
rw_size = (rw_size+0xFFF) & ~0xFFF;
|
||||
|
||||
for (int 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)
|
||||
{
|
||||
diagAbortWithResult(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;
|
||||
|
||||
// Map code memory to a new randomized address
|
||||
virtmemLock();
|
||||
const size_t total_size = (header->size + header->bss_size + 0xFFF) & ~0xFFF;
|
||||
void* map_addr = virtmemFindCodeMemory(total_size, 0);
|
||||
rc = svcMapProcessCodeMemory(g_procHandle, (u64)map_addr, (u64)g_heapAddr, total_size);
|
||||
virtmemUnlock();
|
||||
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 18));
|
||||
|
||||
// .text
|
||||
rc = svcSetProcessMemoryPermission(
|
||||
g_procHandle, (u64)map_addr + header->segments[0].file_off, header->segments[0].size, Perm_R | Perm_X);
|
||||
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(rc);
|
||||
|
||||
// .rodata
|
||||
rc = svcSetProcessMemoryPermission(
|
||||
g_procHandle, (u64)map_addr + header->segments[1].file_off, header->segments[1].size, Perm_R);
|
||||
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(rc);
|
||||
|
||||
// .data + .bss
|
||||
rc = svcSetProcessMemoryPermission(
|
||||
g_procHandle, (u64)map_addr + header->segments[2].file_off, rw_size, Perm_Rw);
|
||||
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(rc);
|
||||
|
||||
const u64 nro_size = header->segments[2].file_off + rw_size;
|
||||
const u64 nro_heap_start = ((u64) g_heapAddr) + nro_size;
|
||||
const 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_SystemApplication, EnvAppletFlags_ApplicationOverride} },
|
||||
{ EntryType_OverrideHeap, M, {0, 0} },
|
||||
{ EntryType_Argv, 0, {0, 0} },
|
||||
{ EntryType_NextLoadPath, 0, {0, 0} },
|
||||
{ EntryType_LastLoadResult, 0, {0, 0} },
|
||||
{ EntryType_SyscallAvailableHint, 0, {UINT64_MAX, UINT64_MAX} },
|
||||
{ EntryType_SyscallAvailableHint2, 0, {UINT64_MAX, 0} },
|
||||
{ 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_Syscalls = &entries[7];
|
||||
|
||||
if (!(g_codeMemoryCapability & BIT(0))) {
|
||||
// Revoke access to svcCreateCodeMemory if it's not available.
|
||||
entry_Syscalls->Value[0x4B/64] &= ~(1UL << (0x4B%64));
|
||||
}
|
||||
|
||||
if (!(g_codeMemoryCapability & BIT(1))) {
|
||||
// Revoke access to svcControlCodeMemory if it's not available for same-process usage.
|
||||
entry_Syscalls->Value[0x4C/64] &= ~(1UL << (0x4C%64)); // svcControlCodeMemory
|
||||
}
|
||||
|
||||
// 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)(uintptr_t)&g_argv[0];
|
||||
// NextLoadPath
|
||||
entries[5].Value[0] = (u64)(uintptr_t)&g_nextNroPath[0];
|
||||
entries[5].Value[1] = (u64)(uintptr_t)&g_nextArgv[0];
|
||||
// LastLoadResult
|
||||
entries[6].Value[0] = g_lastRet;
|
||||
// RandomSeed
|
||||
entries[9].Value[0] = randomGet64();
|
||||
entries[9].Value[1] = randomGet64();
|
||||
// HosVersion
|
||||
entries[11].Value[0] = hosversionGet();
|
||||
entries[11].Value[1] = hosversionIsAtmosphere() ? 0x41544d4f53504852UL : 0; // 'ATMOSPHR'
|
||||
|
||||
g_nroAddr = (u64)map_addr;
|
||||
g_nroSize = nro_size;
|
||||
|
||||
svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PostLoadDll, g_nroAddr, nro_size);
|
||||
|
||||
// write exit detection
|
||||
strcpy(g_nextArgv, EXIT_DETECTION_STR);
|
||||
// jump to trampoline.s
|
||||
nroEntrypointTrampoline(&entries[0], -1, g_nroAddr);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
memcpy(g_savedTls, (const u8*)armGetTls() + 0x100, 0x100);
|
||||
setupHbHeap();
|
||||
getOwnProcessHandle();
|
||||
getCodeMemoryCapability();
|
||||
loadNro();
|
||||
}
|
||||
|
||||
// libnx stuff
|
||||
u32 __nx_applet_type = AppletType_Application;
|
||||
// Minimize fs resource usage
|
||||
u32 __nx_fs_num_sessions = 1;
|
||||
// these aren't needed, keeping them as someone will eventually
|
||||
// copy this code, use fsdev, and not add back these vars.
|
||||
u32 __nx_fsdev_direntry_cache_size = 1;
|
||||
bool __nx_fsdev_support_cwd = false;
|
||||
// enable to always exit to homemenu, dbi does this.
|
||||
// u32 __nx_applet_exit_mode = 1;
|
||||
// u32 __nx_applet_exit_mode = 0;
|
||||
|
||||
void __libnx_initheap(void) {
|
||||
static char g_innerheap[0x4000];
|
||||
|
||||
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;
|
||||
|
||||
// Detect Atmosphère early on. This is required for hosversion logic.
|
||||
// In the future, this check will be replaced by detectMesosphere().
|
||||
Handle dummy;
|
||||
rc = svcConnectToNamedPort(&dummy, "ams");
|
||||
u32 ams_flag = (R_VALUE(rc) != KERNELRESULT(NotFound)) ? BIT(31) : 0;
|
||||
if (R_SUCCEEDED(rc))
|
||||
svcCloseHandle(dummy);
|
||||
|
||||
rc = smInitialize();
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, LibnxError_InitFail_SM));
|
||||
|
||||
rc = setsysInitialize();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
SetSysFirmwareVersion fw;
|
||||
rc = setsysGetFirmwareVersion(&fw);
|
||||
if (R_SUCCEEDED(rc))
|
||||
hosversionSet(ams_flag | MAKEHOSVERSION(fw.major, fw.minor, fw.micro));
|
||||
setsysExit();
|
||||
}
|
||||
|
||||
rc = fsInitialize();
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, LibnxError_InitFail_FS));
|
||||
|
||||
smExit(); // Close SM as we don't need it anymore.
|
||||
}
|
||||
|
||||
void __appExit(void) {
|
||||
|
||||
}
|
||||
|
||||
void __wrap_exit(void) {
|
||||
// exit() effectively never gets called, so let's stub it out.
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 39));
|
||||
}
|
||||
47
hbl/source/trampoline.s
Normal file
47
hbl/source/trampoline.s
Normal file
@@ -0,0 +1,47 @@
|
||||
.section .text.nroEntrypointTrampoline, "ax", %progbits
|
||||
.align 2
|
||||
.global nroEntrypointTrampoline
|
||||
.type nroEntrypointTrampoline, %function
|
||||
.cfi_startproc
|
||||
nroEntrypointTrampoline:
|
||||
|
||||
// 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
|
||||
.global __libnx_exception_entry
|
||||
.type __libnx_exception_entry, %function
|
||||
.cfi_startproc
|
||||
__libnx_exception_entry:
|
||||
|
||||
// Divert execution to the NRO entrypoint (if a NRO is actually loaded).
|
||||
adrp x7, g_nroAddr
|
||||
ldr x7, [x7, #:lo12:g_nroAddr]
|
||||
cbz x7, .Lfail
|
||||
br x7
|
||||
|
||||
.Lfail:
|
||||
// Otherwise, pass this unhandled exception right back to the kernel.
|
||||
mov w0, #0xf801 // KERNELRESULT(UnhandledUserInterrupt)
|
||||
svc 0x28 // svcReturnFromException
|
||||
|
||||
.cfi_endproc
|
||||
Reference in New Issue
Block a user