From 2f4fa3cace5fbb60ef00a3abe930b9dc6518b660 Mon Sep 17 00:00:00 2001 From: chaoskagami Date: Mon, 6 Jun 2016 18:08:31 -0400 Subject: [PATCH] Reboot WIP. This is inefficient and clearly a bastardization of Luma AND Cakes' reboot code, but it seems to work. --- Makefile | 3 + external/Makefile | 2 + external/svc/Makefile | 2 +- external/svc/patches.s | 77 ----------------------- external/svc/reboot_code.s | 96 ++++++++++++++++++++++++++++ external/svc/reboot_hook.s | 125 +++++++++++++++++++++++++++++++++++++ source/config.h | 3 + source/firm/firm.c | 12 ++++ source/firm/headers.h | 6 -- source/menu.c | 8 +-- source/patch/module.c | 2 +- source/patch/reboot.c | 102 ++++++++++++++++++++++++++++++ source/patch_format.h | 5 ++ source/patcher.c | 9 +++ source/start.s | 4 ++ source/std/draw.c | 4 +- 16 files changed, 366 insertions(+), 94 deletions(-) delete mode 100644 external/svc/patches.s create mode 100644 external/svc/reboot_code.s create mode 100644 external/svc/reboot_hook.s create mode 100644 source/patch/reboot.c diff --git a/Makefile b/Makefile index 8c83992..6971f18 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,9 @@ patch: .PHONY: a9lh a9lh: $(dir_out)/arm9loaderhax.bin + # For the reboot patch. + mkdir -p $(dir_out)/corbenik/bits + cp $(dir_out)/arm9loaderhax.bin $(dir_out)/corbenik/bits/corbenik.bin .PHONY: reformat reformat: diff --git a/external/Makefile b/external/Makefile index f30799a..27b623f 100644 --- a/external/Makefile +++ b/external/Makefile @@ -6,6 +6,8 @@ all: loader svc screeninit cp loader/loader.cxi ../out/corbenik/module/loader.cxi cp svc/7b.bin ../out/corbenik/svc/7b.bin cp svc/emunand.bin ../out/corbenik/bits/emunand.bin + cp svc/reboot_hook.bin ../out/corbenik/bits/reboot_hook.bin + cp svc/reboot_code.bin ../out/corbenik/bits/reboot_code.bin cp screeninit/build/screeninit.bin ../out/corbenik/bits/screeninit.bin .PHONY: clean diff --git a/external/svc/Makefile b/external/svc/Makefile index 243b4c8..600ae21 100644 --- a/external/svc/Makefile +++ b/external/svc/Makefile @@ -1,6 +1,6 @@ PATH := $(PATH):$(DEVKITARM)/bin -all: 7b.bin stub.bin emunand.bin +all: 7b.bin stub.bin emunand.bin reboot_hook.bin reboot_code.bin %.o: %.s arm-none-eabi-as -o $@ $< diff --git a/external/svc/patches.s b/external/svc/patches.s deleted file mode 100644 index 44eaf6f..0000000 --- a/external/svc/patches.s +++ /dev/null @@ -1,77 +0,0 @@ -.arm.little - -.create "patch1.bin", 0 -.arm -nand_sd: - ; Original code that still needs to be executed. - mov r4, r0 - mov r5, r1 - mov r7, r2 - mov r6, r3 - ; End. - - ; If we're already trying to access the SD, return. - ldr r2, [r0, #4] - ldr r1, [sdmmc] - cmp r2, r1 - beq nand_sd_ret - - str r1, [r0, #4] ; Set object to be SD - ldr r2, [r0, #8] ; Get sector to read - cmp r2, #0 ; For GW compatibility, see if we're trying to read the ncsd header (sector 0) - - ldr r3, [nand_offset] - add r2, r3 ; Add the offset to the NAND in the SD. - - ldreq r3, [ncsd_header_offset] - addeq r2, r3 ; If we're reading the ncsd header, add the offset of that sector. - - str r2, [r0, #8] ; Store sector to read - - nand_sd_ret: - ; Restore registers. - mov r1, r5 - mov r2, r7 - mov r3, r6 - - ; Return 4 bytes behind where we got called, - ; due to the offset of this function being stored there. - mov r0, lr - add r0, #4 - bx r0 -.pool -nand_offset: .ascii "NAND" ; The starting offset of the emuNAND on the SD. -ncsd_header_offset: .ascii "NCSD" ; The offset of the first emuNAND sector relative to the start of the emuNAND (Used for when the first sector is placed at the end). -sdmmc: .ascii "sdmmc" ; The offset of the sdmmc object. -.close - -.create "patch2.bin", 0 -.arm - .word 0x360003 - .word 0x10100000 - .word 0x1000001 - .word 0x360003 - .word 0x20000000 - .word 0x1010101 - .word 0x200603 - .word 0x8000000 - .word 0x1010101 - .word 0x1C0603 - .word 0x8020000 -.close - -.create "patch3.bin", 0 -.thumb - ldr r4, [_nand_sd_write] - blx r4 -.align 4 -_nand_sd_write: .ascii "mem" -.close - -.create "patch4.bin", 0 -.thumb - ldr r4, [_nand_sd_read] - blx r4 -.align 4 -_nand_sd_read: .ascii "mem" -.close diff --git a/external/svc/reboot_code.s b/external/svc/reboot_code.s new file mode 100644 index 0000000..de404f2 --- /dev/null +++ b/external/svc/reboot_code.s @@ -0,0 +1,96 @@ +.set firm_addr, 0x24000000 // Temporary location where we'll load the FIRM to +.set firm_maxsize, 0x200000 // Random value that's bigger than any of the currently known firm's sizes. + +.section .text +.global _start +_start: + + // Set MPU settings + mrc p15, 0, r0, c2, c0, 0 // dcacheable + mrc p15, 0, r12, c2, c0, 1 // icacheable + mrc p15, 0, r1, c3, c0, 0 // write bufferable + mrc p15, 0, r2, c5, c0, 2 // daccess + mrc p15, 0, r3, c5, c0, 3 // iaccess + ldr r4, =0x18000035 // 0x18000000 128M + bic r2, r2, #0xF0000 // unprotect region 4 + bic r3, r3, #0xF0000 // unprotect region 4 + orr r0, r0, #0x10 // dcacheable region 4 + orr r2, r2, #0x30000 // region 4 r/w + orr r3, r3, #0x30000 // region 4 r/w + orr r12, r12, #0x10 // icacheable region 4 + orr r1, r1, #0x10 // write bufferable region 4 + mcr p15, 0, r0, c2, c0, 0 + mcr p15, 0, r12, c2, c0, 1 + mcr p15, 0, r1, c3, c0, 0 // write bufferable + mcr p15, 0, r2, c5, c0, 2 // daccess + mcr p15, 0, r3, c5, c0, 3 // iaccess + mcr p15, 0, r4, c6, c4, 0 // region 4 (hmmm) + + mrc p15, 0, r0, c2, c0, 0 // dcacheable + mrc p15, 0, r1, c2, c0, 1 // icacheable + mrc p15, 0, r2, c3, c0, 0 // write bufferable + orr r0, r0, #0x20 // dcacheable region 5 + orr r1, r1, #0x20 // icacheable region 5 + orr r2, r2, #0x20 // write bufferable region 5 + mcr p15, 0, r0, c2, c0, 0 // dcacheable + mcr p15, 0, r1, c2, c0, 1 // icacheable + mcr p15, 0, r2, c3, c0, 0 // write bufferable + + // Copy the firmware + mov r4, #firm_addr + add r5, r4, #0x40 // Start of loop + add r6, r5, #0x30 * 3 // End of loop (scan 4 entries) + + copy_firm_loop: + ldr r0, [r5] + cmp r0, #0 + addne r0, r4 // src + ldrne r1, [r5, #4] // dest + ldrne r2, [r5, #8] // size + blne memcpy32 + + cmp r5, r6 + addlo r5, #0x30 + blo copy_firm_loop + + // Flush cache + mov r2, #0 + mov r1, r2 + flush_cache: + mov r0, #0 + mov r3, r2, lsl #30 + flush_cache_inner_loop: + orr r12, r3, r0, lsl#5 + mcr p15, 0, r1, c7, c10, 4 // drain write buffer + mcr p15, 0, r12, c7, c14, 2 // clean and flush dcache entry (index and segment) + add r0, #1 + cmp r0, #0x20 + bcc flush_cache_inner_loop + add r2, #1 + cmp r2, #4 + bcc flush_cache + + // Enable MPU + ldr r0, =0x42078 // alt vector select, enable itcm + mcr p15, 0, r0, c1, c0, 0 + mcr p15, 0, r1, c7, c5, 0 // flush dcache + mcr p15, 0, r1, c7, c6, 0 // flush icache + mcr p15, 0, r1, c7, c10, 4 // drain write buffer + mov r0, #firm_addr + + // Boot FIRM + mov r1, #0x1FFFFFFC + ldr r2, [r0, #8] // arm11 entry + str r2, [r1] + ldr r0, [r0, #0xC] // arm9 entry + bx r0 +.pool + +memcpy32: // memcpy32(void *src, void *dst, unsigned int size) + add r2, r0 + memcpy32_loop: + ldmia r0!, {r3} + stmia r1!, {r3} + cmp r0, r2 + blo memcpy32_loop + bx lr diff --git a/external/svc/reboot_hook.s b/external/svc/reboot_hook.s new file mode 100644 index 0000000..5681b0e --- /dev/null +++ b/external/svc/reboot_hook.s @@ -0,0 +1,125 @@ +.set firm_addr, 0x24000000 // Temporary location where we'll load the FIRM to +.set firm_maxsize, 0x200000 // Random value that's bigger than any of the currently known firm's sizes. + +.section .text +.global _start +_start: + // Interesting registers and locations to keep in mind, set before this code is ran: + // - sp + 0x3A8 - 0x70: FIRM path in exefs. + // - r7 (which is sp + 0x3A8 - 0x198): Reserved space for file handle + // - *(sp + 0x3A8 - 0x198) + 0x28: fread function. + + pxi_wait_recv: + ldr r2, =0x44846 + ldr r0, =0x10008000 + readPxiLoop1: + ldrh r1, [r0, #4] + lsls r1, #0x17 + bmi readPxiLoop1 + ldr r0, [r0, #0xC] + cmp r0, r2 + bne pxi_wait_recv + + // Convert 2 bytes of the path string + // This will be the method of getting the lower 2 bytes of the title ID + // until someone bothers figuring out where the value is derived from. + mov r0, #0 // Result + add r1, sp, #0x3A8 - 0x70 + add r1, #0x22 // The significant bytes + mov r2, #4 // Maximum loops (amount of bytes * 2) + + hex_string_to_int_loop: + ldr r3, [r1], #2 // 2 because it's a utf-16 string. + and r3, #0xFF + + // Check if it"s a number + cmp r3, #'0' + blo hex_string_to_int_end + sub r3, #'0' + cmp r3, #9 + bls hex_string_to_int_calc + + // Check if it"s a capital letter + cmp r3, #'A' - '0' + blo hex_string_to_int_end + sub r3, #'A' - '0' - 0xA // Make the correct value: 0xF >= al >= 0xA + cmp r3, #0xF + bls hex_string_to_int_calc + + // Incorrect value: x > "A" + bhi hex_string_to_int_end + + hex_string_to_int_calc: + orr r0, r3, r0, lsl #4 + subs r2, #1 + bne hex_string_to_int_loop + hex_string_to_int_end: + + // Get the FIRM path + cmp r0, #0x0002 // NATIVE_FIRM + ldreq r1, firm_fname + beq check_fname + + ldr r5, =0x0102 // TWL_FIRM + cmp r0, r5 + ldreq r1, twl_firm_fname + beq check_fname + + ldr r5, =0x0202 // AGB_FIRM + cmp r0, r5 + ldreq r1, agb_firm_fname + beq check_fname + + fallback: + // Fallback: Load specified FIRM from exefs + add r1, sp, #0x3A8-0x70 // Location of exefs string. + b load_firm + + check_fname: + // Check the given string offset + cmp r1, #0 + beq fallback + + load_firm: + // Open file + add r0, r7, #8 + mov r2, #1 + ldr r6, fopen + orr r6, #1 + blx r6 + + cmp r0, #0 // Check if we were able to load the FIRM + bne fallback // Otherwise, try again with the FIRM from exefs. + // This will loop indefinitely if the exefs FIRM fails to load, but whatever. + + // Read file + mov r0, r7 + adr r1, bytes_read + mov r2, #firm_addr + mov r3, #firm_maxsize + ldr r6, [sp, #0x3A8-0x198] + ldr r6, [r6, #0x28] + blx r6 + + // Set kernel state + mov r0, #0 + mov r1, #0 + mov r2, #0 + mov r3, #0 + swi 0x7C + + // Jump to reboot code + ldr r0, reboot_code + swi 0x7B + + die: + b die + +.align 4 +bytes_read: .word 0 +fopen: .ascii "open" +reboot_code: .ascii "rebc" +.pool +firm_fname: .ascii "NATF" +twl_firm_fname: .ascii "TWLF" +agb_firm_fname: .ascii "AGBF" diff --git a/source/config.h b/source/config.h index 4d2f4b8..4c1d0cf 100644 --- a/source/config.h +++ b/source/config.h @@ -105,6 +105,9 @@ struct options_s // Dump titles' code sections as they're loaded by the loader module. #define OPTION_LOADER_DUMPCODE 18 +// Hook firmlaunches. +#define OPTION_REBOOT 19 + // Save log files during boot and from loader. // This will slow things down a bit. #define OPTION_SAVE_LOGS 253 diff --git a/source/firm/firm.c b/source/firm/firm.c index 3ea79f9..a9fcd7c 100644 --- a/source/firm/firm.c +++ b/source/firm/firm.c @@ -433,5 +433,17 @@ boot_cfw() if (patch_firm_all() != 0) return; + if (config.options[OPTION_REBOOT] && config.options[OPTION_RECONFIGURED]) { + fprintf(stderr, "Saving FIRMs for reboot..."); + if (!write_file(firm_loc, PATH_NATIVE_P, firm_size)) + abort("Failed to save prepatched native\n"); + + if (!write_file(twl_firm_loc, PATH_TWL_P, twl_firm_size)) + abort("Failed to save prepatched twl\n"); + + if (!write_file(agb_firm_loc, PATH_AGB_P, agb_firm_size)) + abort("Failed to save prepatched agb\n"); + } + boot_firm(); } diff --git a/source/firm/headers.h b/source/firm/headers.h index 60c0cca..27fcd80 100644 --- a/source/firm/headers.h +++ b/source/firm/headers.h @@ -17,12 +17,6 @@ #define ARM9BIN_MAGIC (0x47704770) #define LGY_ARM9BIN_MAGIC (0xB0862000) -struct memory_header -{ - uint32_t location; - uint32_t size; -} memory_header_t; - typedef struct firm_section_h { uint32_t offset; diff --git a/source/menu.c b/source/menu.c index 9af726b..b49d98d 100644 --- a/source/menu.c +++ b/source/menu.c @@ -21,22 +21,16 @@ static struct options_s options[] = { // Patches. { 0, "\x1b[32;40mGeneral Options\x1b[0m", "", not_option, 0, 0 }, - { 0, "", "", not_option, 0, 0 }, - { OPTION_SVCS, "SVC Replacement", "Replaces ARM11 svc calls, including svcBackdoor. With 11.0 NATIVE_FIRM, you probably want this.", boolean_val, 0, 0 }, - { 0, "", "", not_option, 0, 0 }, + { OPTION_REBOOT, "Reboot Hook", "Hooks firmlaunch to keep the CFW resident on o3DS and allow patching TWL/AGB firms as well.", boolean_val, 0, 0 }, { OPTION_EMUNAND, "Use EmuNAND", "Redirects NAND write/read to the SD.", boolean_val, 0, 0 }, { OPTION_EMUNAND_INDEX, " Index", "Which EmuNAND to use. Currently, 10 maximum (but this is arbitrary)", ranged_val, 0, 0x9 }, - { 0, "", "", not_option, 0, 0 }, - { OPTION_AUTOBOOT, "Autoboot", "Boot the system automatically, unless the R key is held.", boolean_val, 0, 0 }, { OPTION_SILENCE, " Silent mode", "Suppress all debug output during autoboot. You'll see the screen turn on, then off.", boolean_val, 0, 0 }, - { 0, "", "", not_option, 0, 0 }, - { OPTION_READ_ME, "Hide `Help`", "Hides the help option from the main menu.", boolean_val, 0, 0 }, // space diff --git a/source/patch/module.c b/source/patch/module.c index 69da342..5bf79f5 100644 --- a/source/patch/module.c +++ b/source/patch/module.c @@ -38,7 +38,7 @@ PATCH(modules) // Expand firmware module size if needed to accomodate replacement. if (module->contentSize > sysmodule->contentSize) { uint32_t need_units = (module->contentSize - sysmodule->contentSize); - fprintf(stderr, "Module: Would grow %d units but NYI\n", need_units); + fprintf(stderr, "module: Would grow %d units but NYI\n", need_units); continue; // TODO - so in a nutshell, the reason Luma works is because it diff --git a/source/patch/reboot.c b/source/patch/reboot.c new file mode 100644 index 0000000..47f9db5 --- /dev/null +++ b/source/patch/reboot.c @@ -0,0 +1,102 @@ +#include "emunand.h" +#include "../std/memory.h" +#include "../std/draw.h" +#include "../std/fs.h" +#include "../std/abort.h" +#include "../firm/firm.h" +#include "../firm/fcram.h" +#include "../fatfs/sdmmc.h" +#include "../firm/headers.h" +#include "../patch_format.h" + +int wait(); + +uint8_t *getProcess9(uint8_t *pos, uint32_t size, uint32_t *process9Size, uint32_t *process9MemAddr) { + uint8_t *off = memfind(pos, size, "ess9", 4); + *process9Size = *(uint32_t *)(off - 0x60) * 0x200; + *process9MemAddr = *(uint32_t *)(off + 0xC); + //Process9 code offset (start of NCCH + ExeFS offset + ExeFS header size) + return off - 0x204 + (*(uint32_t *)(off - 0x64) * 0x200) + 0x200; +} + +void patch_reboot() { + //Look for firmlaunch code + const uint8_t pattern[] = {0xDE, 0x1F, 0x8D, 0xE2}; + + uint32_t process9Size, process9MemAddr; + uint8_t *process9Offset = getProcess9((uint8_t*)firm_loc + firm_loc->section[2].offset + 0x15000, firm_loc->section[2].size - 0x15000, &process9Size, &process9MemAddr); + + fprintf(stderr, "reboot: proc9 mem @ %x\n", process9MemAddr); + + wait(); + + uint8_t *off = memfind(process9Offset, process9Size, pattern, 4) - 0x10; + + fprintf(stderr, "reboot: firmlaunch @ %x\n", off); + + //Firmlaunch function offset - offset in BLX opcode (A4-16 - ARM DDI 0100E) + 1 + uint32_t fOpenOffset = (uint32_t)(off + 9 - (-((*(uint32_t *)off & 0x00FFFFFF) << 2) & (0xFFFFFF << 2)) - process9Offset + process9MemAddr); + + fprintf(stderr, "reboot: fopen @ %x\n", fOpenOffset); + + wait(); + + //Copy firmlaunch code + FILE* f = fopen(PATH_BITS "/reboot_hook.bin", "r"); + if (!f) + abort("reboot: hook not found on SD\n"); + + uint32_t size = fsize(f); + fread(off, 1, size, f); + fclose(f); + + //Put the fOpen offset in the right location + uint32_t *pos_fopen = (uint32_t *)memfind(off, size, "open", 4); + if (!pos_fopen) + abort("reboot: fopen location missing\n"); + + *pos_fopen = fOpenOffset; + + uint32_t *pos_native = (uint32_t*)memfind(off, size, "NATF", 4); + uint32_t *pos_twl = (uint32_t*)memfind(off, size, "TWLF", 4); + uint32_t *pos_agb = (uint32_t*)memfind(off, size, "AGBF", 4); + + if (!pos_native && !pos_twl && !pos_agb) + abort("reboot: missing string placeholder?\n"); + + fprintf(stderr, "reboot: NATF @ %x\n", pos_native); + fprintf(stderr, "reboot: TWLF @ %x\n", pos_twl); + fprintf(stderr, "reboot: AGBF @ %x\n", pos_agb); + + uint8_t* mem = (uint8_t*)0x01FF8000; // 0x8000 space that will be resident. + + *pos_native = (uint32_t)mem; + memcpy(mem, L"sdmc:", 10); + mem += 10; + for(size_t i=0; i < sizeof(PATH_NATIVE_P); i++, mem += 2) + *mem = PATH_NATIVE_P[i]; + + *pos_twl = (uint32_t)mem; + memcpy(mem, L"sdmc:", 10); + mem += 10; + for(size_t i=0; i < sizeof(PATH_TWL_P); i++, mem += 2) + *mem = PATH_TWL_P[i]; + + *pos_agb = (uint32_t)mem; + memcpy(mem, L"sdmc:", 10); + mem += 10; + for(size_t i=0; i < sizeof(PATH_AGB_P); i++, mem += 2) + *mem = PATH_AGB_P[i]; + + uint32_t *pos_rebc = (uint32_t*)memfind(off, size, "rebc", 4); + *pos_rebc = (uint32_t)mem; + + fprintf(stderr, "reboot: rebc @ %x\n", pos_rebc); + + f = fopen(PATH_BITS "/reboot_code.bin", "r"); + if (!f) + abort("reboot: boot not found on SD\n"); + + fread(mem, 1, fsize(f), f); + fclose(f); +} diff --git a/source/patch_format.h b/source/patch_format.h index dc83c80..8bc587f 100644 --- a/source/patch_format.h +++ b/source/patch_format.h @@ -42,6 +42,11 @@ #define PATH_TEMP PATH_CFW "/cache" // Files that are transient and used to speed operation #define PATH_LOADER_CACHE PATH_TEMP "/loader" // Cached patch bytecode for loader. + +#define PATH_NATIVE_P PATH_TEMP "/p_native" +#define PATH_AGB_P PATH_TEMP "/p_agb" +#define PATH_TWL_P PATH_TEMP "/p_twl" + #define PATH_KEYS PATH_CFW "/keys" // Keyfiles will be loaded from this dir, and // additionally the root if not found. #define PATH_EXEFS PATH_CFW "/exe" // ExeFS overrides, named like '.exefs' - NYI diff --git a/source/patcher.c b/source/patcher.c index d2c60dd..b2be51b 100644 --- a/source/patcher.c +++ b/source/patcher.c @@ -13,6 +13,7 @@ uint32_t wait_key(); extern int patch_services(); extern int patch_modules(); +extern int patch_reboot(); extern int doing_autoboot; @@ -69,6 +70,13 @@ patch_firm_all() wait(); } + // Hook firmlaunch? + if (config.options[OPTION_REBOOT]) { + patch_reboot(); + + wait(); + } + // Inject services? if (config.options[OPTION_SVCS]) { if (patch_services()) { @@ -77,6 +85,7 @@ patch_firm_all() wait(); } + // Use ARM9 hook thread? if (config.options[OPTION_ARM9THREAD]) { // Yes. diff --git a/source/start.s b/source/start.s index 2ec4c5e..bc0b91b 100644 --- a/source/start.s +++ b/source/start.s @@ -2,6 +2,10 @@ .align 4 .global _start _start: + b mpu + + nop +mpu: @ Change the stack pointer mov sp, #0x27000000 diff --git a/source/std/draw.c b/source/std/draw.c index 3dc565a..45225ea 100644 --- a/source/std/draw.c +++ b/source/std/draw.c @@ -49,7 +49,7 @@ clear_disp(uint8_t *screen) if (screen == BOTTOM_SCREEN && config.options[OPTION_SAVE_LOGS]) { FILE *f = fopen(PATH_CFW "/boot.log", "w"); fseek(f, 0, SEEK_END); - for (int i = 0; i < TEXT_BOTTOM_HEIGHT; i++) { + for (int i = 0; i < TEXT_BOTTOM_HEIGHT - 1; i++) { char *text = text_buffer_bottom + (TEXT_BOTTOM_WIDTH * i); for(int j = 0; j < TEXT_BOTTOM_WIDTH; j++) { if (text[j] == 0) @@ -225,7 +225,7 @@ putc(void *buf, const int c) cursor_y[0]++; } - while (cursor_y[0] >= height) { + while (cursor_y[0] >= height - 1) { #ifdef BUFFER // Scroll. for (unsigned int y = 0; y < height - 1; y++) { -- 2.39.5