├── .gitignore ├── LICENSE.md ├── Makefile ├── hbl.json └── source ├── main.c └── trampoline.s /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.elf 3 | *.nso 4 | *.npdm 5 | *.nsp 6 | *.pfs0 7 | *.db 8 | *.zip 9 | *~ 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2017-2018 nx-hbloader Authors 2 | 3 | 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. 4 | 5 | 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITPRO)/libnx/switch_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 19 | # 20 | # NO_ICON: if set to anything, do not use icon. 21 | # NO_NACP: if set to anything, no .nacp file is generated. 22 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 23 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 24 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 25 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 26 | # ICON is the filename of the icon (.jpg), relative to the project folder. 27 | # If not set, it attempts to use one of the following (in this order): 28 | # - .jpg 29 | # - icon.jpg 30 | # - /default_icon.jpg 31 | # 32 | # CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. 33 | # If not set, it attempts to use one of the following (in this order): 34 | # - .json 35 | # - config.json 36 | # If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead 37 | # of a homebrew executable (.nro). This is intended to be used for sysmodules. 38 | # NACP building is skipped as well. 39 | #--------------------------------------------------------------------------------- 40 | TARGET := hbl 41 | BUILD := build 42 | SOURCES := source 43 | DATA := data 44 | INCLUDES := include 45 | #ROMFS := romfs 46 | APP_VERSION := 2.4.4 47 | 48 | ifeq ($(RELEASE),) 49 | APP_VERSION := $(APP_VERSION)-$(shell git describe --dirty --always) 50 | endif 51 | 52 | #--------------------------------------------------------------------------------- 53 | # options for code generation 54 | #--------------------------------------------------------------------------------- 55 | ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE 56 | 57 | CFLAGS := -g -Wall -O2 -ffunction-sections \ 58 | $(ARCH) $(DEFINES) 59 | 60 | CFLAGS += $(INCLUDE) -D__SWITCH__ -DVERSION=\"v$(APP_VERSION)\" 61 | 62 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions 63 | 64 | ASFLAGS := -g $(ARCH) 65 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-wrap,exit -Wl,-Map,$(notdir $*.map) 66 | 67 | LIBS := -lnx 68 | 69 | #--------------------------------------------------------------------------------- 70 | # list of directories containing libraries, this must be the top level containing 71 | # include and lib 72 | #--------------------------------------------------------------------------------- 73 | LIBDIRS := $(PORTLIBS) $(LIBNX) 74 | 75 | 76 | #--------------------------------------------------------------------------------- 77 | # no real need to edit anything past this point unless you need to add additional 78 | # rules for different file extensions 79 | #--------------------------------------------------------------------------------- 80 | ifneq ($(BUILD),$(notdir $(CURDIR))) 81 | #--------------------------------------------------------------------------------- 82 | 83 | export OUTPUT := $(CURDIR)/$(TARGET) 84 | export TOPDIR := $(CURDIR) 85 | 86 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 87 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 88 | 89 | export DEPSDIR := $(CURDIR)/$(BUILD) 90 | 91 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 92 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 93 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 94 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 95 | 96 | #--------------------------------------------------------------------------------- 97 | # use CXX for linking C++ projects, CC for standard C 98 | #--------------------------------------------------------------------------------- 99 | ifeq ($(strip $(CPPFILES)),) 100 | #--------------------------------------------------------------------------------- 101 | export LD := $(CC) 102 | #--------------------------------------------------------------------------------- 103 | else 104 | #--------------------------------------------------------------------------------- 105 | export LD := $(CXX) 106 | #--------------------------------------------------------------------------------- 107 | endif 108 | #--------------------------------------------------------------------------------- 109 | 110 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 111 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 112 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 113 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 114 | 115 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 116 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 117 | -I$(CURDIR)/$(BUILD) 118 | 119 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 120 | 121 | ifeq ($(strip $(CONFIG_JSON)),) 122 | jsons := $(wildcard *.json) 123 | ifneq (,$(findstring $(TARGET).json,$(jsons))) 124 | export APP_JSON := $(TOPDIR)/$(TARGET).json 125 | else 126 | ifneq (,$(findstring config.json,$(jsons))) 127 | export APP_JSON := $(TOPDIR)/config.json 128 | endif 129 | endif 130 | else 131 | export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) 132 | endif 133 | 134 | ifeq ($(strip $(ICON)),) 135 | icons := $(wildcard *.jpg) 136 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 137 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 138 | else 139 | ifneq (,$(findstring icon.jpg,$(icons))) 140 | export APP_ICON := $(TOPDIR)/icon.jpg 141 | endif 142 | endif 143 | else 144 | export APP_ICON := $(TOPDIR)/$(ICON) 145 | endif 146 | 147 | ifeq ($(strip $(NO_ICON)),) 148 | export NROFLAGS += --icon=$(APP_ICON) 149 | endif 150 | 151 | ifeq ($(strip $(NO_NACP)),) 152 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 153 | endif 154 | 155 | ifneq ($(APP_TITLEID),) 156 | export NACPFLAGS += --titleid=$(APP_TITLEID) 157 | endif 158 | 159 | ifneq ($(ROMFS),) 160 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 161 | endif 162 | 163 | .PHONY: $(BUILD) clean all 164 | 165 | #--------------------------------------------------------------------------------- 166 | all: $(BUILD) 167 | 168 | $(BUILD): 169 | @[ -d $@ ] || mkdir -p $@ 170 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 171 | 172 | #--------------------------------------------------------------------------------- 173 | clean: 174 | @echo clean ... 175 | ifeq ($(strip $(APP_JSON)),) 176 | @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf 177 | else 178 | @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf 179 | endif 180 | 181 | 182 | #--------------------------------------------------------------------------------- 183 | else 184 | .PHONY: all 185 | 186 | DEPENDS := $(OFILES:.o=.d) 187 | 188 | #--------------------------------------------------------------------------------- 189 | # main targets 190 | #--------------------------------------------------------------------------------- 191 | ifeq ($(strip $(APP_JSON)),) 192 | 193 | all : $(OUTPUT).nro 194 | 195 | ifeq ($(strip $(NO_NACP)),) 196 | $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp 197 | else 198 | $(OUTPUT).nro : $(OUTPUT).elf 199 | endif 200 | 201 | else 202 | 203 | all : $(OUTPUT).nsp 204 | 205 | $(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm 206 | 207 | $(OUTPUT).nso : $(OUTPUT).elf 208 | 209 | endif 210 | 211 | $(OUTPUT).elf : $(OFILES) 212 | 213 | $(OFILES_SRC) : $(HFILES_BIN) 214 | 215 | #--------------------------------------------------------------------------------- 216 | # you need a rule like this for each extension you use as binary data 217 | #--------------------------------------------------------------------------------- 218 | %.bin.o %_bin.h : %.bin 219 | #--------------------------------------------------------------------------------- 220 | @echo $(notdir $<) 221 | @$(bin2o) 222 | 223 | -include $(DEPENDS) 224 | 225 | #--------------------------------------------------------------------------------------- 226 | endif 227 | #--------------------------------------------------------------------------------------- 228 | -------------------------------------------------------------------------------- /hbl.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hbloader", 3 | "title_id": "0x010000000000100D", 4 | "title_id_range_min": "0x010000000000100D", 5 | "title_id_range_max": "0x010000000000100D", 6 | "main_thread_stack_size": "0x100000", 7 | "main_thread_priority": 44, 8 | "default_cpu_id": 0, 9 | "process_category": 0, 10 | "pool_partition": 0, 11 | "is_64_bit": true, 12 | "address_space_type": 1, 13 | "is_retail": true, 14 | "filesystem_access": { 15 | "permissions": "0xFFFFFFFFFFFFFFFF", 16 | "content_owner_ids": [ 17 | "0x0100000000001000" 18 | ] 19 | }, 20 | "service_host": [ 21 | "*" 22 | ], 23 | "service_access": [ 24 | "*" 25 | ], 26 | "kernel_capabilities": [ 27 | { 28 | "type": "kernel_flags", 29 | "value": { 30 | "highest_thread_priority": 59, 31 | "lowest_thread_priority": 28, 32 | "highest_cpu_id": 2, 33 | "lowest_cpu_id": 0 34 | } 35 | }, 36 | { 37 | "type": "syscalls", 38 | "value": { 39 | "svcUnassigned00": "0x00", 40 | "svcSetHeapSize": "0x01", 41 | "svcSetMemoryPermission": "0x02", 42 | "svcSetMemoryAttribute": "0x03", 43 | "svcMapMemory": "0x04", 44 | "svcUnmapMemory": "0x05", 45 | "svcQueryMemory": "0x06", 46 | "svcExitProcess": "0x07", 47 | "svcCreateThread": "0x08", 48 | "svcStartThread": "0x09", 49 | "svcExitThread": "0x0A", 50 | "svcSleepThread": "0x0B", 51 | "svcGetThreadPriority": "0x0C", 52 | "svcSetThreadPriority": "0x0D", 53 | "svcGetThreadCoreMask": "0x0E", 54 | "svcSetThreadCoreMask": "0x0F", 55 | "svcGetCurrentProcessorNumber": "0x10", 56 | "svcSignalEvent": "0x11", 57 | "svcClearEvent": "0x12", 58 | "svcMapSharedMemory": "0x13", 59 | "svcUnmapSharedMemory": "0x14", 60 | "svcCreateTransferMemory": "0x15", 61 | "svcCloseHandle": "0x16", 62 | "svcResetSignal": "0x17", 63 | "svcWaitSynchronization": "0x18", 64 | "svcCancelSynchronization": "0x19", 65 | "svcArbitrateLock": "0x1A", 66 | "svcArbitrateUnlock": "0x1B", 67 | "svcWaitProcessWideKeyAtomic": "0x1C", 68 | "svcSignalProcessWideKey": "0x1D", 69 | "svcGetSystemTick": "0x1E", 70 | "svcConnectToNamedPort": "0x1F", 71 | "svcSendSyncRequestLight": "0x20", 72 | "svcSendSyncRequest": "0x21", 73 | "svcSendSyncRequestWithUserBuffer": "0x22", 74 | "svcSendAsyncRequestWithUserBuffer": "0x23", 75 | "svcGetProcessId": "0x24", 76 | "svcGetThreadId": "0x25", 77 | "svcBreak": "0x26", 78 | "svcOutputDebugString": "0x27", 79 | "svcReturnFromException": "0x28", 80 | "svcGetInfo": "0x29", 81 | "svcFlushEntireDataCache": "0x2A", 82 | "svcFlushDataCache": "0x2B", 83 | "svcMapPhysicalMemory": "0x2C", 84 | "svcUnmapPhysicalMemory": "0x2D", 85 | "svcGetDebugFutureThreadInfo": "0x2E", 86 | "svcGetLastThreadInfo": "0x2F", 87 | "svcGetResourceLimitLimitValue": "0x30", 88 | "svcGetResourceLimitCurrentValue": "0x31", 89 | "svcSetThreadActivity": "0x32", 90 | "svcGetThreadContext3": "0x33", 91 | "svcWaitForAddress": "0x34", 92 | "svcSignalToAddress": "0x35", 93 | "svcSynchronizePreemptionState": "0x36", 94 | "svcGetResourceLimitPeakValue": "0x37", 95 | "svcUnassigned38": "0x38", 96 | "svcCreateIoPool": "0x39", 97 | "svcCreateIoRegion": "0x3A", 98 | "svcUnassigned3B": "0x3B", 99 | "svcKernelDebug": "0x3C", 100 | "svcChangeKernelTraceState": "0x3D", 101 | "svcUnassigned3E": "0x3E", 102 | "svcUnassigned3F": "0x3F", 103 | "svcCreateSession": "0x40", 104 | "svcAcceptSession": "0x41", 105 | "svcReplyAndReceiveLight": "0x42", 106 | "svcReplyAndReceive": "0x43", 107 | "svcReplyAndReceiveWithUserBuffer": "0x44", 108 | "svcCreateEvent": "0x45", 109 | "svcMapIoRegion": "0x46", 110 | "svcUnmapIoRegion": "0x47", 111 | "svcMapPhysicalMemoryUnsafe": "0x48", 112 | "svcUnmapPhysicalMemoryUnsafe": "0x49", 113 | "svcSetUnsafeLimit": "0x4A", 114 | "svcCreateCodeMemory": "0x4B", 115 | "svcControlCodeMemory": "0x4C", 116 | "svcSleepSystem": "0x4D", 117 | "svcReadWriteRegister": "0x4E", 118 | "svcSetProcessActivity": "0x4F", 119 | "svcCreateSharedMemory": "0x50", 120 | "svcMapTransferMemory": "0x51", 121 | "svcUnmapTransferMemory": "0x52", 122 | "svcCreateInterruptEvent": "0x53", 123 | "svcQueryPhysicalAddress": "0x54", 124 | "svcQueryIoMapping": "0x55", 125 | "svcCreateDeviceAddressSpace": "0x56", 126 | "svcAttachDeviceAddressSpace": "0x57", 127 | "svcDetachDeviceAddressSpace": "0x58", 128 | "svcMapDeviceAddressSpaceByForce": "0x59", 129 | "svcMapDeviceAddressSpaceAligned": "0x5A", 130 | "svcMapDeviceAddressSpace": "0x5B", 131 | "svcUnmapDeviceAddressSpace": "0x5C", 132 | "svcInvalidateProcessDataCache": "0x5D", 133 | "svcStoreProcessDataCache": "0x5E", 134 | "svcFlushProcessDataCache": "0x5F", 135 | "svcDebugActiveProcess": "0x60", 136 | "svcBreakDebugProcess": "0x61", 137 | "svcTerminateDebugProcess": "0x62", 138 | "svcGetDebugEvent": "0x63", 139 | "svcContinueDebugEvent": "0x64", 140 | "svcGetProcessList": "0x65", 141 | "svcGetThreadList": "0x66", 142 | "svcGetDebugThreadContext": "0x67", 143 | "svcSetDebugThreadContext": "0x68", 144 | "svcQueryDebugProcessMemory": "0x69", 145 | "svcReadDebugProcessMemory": "0x6A", 146 | "svcWriteDebugProcessMemory": "0x6B", 147 | "svcSetHardwareBreakPoint": "0x6C", 148 | "svcGetDebugThreadParam": "0x6D", 149 | "svcUnassigned6E": "0x6E", 150 | "svcGetSystemInfo": "0x6F", 151 | "svcCreatePort": "0x70", 152 | "svcManageNamedPort": "0x71", 153 | "svcConnectToPort": "0x72", 154 | "svcSetProcessMemoryPermission": "0x73", 155 | "svcMapProcessMemory": "0x74", 156 | "svcUnmapProcessMemory": "0x75", 157 | "svcQueryProcessMemory": "0x76", 158 | "svcMapProcessCodeMemory": "0x77", 159 | "svcUnmapProcessCodeMemory": "0x78", 160 | "svcCreateProcess": "0x79", 161 | "svcStartProcess": "0x7A", 162 | "svcTerminateProcess": "0x7B", 163 | "svcGetProcessInfo": "0x7C", 164 | "svcCreateResourceLimit": "0x7D", 165 | "svcSetResourceLimitLimitValue": "0x7E", 166 | "svcCallSecureMonitor": "0x7F", 167 | "svcUnassigned80": "0x80", 168 | "svcUnassigned81": "0x81", 169 | "svcUnassigned82": "0x82", 170 | "svcUnassigned83": "0x83", 171 | "svcUnassigned84": "0x84", 172 | "svcUnassigned85": "0x85", 173 | "svcUnassigned86": "0x86", 174 | "svcUnassigned87": "0x87", 175 | "svcUnassigned88": "0x88", 176 | "svcUnassigned89": "0x89", 177 | "svcUnassigned8A": "0x8A", 178 | "svcUnassigned8B": "0x8B", 179 | "svcUnassigned8C": "0x8C", 180 | "svcUnassigned8D": "0x8D", 181 | "svcUnassigned8E": "0x8E", 182 | "svcUnassigned8F": "0x8F", 183 | "svcMapInsecureMemory": "0x90", 184 | "svcUnmapInsecureMemory": "0x91", 185 | "svcUnassigned92": "0x92", 186 | "svcUnassigned93": "0x93", 187 | "svcUnassigned94": "0x94", 188 | "svcUnassigned95": "0x95", 189 | "svcUnassigned96": "0x96", 190 | "svcUnassigned97": "0x97", 191 | "svcUnassigned98": "0x98", 192 | "svcUnassigned99": "0x99", 193 | "svcUnassigned9A": "0x9A", 194 | "svcUnassigned9B": "0x9B", 195 | "svcUnassigned9C": "0x9C", 196 | "svcUnassigned9D": "0x9D", 197 | "svcUnassigned9E": "0x9E", 198 | "svcUnassigned9F": "0x9F", 199 | "svcUnassignedA0": "0xA0", 200 | "svcUnassignedA1": "0xA1", 201 | "svcUnassignedA2": "0xA2", 202 | "svcUnassignedA3": "0xA3", 203 | "svcUnassignedA4": "0xA4", 204 | "svcUnassignedA5": "0xA5", 205 | "svcUnassignedA6": "0xA6", 206 | "svcUnassignedA7": "0xA7", 207 | "svcUnassignedA8": "0xA8", 208 | "svcUnassignedA9": "0xA9", 209 | "svcUnassignedAA": "0xAA", 210 | "svcUnassignedAB": "0xAB", 211 | "svcUnassignedAC": "0xAC", 212 | "svcUnassignedAD": "0xAD", 213 | "svcUnassignedAE": "0xAE", 214 | "svcUnassignedAF": "0xAF", 215 | "svcUnassignedB0": "0xB0", 216 | "svcUnassignedB1": "0xB1", 217 | "svcUnassignedB2": "0xB2", 218 | "svcUnassignedB3": "0xB3", 219 | "svcUnassignedB4": "0xB4", 220 | "svcUnassignedB5": "0xB5", 221 | "svcUnassignedB6": "0xB6", 222 | "svcUnassignedB7": "0xB7", 223 | "svcUnassignedB8": "0xB8", 224 | "svcUnassignedB9": "0xB9", 225 | "svcUnassignedBA": "0xBA", 226 | "svcUnassignedBB": "0xBB", 227 | "svcUnassignedBC": "0xBC", 228 | "svcUnassignedBD": "0xBD", 229 | "svcUnassignedBE": "0xBE", 230 | "svcUnassignedBF": "0xBF" 231 | } 232 | }, 233 | { 234 | "type": "application_type", 235 | "value": 2 236 | }, 237 | { 238 | "type": "min_kernel_version", 239 | "value": "0x30" 240 | }, 241 | { 242 | "type": "handle_table_size", 243 | "value": 512 244 | }, 245 | { 246 | "type": "debug_flags", 247 | "value": { 248 | "allow_debug": false, 249 | "force_debug_prod": false, 250 | "force_debug": true 251 | } 252 | }, 253 | { 254 | "type": "map_region", 255 | "value": [ 256 | { 257 | "region_type": 1, 258 | "is_ro": true 259 | } 260 | ] 261 | } 262 | ] 263 | } 264 | -------------------------------------------------------------------------------- /source/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define DEFAULT_NRO "sdmc:/hbmenu.nro" 7 | 8 | const char g_noticeText[] = 9 | "nx-hbloader " VERSION "\0" 10 | "Do you mean to tell me that you're thinking seriously of building that way, when and if you are an architect?"; 11 | 12 | static char g_argv[2048]; 13 | static char g_nextArgv[2048]; 14 | static char g_nextNroPath[512]; 15 | u64 g_nroAddr = 0; 16 | static u64 g_nroSize = 0; 17 | static NroHeader g_nroHeader; 18 | static bool g_isApplication = 0; 19 | 20 | static bool g_isAutomaticGameplayRecording = 0; 21 | static enum { 22 | CodeMemoryUnavailable = 0, 23 | CodeMemoryForeignProcess = BIT(0), 24 | CodeMemorySameProcess = BIT(0) | BIT(1), 25 | } g_codeMemoryCapability = CodeMemoryUnavailable; 26 | 27 | static Handle g_procHandle; 28 | 29 | static void* g_heapAddr; 30 | static size_t g_heapSize; 31 | 32 | static u64 g_appletHeapSize = 0; 33 | static u64 g_appletHeapReservationSize = 0; 34 | 35 | static u128 g_userIdStorage; 36 | 37 | static u8 g_savedTls[0x100]; 38 | 39 | // Minimize fs resource usage 40 | u32 __nx_fs_num_sessions = 1; 41 | u32 __nx_fsdev_direntry_cache_size = 1; 42 | bool __nx_fsdev_support_cwd = false; 43 | 44 | // Used by trampoline.s 45 | Result g_lastRet = 0; 46 | 47 | void NX_NORETURN nroEntrypointTrampoline(const ConfigEntry* entries, u64 handle, u64 entrypoint); 48 | 49 | void __libnx_initheap(void) 50 | { 51 | static char g_innerheap[0x4000]; 52 | 53 | extern char* fake_heap_start; 54 | extern char* fake_heap_end; 55 | 56 | fake_heap_start = &g_innerheap[0]; 57 | fake_heap_end = &g_innerheap[sizeof g_innerheap]; 58 | } 59 | 60 | static Result readSetting(const char* key, void* buf, size_t size) 61 | { 62 | Result rc; 63 | u64 actual_size; 64 | const char* const section_name = "hbloader"; 65 | rc = setsysGetSettingsItemValueSize(section_name, key, &actual_size); 66 | if (R_SUCCEEDED(rc) && actual_size != size) 67 | rc = MAKERESULT(Module_Libnx, LibnxError_BadInput); 68 | if (R_SUCCEEDED(rc)) 69 | rc = setsysGetSettingsItemValue(section_name, key, buf, size, &actual_size); 70 | if (R_SUCCEEDED(rc) && actual_size != size) 71 | rc = MAKERESULT(Module_Libnx, LibnxError_BadInput); 72 | if (R_FAILED(rc)) memset(buf, 0, size); 73 | return rc; 74 | } 75 | 76 | void __appInit(void) 77 | { 78 | Result rc; 79 | 80 | // Detect Atmosphère early on. This is required for hosversion logic. 81 | // In the future, this check will be replaced by detectMesosphere(). 82 | Handle dummy; 83 | rc = svcConnectToNamedPort(&dummy, "ams"); 84 | u32 ams_flag = (R_VALUE(rc) != KERNELRESULT(NotFound)) ? BIT(31) : 0; 85 | if (R_SUCCEEDED(rc)) 86 | svcCloseHandle(dummy); 87 | 88 | rc = smInitialize(); 89 | if (R_FAILED(rc)) 90 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 1)); 91 | 92 | rc = setsysInitialize(); 93 | if (R_SUCCEEDED(rc)) { 94 | SetSysFirmwareVersion fw; 95 | rc = setsysGetFirmwareVersion(&fw); 96 | if (R_SUCCEEDED(rc)) 97 | hosversionSet(ams_flag | MAKEHOSVERSION(fw.major, fw.minor, fw.micro)); 98 | readSetting("applet_heap_size", &g_appletHeapSize, sizeof(g_appletHeapSize)); 99 | readSetting("applet_heap_reservation_size", &g_appletHeapReservationSize, sizeof(g_appletHeapReservationSize)); 100 | setsysExit(); 101 | } 102 | 103 | rc = fsInitialize(); 104 | if (R_FAILED(rc)) 105 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 2)); 106 | } 107 | 108 | void __wrap_exit(void) 109 | { 110 | // exit() effectively never gets called, so let's stub it out. 111 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 39)); 112 | } 113 | 114 | static u64 calculateMaxHeapSize(void) 115 | { 116 | u64 size = 0; 117 | u64 mem_available = 0, mem_used = 0; 118 | 119 | svcGetInfo(&mem_available, InfoType_TotalMemorySize, CUR_PROCESS_HANDLE, 0); 120 | svcGetInfo(&mem_used, InfoType_UsedMemorySize, CUR_PROCESS_HANDLE, 0); 121 | 122 | if (mem_available > mem_used+0x200000) 123 | size = (mem_available - mem_used - 0x200000) & ~0x1FFFFF; 124 | if (size == 0) 125 | size = 0x2000000*16; 126 | if (size > 0x6000000 && g_isAutomaticGameplayRecording) 127 | size -= 0x6000000; 128 | 129 | return size; 130 | } 131 | 132 | static void setupHbHeap(void) 133 | { 134 | void* addr = NULL; 135 | u64 size = calculateMaxHeapSize(); 136 | 137 | if (!g_isApplication) { 138 | if (g_appletHeapSize) { 139 | u64 requested_size = (g_appletHeapSize + 0x1FFFFF) &~ 0x1FFFFF; 140 | if (requested_size < size) 141 | size = requested_size; 142 | } 143 | else if (g_appletHeapReservationSize) { 144 | u64 reserved_size = (g_appletHeapReservationSize + 0x1FFFFF) &~ 0x1FFFFF; 145 | if (reserved_size < size) 146 | size -= reserved_size; 147 | } 148 | } 149 | 150 | Result rc = svcSetHeapSize(&addr, size); 151 | 152 | if (R_FAILED(rc) || addr==NULL) 153 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 9)); 154 | 155 | g_heapAddr = addr; 156 | g_heapSize = size; 157 | } 158 | 159 | static void procHandleReceiveThread(void* arg) 160 | { 161 | Handle session = (Handle)(uintptr_t)arg; 162 | Result rc; 163 | 164 | void* base = armGetTls(); 165 | hipcMakeRequestInline(base); 166 | 167 | s32 idx = 0; 168 | rc = svcReplyAndReceive(&idx, &session, 1, INVALID_HANDLE, UINT64_MAX); 169 | if (R_FAILED(rc)) 170 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 15)); 171 | 172 | HipcParsedRequest r = hipcParseRequest(base); 173 | if (r.meta.num_copy_handles != 1) 174 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 17)); 175 | 176 | g_procHandle = r.data.copy_handles[0]; 177 | svcCloseHandle(session); 178 | } 179 | 180 | // Sets g_isApplication if the hbloader process is running as an Application. 181 | static void getIsApplication(void) 182 | { 183 | Result rc; 184 | 185 | // Try asking the kernel directly (only works on [9.0.0+] or mesosphère) 186 | u64 flag=0; 187 | rc = svcGetInfo(&flag, InfoType_IsApplication, CUR_PROCESS_HANDLE, 0); 188 | if (R_SUCCEEDED(rc)) { 189 | g_isApplication = flag!=0; 190 | return; 191 | } 192 | 193 | // Retrieve our process' PID 194 | u64 cur_pid=0; 195 | rc = svcGetProcessId(&cur_pid, CUR_PROCESS_HANDLE); 196 | if (R_FAILED(rc)) diagAbortWithResult(rc); // shouldn't happen 197 | 198 | // Try reading the current application PID through pm:shell - if it matches ours then we are indeed an application 199 | rc = pmshellInitialize(); 200 | if (R_SUCCEEDED(rc)) { 201 | u64 app_pid=0; 202 | rc = pmshellGetApplicationProcessIdForShell(&app_pid); 203 | pmshellExit(); 204 | 205 | if (cur_pid == app_pid) 206 | g_isApplication = 1; 207 | } 208 | } 209 | 210 | // Sets g_isAutomaticGameplayRecording if the current program has automatic gameplay recording enabled in its NACP. 211 | //Gets the control.nacp for the current program id, and then sets g_isAutomaticGameplayRecording if less memory should be allocated. 212 | static void getIsAutomaticGameplayRecording(void) 213 | { 214 | Result rc; 215 | 216 | // Do nothing if the HOS version predates [4.0.0], or we're not an application. 217 | if (hosversionBefore(4,0,0) || !g_isApplication) 218 | return; 219 | 220 | // Retrieve our process' Program ID 221 | u64 cur_progid=0; 222 | rc = svcGetInfo(&cur_progid, InfoType_ProgramId, CUR_PROCESS_HANDLE, 0); 223 | if (R_FAILED(rc)) diagAbortWithResult(rc); // shouldn't happen 224 | 225 | // Try reading our NACP 226 | rc = nsInitialize(); 227 | if (R_SUCCEEDED(rc)) { 228 | NsApplicationControlData data; // note: this is 144KB, which still fits comfortably within the 1MB of stack we have 229 | u64 size=0; 230 | rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, cur_progid, &data, sizeof(data), &size); 231 | nsExit(); 232 | 233 | if (R_SUCCEEDED(rc) && data.nacp.video_capture == 2) 234 | g_isAutomaticGameplayRecording = 1; 235 | } 236 | } 237 | 238 | static void getOwnProcessHandle(void) 239 | { 240 | Result rc; 241 | 242 | Handle server_handle, client_handle; 243 | rc = svcCreateSession(&server_handle, &client_handle, 0, 0); 244 | if (R_FAILED(rc)) 245 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 12)); 246 | 247 | Thread t; 248 | rc = threadCreate(&t, &procHandleReceiveThread, (void*)(uintptr_t)server_handle, NULL, 0x1000, 0x20, 0); 249 | if (R_FAILED(rc)) 250 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 10)); 251 | 252 | rc = threadStart(&t); 253 | if (R_FAILED(rc)) 254 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 13)); 255 | 256 | hipcMakeRequestInline(armGetTls(), 257 | .num_copy_handles = 1, 258 | ).copy_handles[0] = CUR_PROCESS_HANDLE; 259 | 260 | svcSendSyncRequest(client_handle); 261 | svcCloseHandle(client_handle); 262 | 263 | threadWaitForExit(&t); 264 | threadClose(&t); 265 | } 266 | 267 | static bool isKernel5xOrLater(void) 268 | { 269 | u64 dummy = 0; 270 | Result rc = svcGetInfo(&dummy, InfoType_UserExceptionContextAddress, INVALID_HANDLE, 0); 271 | return R_VALUE(rc) != KERNELRESULT(InvalidEnumValue); 272 | } 273 | 274 | static bool isKernel4x(void) 275 | { 276 | u64 dummy = 0; 277 | Result rc = svcGetInfo(&dummy, InfoType_InitialProcessIdRange, INVALID_HANDLE, 0); 278 | return R_VALUE(rc) != KERNELRESULT(InvalidEnumValue); 279 | } 280 | 281 | static void getCodeMemoryCapability(void) 282 | { 283 | if (detectMesosphere()) { 284 | // Mesosphère allows for same-process code memory usage. 285 | g_codeMemoryCapability = CodeMemorySameProcess; 286 | } else if (isKernel5xOrLater()) { 287 | // On [5.0.0+], the kernel does not allow the creator process of a CodeMemory object 288 | // to use svcControlCodeMemory on itself, thus returning InvalidMemoryState (0xD401). 289 | // However the kernel can be patched to support same-process usage of CodeMemory. 290 | // We can detect that by passing a bad operation and observe if we actually get InvalidEnumValue (0xF001). 291 | Handle code; 292 | Result rc = svcCreateCodeMemory(&code, g_heapAddr, 0x1000); 293 | if (R_SUCCEEDED(rc)) { 294 | rc = svcControlCodeMemory(code, (CodeMapOperation)-1, 0, 0x1000, 0); 295 | svcCloseHandle(code); 296 | 297 | if (R_VALUE(rc) == KERNELRESULT(InvalidEnumValue)) 298 | g_codeMemoryCapability = CodeMemorySameProcess; 299 | else 300 | g_codeMemoryCapability = CodeMemoryForeignProcess; 301 | } 302 | } else if (isKernel4x()) { 303 | // On [4.0.0-4.1.0] there is no such restriction on same-process CodeMemory usage. 304 | g_codeMemoryCapability = CodeMemorySameProcess; 305 | } else { 306 | // This kernel is too old to support CodeMemory syscalls. 307 | g_codeMemoryCapability = CodeMemoryUnavailable; 308 | } 309 | } 310 | 311 | void loadNro(void) 312 | { 313 | NroHeader* header = NULL; 314 | size_t rw_size=0; 315 | Result rc=0; 316 | 317 | memcpy((u8*)armGetTls() + 0x100, g_savedTls, 0x100); 318 | 319 | if (g_nroSize > 0) 320 | { 321 | // Unmap previous NRO. 322 | header = &g_nroHeader; 323 | rw_size = header->segments[2].size + header->bss_size; 324 | rw_size = (rw_size+0xFFF) & ~0xFFF; 325 | 326 | svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PreUnloadDll, g_nroAddr, g_nroSize); 327 | 328 | // .text 329 | rc = svcUnmapProcessCodeMemory( 330 | g_procHandle, g_nroAddr + header->segments[0].file_off, ((u64) g_heapAddr) + header->segments[0].file_off, header->segments[0].size); 331 | 332 | if (R_FAILED(rc)) 333 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 24)); 334 | 335 | // .rodata 336 | rc = svcUnmapProcessCodeMemory( 337 | g_procHandle, g_nroAddr + header->segments[1].file_off, ((u64) g_heapAddr) + header->segments[1].file_off, header->segments[1].size); 338 | 339 | if (R_FAILED(rc)) 340 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 25)); 341 | 342 | // .data + .bss 343 | rc = svcUnmapProcessCodeMemory( 344 | g_procHandle, g_nroAddr + header->segments[2].file_off, ((u64) g_heapAddr) + header->segments[2].file_off, rw_size); 345 | 346 | if (R_FAILED(rc)) 347 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 26)); 348 | 349 | svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PostUnloadDll, g_nroAddr, g_nroSize); 350 | 351 | g_nroAddr = g_nroSize = 0; 352 | } 353 | 354 | if (g_nextNroPath[0] == '\0') 355 | { 356 | memcpy(g_nextNroPath, DEFAULT_NRO, sizeof(DEFAULT_NRO)); 357 | memcpy(g_nextArgv, DEFAULT_NRO, sizeof(DEFAULT_NRO)); 358 | } 359 | 360 | memcpy(g_argv, g_nextArgv, sizeof g_argv); 361 | 362 | svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PreLoadDll, (uintptr_t)g_argv, sizeof(g_argv)); 363 | 364 | uint8_t *nrobuf = (uint8_t*) g_heapAddr; 365 | 366 | NroStart* start = (NroStart*) (nrobuf + 0); 367 | header = (NroHeader*) (nrobuf + sizeof(NroStart)); 368 | uint8_t* rest = (uint8_t*) (nrobuf + sizeof(NroStart) + sizeof(NroHeader)); 369 | 370 | rc = fsdevMountSdmc(); 371 | if (R_FAILED(rc)) 372 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 404)); 373 | 374 | int fd = open(g_nextNroPath, O_RDONLY); 375 | if (fd < 0) 376 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 3)); 377 | 378 | // Reset NRO path to load hbmenu by default next time. 379 | g_nextNroPath[0] = '\0'; 380 | 381 | if (read(fd, start, sizeof(*start)) != sizeof(*start)) 382 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 4)); 383 | 384 | if (read(fd, header, sizeof(*header)) != sizeof(*header)) 385 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 4)); 386 | 387 | if (header->magic != NROHEADER_MAGIC) 388 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 5)); 389 | 390 | size_t rest_size = header->size - (sizeof(NroStart) + sizeof(NroHeader)); 391 | if (read(fd, rest, rest_size) != rest_size) 392 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 7)); 393 | 394 | close(fd); 395 | fsdevUnmountAll(); 396 | 397 | size_t total_size = header->size + header->bss_size; 398 | total_size = (total_size+0xFFF) & ~0xFFF; 399 | 400 | rw_size = header->segments[2].size + header->bss_size; 401 | rw_size = (rw_size+0xFFF) & ~0xFFF; 402 | 403 | int i; 404 | for (i=0; i<3; i++) 405 | { 406 | if (header->segments[i].file_off >= header->size || header->segments[i].size > header->size || 407 | (header->segments[i].file_off + header->segments[i].size) > header->size) 408 | { 409 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 6)); 410 | } 411 | } 412 | 413 | // todo: Detect whether NRO fits into heap or not. 414 | 415 | // Copy header to elsewhere because we're going to unmap it next. 416 | memcpy(&g_nroHeader, header, sizeof(g_nroHeader)); 417 | header = &g_nroHeader; 418 | 419 | // Map code memory to a new randomized address 420 | virtmemLock(); 421 | void* map_addr = virtmemFindCodeMemory(total_size, 0); 422 | rc = svcMapProcessCodeMemory(g_procHandle, (u64)map_addr, (u64)nrobuf, total_size); 423 | virtmemUnlock(); 424 | 425 | if (R_FAILED(rc)) 426 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 18)); 427 | 428 | // .text 429 | rc = svcSetProcessMemoryPermission( 430 | g_procHandle, (u64)map_addr + header->segments[0].file_off, header->segments[0].size, Perm_R | Perm_X); 431 | 432 | if (R_FAILED(rc)) 433 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 19)); 434 | 435 | // .rodata 436 | rc = svcSetProcessMemoryPermission( 437 | g_procHandle, (u64)map_addr + header->segments[1].file_off, header->segments[1].size, Perm_R); 438 | 439 | if (R_FAILED(rc)) 440 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 20)); 441 | 442 | // .data + .bss 443 | rc = svcSetProcessMemoryPermission( 444 | g_procHandle, (u64)map_addr + header->segments[2].file_off, rw_size, Perm_Rw); 445 | 446 | if (R_FAILED(rc)) 447 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 21)); 448 | 449 | u64 nro_size = header->segments[2].file_off + rw_size; 450 | u64 nro_heap_start = ((u64) g_heapAddr) + nro_size; 451 | u64 nro_heap_size = g_heapSize + (u64) g_heapAddr - (u64) nro_heap_start; 452 | 453 | #define M EntryFlag_IsMandatory 454 | 455 | static ConfigEntry entries[] = { 456 | { EntryType_MainThreadHandle, 0, {0, 0} }, 457 | { EntryType_ProcessHandle, 0, {0, 0} }, 458 | { EntryType_AppletType, 0, {AppletType_LibraryApplet, 0} }, 459 | { EntryType_OverrideHeap, M, {0, 0} }, 460 | { EntryType_Argv, 0, {0, 0} }, 461 | { EntryType_NextLoadPath, 0, {0, 0} }, 462 | { EntryType_LastLoadResult, 0, {0, 0} }, 463 | { EntryType_SyscallAvailableHint, 0, {UINT64_MAX, UINT64_MAX} }, 464 | { EntryType_SyscallAvailableHint2, 0, {UINT64_MAX, 0} }, 465 | { EntryType_RandomSeed, 0, {0, 0} }, 466 | { EntryType_UserIdStorage, 0, {(u64)(uintptr_t)&g_userIdStorage, 0} }, 467 | { EntryType_HosVersion, 0, {0, 0} }, 468 | { EntryType_EndOfList, 0, {(u64)(uintptr_t)g_noticeText, sizeof(g_noticeText)} } 469 | }; 470 | 471 | ConfigEntry *entry_AppletType = &entries[2]; 472 | ConfigEntry *entry_Syscalls = &entries[7]; 473 | 474 | if (g_isApplication) { 475 | entry_AppletType->Value[0] = AppletType_SystemApplication; 476 | entry_AppletType->Value[1] = EnvAppletFlags_ApplicationOverride; 477 | } 478 | 479 | if (!(g_codeMemoryCapability & BIT(0))) { 480 | // Revoke access to svcCreateCodeMemory if it's not available. 481 | entry_Syscalls->Value[0x4B/64] &= ~(1UL << (0x4B%64)); 482 | } 483 | 484 | if (!(g_codeMemoryCapability & BIT(1))) { 485 | // Revoke access to svcControlCodeMemory if it's not available for same-process usage. 486 | entry_Syscalls->Value[0x4C/64] &= ~(1UL << (0x4C%64)); // svcControlCodeMemory 487 | } 488 | 489 | // MainThreadHandle 490 | entries[0].Value[0] = envGetMainThreadHandle(); 491 | // ProcessHandle 492 | entries[1].Value[0] = g_procHandle; 493 | // OverrideHeap 494 | entries[3].Value[0] = nro_heap_start; 495 | entries[3].Value[1] = nro_heap_size; 496 | // Argv 497 | entries[4].Value[1] = (u64)(uintptr_t)&g_argv[0]; 498 | // NextLoadPath 499 | entries[5].Value[0] = (u64)(uintptr_t)&g_nextNroPath[0]; 500 | entries[5].Value[1] = (u64)(uintptr_t)&g_nextArgv[0]; 501 | // LastLoadResult 502 | entries[6].Value[0] = g_lastRet; 503 | // RandomSeed 504 | entries[9].Value[0] = randomGet64(); 505 | entries[9].Value[1] = randomGet64(); 506 | // HosVersion 507 | entries[11].Value[0] = hosversionGet(); 508 | entries[11].Value[1] = hosversionIsAtmosphere() ? 0x41544d4f53504852UL : 0; // 'ATMOSPHR' 509 | 510 | g_nroAddr = (u64)map_addr; 511 | g_nroSize = nro_size; 512 | 513 | svcBreak(BreakReason_NotificationOnlyFlag | BreakReason_PostLoadDll, g_nroAddr, g_nroSize); 514 | 515 | nroEntrypointTrampoline(&entries[0], -1, g_nroAddr); 516 | } 517 | 518 | int main(int argc, char **argv) 519 | { 520 | memcpy(g_savedTls, (u8*)armGetTls() + 0x100, 0x100); 521 | 522 | getIsApplication(); 523 | getIsAutomaticGameplayRecording(); 524 | smExit(); // Close SM as we don't need it anymore. 525 | setupHbHeap(); 526 | getOwnProcessHandle(); 527 | getCodeMemoryCapability(); 528 | loadNro(); 529 | 530 | diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 8)); 531 | return 0; 532 | } 533 | -------------------------------------------------------------------------------- /source/trampoline.s: -------------------------------------------------------------------------------- 1 | .section .text.nroEntrypointTrampoline, "ax", %progbits 2 | .align 2 3 | .global nroEntrypointTrampoline 4 | .type nroEntrypointTrampoline, %function 5 | .cfi_startproc 6 | nroEntrypointTrampoline: 7 | 8 | // Reset stack pointer. 9 | adrp x8, __stack_top //Defined in libnx. 10 | ldr x8, [x8, #:lo12:__stack_top] 11 | mov sp, x8 12 | 13 | // Call NRO. 14 | blr x2 15 | 16 | // Save retval 17 | adrp x1, g_lastRet 18 | str w0, [x1, #:lo12:g_lastRet] 19 | 20 | // Reset stack pointer and load next NRO. 21 | adrp x8, __stack_top 22 | ldr x8, [x8, #:lo12:__stack_top] 23 | mov sp, x8 24 | 25 | b loadNro 26 | 27 | .cfi_endproc 28 | 29 | .section .text.__libnx_exception_entry, "ax", %progbits 30 | .align 2 31 | .global __libnx_exception_entry 32 | .type __libnx_exception_entry, %function 33 | .cfi_startproc 34 | __libnx_exception_entry: 35 | 36 | // Divert execution to the NRO entrypoint (if a NRO is actually loaded). 37 | adrp x7, g_nroAddr 38 | ldr x7, [x7, #:lo12:g_nroAddr] 39 | cbz x7, .Lfail 40 | br x7 41 | 42 | .Lfail: 43 | // Otherwise, pass this unhandled exception right back to the kernel. 44 | mov w0, #0xf801 // KERNELRESULT(UnhandledUserInterrupt) 45 | svc 0x28 // svcReturnFromException 46 | 47 | .cfi_endproc 48 | --------------------------------------------------------------------------------