diff --git a/.gitignore b/.gitignore index 7409beb..da6e2f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ out build loader/build +injector/build *.bin *.3dsx *.smdh diff --git a/Makefile b/Makefile index d79f34d..3002ab2 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ version := $(shell git describe --abbrev=0 --tags) dir_source := source dir_patches := patches dir_loader := loader +dir_injector := injector dir_mset := CakeHax dir_ninjhax := CakeBrah dir_build := build @@ -47,6 +48,7 @@ clean: @$(MAKE) $(FLAGS) -C $(dir_ninjhax) clean @rm -rf $(dir_out) $(dir_build) @$(MAKE) -C $(dir_loader) clean + @$(MAKE) -C $(dir_injector) clean $(dir_out): @mkdir -p "$(dir_out)/aurei/payloads" @@ -67,12 +69,13 @@ $(dir_out)/3ds/$(name): $(dir_out) $(dir_out)/$(name).zip: launcher a9lh ninjhax @cd $(dir_out) && zip -9 -r $(name) * -$(dir_build)/patches.h: $(dir_patches)/emunand.s $(dir_patches)/reboot.s +$(dir_build)/patches.h: $(dir_patches)/emunand.s $(dir_patches)/reboot.s $(dir_injector)/Makefile @mkdir -p "$(dir_build)" @armips $< @armips $(word 2,$^) - @mv emunand.bin reboot.bin $(dir_build) - @bin2c -o $@ -n emunand $(dir_build)/emunand.bin -n reboot $(dir_build)/reboot.bin + @$(MAKE) -C $(dir_injector) + @mv emunand.bin reboot.bin $(dir_injector)/injector.cxi $(dir_build) + @bin2c -o $@ -n emunand $(dir_build)/emunand.bin -n reboot $(dir_build)/reboot.bin -n injector $(dir_build)/injector.cxi $(dir_build)/loader.h: $(dir_loader)/Makefile @$(MAKE) -C $(dir_loader) diff --git a/injector/LICENSE b/injector/LICENSE new file mode 100644 index 0000000..5fcfe11 --- /dev/null +++ b/injector/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Yifan Lu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/injector/Makefile b/injector/Makefile new file mode 100644 index 0000000..b6afb45 --- /dev/null +++ b/injector/Makefile @@ -0,0 +1,161 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +MAKEROM ?= makerom +include $(DEVKITARM)/3ds_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 +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft + +CFLAGS := -flto -Wall -O2 -mword-relocations \ + -ffast-math -ffunction-sections -fdata-sections \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM11 -D_3DS + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu99 + +ASFLAGS := $(ARCH) +LDFLAGS = -flto -Xlinker --defsym="__start__=0x14000000" -specs=3dsx.specs $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lctru + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + + +#--------------------------------------------------------------------------------- +# 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))) +PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) +SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) +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)) \ + $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ + $(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) + +.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) $(OUTPUT).cxi $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).cxi : $(OUTPUT).elf + $(MAKEROM) -f ncch -rsf ../loader.rsf -nocodepadding -o $@ -elf $< + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# rules for assembling GPU shaders +#--------------------------------------------------------------------------------- +define shader-as + $(eval CURBIN := $(patsubst %.shbin.o,%.shbin,$(notdir $@))) + picasso -o $(CURBIN) $1 + bin2s $(CURBIN) | $(AS) -o $@ + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h + echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h +endef + +%.shbin.o : %.v.pica %.g.pica + @echo $(notdir $^) + @$(call shader-as,$^) + +%.shbin.o : %.v.pica + @echo $(notdir $<) + @$(call shader-as,$<) + +%.shbin.o : %.shlist + @echo $(notdir $<) + @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file))) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/injector/README.md b/injector/README.md new file mode 100644 index 0000000..e985160 --- /dev/null +++ b/injector/README.md @@ -0,0 +1,30 @@ +3DS Loader Replacement +====================== + +This is an open source implementation of 3DS `loader` system module--with +additional features. The current aim of the project is to provide a nice +entry point for patching 3DS modules. + +## Roadmap +Right now, this can serve as an open-source replacement for the built in loader. +There is additional support for patching any executable after it's loaded but +before it starts. For example, you can patch `menu` to skip region checks and +have region free game launching directly from the home menu. There is also +support for SDMC reading (not found in original loader implementation) which +means that patches can be loaded from the SD card. Ultimately, there would be +a patch system that supports easy loading of patches from the SD card. + +## Build +You need a working 3DS build environment with a fairly recent copy of devkitARM, +ctrulib, and makerom. If you see any errors in the build process, it's likely +that you're using an older version. + +Currently, there is no support for FIRM building, so you need to do some steps +manually. First, you have to add padding to make sure the NCCH is of the right +size to drop in as a replacement. A hacky way is +[this patch](http://pastebin.com/nyKXLnNh) which adds junk data. Play around +with the size value to get the NCCH to be the exact same size as the one +found in your decrypted FIRM dump. + +Once you have a NCCH of the right size, just replace it in your decrypted FIRM +and find a way to launch it (for example with ReiNAND). diff --git a/injector/loader.rsf b/injector/loader.rsf new file mode 100644 index 0000000..dfeeb0e --- /dev/null +++ b/injector/loader.rsf @@ -0,0 +1,115 @@ +BasicInfo: + Title : loader + CompanyCode : "00" + ProductCode : 0828builder + ContentType : Application + Logo : None + +TitleInfo: + UniqueId : 0x13 + Category : Base + Version : 2 + +Option: + UseOnSD : false + FreeProductCode : true # Removes limitations on ProductCode + MediaFootPadding : false # If true CCI files are created with padding + EnableCrypt : false # Enables encryption for NCCH and CIA + EnableCompress : true # Compresses exefs code + +AccessControlInfo: + IdealProcessor : 1 + AffinityMask : 3 + + Priority : 20 + + DisableDebug : true + EnableForceDebug : false + CanWriteSharedPage : false + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : false + PermitMainFunctionArgument : false + CanShareDeviceMemory : false + RunnableOnSleep : true + SpecialMemoryArrange : true + ResourceLimitCategory : Other + + CoreVersion : 2 + DescVersion : 2 + + MemoryType : Base # Application / System / Base + HandleTableSize: 0 + SystemCallAccess: + AcceptSession: 74 + ArbitrateAddress: 34 + Break: 60 + CancelTimer: 28 + ClearEvent: 25 + ClearTimer: 29 + CloseHandle: 35 + ConnectToPort: 45 + ControlMemory: 1 + CreateAddressArbiter: 33 + CreateCodeSet: 115 + CreateEvent: 23 + CreateMemoryBlock: 30 + CreateMutex: 19 + CreatePort: 71 + CreateProcess: 117 + CreateSemaphore: 21 + CreateSessionToPort: 72 + CreateThread: 8 + CreateTimer: 26 + DuplicateHandle: 39 + ExitProcess: 3 + ExitThread: 9 + GetCurrentProcessorNumber: 17 + GetHandleInfo: 41 + GetProcessId: 53 + GetProcessIdealProcessor: 6 + GetProcessIdOfThread: 54 + GetProcessInfo: 43 + GetResourceLimit: 56 + GetResourceLimitCurrentValues: 58 + GetResourceLimitLimitValues: 57 + GetSystemInfo: 42 + GetSystemTick: 40 + GetThreadContext: 59 + GetThreadId: 55 + GetThreadIdealProcessor: 15 + GetThreadInfo: 44 + GetThreadPriority: 11 + MapMemoryBlock: 31 + OutputDebugString: 61 + QueryMemory: 2 + RandomStub: 116 + ReleaseMutex: 20 + ReleaseSemaphore: 22 + ReplyAndReceive1: 75 + ReplyAndReceive2: 76 + ReplyAndReceive3: 77 + ReplyAndReceive4: 78 + ReplyAndReceive: 79 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + SetThreadPriority: 12 + SetTimer: 27 + SignalEvent: 24 + SleepThread: 10 + UnmapMemoryBlock: 32 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + InterruptNumbers: + ServiceAccessControl: + - fs:LDR + FileSystemAccess: + - DirectSdmc + - CtrNandRw + +SystemControlInfo: + SaveDataSize: 0KB # It doesn't use any save data. + RemasterVersion: 0 + StackSize: 0x1000 \ No newline at end of file diff --git a/injector/source/exheader.h b/injector/source/exheader.h new file mode 100755 index 0000000..be23527 --- /dev/null +++ b/injector/source/exheader.h @@ -0,0 +1,97 @@ +#pragma once + +#include <3ds/types.h> + +typedef struct +{ + u8 reserved[5]; + u8 flag; + u8 remasterversion[2]; +} PACKED exheader_systeminfoflags; + +typedef struct +{ + u32 address; + u32 nummaxpages; + u32 codesize; +} PACKED exheader_codesegmentinfo; + +typedef struct +{ + u8 name[8]; + exheader_systeminfoflags flags; + exheader_codesegmentinfo text; + u8 stacksize[4]; + exheader_codesegmentinfo ro; + u8 reserved[4]; + exheader_codesegmentinfo data; + u32 bsssize; +} PACKED exheader_codesetinfo; + +typedef struct +{ + u64 programid[0x30]; +} PACKED exheader_dependencylist; + +typedef struct +{ + u8 savedatasize[4]; + u8 reserved[4]; + u8 jumpid[8]; + u8 reserved2[0x30]; +} PACKED exheader_systeminfo; + +typedef struct +{ + u8 extsavedataid[8]; + u8 systemsavedataid[8]; + u8 reserved[8]; + u8 accessinfo[7]; + u8 otherattributes; +} PACKED exheader_storageinfo; + +typedef struct +{ + u64 programid; + u8 flags[8]; + u16 resourcelimitdescriptor[0x10]; + exheader_storageinfo storageinfo; + u64 serviceaccesscontrol[0x20]; + u8 reserved[0x1f]; + u8 resourcelimitcategory; +} PACKED exheader_arm11systemlocalcaps; + +typedef struct +{ + u32 descriptors[28]; + u8 reserved[0x10]; +} PACKED exheader_arm11kernelcapabilities; + +typedef struct +{ + u8 descriptors[15]; + u8 descversion; +} PACKED exheader_arm9accesscontrol; + +typedef struct +{ + // systemcontrol info { + // coreinfo { + exheader_codesetinfo codesetinfo; + exheader_dependencylist deplist; + // } + exheader_systeminfo systeminfo; + // } + // accesscontrolinfo { + exheader_arm11systemlocalcaps arm11systemlocalcaps; + exheader_arm11kernelcapabilities arm11kernelcaps; + exheader_arm9accesscontrol arm9accesscontrol; + // } + struct { + u8 signature[0x100]; + u8 ncchpubkeymodulus[0x100]; + exheader_arm11systemlocalcaps arm11systemlocalcaps; + exheader_arm11kernelcapabilities arm11kernelcaps; + exheader_arm9accesscontrol arm9accesscontrol; + } PACKED accessdesc; +} PACKED exheader_header; diff --git a/injector/source/fsldr.c b/injector/source/fsldr.c new file mode 100644 index 0000000..e90491c --- /dev/null +++ b/injector/source/fsldr.c @@ -0,0 +1,109 @@ +#include <3ds.h> +#include "fsldr.h" +#include "fsreg.h" +#include "srvsys.h" + +#define SDK_VERSION 0x70200C8 + +static Handle fsldrHandle; +static int fsldrRefCount; + +// MAKE SURE fsreg has been init before calling this +static Result fsldrPatchPermissions(void) +{ + u32 pid; + Result res; + FS_ProgramInfo info; + u32 storage[8] = {0}; + + storage[6] = 0x680; // SDMC access and NAND access flag + info.programId = 0x0004013000001302LL; // loader PID + info.mediaType = MEDIATYPE_NAND; + res = svcGetProcessId(&pid, 0xFFFF8001); + if (R_SUCCEEDED(res)) + { + res = FSREG_Register(pid, 0xFFFF000000000000LL, &info, (u8 *)storage); + } + return res; +} + +Result fsldrInit(void) +{ + Result ret = 0; + + if (AtomicPostIncrement(&fsldrRefCount)) return 0; + + ret = srvSysGetServiceHandle(&fsldrHandle, "fs:LDR"); + if (R_SUCCEEDED(ret)) + { + fsldrPatchPermissions(); + ret = FSLDR_InitializeWithSdkVersion(fsldrHandle, SDK_VERSION); + ret = FSLDR_SetPriority(0); + if (R_FAILED(ret)) svcBreak(USERBREAK_ASSERT); + } + else + { + AtomicDecrement(&fsldrRefCount); + } + + return ret; +} + +void fsldrExit(void) +{ + if (AtomicDecrement(&fsldrRefCount)) return; + svcCloseHandle(fsldrHandle); +} + +Result FSLDR_InitializeWithSdkVersion(Handle session, u32 version) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x861,1,2); // 0x8610042 + cmdbuf[1] = version; + cmdbuf[2] = 32; + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(session))) return ret; + + return cmdbuf[1]; +} + +Result FSLDR_SetPriority(u32 priority) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x862,1,0); // 0x8620040 + cmdbuf[1] = priority; + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsldrHandle))) return ret; + + return cmdbuf[1]; +} + +Result FSLDR_OpenFileDirectly(Handle* out, FS_Archive archive, FS_Path path, u32 openFlags, u32 attributes) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x803,8,4); // 0x8030204 + cmdbuf[1] = 0; + cmdbuf[2] = archive.id; + cmdbuf[3] = archive.lowPath.type; + cmdbuf[4] = archive.lowPath.size; + cmdbuf[5] = path.type; + cmdbuf[6] = path.size; + cmdbuf[7] = openFlags; + cmdbuf[8] = attributes; + cmdbuf[9] = IPC_Desc_StaticBuffer(archive.lowPath.size, 2); + cmdbuf[10] = (u32) archive.lowPath.data; + cmdbuf[11] = IPC_Desc_StaticBuffer(path.size, 0); + cmdbuf[12] = (u32) path.data; + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsldrHandle))) return ret; + + if(out) *out = cmdbuf[3]; + + return cmdbuf[1]; +} diff --git a/injector/source/fsldr.h b/injector/source/fsldr.h new file mode 100644 index 0000000..115a745 --- /dev/null +++ b/injector/source/fsldr.h @@ -0,0 +1,9 @@ +#pragma once + +#include <3ds/types.h> + +Result fsldrInit(void); +void fsldrExit(void); +Result FSLDR_InitializeWithSdkVersion(Handle session, u32 version); +Result FSLDR_SetPriority(u32 priority); +Result FSLDR_OpenFileDirectly(Handle* out, FS_Archive archive, FS_Path path, u32 openFlags, u32 attributes); diff --git a/injector/source/fsreg.c b/injector/source/fsreg.c new file mode 100644 index 0000000..d8301bb --- /dev/null +++ b/injector/source/fsreg.c @@ -0,0 +1,116 @@ +#include <3ds.h> +#include +#include "fsreg.h" +#include "srvsys.h" + +static Handle fsregHandle; +static int fsregRefCount; + +Result fsregInit(void) +{ + Result ret = 0; + + if (AtomicPostIncrement(&fsregRefCount)) return 0; + + ret = srvSysGetServiceHandle(&fsregHandle, "fs:REG"); + + if (R_FAILED(ret)) AtomicDecrement(&fsregRefCount); + return ret; +} + +void fsregExit(void) +{ + if (AtomicDecrement(&fsregRefCount)) return; + svcCloseHandle(fsregHandle); +} + +Result FSREG_CheckHostLoadId(u64 prog_handle) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x406,2,0); // 0x4060080 + cmdbuf[1] = (u32) (prog_handle); + cmdbuf[2] = (u32) (prog_handle >> 32); + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsregHandle))) return ret; + + return cmdbuf[1]; +} + +Result FSREG_LoadProgram(u64 *prog_handle, FS_ProgramInfo *title) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x404,4,0); // 0x4040100 + memcpy(&cmdbuf[1], &title->programId, sizeof(u64)); + *(u8 *)&cmdbuf[3] = title->mediaType; + memcpy(((u8 *)&cmdbuf[3])+1, &title->padding, 7); + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsregHandle))) return ret; + *prog_handle = *(u64 *)&cmdbuf[2]; + + return cmdbuf[1]; +} + +Result FSREG_GetProgramInfo(exheader_header *exheader, u32 entry_count, u64 prog_handle) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x403,3,0); // 0x40300C0 + cmdbuf[1] = entry_count; + *(u64 *)&cmdbuf[2] = prog_handle; + cmdbuf[64] = ((entry_count << 10) << 14) | 2; + cmdbuf[65] = (u32) exheader; + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsregHandle))) return ret; + + return cmdbuf[1]; +} + +Result FSREG_UnloadProgram(u64 prog_handle) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x405,2,0); // 0x4050080 + cmdbuf[1] = (u32) (prog_handle); + cmdbuf[2] = (u32) (prog_handle >> 32); + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsregHandle))) return ret; + + return cmdbuf[1]; +} + +Result FSREG_Unregister(u32 pid) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x402,1,0); // 0x4020040 + cmdbuf[1] = pid; + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsregHandle))) return ret; + + return cmdbuf[1]; +} + +Result FSREG_Register(u32 pid, u64 prog_handle, FS_ProgramInfo *info, void *storageinfo) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x401,0xf,0); // 0x40103C0 + cmdbuf[1] = pid; + *(u64 *)&cmdbuf[2] = prog_handle; + memcpy(&cmdbuf[4], &info->programId, sizeof(u64)); + *(u8 *)&cmdbuf[6] = info->mediaType; + memcpy(((u8 *)&cmdbuf[6])+1, &info->padding, 7); + memcpy((u8 *)&cmdbuf[8], storageinfo, 32); + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(fsregHandle))) return ret; + + return cmdbuf[1]; +} diff --git a/injector/source/fsreg.h b/injector/source/fsreg.h new file mode 100644 index 0000000..c2de1ed --- /dev/null +++ b/injector/source/fsreg.h @@ -0,0 +1,13 @@ +#pragma once + +#include <3ds/types.h> +#include "exheader.h" + +Result fsregInit(void); +void fsregExit(void); +Result FSREG_CheckHostLoadId(u64 prog_handle); +Result FSREG_LoadProgram(u64 *prog_handle, FS_ProgramInfo *title); +Result FSREG_GetProgramInfo(exheader_header *exheader, u32 entry_count, u64 prog_handle); +Result FSREG_UnloadProgram(u64 prog_handle); +Result FSREG_Unregister(u32 pid); +Result FSREG_Register(u32 pid, u64 prog_handle, FS_ProgramInfo *info, void *storageinfo); diff --git a/injector/source/ifile.c b/injector/source/ifile.c new file mode 100644 index 0000000..e360abc --- /dev/null +++ b/injector/source/ifile.c @@ -0,0 +1,105 @@ +#include <3ds.h> +#include "ifile.h" +#include "fsldr.h" + +Result IFile_Open(IFile *file, FS_Archive archive, FS_Path path, u32 flags) +{ + Result res; + + res = FSLDR_OpenFileDirectly(&file->handle, archive, path, flags, 0); + file->pos = 0; + file->size = 0; + return res; +} + +Result IFile_Close(IFile *file) +{ + return FSFILE_Close(file->handle); +} + +Result IFile_GetSize(IFile *file, u64 *size) +{ + Result res; + + res = FSFILE_GetSize(file->handle, size); + file->size = *size; + return res; +} + +Result IFile_Read(IFile *file, u64 *total, void *buffer, u32 len) +{ + u32 read; + u32 left; + char *buf; + u64 cur; + Result res; + + if (len == 0) + { + *total = 0; + return 0; + } + + buf = (char *)buffer; + cur = 0; + left = len; + while (1) + { + res = FSFILE_Read(file->handle, &read, file->pos, buf, left); + if (R_FAILED(res)) + { + break; + } + + cur += read; + file->pos += read; + if (read == left) + { + break; + } + buf += read; + left -= read; + } + + *total = cur; + return res; +} + +Result IFile_Write(IFile *file, u64 *total, void *buffer, u32 len, u32 flags) +{ + u32 written; + u32 left; + char *buf; + u64 cur; + Result res; + + if (len == 0) + { + *total = 0; + return 0; + } + + buf = (char *)buffer; + cur = 0; + left = len; + while (1) + { + res = FSFILE_Write(file->handle, &written, file->pos, buf, left, flags); + if (R_FAILED(res)) + { + break; + } + + cur += written; + file->pos += written; + if (written == left) + { + break; + } + buf += written; + left -= written; + } + + *total = cur; + return res; +} diff --git a/injector/source/ifile.h b/injector/source/ifile.h new file mode 100644 index 0000000..b60e180 --- /dev/null +++ b/injector/source/ifile.h @@ -0,0 +1,16 @@ +#pragma once + +#include <3ds/types.h> + +typedef struct +{ + Handle handle; + u64 pos; + u64 size; +} IFile; + +Result IFile_Open(IFile *file, FS_Archive archive, FS_Path path, u32 flags); +Result IFile_Close(IFile *file); +Result IFile_GetSize(IFile *file, u64 *size); +Result IFile_Read(IFile *file, u64 *total, void *buffer, u32 len); +Result IFile_Write(IFile *file, u64 *total, void *buffer, u32 len, u32 flags); diff --git a/injector/source/loader.c b/injector/source/loader.c new file mode 100644 index 0000000..8eff8cc --- /dev/null +++ b/injector/source/loader.c @@ -0,0 +1,593 @@ +#include <3ds.h> +#include +#include +#include "patcher.h" +#include "exheader.h" +#include "ifile.h" +#include "fsldr.h" +#include "fsreg.h" +#include "pxipm.h" +#include "srvsys.h" + +#define MAX_SESSIONS 1 + +const char CODE_PATH[] = {0x01, 0x00, 0x00, 0x00, 0x2E, 0x63, 0x6F, 0x64, 0x65, 0x00, 0x00, 0x00}; + +typedef struct +{ + u32 text_addr; + u32 text_size; + u32 ro_addr; + u32 ro_size; + u32 data_addr; + u32 data_size; + u32 total_size; +} prog_addrs_t; + +static Handle g_handles[MAX_SESSIONS+2]; +static int g_active_handles; +static u64 g_cached_prog_handle; +static exheader_header g_exheader; +static char g_ret_buf[1024]; + +static int lzss_decompress(u8 *end) +{ + unsigned int v1; // r1@2 + u8 *v2; // r2@2 + u8 *v3; // r3@2 + u8 *v4; // r1@2 + char v5; // r5@4 + char v6; // t1@4 + signed int v7; // r6@4 + int v9; // t1@7 + u8 *v11; // r3@8 + int v12; // r12@8 + int v13; // t1@8 + int v14; // t1@8 + unsigned int v15; // r7@8 + int v16; // r12@8 + int ret; + + ret = 0; + if ( end ) + { + v1 = *((u32 *)end - 2); + v2 = &end[*((u32 *)end - 1)]; + v3 = &end[-(v1 >> 24)]; + v4 = &end[-(v1 & 0xFFFFFF)]; + while ( v3 > v4 ) + { + v6 = *(v3-- - 1); + v5 = v6; + v7 = 8; + while ( 1 ) + { + if ( (v7-- < 1) ) + break; + if ( v5 & 0x80 ) + { + v13 = *(v3 - 1); + v11 = v3 - 1; + v12 = v13; + v14 = *(v11 - 1); + v3 = v11 - 1; + v15 = ((v14 | (v12 << 8)) & 0xFFFF0FFF) + 2; + v16 = v12 + 32; + do + { + ret = v2[v15]; + *(v2-- - 1) = ret; + v16 -= 16; + } + while ( !(v16 < 0) ); + } + else + { + v9 = *(v3-- - 1); + ret = v9; + *(v2-- - 1) = v9; + } + v5 *= 2; + if ( v3 <= v4 ) + return ret; + } + } + } + return ret; +} + +static Result allocate_shared_mem(prog_addrs_t *shared, prog_addrs_t *vaddr, int flags) +{ + u32 dummy; + + memcpy(shared, vaddr, sizeof(prog_addrs_t)); + shared->text_addr = 0x10000000; + shared->ro_addr = shared->text_addr + (shared->text_size << 12); + shared->data_addr = shared->ro_addr + (shared->ro_size << 12); + return svcControlMemory(&dummy, shared->text_addr, 0, shared->total_size << 12, (flags & 0xF00) | MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE); +} + +static Result load_code(u64 progid, prog_addrs_t *shared, u64 prog_handle, int is_compressed) +{ + IFile file; + FS_Archive archive; + FS_Path path; + Result res; + u64 size; + u64 total; + + archive.id = ARCHIVE_SAVEDATA_AND_CONTENT2; + archive.lowPath.type = PATH_BINARY; + archive.lowPath.data = &prog_handle; + archive.lowPath.size = 8; + //archive.handle = prog_handle; // not needed + path.type = PATH_BINARY; + path.data = CODE_PATH; + path.size = sizeof(CODE_PATH); + if (R_FAILED(IFile_Open(&file, archive, path, FS_OPEN_READ))) + { + svcBreak(USERBREAK_ASSERT); + } + + // get file size + if (R_FAILED(IFile_GetSize(&file, &size))) + { + IFile_Close(&file); + svcBreak(USERBREAK_ASSERT); + } + + // check size + if (size > (u64)shared->total_size << 12) + { + IFile_Close(&file); + return 0xC900464F; + } + + // read code + res = IFile_Read(&file, &total, (void *)shared->text_addr, size); + IFile_Close(&file); // done reading + if (R_FAILED(res)) + { + svcBreak(USERBREAK_ASSERT); + } + + // decompress + if (is_compressed) + { + lzss_decompress((u8 *)shared->text_addr + size); + } + + // patch + patch_code(progid, (u8 *)shared->text_addr, shared->total_size << 12); + + return 0; +} + +static Result loader_GetProgramInfo(exheader_header *exheader, u64 prog_handle) +{ + Result res; + + if (prog_handle >> 32 == 0xFFFF0000) + { + return FSREG_GetProgramInfo(exheader, 1, prog_handle); + } + else + { + res = FSREG_CheckHostLoadId(prog_handle); + //if ((res >= 0 && (unsigned)res >> 27) || (res < 0 && ((unsigned)res >> 27)-32)) + //so use PXIPM if FSREG fails OR returns "info", is the second condition a bug? + if (R_FAILED(res) || (R_SUCCEEDED(res) && R_LEVEL(res) != RL_SUCCESS)) + { + return PXIPM_GetProgramInfo(exheader, prog_handle); + } + else + { + return FSREG_GetProgramInfo(exheader, 1, prog_handle); + } + } +} + +static Result loader_LoadProcess(Handle *process, u64 prog_handle) +{ + Result res; + int count; + u32 flags; + u32 desc; + u32 dummy; + prog_addrs_t shared_addr; + prog_addrs_t vaddr; + Handle codeset; + CodeSetInfo codesetinfo; + u32 data_mem_size; + u64 progid; + + // make sure the cached info corrosponds to the current prog_handle + if (g_cached_prog_handle != prog_handle) + { + res = loader_GetProgramInfo(&g_exheader, prog_handle); + g_cached_prog_handle = prog_handle; + if (res < 0) + { + g_cached_prog_handle = 0; + return res; + } + } + + // get kernel flags + flags = 0; + for (count = 0; count < 28; count++) + { + desc = g_exheader.arm11kernelcaps.descriptors[count]; + if (0x1FE == desc >> 23) + { + flags = desc & 0xF00; + } + } + if (flags == 0) + { + return MAKERESULT(RL_PERMANENT, RS_INVALIDARG, 1, 2); + } + + // allocate process memory + vaddr.text_addr = g_exheader.codesetinfo.text.address; + vaddr.text_size = (g_exheader.codesetinfo.text.codesize + 4095) >> 12; + vaddr.ro_addr = g_exheader.codesetinfo.ro.address; + vaddr.ro_size = (g_exheader.codesetinfo.ro.codesize + 4095) >> 12; + vaddr.data_addr = g_exheader.codesetinfo.data.address; + vaddr.data_size = (g_exheader.codesetinfo.data.codesize + 4095) >> 12; + data_mem_size = (g_exheader.codesetinfo.data.codesize + g_exheader.codesetinfo.bsssize + 4095) >> 12; + vaddr.total_size = vaddr.text_size + vaddr.ro_size + vaddr.data_size; + if ((res = allocate_shared_mem(&shared_addr, &vaddr, flags)) < 0) + { + return res; + } + + // load code + progid = g_exheader.arm11systemlocalcaps.programid; + if ((res = load_code(progid, &shared_addr, prog_handle, g_exheader.codesetinfo.flags.flag & 1)) >= 0) + { + memcpy(&codesetinfo.name, g_exheader.codesetinfo.name, 8); + codesetinfo.program_id = progid; + codesetinfo.text_addr = vaddr.text_addr; + codesetinfo.text_size = vaddr.text_size; + codesetinfo.text_size_total = vaddr.text_size; + codesetinfo.ro_addr = vaddr.ro_addr; + codesetinfo.ro_size = vaddr.ro_size; + codesetinfo.ro_size_total = vaddr.ro_size; + codesetinfo.rw_addr = vaddr.data_addr; + codesetinfo.rw_size = vaddr.data_size; + codesetinfo.rw_size_total = data_mem_size; + res = svcCreateCodeSet(&codeset, &codesetinfo, (void *)shared_addr.text_addr, (void *)shared_addr.ro_addr, (void *)shared_addr.data_addr); + if (res >= 0) + { + res = svcCreateProcess(process, codeset, g_exheader.arm11kernelcaps.descriptors, count); + svcCloseHandle(codeset); + if (res >= 0) + { + return 0; + } + } + } + + svcControlMemory(&dummy, shared_addr.text_addr, 0, shared_addr.total_size << 12, MEMOP_FREE, 0); + return res; +} + +static Result loader_RegisterProgram(u64 *prog_handle, FS_ProgramInfo *title, FS_ProgramInfo *update) +{ + Result res; + u64 prog_id; + + prog_id = title->programId; + if (prog_id >> 32 != 0xFFFF0000) + { + res = FSREG_CheckHostLoadId(prog_id); + //if ((res >= 0 && (unsigned)res >> 27) || (res < 0 && ((unsigned)res >> 27)-32)) + if (R_FAILED(res) || (R_SUCCEEDED(res) && R_LEVEL(res) != RL_SUCCESS)) + { + res = PXIPM_RegisterProgram(prog_handle, title, update); + if (res < 0) + { + return res; + } + if (*prog_handle >> 32 != 0xFFFF0000) + { + res = FSREG_CheckHostLoadId(*prog_handle); + //if ((res >= 0 && (unsigned)res >> 27) || (res < 0 && ((unsigned)res >> 27)-32)) + if (R_FAILED(res) || (R_SUCCEEDED(res) && R_LEVEL(res) != RL_SUCCESS)) + { + return 0; + } + } + svcBreak(USERBREAK_ASSERT); + } + } + + if ((title->mediaType != update->mediaType) || (prog_id != update->programId)) + { + svcBreak(USERBREAK_ASSERT); + } + res = FSREG_LoadProgram(prog_handle, title); + if (R_SUCCEEDED(res)) + { + if (*prog_handle >> 32 == 0xFFFF0000) + { + return 0; + } + res = FSREG_CheckHostLoadId(*prog_handle); + //if ((res >= 0 && (unsigned)res >> 27) || (res < 0 && ((unsigned)res >> 27)-32)) + if (R_FAILED(res) || (R_SUCCEEDED(res) && R_LEVEL(res) != RL_SUCCESS)) + { + svcBreak(USERBREAK_ASSERT); + } + } + return res; +} + +static Result loader_UnregisterProgram(u64 prog_handle) +{ + Result res; + + if (prog_handle >> 32 == 0xFFFF0000) + { + return FSREG_UnloadProgram(prog_handle); + } + else + { + res = FSREG_CheckHostLoadId(prog_handle); + //if ((res >= 0 && (unsigned)res >> 27) || (res < 0 && ((unsigned)res >> 27)-32)) + if (R_FAILED(res) || (R_SUCCEEDED(res) && R_LEVEL(res) != RL_SUCCESS)) + { + return PXIPM_UnregisterProgram(prog_handle); + } + else + { + return FSREG_UnloadProgram(prog_handle); + } + } +} + +static void handle_commands(void) +{ + FS_ProgramInfo title; + FS_ProgramInfo update; + u32* cmdbuf; + u16 cmdid; + int res; + Handle handle; + u64 prog_handle; + + cmdbuf = getThreadCommandBuffer(); + cmdid = cmdbuf[0] >> 16; + res = 0; + switch (cmdid) + { + case 1: // LoadProcess + { + res = loader_LoadProcess(&handle, *(u64 *)&cmdbuf[1]); + cmdbuf[0] = 0x10042; + cmdbuf[1] = res; + cmdbuf[2] = 16; + cmdbuf[3] = handle; + break; + } + case 2: // RegisterProgram + { + memcpy(&title, &cmdbuf[1], sizeof(FS_ProgramInfo)); + memcpy(&update, &cmdbuf[5], sizeof(FS_ProgramInfo)); + res = loader_RegisterProgram(&prog_handle, &title, &update); + cmdbuf[0] = 0x200C0; + cmdbuf[1] = res; + *(u64 *)&cmdbuf[2] = prog_handle; + break; + } + case 3: // UnregisterProgram + { + if (g_cached_prog_handle == prog_handle) + { + g_cached_prog_handle = 0; + } + cmdbuf[0] = 0x30040; + cmdbuf[1] = loader_UnregisterProgram(*(u64 *)&cmdbuf[1]); + break; + } + case 4: // GetProgramInfo + { + prog_handle = *(u64 *)&cmdbuf[1]; + if (prog_handle != g_cached_prog_handle) + { + res = loader_GetProgramInfo(&g_exheader, prog_handle); + if (res >= 0) + { + g_cached_prog_handle = prog_handle; + } + else + { + g_cached_prog_handle = 0; + } + } + memcpy(&g_ret_buf, &g_exheader, 1024); + cmdbuf[0] = 0x40042; + cmdbuf[1] = res; + cmdbuf[2] = 0x1000002; + cmdbuf[3] = (u32) &g_ret_buf; + break; + } + default: // error + { + cmdbuf[0] = 0x40; + cmdbuf[1] = 0xD900182F; + break; + } + } +} + +static Result should_terminate(int *term_request) +{ + u32 notid; + Result ret; + + ret = srvSysReceiveNotification(¬id); + if (R_FAILED(ret)) + { + return ret; + } + if (notid == 0x100) // term request + { + *term_request = 1; + } + return 0; +} + +// this is called before main +void __appInit() +{ + srvSysInit(); + fsregInit(); + fsldrInit(); + pxipmInit(); +} + +// this is called after main exits +void __appExit() +{ + pxipmExit(); + fsldrExit(); + fsregExit(); + srvSysExit(); +} + +// stubs for non-needed pre-main functions +void __sync_init(); +void __sync_fini(); +void __system_initSyscalls(); + +void __ctru_exit(int rc) +{ + __appExit(); + __sync_fini(); + svcExitProcess(); +} + +void initSystem(void (*retAddr)(void)) +{ + __sync_init(); + __system_initSyscalls(); + __appInit(); +} + +int main() +{ + Result ret; + Handle handle; + Handle reply_target; + Handle *srv_handle; + Handle *notification_handle; + s32 index; + int i; + int term_request; + u32* cmdbuf; + + ret = 0; + + srv_handle = &g_handles[1]; + notification_handle = &g_handles[0]; + + if (R_FAILED(srvSysRegisterService(srv_handle, "Loader", MAX_SESSIONS))) + { + svcBreak(USERBREAK_ASSERT); + } + + if (R_FAILED(srvSysEnableNotification(notification_handle))) + { + svcBreak(USERBREAK_ASSERT); + } + + g_active_handles = 2; + g_cached_prog_handle = 0; + index = 1; + + reply_target = 0; + term_request = 0; + do + { + if (reply_target == 0) + { + cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = 0xFFFF0000; + } + ret = svcReplyAndReceive(&index, g_handles, g_active_handles, reply_target); + + if (R_FAILED(ret)) + { + // check if any handle has been closed + if (ret == 0xC920181A) + { + if (index == -1) + { + for (i = 2; i < MAX_SESSIONS+2; i++) + { + if (g_handles[i] == reply_target) + { + index = i; + break; + } + } + } + svcCloseHandle(g_handles[index]); + g_handles[index] = g_handles[g_active_handles-1]; + g_active_handles--; + reply_target = 0; + } + else + { + svcBreak(USERBREAK_ASSERT); + } + } + else + { + // process responses + reply_target = 0; + switch (index) + { + case 0: // notification + { + if (R_FAILED(should_terminate(&term_request))) + { + svcBreak(USERBREAK_ASSERT); + } + break; + } + case 1: // new session + { + if (R_FAILED(svcAcceptSession(&handle, *srv_handle))) + { + svcBreak(USERBREAK_ASSERT); + } + if (g_active_handles < MAX_SESSIONS+2) + { + g_handles[g_active_handles] = handle; + g_active_handles++; + } + else + { + svcCloseHandle(handle); + } + break; + } + default: // session + { + handle_commands(); + reply_target = g_handles[index]; + break; + } + } + } + } while (!term_request || g_active_handles != 2); + + srvSysUnregisterService("Loader"); + svcCloseHandle(*srv_handle); + svcCloseHandle(*notification_handle); + + return 0; +} diff --git a/injector/source/patcher.c b/injector/source/patcher.c new file mode 100644 index 0000000..b38b558 --- /dev/null +++ b/injector/source/patcher.c @@ -0,0 +1,274 @@ +#include <3ds.h> +#include +#include "patcher.h" +#include "ifile.h" + +#ifndef PATH_MAX +#define PATH_MAX 255 +#endif + +static u32 config = 0; +static u8 secureinfo[0x111] = {0}; + +//Quick Search algorithm, adapted from http://igm.univ-mlv.fr/~lecroq/string/node19.html#SECTION00190 +static u8 *memsearch(u8 *startPos, const void *pattern, u32 size, u32 patternSize){ + const u8 *patternc = (const u8 *)pattern; + + //Preprocessing + int table[256]; + + for(u32 i = 0; i < 256; ++i) + table[i] = patternSize + 1; + for(u32 i = 0; i < patternSize; ++i) + table[patternc[i]] = patternSize - i; + + //Searching + u32 j = 0; + + while(j <= size - patternSize){ + if(memcmp(patternc, startPos + j, patternSize) == 0) + return startPos + j; + j += table[startPos[j + patternSize]]; + } + + return NULL; +} + +static u32 patch_memory(u8 *start, u32 size, const void *pattern, u32 patsize, int offset, const void *replace, u32 repsize, u32 count){ + u32 i; + + for(i = 0; i < count; i++){ + u8 *found = memsearch(start, pattern, size, patsize); + if(found == NULL) + break; + + memcpy(found + offset, replace, repsize); + + u32 at = (u32)(found - start); + + if(at + patsize > size) size = 0; + else size = size - (at + patsize); + + start = found + patsize; + } + + return i; +} + +static int file_open(IFile *file, FS_ArchiveID id, const char *path, int flags){ + FS_Archive archive; + FS_Path ppath; + + size_t len = strnlen(path, PATH_MAX); + archive.id = id; + archive.lowPath.type = PATH_EMPTY; + archive.lowPath.size = 1; + archive.lowPath.data = (u8 *)""; + ppath.type = PATH_ASCII; + ppath.data = path; + ppath.size = len+1; + return IFile_Open(file, archive, ppath, flags); +} + +static int patch_secureinfo(){ + IFile file; + Result ret; + u64 total; + + if(secureinfo[0] == 0xFF) + return 0; + + ret = file_open(&file, ARCHIVE_SDMC, "/SecureInfo_A", FS_OPEN_READ); + if(R_SUCCEEDED(ret)){ + ret = IFile_Read(&file, &total, secureinfo, sizeof(secureinfo)); + IFile_Close(&file); + if(R_SUCCEEDED(ret) && total == sizeof(secureinfo)){ + ret = file_open(&file, ARCHIVE_NAND_RW, "/sys/SecureInfo_C", FS_OPEN_WRITE | FS_OPEN_CREATE); + if(R_SUCCEEDED(ret)){ + ret = IFile_Write(&file, &total, secureinfo, sizeof(secureinfo), FS_WRITE_FLUSH); + IFile_Close(&file); + } + secureinfo[0] = 0xFF; // we repurpose this byte as status + } + } + else { // get file from NAND + ret = file_open(&file, ARCHIVE_NAND_RW, "/sys/SecureInfo_C", FS_OPEN_READ); + if(R_SUCCEEDED(ret)){ + ret = IFile_Read(&file, &total, secureinfo, sizeof(secureinfo)); + IFile_Close(&file); + if(R_SUCCEEDED(ret) && total == sizeof(secureinfo)) + secureinfo[0] = 0xFF; + } + } + + return ret; +} + +static int open_config(){ + IFile file; + Result ret; + u64 total; + + if(config) + return 0; + + ret = file_open(&file, ARCHIVE_SDMC, "/aurei/config.bin", FS_OPEN_READ); + if(R_SUCCEEDED(ret)){ + ret = IFile_Read(&file, &total, (void *)&config, 3); + IFile_Close(&file); + } + + return ret; +} + +u32 patch_code(u64 progid, u8 *code, u32 size){ + if( progid == 0x0004003000008F02LL || // USA Menu + progid == 0x0004003000008202LL || // JPN Menu + progid == 0x0004003000009802LL || // EUR Menu + progid == 0x000400300000A102LL || // CHN Menu + progid == 0x000400300000A902LL || // KOR Menu + progid == 0x000400300000B102LL // TWN Menu + ){ + static const u8 regionFreePattern[] = { + 0x00, 0x00, 0x55, 0xE3, 0x01, 0x10, 0xA0, 0xE3 + }; + static const u8 regionFreePatch[] = { + 0x01, 0x00, 0xA0, 0xE3, 0x1E, 0xFF, 0x2F, 0xE1 + }; + + patch_memory(code, size, + regionFreePattern, + sizeof(regionFreePattern), -16, + regionFreePatch, + sizeof(regionFreePatch), 1 + ); + } + else if(progid == 0x0004013000002C02LL){ // NIM + static const u8 blockAutoUpdatesPattern[] = { + 0x25, 0x79, 0x0B, 0x99 + }; + static const u8 blockAutoUpdatesPatch[] = { + 0xE3, 0xA0 + }; + static const u8 blockEShopUpdateCheckPattern[] = { + 0x30, 0xB5, 0xF1, 0xB0 + }; + static const u8 blockEShopUpdateCheckPatch[] = { + 0x00, 0x20, 0x08, 0x60, 0x70, 0x47 + }; + static const u8 countryRespPattern[] = { + 0x01, 0x20, 0x01, 0x90, 0x22, 0x46, 0x06, 0x9B + }; + static const char countryRespPatchModel[] = { + 0x06, 0x9A, 0x03, 0x20, 0x90, 0x47, 0x55, 0x21, 0x01, 0x70, 0x53, 0x21, 0x41, 0x70, 0x00, 0x21, + 0x81, 0x70, 0x60, 0x61, 0x00, 0x20 + }; + const char *country; + char countryRespPatch[sizeof(countryRespPatchModel)]; + + patch_memory(code, size, + blockAutoUpdatesPattern, + sizeof(blockAutoUpdatesPattern), 0, + blockAutoUpdatesPatch, + sizeof(blockAutoUpdatesPatch), 1 + ); + patch_memory(code, size, + blockEShopUpdateCheckPattern, + sizeof(blockEShopUpdateCheckPattern), 0, + blockEShopUpdateCheckPatch, + sizeof(blockEShopUpdateCheckPatch), 1 + ); + if(R_SUCCEEDED(patch_secureinfo())){ + switch(secureinfo[0x100]){ + case 1: country = "US"; break; + case 2: country = "GB"; break; // sorry rest-of-Europe, you have to change this + case 3: country = "AU"; break; + case 4: country = "CN"; break; + case 5: country = "KR"; break; + case 6: country = "TW"; break; + default: case 0: country = "JP"; break; + } + + // patch XML response Country + memcpy(countryRespPatch, + countryRespPatchModel, + sizeof(countryRespPatchModel) + ); + countryRespPatch[6] = country[0]; + countryRespPatch[10] = country[1]; + patch_memory(code, size, + countryRespPattern, + sizeof(countryRespPattern), 0, + countryRespPatch, + sizeof(countryRespPatch), 1 + ); + } + } + else if( + progid == 0x0004001000021000LL || // USA MSET + progid == 0x0004001000020000LL || // JPN MSET + progid == 0x0004001000022000LL || // EUR MSET + progid == 0x0004001000026000LL || // CHN MSET + progid == 0x0004001000027000LL || // KOR MSET + progid == 0x0004001000028000LL // TWN MSET + ){ + if(R_SUCCEEDED(open_config()) && ((config >> 5) & 0x1)){ + static const u16 VerPattern[] = u"Ver."; + const u32 currentFirm = ((config >> 12) & 0x1); + const u32 currentNand = ((config >> 13) & 0x3); + + patch_memory(code, size, + VerPattern, + sizeof(VerPattern) - sizeof(u16), 0, + currentNand ? ((currentNand == 1) ? ((currentFirm == 1) ? u" Emu" : u"Emu9") : u"Emu2") : + ((currentFirm == 1) ? u" Sys" : u"Sys9"), + sizeof(VerPattern) - sizeof(u16), 1 + ); + } + } + else if (progid == 0x0004013000008002LL){ // NS + static const u8 stopCartUpdatesPattern[] = { + 0x0C, 0x18, 0xE1, 0xD8 + }; + static const u8 stopCartUpdatesPatch[] = { + 0x0B, 0x18, 0x21, 0xC8 + }; + + patch_memory(code, size, + stopCartUpdatesPattern, + sizeof(stopCartUpdatesPattern), 0, + stopCartUpdatesPatch, + sizeof(stopCartUpdatesPatch), 2 + ); + } + else if(progid == 0x0004013000001702LL){ // CFG + static const u8 secureinfoSigCheckPattern[] = { + 0x06, 0x46, 0x10, 0x48, 0xFC + }; + static const u8 secureinfoSigCheckPatch[] = { + 0x00, 0x26 + }; + static const u16 secureinfoFilenamePattern[] = u"SecureInfo_"; + static const u16 secureinfoFilenamePatch[] = u"C"; + + // disable SecureInfo signature check + patch_memory(code, size, + secureinfoSigCheckPattern, + sizeof(secureinfoSigCheckPattern), 0, + secureinfoSigCheckPatch, + sizeof(secureinfoSigCheckPatch), 1 + ); + if(R_SUCCEEDED(patch_secureinfo())){ + // use SecureInfo_C + patch_memory(code, size, + secureinfoFilenamePattern, + sizeof(secureinfoFilenamePattern) - sizeof(u16), + sizeof(secureinfoFilenamePattern) - sizeof(u16), + secureinfoFilenamePatch, + sizeof(secureinfoFilenamePatch) - sizeof(u16), 2 + ); + } + } + + return 0; +} diff --git a/injector/source/patcher.h b/injector/source/patcher.h new file mode 100644 index 0000000..8c4545c --- /dev/null +++ b/injector/source/patcher.h @@ -0,0 +1,5 @@ +#pragma once + +#include <3ds/types.h> + +u32 patch_code(u64 progid, u8 *code, u32 size); diff --git a/injector/source/pxipm.c b/injector/source/pxipm.c new file mode 100644 index 0000000..d85d58f --- /dev/null +++ b/injector/source/pxipm.c @@ -0,0 +1,74 @@ +#include <3ds.h> +#include +#include "pxipm.h" +#include "srvsys.h" + +static Handle pxipmHandle; +static int pxipmRefCount; + +Result pxipmInit(void) +{ + Result ret = 0; + + if (AtomicPostIncrement(&pxipmRefCount)) return 0; + + ret = srvSysGetServiceHandle(&pxipmHandle, "PxiPM"); + + if (R_FAILED(ret)) AtomicDecrement(&pxipmRefCount); + return ret; +} + +void pxipmExit(void) +{ + if (AtomicDecrement(&pxipmRefCount)) return; + svcCloseHandle(pxipmHandle); +} + +Result PXIPM_RegisterProgram(u64 *prog_handle, FS_ProgramInfo *title, FS_ProgramInfo *update) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x2,8,0); // 0x20200 + memcpy(&cmdbuf[1], &title->programId, sizeof(u64)); + *(u8 *)&cmdbuf[3] = title->mediaType; + memcpy(((u8 *)&cmdbuf[3])+1, &title->padding, 7); + memcpy(&cmdbuf[5], &update->programId, sizeof(u64)); + *(u8 *)&cmdbuf[7] = update->mediaType; + memcpy(((u8 *)&cmdbuf[7])+1, &update->padding, 7); + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(pxipmHandle))) return ret; + *prog_handle = *(u64*)&cmdbuf[2]; + + return cmdbuf[1]; +} + +Result PXIPM_GetProgramInfo(exheader_header *exheader, u64 prog_handle) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x1,2,2); // 0x10082 + cmdbuf[1] = (u32) (prog_handle); + cmdbuf[2] = (u32) (prog_handle >> 32); + cmdbuf[3] = (0x400 << 8) | 0x4; + cmdbuf[4] = (u32) exheader; + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(pxipmHandle))) return ret; + + return cmdbuf[1]; +} + +Result PXIPM_UnregisterProgram(u64 prog_handle) +{ + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x3,2,0); // 0x30080 + cmdbuf[1] = (u32) (prog_handle); + cmdbuf[2] = (u32) (prog_handle >> 32); + + Result ret = 0; + if(R_FAILED(ret = svcSendSyncRequest(pxipmHandle))) return ret; + + return cmdbuf[1]; +} diff --git a/injector/source/pxipm.h b/injector/source/pxipm.h new file mode 100644 index 0000000..e9d1feb --- /dev/null +++ b/injector/source/pxipm.h @@ -0,0 +1,10 @@ +#pragma once + +#include <3ds/types.h> +#include "exheader.h" + +Result pxipmInit(void); +void pxipmExit(void); +Result PXIPM_RegisterProgram(u64 *prog_handle, FS_ProgramInfo *title, FS_ProgramInfo *update); +Result PXIPM_GetProgramInfo(exheader_header *exheader, u64 prog_handle); +Result PXIPM_UnregisterProgram(u64 prog_handle); diff --git a/injector/source/srvsys.c b/injector/source/srvsys.c new file mode 100644 index 0000000..1bd05ed --- /dev/null +++ b/injector/source/srvsys.c @@ -0,0 +1,154 @@ +#include <3ds.h> +#include +#include "srvsys.h" + +static Handle srvHandle; +static int srvRefCount; +static RecursiveLock initLock; +static int initLockinit = 0; + +Result srvSysInit() +{ + Result rc = 0; + + if (!initLockinit) + { + RecursiveLock_Init(&initLock); + } + + RecursiveLock_Lock(&initLock); + + if (srvRefCount > 0) + { + RecursiveLock_Unlock(&initLock); + return MAKERESULT(RL_INFO, RS_NOP, 25, RD_ALREADY_INITIALIZED); + } + + while (1) + { + rc = svcConnectToPort(&srvHandle, "srv:"); + if (R_LEVEL(rc) != RL_PERMANENT || + R_SUMMARY(rc) != RS_NOTFOUND || + R_DESCRIPTION(rc) != RD_NOT_FOUND + ) break; + svcSleepThread(500000); + } + if (R_SUCCEEDED(rc)) + { + rc = srvSysRegisterClient(); + srvRefCount++; + } + + RecursiveLock_Unlock(&initLock); + return rc; +} + +Result srvSysRegisterClient(void) +{ + Result rc = 0; + u32* cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x1,0,2); // 0x10002 + cmdbuf[1] = IPC_Desc_CurProcessHandle(); + + if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc; + + return cmdbuf[1]; +} + +Result srvSysExit() +{ + Result rc; + RecursiveLock_Lock(&initLock); + + if (srvRefCount > 1) + { + srvRefCount--; + RecursiveLock_Unlock(&initLock); + return MAKERESULT(RL_INFO, RS_NOP, 25, RD_BUSY); + } + + if (srvHandle != 0) svcCloseHandle(srvHandle); + else svcBreak(USERBREAK_ASSERT); + rc = (Result)srvHandle; // yeah, I think this is a benign bug + srvHandle = 0; + srvRefCount--; + RecursiveLock_Unlock(&initLock); + return rc; +} + +Result srvSysGetServiceHandle(Handle* out, const char* name) +{ + Result rc = 0; + u32* cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x5,4,0); // 0x50100 + strncpy((char*) &cmdbuf[1], name,8); + cmdbuf[3] = strlen(name); + cmdbuf[4] = 0x0; + + if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc; + + if(out) *out = cmdbuf[3]; + + return cmdbuf[1]; +} + +Result srvSysEnableNotification(Handle* semaphoreOut) +{ + Result rc = 0; + u32* cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x2,0,0); + + if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc; + + if(semaphoreOut) *semaphoreOut = cmdbuf[3]; + + return cmdbuf[1]; +} + +Result srvSysReceiveNotification(u32* notificationIdOut) +{ + Result rc = 0; + u32* cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0xB,0,0); // 0xB0000 + + if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc; + + if(notificationIdOut) *notificationIdOut = cmdbuf[2]; + + return cmdbuf[1]; +} + +Result srvSysRegisterService(Handle* out, const char* name, int maxSessions) +{ + Result rc = 0; + u32* cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x3,4,0); // 0x30100 + strncpy((char*) &cmdbuf[1], name,8); + cmdbuf[3] = strlen(name); + cmdbuf[4] = maxSessions; + + if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc; + + if(out) *out = cmdbuf[3]; + + return cmdbuf[1]; +} + +Result srvSysUnregisterService(const char* name) +{ + Result rc = 0; + u32* cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x4,3,0); // 0x400C0 + strncpy((char*) &cmdbuf[1], name,8); + cmdbuf[3] = strlen(name); + + if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc; + + return cmdbuf[1]; +} diff --git a/injector/source/srvsys.h b/injector/source/srvsys.h new file mode 100644 index 0000000..7c1c0a8 --- /dev/null +++ b/injector/source/srvsys.h @@ -0,0 +1,47 @@ +/** + * @file srv.h + * @brief Service API. + */ +#pragma once + +/// Initializes the service API. +Result srvSysInit(void); + +/// Exits the service API. +Result srvSysExit(void); + +/** + * @brief Retrieves a service handle, retrieving from the environment handle list if possible. + * @param out Pointer to write the handle to. + * @param name Name of the service. + */ +Result srvSysGetServiceHandle(Handle* out, const char* name); + +/// Registers the current process as a client to the service API. +Result srvSysRegisterClient(void); + +/** + * @brief Enables service notificatios, returning a notification semaphore. + * @param semaphoreOut Pointer to output the notification semaphore to. + */ +Result srvSysEnableNotification(Handle* semaphoreOut); + +/** + * @brief Receives a notification. + * @param notificationIdOut Pointer to output the ID of the received notification to. + */ +Result srvSysReceiveNotification(u32* notificationIdOut); + +/** + * @brief Registers the current process as a service. + * @param out Pointer to write the service handle to. + * @param name Name of the service. + * @param maxSessions Maximum number of sessions the service can handle. + */ +Result srvSysRegisterService(Handle* out, const char* name, int maxSessions); + +/** + * @brief Unregisters the current process as a service. + * @param name Name of the service. + */ +Result srvSysUnregisterService(const char* name); diff --git a/source/buttons.h b/source/buttons.h index cdd7f77..52baeda 100644 --- a/source/buttons.h +++ b/source/buttons.h @@ -13,10 +13,14 @@ #define BUTTON_L1 (1 << 9) #define BUTTON_A 1 #define BUTTON_B (1 << 1) +#define BUTTON_SELECT (1 << 2) +#define BUTTON_START (1 << 3) +#define BUTTON_RIGHT (1 << 4) +#define BUTTON_LEFT (1 << 5) #define BUTTON_UP (1 << 6) #define BUTTON_DOWN (1 << 7) -#define BUTTON_START (1 << 3) -#define BUTTON_SELECT (1 << 2) -#define SAFE_MODE (BUTTON_R1 | BUTTON_L1 | BUTTON_A | BUTTON_UP) -#define OPTION_BUTTONS (BUTTON_R1 | BUTTON_L1 | BUTTON_A) -#define PAYLOAD_BUTTONS ((BUTTON_L1 | BUTTON_A) ^ 0xFFF) \ No newline at end of file +#define BUTTON_L1R1 ((1 << 8) | (1 << 9)) +#define SAFE_MODE (BUTTON_L1R1 | BUTTON_A | BUTTON_UP) +#define OPTION_BUTTONS (BUTTON_L1R1 | BUTTON_A) +#define PAYLOAD_BUTTONS ((BUTTON_L1 | BUTTON_A) ^ 0xFFF) +#define MENU_BUTTONS (BUTTON_LEFT | BUTTON_RIGHT | BUTTON_UP | BUTTON_DOWN | BUTTON_A | BUTTON_START) \ No newline at end of file diff --git a/source/draw.c b/source/draw.c index 741957d..5d316f3 100644 --- a/source/draw.c +++ b/source/draw.c @@ -42,41 +42,41 @@ void loadSplash(void){ } } -void drawCharacter(char character, int pos_x, int pos_y, u32 color){ +void drawCharacter(char character, int posX, int posY, u32 color){ u8 *const select = fb->top_left; for(int y = 0; y < 8; y++){ - unsigned char char_pos = font[character * 8 + y]; + char charPos = font[character * 8 + y]; for(int x = 7; x >= 0; x--){ - int screen_pos = (pos_x * SCREEN_TOP_HEIGHT * 3 + (SCREEN_TOP_HEIGHT - y - pos_y - 1) * 3) + (7 - x) * 3 * SCREEN_TOP_HEIGHT; + int screenPos = (posX * SCREEN_TOP_HEIGHT * 3 + (SCREEN_TOP_HEIGHT - y - posY - 1) * 3) + (7 - x) * 3 * SCREEN_TOP_HEIGHT; - if ((char_pos >> x) & 1) { - select[screen_pos] = color >> 16; - select[screen_pos + 1] = color >> 8; - select[screen_pos + 2] = color; + if ((charPos >> x) & 1) { + select[screenPos] = color >> 16; + select[screenPos + 1] = color >> 8; + select[screenPos + 2] = color; } } } } -int drawString(const char *string, int pos_x, int pos_y, u32 color){ +int drawString(const char *string, int posX, int posY, u32 color){ int length = strlen(string); for(int i = 0, line_i = 0; i < length; i++, line_i++){ if(string[i] == '\n'){ - pos_y += SPACING_VERT; + posY += SPACING_Y; line_i = 0; i++; - } else if(line_i >= (SCREEN_TOP_WIDTH - pos_x) / SPACING_HORIZ){ + } else if(line_i >= (SCREEN_TOP_WIDTH - posX) / SPACING_X){ // Make sure we never get out of the screen. - pos_y += SPACING_VERT; + posY += SPACING_Y; line_i = 2; // Little offset so we know the same string continues. if(string[i] == ' ') i++; // Spaces at the start look weird } - drawCharacter(string[i], pos_x + line_i * SPACING_HORIZ, pos_y, color); + drawCharacter(string[i], posX + line_i * SPACING_X, posY, color); } - return pos_y; + return posY; } \ No newline at end of file diff --git a/source/draw.h b/source/draw.h index 4ccb3b0..ab69375 100644 --- a/source/draw.h +++ b/source/draw.h @@ -10,10 +10,10 @@ #include "types.h" -#define SPACING_VERT 10 -#define SPACING_HORIZ 8 +#define SPACING_Y 10 +#define SPACING_X 8 void loadSplash(void); void clearScreens(void); -void drawCharacter(char character, int pos_x, int pos_y, u32 color); -int drawString(const char *string, int pos_x, int pos_y, u32 color); \ No newline at end of file +void drawCharacter(char character, int posX, int posY, u32 color); +int drawString(const char *string, int posX, int posY, u32 color); \ No newline at end of file diff --git a/source/emunand.c b/source/emunand.c index 388fed5..30a0151 100644 --- a/source/emunand.c +++ b/source/emunand.c @@ -15,17 +15,17 @@ void getEmunandSect(u32 *off, u32 *head, u32 emuNAND){ u32 nandOffset = emuNAND == 1 ? 0 : (nandSize > 0x200000 ? 0x400000 : 0x200000); - //Check for Gateway emuNAND - if(sdmmc_sdcard_readsectors(nandOffset + nandSize, 1, temp) == 0){ + //Check for RedNAND + if(sdmmc_sdcard_readsectors(nandOffset + 1, 1, temp) == 0){ if(*(u32 *)(temp + 0x100) == NCSD_MAGIC){ + *off = nandOffset + 1; + *head = nandOffset + 1; + } + //Check for Gateway emuNAND + else if(sdmmc_sdcard_readsectors(nandOffset + nandSize, 1, temp) == 0){ + if(*(u32 *)(temp + 0x100) == NCSD_MAGIC){ *off = nandOffset; *head = nandOffset + nandSize; - } - //Check for RedNAND - else if(sdmmc_sdcard_readsectors(nandOffset + 1, 1, temp) == 0){ - if(*(u32 *)(temp + 0x100) == NCSD_MAGIC){ - *off = nandOffset + 1; - *head = nandOffset + 1; } //Fallback to the first emuNAND if there's no second one else if(emuNAND == 2) getEmunandSect(off, head, 1); @@ -33,32 +33,32 @@ void getEmunandSect(u32 *off, u32 *head, u32 emuNAND){ } } -u32 getSDMMC(void *pos, u32 size){ +u32 getSDMMC(u8 *pos, u32 size){ //Look for struct code const u8 pattern[] = {0x21, 0x20, 0x18, 0x20}; - const u8 *off = (u8 *)memsearch(pos, pattern, size, 4) - 1; + const u8 *off = memsearch(pos, pattern, size, 4) - 1; return *(u32 *)(off + 0x0A) + *(u32 *)(off + 0x0E); } -void getEmuRW(void *pos, u32 size, u32 *readOff, u32 *writeOff){ +void getEmuRW(u8 *pos, u32 size, u32 *readOff, u32 *writeOff){ //Look for read/write code const u8 pattern[] = {0x1E, 0x00, 0xC8, 0x05}; - *writeOff = (u32)memsearch(pos, pattern, size, 4) - 6; - *readOff = (u32)memsearch((void *)(*writeOff - 0x1000), pattern, 0x1000, 4) - 6; + *readOff = (u32)memsearch(pos, pattern, size, 4) - 6; + *writeOff = (u32)memsearch((u8 *)(*readOff + 0xA), pattern, 0x100, 4) - 6; } -u32 *getMPU(void *pos, u32 size){ +u32 *getMPU(u8 *pos, u32 size){ //Look for MPU pattern const u8 pattern[] = {0x03, 0x00, 0x24, 0x00}; return (u32 *)memsearch(pos, pattern, size, 4); } -void *getEmuCode(u8 *pos, u32 size, u8 *proc9Offset){ - const u8 pattern[] = {0x00, 0xFF, 0xFF, 0xFF}; - +void *getEmuCode(u8 *proc9Offset){ + const u8 pattern[] = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00}; + //Looking for the last free space before Process9 - return (u8 *)memsearch(pos, pattern, size - (size - (u32)(proc9Offset - pos)), 4) + 0xD; + return memsearch(proc9Offset - 0x3000, pattern, 0x3000, 6) + 0x455; } \ No newline at end of file diff --git a/source/emunand.h b/source/emunand.h index 00605f8..9ec05e7 100644 --- a/source/emunand.h +++ b/source/emunand.h @@ -11,7 +11,7 @@ #define NCSD_MAGIC (0x4453434E) void getEmunandSect(u32 *off, u32 *head, u32 emuNAND); -u32 getSDMMC(void *pos, u32 size); -void getEmuRW(void *pos, u32 size, u32 *readOff, u32 *writeOff); -u32 *getMPU(void *pos, u32 size); -void *getEmuCode(u8 *pos, u32 size, u8 *proc9Offset); \ No newline at end of file +u32 getSDMMC(u8 *pos, u32 size); +void getEmuRW(u8 *pos, u32 size, u32 *readOff, u32 *writeOff); +u32 *getMPU(u8 *pos, u32 size); +void *getEmuCode(u8 *proc9Offset); \ No newline at end of file diff --git a/source/firm.c b/source/firm.c index dbd3a45..e56157d 100755 --- a/source/firm.c +++ b/source/firm.c @@ -18,7 +18,7 @@ #include "../build/patches.h" //FIRM patches version -#define PATCH_VER 1 +#define PATCH_VER 2 static firmHeader *const firm = (firmHeader *)0x24000000; static const firmSectionHeader *section; @@ -48,8 +48,8 @@ void setupCFW(void){ //Attempt to read the configuration file const char configPath[] = "aurei/config.bin"; - u16 config = 0; - u32 needConfig = fileRead(&config, configPath, 2) ? 1 : 2; + u32 config = 0; + u32 needConfig = fileRead(&config, configPath, 3) ? 1 : 2; //Determine if A9LH is installed and the user has an updated sysNAND u32 updatedSys; @@ -66,21 +66,18 @@ void setupCFW(void){ updatedSys = 0; } - //Determine if the user chose to use pre-patched FIRMs - u32 usePatchedFirmSet = (config >> 1) & 0x1; - /* If booting with A9LH, it's a MCU reboot and a previous configuration exists, try to force boot options */ if(a9lhBoot && previousFirm && needConfig == 1){ //Always force a sysNAND boot when quitting AGB_FIRM if(previousFirm == 0x7){ - if(!updatedSys) mode = (config >> 8) & 0x1; + if(!updatedSys) mode = (config >> 12) & 0x1; emuNAND = 0; needConfig = 0; //Else, force the last used boot options unless A, L or R are pressed } else if(!(pressed & OPTION_BUTTONS)){ - mode = (config >> 8) & 0x1; - emuNAND = (config >> 9) & 0x1; + mode = (config >> 12) & 0x1; + emuNAND = (config >> 13) & 0x3; needConfig = 0; } } @@ -94,45 +91,45 @@ void setupCFW(void){ //If no configuration file exists or SELECT is held, load configuration menu if(needConfig == 2 || (pressed & BUTTON_SELECT)) - configureCFW(configPath); + configureCFW(configPath, patchedFirms[3]); //If screens are inited, load splash screen if(PDN_GPU_CNT != 0x1) loadSplash(); /* If L is pressed, boot 9.0 FIRM */ - mode = (pressed & BUTTON_L1) ? 0 : 1; + mode = ((config >> 3) & 0x1) ? ((!(pressed & BUTTON_L1R1)) ? 0 : 1) : + ((pressed & BUTTON_L1) ? 0 : 1); /* If L or R aren't pressed on a 9.0/9.2 sysNAND, or the 9.0 FIRM is selected or R is pressed on a > 9.2 sysNAND, boot emuNAND */ if((updatedSys && (!mode || (pressed & BUTTON_R1))) || (!updatedSys && mode && !(pressed & BUTTON_R1))){ //If not 9.0 FIRM and B is pressed, attempt booting the second emuNAND - emuNAND = (mode && (pressed & BUTTON_B)) ? 2 : 1; + emuNAND = (mode && ((!(pressed & BUTTON_B)) == ((config >> 4) & 0x1))) ? 2 : 1; } else emuNAND = 0; + u32 tempConfig = (PATCH_VER << 17) | (a9lhSetup << 16) | (emuNAND << 13) | (mode << 12); + /* If tha FIRM patches version is different or user switched to/from A9LH, - and "Use pre-patched FIRMs" is set, delete all patched FIRMs */ - u16 bootConfig = (PATCH_VER << 11) | (a9lhSetup << 10); - if(usePatchedFirmSet && bootConfig != (config & 0xFC00)) + delete all patched FIRMs */ + if((tempConfig & 0xFF0000) != (config & 0xFF0000)) deleteFirms(patchedFirms, sizeof(patchedFirms) / sizeof(char *)); - //We also need to remember the used boot mode on A9LH - if(a9lhBoot) bootConfig |= (mode << 8) | (emuNAND << 9); - //If the boot configuration is different from previously, overwrite it - if(bootConfig != (config & 0xFF00)){ - //Preserve user settings (first byte) - u16 tempConfig = ((config & 0xFF) | bootConfig); - fileWrite(&tempConfig, configPath, 2); + if((tempConfig & 0xFFF000) != (config & 0xFFF000)){ + //Preserve user settings (first 12 bits) + tempConfig |= config & 0xFFF; + fileWrite(&tempConfig, configPath, 3); } } - //Determine which patched FIRM we need to write or attempt to use (if any) + /* Determine which patched FIRM we need to write or attempt to use (if any). + Patched 9.0 FIRM is only needed if "Use pre-patched FIRMs" is set */ selectedFirm = mode ? (emuNAND ? (emuNAND == 1 ? 2 : 3) : 1) : - (usePatchedFirmSet ? 4 : 0); + (((config >> 1) & 0x1) ? 4 : 0); - //Determine if we need to use a pre-patched FIRM - usePatchedFirm = (usePatchedFirmSet && fileExists(patchedFirms[selectedFirm - 1])) ? 1 : 0; + //If "Use pre-patched FIRMs" is set and the appropriate FIRM exists, use it + usePatchedFirm = (((config >> 1) & 0x1) && fileExists(patchedFirms[selectedFirm - 1])) ? 1 : 0; } //Load FIRM into FCRAM @@ -182,7 +179,7 @@ static void loadEmu(u8 *proc9Offset){ if(!emuHeader) error("No emuNAND has been detected"); //Copy emuNAND code - void *emuCodeOffset = getEmuCode(arm9Section, section[2].size, proc9Offset); + void *emuCodeOffset = getEmuCode(proc9Offset); memcpy(emuCodeOffset, emunand, emunand_size); //Add the data of the found emuNAND @@ -266,6 +263,18 @@ void patchFirm(void){ *(u16 *)sigOffset2 = sigPatch[0]; *((u16 *)sigOffset2 + 1) = sigPatch[1]; + //Replace the FIRM loader with the injector + u32 loaderOffset, + loaderSize; + + getLoader((u8 *)firm + section[0].offset, section[0].size, &loaderOffset, &loaderSize); + if(injector_size <= (int)loaderSize){ + memset((void *)loaderOffset, 0, loaderSize); + memcpy((void *)loaderOffset, injector, injector_size); + *((u32 *)loaderOffset + 0x41) = loaderSize / 0x200; + *((u32 *)loaderOffset + 0x69) = loaderSize / 0x200 - 5; + } + //Patch ARM9 entrypoint on N3DS to skip arm9loader if(console) firm->arm9Entry = (u8 *)0x801B01C; diff --git a/source/memory.c b/source/memory.c index 1e78c5d..07791e9 100644 --- a/source/memory.c +++ b/source/memory.c @@ -2,6 +2,8 @@ * memory.c * by Reisyukaku / Aurora Wright * Copyright (c) 2016 All Rights Reserved +* +* Quick Search algorithm adapted from http://igm.univ-mlv.fr/~lecroq/string/node19.html#SECTION00190 */ #include "memory.h" @@ -35,8 +37,25 @@ int memcmp(const void *buf1, const void *buf2, u32 size){ return 0; } -void *memsearch(void *start_pos, const void *search, u32 size, u32 size_search){ - for(u8 *pos = (u8 *)start_pos + size - size_search; pos >= (u8 *)start_pos; pos--) - if(memcmp(pos, search, size_search) == 0) return pos; +u8 *memsearch(u8 *startPos, const void *pattern, u32 size, u32 patternSize){ + const u8 *patternc = (const u8 *)pattern; + + //Preprocessing + int table[256]; + + for(u32 i = 0; i < 256; ++i) + table[i] = patternSize + 1; + for(u32 i = 0; i < patternSize; ++i) + table[patternc[i]] = patternSize - i; + + //Searching + u32 j = 0; + + while(j <= size - patternSize){ + if(memcmp(patternc, startPos + j, patternSize) == 0) + return startPos + j; + j += table[startPos[j + patternSize]]; + } + return NULL; } \ No newline at end of file diff --git a/source/memory.h b/source/memory.h index 7b1cfcb..2b54ac8 100644 --- a/source/memory.h +++ b/source/memory.h @@ -2,6 +2,8 @@ * memory.h * by Reisyukaku / Aurora Wright * Copyright (c) 2016 All Rights Reserved +* +* Quick Search algorithm adapted from http://igm.univ-mlv.fr/~lecroq/string/node19.html#SECTION00190 */ #pragma once @@ -12,4 +14,4 @@ void memcpy(void *dest, const void *src, u32 size); void memset(void *dest, int filler, u32 size); void memset32(void *dest, u32 filler, u32 size); int memcmp(const void *buf1, const void *buf2, u32 size); -void *memsearch(void *start_pos, const void *search, u32 size, u32 size_search); \ No newline at end of file +u8 *memsearch(u8 *startPos, const void *pattern, u32 size, u32 patternSize); \ No newline at end of file diff --git a/source/patches.c b/source/patches.c index 60c10d4..aef053b 100644 --- a/source/patches.c +++ b/source/patches.c @@ -23,24 +23,24 @@ const u16 writeBlock[2] = {0x2000, 0x46C0}; * Functions **************************************************/ -u8 *getProc9(void *pos, u32 size){ - return (u8 *)memsearch(pos, "ess9", size, 4); +u8 *getProc9(u8 *pos, u32 size){ + return memsearch(pos, "ess9", size, 4); } -void getSigChecks(void *pos, u32 size, u32 *off, u32 *off2){ +void getSigChecks(u8 *pos, u32 size, u32 *off, u32 *off2){ //Look for signature checks - const u8 pattern[] = {0xC0, 0x1C, 0x76, 0xE7}; - const u8 pattern2[] = {0xB5, 0x22, 0x4D, 0x0C}; + const u8 pattern[] = {0xC0, 0x1C, 0x76, 0xE7}, + pattern2[] = {0xB5, 0x22, 0x4D, 0x0C}; *off = (u32)memsearch(pos, pattern, size, 4); *off2 = (u32)memsearch(pos, pattern2, size, 4) - 1; } -void *getReboot(void *pos, u32 size){ +void *getReboot(u8 *pos, u32 size){ //Look for FIRM reboot code const u8 pattern[] = {0xDE, 0x1F, 0x8D, 0xE2}; - return (u8 *)memsearch(pos, pattern, size, 4) - 0x10; + return memsearch(pos, pattern, size, 4) - 0x10; } u32 getfOpen(u8 *proc9Offset, void *rebootOffset){ @@ -53,10 +53,17 @@ u32 getfOpen(u8 *proc9Offset, void *rebootOffset){ return (u32)rebootOffset + 9 - (-((*(u32 *)rebootOffset & 0x00FFFFFF) << 2) & 0xFFFFF) - p9CodeOff + p9MemAddr; } -u16 *getFirmWrite(void *pos, u32 size){ +u16 *getFirmWrite(u8 *pos, u32 size){ //Look for FIRM writing code - u8 *const off = (u8 *)memsearch(pos, "exe:", size, 4); + u8 *const off = memsearch(pos, "exe:", size, 4); const u8 pattern[] = {0x00, 0x28, 0x01, 0xDA}; return (u16 *)memsearch(off - 0x100, pattern, 0x100, 4); +} + +void getLoader(u8 *pos, u32 size, u32 *loaderOffset, u32 *loaderSize){ + u8 *const off = memsearch(pos, "loade", size, 5); + + *loaderOffset = (u32)off - 0x200; + *loaderSize = *(u32 *)(off - 0xFC) * 0x200; } \ No newline at end of file diff --git a/source/patches.h b/source/patches.h index fd5876c..08d5b32 100644 --- a/source/patches.h +++ b/source/patches.h @@ -19,8 +19,9 @@ const u16 writeBlock[2]; /************************************************** * Functions **************************************************/ -u8 *getProc9(void *pos, u32 size); -void getSigChecks(void *pos, u32 size, u32 *off, u32 *off2); -void *getReboot(void *pos, u32 size); +u8 *getProc9(u8 *pos, u32 size); +void getSigChecks(u8 *pos, u32 size, u32 *off, u32 *off2); +void *getReboot(u8 *pos, u32 size); u32 getfOpen(u8 *proc9Offset, void *rebootOffset); -u16 *getFirmWrite(void *pos, u32 size); \ No newline at end of file +u16 *getFirmWrite(u8 *pos, u32 size); +void getLoader(u8 *pos, u32 size, u32 *loaderOffset, u32 *loaderSize); \ No newline at end of file diff --git a/source/utils.c b/source/utils.c index 0f53917..5dcf2a9 100644 --- a/source/utils.c +++ b/source/utils.c @@ -11,19 +11,14 @@ #include "i2c.h" #include "buttons.h" -//Number of options that can be configured -#define OPTIONS 3 - #define COLOR_TITLE 0xFF9900 #define COLOR_WHITE 0xFFFFFF #define COLOR_RED 0x0000FF #define COLOR_BLACK 0x000000 -struct options { - char *text[OPTIONS]; - int pos_y[OPTIONS]; - u32 enabled[OPTIONS]; - u32 selected; +struct option { + int posY; + u32 enabled; }; static u16 waitInput(void){ @@ -48,52 +43,75 @@ static u16 waitInput(void){ return key; } -void configureCFW(const char *configPath){ - struct options options; - - options.text[0] = "( ) Updated SysNAND mode (A9LH-only)"; - options.text[1] = "( ) Use pre-patched FIRMs"; - options.text[2] = "( ) Force A9LH detection"; - +void configureCFW(const char *configPath, const char *firm90Path){ initScreens(); drawString(CONFIG_TITLE, 10, 10, COLOR_TITLE); drawString("Press A to select, START to save and reboot", 10, 30, COLOR_WHITE); + const char *optionsText[] = { "( ) Updated SysNAND mode (A9LH-only)", + "( ) Use pre-patched FIRMs", + "( ) Force A9LH detection", + "( ) Use 9.0 FIRM as default", + "( ) Use second EmuNAND as default", + "( ) Show current NAND in System Settings" }; + + u32 optionsAmount = sizeof(optionsText) / sizeof(char *); + struct option options[optionsAmount]; + //Read and parse the existing configuration - u16 tempConfig = 0; - fileRead(&tempConfig, configPath, 2); - for(u32 i = 0; i < OPTIONS; i++) - options.enabled[i] = (tempConfig >> i) & 0x1; + u32 tempConfig = 0; + fileRead(&tempConfig, configPath, 3); + for(u32 i = 0; i < optionsAmount; i++) + options[i].enabled = (tempConfig >> i) & 0x1; //Pre-select the first configuration option - options.selected = 0; + u32 selectedOption = 0; //Boring configuration menu while(1){ u16 pressed = 0; do{ - for(u32 i = 0; i < OPTIONS; i++){ - options.pos_y[i] = drawString(options.text[i], 10, !i ? 60 : options.pos_y[i - 1] + SPACING_VERT, options.selected == i ? COLOR_RED : COLOR_WHITE); - drawCharacter('x', 10 + SPACING_HORIZ, options.pos_y[i], options.enabled[i] ? (options.selected == i ? COLOR_RED : COLOR_WHITE) : COLOR_BLACK); + for(u32 i = 0; i < optionsAmount; i++){ + options[i].posY = drawString(optionsText[i], 10, !i ? 60 : options[i - 1].posY + SPACING_Y, selectedOption == i ? COLOR_RED : COLOR_WHITE); + drawCharacter('x', 10 + SPACING_X, options[i].posY, options[i].enabled ? (selectedOption == i ? COLOR_RED : COLOR_WHITE) : COLOR_BLACK); } pressed = waitInput(); - } while(!(pressed & (BUTTON_UP | BUTTON_DOWN | BUTTON_A | BUTTON_START))); + } while(!(pressed & MENU_BUTTONS)); - if(pressed == BUTTON_UP) options.selected = !options.selected ? OPTIONS - 1 : options.selected - 1; - else if(pressed == BUTTON_DOWN) options.selected = options.selected == OPTIONS - 1 ? 0 : options.selected + 1; - else if(pressed == BUTTON_A) options.enabled[options.selected] = !options.enabled[options.selected]; - else if(pressed == BUTTON_START) break; + switch(pressed){ + case BUTTON_UP: + selectedOption = !selectedOption ? optionsAmount - 1 : selectedOption - 1; + break; + case BUTTON_DOWN: + selectedOption = selectedOption == optionsAmount - 1 ? 0 : selectedOption + 1; + break; + case BUTTON_LEFT: + selectedOption = 0; + break; + case BUTTON_RIGHT: + selectedOption = optionsAmount - 1; + break; + case BUTTON_A: + options[selectedOption].enabled = !options[selectedOption].enabled; + break; + } + + if(pressed == BUTTON_START) break; } - //Preserve the last-used boot options (second byte) - tempConfig &= 0xFF00; + //If the user has been using A9LH and the "Updated SysNAND" setting changed, delete the patched 9.0 FIRM + if(((tempConfig >> 16) & 0x1) && ((tempConfig & 0x1) != options[0].enabled)) + fileDelete(firm90Path); + + //Preserve the last-used boot options (last 12 bits) + tempConfig &= 0xFFF000; //Parse and write the selected options - for(u32 i = 0; i < OPTIONS; i++) - tempConfig |= options.enabled[i] << i; - fileWrite(&tempConfig, configPath, 2); + for(u32 i = 0; i < optionsAmount; i++) + tempConfig |= options[i].enabled << i; + fileWrite(&tempConfig, configPath, 3); //Reboot i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 2); @@ -111,8 +129,8 @@ void error(const char *message){ initScreens(); drawString("An error has occurred:", 10, 10, COLOR_RED); - int pos_y = drawString(message, 10, 30, COLOR_WHITE); - drawString("Press any button to shutdown", 10, pos_y + 2 * SPACING_VERT, COLOR_WHITE); + int posY = drawString(message, 10, 30, COLOR_WHITE); + drawString("Press any button to shutdown", 10, posY + 2 * SPACING_Y, COLOR_WHITE); waitInput(); diff --git a/source/utils.h b/source/utils.h index f9536c6..d81795b 100644 --- a/source/utils.h +++ b/source/utils.h @@ -8,6 +8,6 @@ #include "types.h" -void configureCFW(const char *configPath); +void configureCFW(const char *configPath, const char *firm90Path); void deleteFirms(const char *firmPaths[], u32 firms); void error(const char *message); \ No newline at end of file