This is inefficient and clearly a bastardization of Luma AND Cakes' reboot code, but it seems to work.
.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:
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
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 $@ $<
+++ /dev/null
-.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
--- /dev/null
+.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
--- /dev/null
+.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"
// 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
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();
}
#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;
// 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
// 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
--- /dev/null
+#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);
+}
#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 '<titleid>.exefs' - NYI
extern int patch_services();
extern int patch_modules();
+extern int patch_reboot();
extern int doing_autoboot;
wait();
}
+ // Hook firmlaunch?
+ if (config.options[OPTION_REBOOT]) {
+ patch_reboot();
+
+ wait();
+ }
+
// Inject services?
if (config.options[OPTION_SVCS]) {
if (patch_services()) {
wait();
}
+
// Use ARM9 hook thread?
if (config.options[OPTION_ARM9THREAD]) {
// Yes.
.align 4
.global _start
_start:
+ b mpu
+
+ nop
+mpu:
@ Change the stack pointer
mov sp, #0x27000000
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)
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++) {