├── .gitignore ├── README.md ├── ctr-elf.sh ├── e2elf.ld └── exefs2elf.py /.gitignore: -------------------------------------------------------------------------------- 1 | workdir/*.elf 2 | workdir/*.ld 3 | workdir/*.bin 4 | workdir/*.o 5 | 6 | __pycache__/* 7 | 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ctr-elf 2 | #### Creates an ELF from a 3DS executable EXEFS 3 | 4 | `ctr-elf.sh`: extract exefs from file and convert to elf. 5 | Simply run `ctr-elf.sh [path-to-file]`. 6 | 7 | `exefs2elf.py`: convert exefs to elf file. 8 | Place the program's exheader (`exh.bin`) and exefs folder (`exefs/`) inside `workdir/` and run. 9 | 10 | #### Forked from [44670's patchrom](https://github.com/44670/patchrom) 11 | -------------------------------------------------------------------------------- /ctr-elf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | WD=$( pwd ) 4 | DIR=$( cd "$( dirname "$0" )" && pwd ) 5 | 6 | invalid_file() { 7 | echo "ERROR: input file is invalid" 8 | exit 1 9 | } 10 | 11 | if ! [ -r "$1" ]; then 12 | invalid_file 13 | fi 14 | 15 | mkdir -p "$DIR/workdir" 16 | ctrtool --exefsdir="$DIR/workdir/exefs" "$1" &>/dev/null || invalid_file 17 | ctrtool --exheader="$DIR/workdir/exh.bin" "$1" &>/dev/null || invalid_file 18 | ( cd "$DIR" && python exefs2elf.py ) 19 | mv "$DIR/workdir/exefs.elf" "$WD" 20 | rm -rf "$DIR/workdir" 21 | -------------------------------------------------------------------------------- /e2elf.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") 2 | OUTPUT_ARCH(arm) 3 | 4 | MEMORY 5 | { 6 | memregion : ORIGIN = %memorigin%, LENGTH = 100000K 7 | } 8 | 9 | __memregion_start = ORIGIN(memregion); 10 | __memregion_top = ORIGIN(memregion) + LENGTH(memregion); 11 | 12 | PHDRS 13 | { 14 | text PT_LOAD FLAGS(5); 15 | rodata PT_LOAD FLAGS(4); 16 | data PT_LOAD FLAGS(6); 17 | bss PT_LOAD; 18 | } 19 | 20 | SECTIONS 21 | { 22 | .init : ALIGN(0x1000) 23 | { 24 | __text_start = .; 25 | KEEP (*(.init)) 26 | /*. = ALIGN(4); REQUIRED. LD is flaky without it. */ 27 | } >memregion :text 28 | 29 | .plt : 30 | { 31 | *(.plt) 32 | } >memregion = 0xff 33 | 34 | .text : ALIGN (0x1000) 35 | { 36 | 37 | *(.text*) 38 | *(.stub) 39 | /* .gnu.warning sections are handled specially by elf32.em. */ 40 | *(.gnu.warning) 41 | *(.gnu.linkonce.t*) 42 | *(.glue_7) 43 | *(.glue_7t) 44 | /*. = ALIGN(4); REQUIRED. LD is flaky without it. */ 45 | } >memregion 46 | 47 | .fini : 48 | { 49 | KEEP (*(.fini)) 50 | . = ALIGN(0x1000); 51 | } >memregion 52 | 53 | __text_end = .; 54 | 55 | .rodata : 56 | { 57 | *(.rodata) 58 | *all.rodata*(*) 59 | *(.roda) 60 | *(.rodata.*) 61 | *(.gnu.linkonce.r*) 62 | SORT(CONSTRUCTORS) 63 | . = ALIGN(0x1000); /* REQUIRED. LD is flaky without it. */ 64 | } >memregion :rodata 65 | 66 | .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >memregion 67 | __exidx_start = .; 68 | .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >memregion 69 | __exidx_end = .; 70 | 71 | /* Ensure the __preinit_array_start label is properly aligned. We 72 | could instead move the label definition inside the section, but 73 | the linker would then create the section even if it turns out to 74 | be empty, which isn't pretty. */ 75 | . = ALIGN(32 / 8); 76 | PROVIDE (__preinit_array_start = .); 77 | .preinit_array : { KEEP (*(.preinit_array)) } >memregion : data 78 | PROVIDE (__preinit_array_end = .); 79 | PROVIDE (__init_array_start = .); 80 | .init_array : { KEEP (*(.init_array)) } >memregion : data 81 | PROVIDE (__init_array_end = .); 82 | PROVIDE (__fini_array_start = .); 83 | .fini_array : { KEEP (*(.fini_array)) } >memregion = 0xff 84 | PROVIDE (__fini_array_end = .); 85 | 86 | .ctors : ALIGN(0x1000) 87 | { 88 | /* gcc uses crtbegin.o to find the start of the constructors, so 89 | we make sure it is first. Because this is a wildcard, it 90 | doesn't matter if the user does not actually link against 91 | crtbegin.o; the linker won't look for a file to match a 92 | wildcard. The wildcard also means that it doesn't matter which 93 | directory crtbegin.o is in. */ 94 | KEEP (*crtbegin.o(.ctors)) 95 | KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) 96 | KEEP (*(SORT(.ctors.*))) 97 | KEEP (*(.ctors)) 98 | /*. = ALIGN(4); REQUIRED. LD is flaky without it. */ 99 | } >memregion = 0xff 100 | 101 | .dtors : ALIGN(0x1000) 102 | { 103 | KEEP (*crtbegin.o(.dtors)) 104 | KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) 105 | KEEP (*(SORT(.dtors.*))) 106 | KEEP (*(.dtors)) 107 | /*. = ALIGN(4); REQUIRED. LD is flaky without it. */ 108 | } >memregion = 0xff 109 | 110 | .eh_frame : ALIGN(0x1000) 111 | { 112 | KEEP (*(.eh_frame)) 113 | /*. = ALIGN(4); REQUIRED. LD is flaky without it. */ 114 | } >memregion = 0xff 115 | 116 | .gcc_except_table : ALIGN(0x1000) 117 | { 118 | *(.gcc_except_table) 119 | /*. = ALIGN(4); REQUIRED. LD is flaky without it. */ 120 | } >memregion = 0xff 121 | 122 | .jcr : { KEEP (*(.jcr)) } >memregion = 0 123 | 124 | .got : 125 | { 126 | __got_start = ABSOLUTE(.); 127 | *(.got.plt) *(.got) 128 | } >memregion = 0 129 | __got_end = . ; 130 | 131 | .memregion ALIGN(4) : 132 | { 133 | __memregion_start = ABSOLUTE(.); 134 | *(.memregion) 135 | *memregion.*(.text) 136 | . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ 137 | __memregion_end = ABSOLUTE(.); 138 | } >memregion = 0xff 139 | 140 | 141 | .data ALIGN(4) : 142 | { 143 | __data_start = ABSOLUTE(.); 144 | *(.data) 145 | *(.data.*) 146 | *(.gnu.linkonce.d*) 147 | CONSTRUCTORS 148 | . = ALIGN(0x1000); 149 | __data_end = ABSOLUTE(.); 150 | } >memregion : data 151 | 152 | .bss ALIGN(0x1000) : 153 | { 154 | . = . + %bsssize%; 155 | } >memregion : bss 156 | 157 | __bss_end = .; 158 | __bss_end__ = .; 159 | 160 | _end = .; 161 | __end__ = .; 162 | PROVIDE (end = _end); 163 | 164 | /* Stabs debugging sections. */ 165 | .stab 0 : { *(.stab) } 166 | .stabstr 0 : { *(.stabstr) } 167 | .stab.excl 0 : { *(.stab.excl) } 168 | .stab.exclstr 0 : { *(.stab.exclstr) } 169 | .stab.index 0 : { *(.stab.index) } 170 | .stab.indexstr 0 : { *(.stab.indexstr) } 171 | .comment 0 : { *(.comment) } 172 | 173 | /* DWARF debug sections. 174 | Symbols in the DWARF debugging sections are relative to the beginning 175 | of the section so we begin them at 0. */ 176 | /* DWARF 1 */ 177 | .debug 0 : { *(.debug) } 178 | .line 0 : { *(.line) } 179 | /* GNU DWARF 1 extensions */ 180 | .debug_srcinfo 0 : { *(.debug_srcinfo) } 181 | .debug_sfnames 0 : { *(.debug_sfnames) } 182 | /* DWARF 1.1 and DWARF 2 */ 183 | .debug_aranges 0 : { *(.debug_aranges) } 184 | .debug_pubnames 0 : { *(.debug_pubnames) } 185 | /* DWARF 2 */ 186 | .debug_info 0 : { *(.debug_info) } 187 | .debug_abbrev 0 : { *(.debug_abbrev) } 188 | .debug_line 0 : { *(.debug_line) } 189 | .debug_frame 0 : { *(.debug_frame) } 190 | .debug_str 0 : { *(.debug_str) } 191 | .debug_loc 0 : { *(.debug_loc) } 192 | .debug_macinfo 0 : { *(.debug_macinfo) } 193 | /* SGI/MIPS DWARF 2 extensions */ 194 | .debug_weaknames 0 : { *(.debug_weaknames) } 195 | .debug_funcnames 0 : { *(.debug_funcnames) } 196 | .debug_typenames 0 : { *(.debug_typenames) } 197 | .debug_varnames 0 : { *(.debug_varnames) } 198 | 199 | .stack 0x80000 : { _stack = .; *(.stack) } 200 | /* These must appear regardless of . */ 201 | } -------------------------------------------------------------------------------- /exefs2elf.py: -------------------------------------------------------------------------------- 1 | # convert exefs to elf 2 | import sys 3 | import os 4 | import struct 5 | 6 | CC = "arm-none-eabi-gcc" 7 | CP = "arm-none-eabi-g++" 8 | OC = "arm-none-eabi-objcopy" 9 | LD = "arm-none-eabi-ld" 10 | 11 | def run(cmd): 12 | os.system(cmd) 13 | 14 | def writefile(path, s): 15 | with open(path, "wb") as f: 16 | f.write(str(s)) 17 | 18 | with open("workdir/exh.bin", "rb") as f: 19 | exh = f.read(64) 20 | 21 | (textBase, textPages, roPages, rwPages, bssSize) = struct.unpack('16x ii 12x i 12x i 4x i', exh) 22 | textSize = textPages * 0x1000 23 | roSize = roPages * 0x1000 24 | rwSize = rwPages * 0x1000 25 | bssSize = (int(bssSize / 0x1000) + 1) * 0x1000 26 | 27 | print("textBase: {:08x}".format(textBase)) 28 | print("textSize: {:08x}".format(textSize)) 29 | print("roSize: {:08x}".format(roSize)) 30 | print("rwSize: {:08x}".format(rwSize)) 31 | print("bssSize: {:08x}".format(bssSize)) 32 | 33 | if (textBase != 0x100000): 34 | print('WARNING: textBase mismatch, might be an encrypted exheader file.') 35 | 36 | exefsPath = 'workdir/exefs/' 37 | with open(exefsPath + 'code.bin', "rb") as f: 38 | text = f.read(textSize) 39 | ro = f.read(roSize) 40 | rw = f.read(rwSize) 41 | 42 | with open('e2elf.ld', 'r') as f: 43 | ldscript = f.read() 44 | ldscript = ldscript.replace('%memorigin%', str(textBase)) 45 | ldscript = ldscript.replace('%bsssize%', str(bssSize)) 46 | writefile('workdir/e2elf.ld', ldscript) 47 | 48 | writefile(exefsPath + 'text.bin', text) 49 | writefile(exefsPath + 'ro.bin', ro) 50 | writefile(exefsPath + 'rw.bin', rw) 51 | 52 | objfiles = '' 53 | for i in (('text', 'text'), ('ro', 'rodata'), ('rw', 'data')): 54 | desc, sec_name = i 55 | run('{0} -I binary -O elf32-littlearm --rename-section .data=.{1} {2}{3}.bin {2}{3}.o' 56 | .format(OC, sec_name, exefsPath, desc)) 57 | objfiles += '{0}{1}.o '.format(exefsPath, desc) 58 | 59 | run(LD + ' --accept-unknown-input-arch -T workdir/e2elf.ld -o workdir/exefs.elf ' + objfiles) --------------------------------------------------------------------------------