commit b9aa6736243f9fef9ad1f06cb4a6659640dc5f35 Author: plutooo Date: Tue Apr 24 00:10:50 2018 +0200 Public release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e6aeee7 --- /dev/null +++ b/Makefile @@ -0,0 +1,184 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITA64)),) +$(error "Please set DEVKITA64 in your environment. export DEVKITA64=DEVKITA64") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITA64)/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". +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +EXEFS_SRC := exefs_src + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 \ + -ffast-math \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -DSWITCH + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=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 $(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 + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).pfs0 $(OUTPUT).nro + +$(OUTPUT).pfs0 : $(OUTPUT).nso + +$(OUTPUT).nso : $(OUTPUT).elf + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +$(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/exefs_src/main.npdm b/exefs_src/main.npdm new file mode 100644 index 0000000..17c73a3 Binary files /dev/null and b/exefs_src/main.npdm differ diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..42432ca --- /dev/null +++ b/source/main.c @@ -0,0 +1,345 @@ +#include +#include +#include +#include "nro.h" + +#define MODULE_HBL 347 + +const char* g_easterEgg = "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]; +static u64 g_nroAddr = 0; +static u64 g_nroSize = 0; +static NroHeader g_nroHeader; + +static u8 g_savedTls[0x100]; + +// 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) +{ + (void) g_easterEgg[0]; + + Result rc; + + rc = smInitialize(); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 1)); + + rc = fsInitialize(); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 2)); + + fsdevInit(); +} + +static void* g_heapAddr; +static size_t g_heapSize; + +void setupHbHeap(void) +{ + u64 size = 0; + void* addr = NULL; + u64 mem_available = 0, mem_used = 0; + Result rc=0; + + svcGetInfo(&mem_available, 6, CUR_PROCESS_HANDLE, 0); + svcGetInfo(&mem_used, 7, CUR_PROCESS_HANDLE, 0); + if (mem_available > mem_used+0x200000) + size = (mem_available - mem_used - 0x200000) & ~0x1FFFFF; + if (size==0) + size = 0x2000000*16; + + rc = svcSetHeapSize(&addr, size); + + if (R_FAILED(rc) || addr==NULL) + fatalSimple(MAKERESULT(MODULE_HBL, 9)); + + g_heapAddr = addr; + g_heapSize = size; +} + +static Handle g_port; +static Handle g_procHandle; + +void threadFunc(void* ctx) +{ + Handle session; + Result rc; + + rc = svcWaitSynchronizationSingle(g_port, -1); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 22)); + + rc = svcAcceptSession(&session, g_port); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 14)); + + s32 idx = 0; + rc = svcReplyAndReceive(&idx, &session, 1, 0, -1); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 15)); + + IpcParsedCommand ipc; + rc = ipcParse(&ipc); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 16)); + + if (ipc.NumHandles != 1) + fatalSimple(MAKERESULT(MODULE_HBL, 17)); + + g_procHandle = ipc.Handles[0]; + svcCloseHandle(session); +} + +void getOwnProcessHandle(void) +{ + static Thread t; + Result rc; + + rc = threadCreate(&t, &threadFunc, NULL, 0x1000, 0x20, 0); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 10)); + + rc = smUnregisterService("appletOE"); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 11)); + + rc = smRegisterService(&g_port, "appletOE", false, 1); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 12)); + + rc = threadStart(&t); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 13)); + + Service srv; + rc = smGetService(&srv, "appletOE"); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 23)); + + IpcCommand ipc; + ipcSendHandleCopy(&ipc, 0xffff8001); + + struct { + int x, y; + }* raw; + + raw = ipcPrepareHeader(&ipc, sizeof(*raw)); + raw->x = raw->y = 0; + + rc = serviceIpcDispatch(&srv); + + threadWaitForExit(&t); + threadClose(&t); + + serviceClose(&srv); + svcCloseHandle(g_port); + + smExit(); +} + +void loadNro(void) +{ + NroHeader* header = NULL; + size_t rw_size=0; + Result rc=0; + + svcSleepThread(1000000000);//Wait for sm-sysmodule to handle closing the sm session from this process. Without this delay smInitialize will fail once eventually used later. + //TODO: Lower the above delay-value? + + memcpy((u8*)armGetTls() + 0x100, g_savedTls, 0x100); + + if (g_nroSize > 0) + { + // Unmap previous NRO. + header = &g_nroHeader; + rw_size = header->Segments[2].Size + header->bssSize; + rw_size = (rw_size+0xFFF) & ~0xFFF; + + // .text + rc = svcUnmapProcessCodeMemory( + g_procHandle, g_nroAddr + header->Segments[0].FileOff, ((u64) g_heapAddr) + header->Segments[0].FileOff, header->Segments[0].Size); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 24)); + + // .rodata + rc = svcUnmapProcessCodeMemory( + g_procHandle, g_nroAddr + header->Segments[1].FileOff, ((u64) g_heapAddr) + header->Segments[1].FileOff, header->Segments[1].Size); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 25)); + + // .data + .bss + rc = svcUnmapProcessCodeMemory( + g_procHandle, g_nroAddr + header->Segments[2].FileOff, ((u64) g_heapAddr) + header->Segments[2].FileOff, rw_size); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 26)); + + g_nroAddr = g_nroSize = 0; + } + + if (strlen(g_nextNroPath) == 0) + { + strcpy(g_nextNroPath, "sdmc:/hbmenu.nro"); + strcpy(g_nextArgv, "sdmc:/hbmenu.nro"); + } + + 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_HBL, 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_HBL, 4)); + + if (fread(header, sizeof(*header), 1, f) != 1) + fatalSimple(MAKERESULT(MODULE_HBL, 4)); + + if(header->Magic != NROHEADER_MAGICNUM) + fatalSimple(MAKERESULT(MODULE_HBL, 5)); + + size_t rest_size = header->size - (sizeof(NroStart) + sizeof(NroHeader)); + if (fread(rest, rest_size, 1, f) != 1) + fatalSimple(MAKERESULT(MODULE_HBL, 7)); + + fclose(f); + + size_t total_size = header->size + header->bssSize; + total_size = (total_size+0xFFF) & ~0xFFF; + + rw_size = header->Segments[2].Size + header->bssSize; + rw_size = (rw_size+0xFFF) & ~0xFFF; + + int i; + for (i=0; i<3; i++) + { + if (header->Segments[i].FileOff >= header->size || header->Segments[i].Size > header->size || + (header->Segments[i].FileOff + header->Segments[i].Size) > header->size) + { + fatalSimple(MAKERESULT(MODULE_HBL, 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_HBL, 18)); + + // .text + rc = svcSetProcessMemoryPermission( + g_procHandle, map_addr + header->Segments[0].FileOff, header->Segments[0].Size, PERM_R | PERM_X); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 19)); + + // .rodata + rc = svcSetProcessMemoryPermission( + g_procHandle, map_addr + header->Segments[1].FileOff, header->Segments[1].Size, PERM_R); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 20)); + + // .data + .bss + rc = svcSetProcessMemoryPermission( + g_procHandle, map_addr + header->Segments[2].FileOff, rw_size, PERM_RW); + + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(MODULE_HBL, 21)); + + u64 nro_size = header->Segments[2].FileOff + 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, 0x1fc00000007ffff} }, + { EntryType_EndOfList, 0, {0, 0} } + }; + + // 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; + + u64 entrypoint = map_addr; + + g_nroAddr = map_addr; + g_nroSize = nro_size; + + memset(__stack_top - STACK_SIZE, 0, STACK_SIZE); + + extern NORETURN void nroEntrypointTrampoline(u64 entries_ptr, u64 handle, u64 entrypoint); + nroEntrypointTrampoline((u64) entries, -1, entrypoint); +} + +int main(int argc, char **argv) +{ + memcpy(g_savedTls, (u8*)armGetTls() + 0x100, 0x100); + + setupHbHeap(); + getOwnProcessHandle(); + loadNro(); + + fatalSimple(MAKERESULT(MODULE_HBL, 8)); + return 0; +} diff --git a/source/nro.h b/source/nro.h new file mode 100644 index 0000000..50b5510 --- /dev/null +++ b/source/nro.h @@ -0,0 +1,42 @@ +#pragma once + +#define NROHEADER_MAGICNUM 0x304f524e + +#define ASSETHEADER_MAGICNUM 0x54455341 +#define ASSETHEADER_VERSION 0 + +typedef struct { + u32 FileOff; + u32 Size; +} NsoSegment; + +typedef struct { + u32 unused; + u32 modOffset; + u8 Padding[8]; +} NroStart; + +typedef struct { + u32 Magic; + u32 Unk1; + u32 size; + u32 Unk2; + NsoSegment Segments[3]; + u32 bssSize; + u32 Unk3; + u8 BuildId[0x20]; + u8 Padding[0x20]; +} NroHeader; + +typedef struct { + u64 offset; + u64 size; +} AssetSection; + +typedef struct { + u32 magic; + u32 version; + AssetSection icon; + AssetSection nacp; + AssetSection romfs; +} AssetHeader; diff --git a/source/trampoline.s b/source/trampoline.s new file mode 100644 index 0000000..acc25d8 --- /dev/null +++ b/source/trampoline.s @@ -0,0 +1,31 @@ +.section .text.nroEntrypointTrampoline, "ax", %progbits + +.global nroEntrypointTrampoline +.type nroEntrypointTrampoline, %function +.align 2 + +.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 + add x1, x1, #:lo12:g_lastRet + str x0, [x1] + + // Reset stack pointer and load next NRO. + adrp x8, __stack_top + ldr x8, [x8, #:lo12:__stack_top] + mov sp, x8 + + b loadNro + +.cfi_endproc