├── .gitignore ├── tools ├── lazyxor-darwin ├── lazyxor-linux ├── lazyxor-win32.exe └── lazyxor.c ├── README.md ├── LICENSE.md ├── restore-firm0firm1.py └── install-b9s.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.firm 3 | *.sha 4 | *.img 5 | *.bak 6 | work/ 7 | -------------------------------------------------------------------------------- /tools/lazyxor-darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaveamac/hardmod-b9s-installer/HEAD/tools/lazyxor-darwin -------------------------------------------------------------------------------- /tools/lazyxor-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaveamac/hardmod-b9s-installer/HEAD/tools/lazyxor-linux -------------------------------------------------------------------------------- /tools/lazyxor-win32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaveamac/hardmod-b9s-installer/HEAD/tools/lazyxor-win32.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hardmod-b9s-installer 2 | 3 | Installs [boot9strap](https://github.com/SciresM/boot9strap) to a Nintendo 3DS system via hardware. See [3DS Guide](https://3ds.guide) for instructions. 4 | 5 | Currently operates on a NAND dump at `NAND.bin`. Soon it will be able to work directly on the device with an interactive prompt. 6 | 7 | ## License 8 | `install-b9s.py` and `restore-firm0firm1.py` are under the MIT license. `lazyxor.c` is by an anonymous contributor. 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Ian Burgwin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tools/lazyxor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef WIN32 7 | #include 8 | #include 9 | #endif 10 | 11 | uint8_t* fileRead(const char* path, size_t *size) 12 | { 13 | uint8_t *buffer = NULL; 14 | FILE *f = fopen(path, "rb"); 15 | if (!f) return NULL; 16 | 17 | fseek(f, 0, SEEK_END); 18 | size_t filesize = ftell(f); 19 | fseek(f, 0, SEEK_SET); 20 | 21 | buffer = (uint8_t*)malloc(filesize); 22 | if (!buffer) {*size = 0; return NULL;} 23 | 24 | fread(buffer, 1, filesize, f); 25 | fclose(f); 26 | 27 | *size = filesize; 28 | 29 | return buffer; 30 | } 31 | 32 | void fileWriteStdout(void *buffer, size_t buffer_size) 33 | { 34 | #ifdef WIN32 35 | int res = setmode(fileno(stdout), O_BINARY); 36 | if (res == -1) 37 | { 38 | fprintf(stderr, "Unable to re-open stdout in binary mode, skipping write\n"); 39 | return; 40 | } 41 | #endif 42 | fwrite(buffer, 1, buffer_size, stdout); 43 | } 44 | 45 | void fileWrite(const char* path, void* buffer, size_t buffer_size) 46 | { 47 | if (!buffer) { return; } 48 | FILE *f = fopen(path, "wb"); 49 | if (!f) return; 50 | 51 | fwrite(buffer, 1, buffer_size, f); 52 | fclose(f); 53 | } 54 | 55 | int main(int argc, char** argv) 56 | { 57 | if(argc < 3) 58 | { 59 | printf("Usage: %s file1 file2 [outputfile]\n", argv[0]); 60 | return 0; 61 | } 62 | 63 | char* file1 = argv[1]; 64 | size_t file1size = 0; 65 | char* file2 = argv[2]; 66 | size_t file2size = 0; 67 | 68 | uint8_t* buffer1 = fileRead(file1, &file1size); 69 | uint8_t* buffer2 = fileRead(file2, &file2size); 70 | if (buffer1 && buffer2) 71 | { 72 | size_t counter = 0; 73 | while (counter < file1size) 74 | { 75 | buffer1[counter] ^= buffer2[counter]; 76 | counter++; 77 | } 78 | 79 | if (argc == 4) 80 | { 81 | fileWrite(argv[3], buffer1, file1size); 82 | } 83 | else 84 | { 85 | fileWriteStdout(buffer1, file1size); 86 | } 87 | } 88 | 89 | free(buffer1); 90 | free(buffer2); 91 | 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /restore-firm0firm1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import time 6 | 7 | 8 | def doexit(msg, errcode=0): 9 | print(msg) 10 | input('Press Enter to continue...') 11 | sys.exit(errcode) 12 | 13 | 14 | def doexit(msg, errcode=0): 15 | print(msg) 16 | input('Press Enter to continue...') 17 | sys.exit(errcode) 18 | 19 | 20 | if not os.path.isfile('NAND-patched.bin'): 21 | doexit('NAND-patched.bin not found.', errcode=1) 22 | 23 | use_separate = True # use separate firm(0/1)_enc.bak 24 | if os.path.isfile('firm0firm1.bak'): 25 | print('Using firm0firm1.bak.') 26 | elif os.path.isfile('firm0_enc.bak') and os.path.isfile('firm1_enc.bak'): 27 | print('Using firm0_enc.bak and firm1_enc.bak') 28 | use_separate = True 29 | else: 30 | doexit('FIRM backup was not found.\n' 31 | 'This can be in a single "firm0firm1.bak" file, or\n' 32 | 'two "firm0_enc.bak" and "firm1_enc.bak" files.', errcode=1) 33 | 34 | if os.path.isfile('NAND-unpatched.bin'): 35 | doexit('NAND-unpatched.bin was found.\n' 36 | 'In order to prevent writing a good backup with a bad one, the ' 37 | 'install has stopped. Please move or delete the old file if you ' 38 | 'are sure you want to continue.', errcode=1) 39 | 40 | readsize = 0x100000 # must be divisible by 0x3AF00000 and 0x4D800000 41 | 42 | print('Trying to open NAND-patched.bin...') 43 | with open('NAND-patched.bin', 'rb+') as nand: 44 | print('Restoring FIRM0FIRM1.') 45 | nand.seek(0xB130000) 46 | if use_separate: 47 | with open('firm0_enc.bak', 'rb') as f: 48 | firm_final = f.read(0x400000).ljust(0x400000, b'\0') 49 | with open('firm1_enc.bak', 'rb') as f: 50 | firm_final += f.read(0x400000).ljust(0x400000, b'\0') 51 | else: 52 | with open('firm0firm1.bak', 'rb') as f: 53 | firm_final = f.read(0x800000) 54 | start_time = time.time() 55 | for curr in range(0x800000 // readsize): 56 | print('Writing {:06X} ({:>5.1f}%)'.format((curr + 1) * readsize, 57 | (((curr + 1) * readsize) / 0x800000) * 100), end='\r') 58 | nand.write(bytes(firm_final[curr * readsize:(curr + 1) * readsize])) 59 | 60 | os.rename('NAND-patched.bin', 'NAND-unpatched.bin') 61 | 62 | doexit('\nWriting finished in {:>.2f} seconds.'.format( 63 | time.time() - start_time)) 64 | -------------------------------------------------------------------------------- /install-b9s.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import hashlib 4 | import os 5 | import shutil 6 | import subprocess 7 | import sys 8 | import time 9 | 10 | 11 | def doexit(msg, errcode=0): 12 | print(msg) 13 | input('Press Enter to continue...') 14 | sys.exit(errcode) 15 | 16 | 17 | if not os.path.isfile('NAND.bin'): 18 | doexit('NAND.bin not found.', errcode=1) 19 | 20 | if os.path.isfile('firm0firm1.bak'): 21 | doexit('firm0firm1.bak was found.\n' 22 | 'In order to prevent writing a good backup with a bad one, the ' 23 | 'install has stopped. Please move or delete the old file if you ' 24 | 'are sure you want to continue. If you would like to restore, use ' 25 | '`restore-firm0firm1`.', 26 | errcode=1) 27 | 28 | if os.path.isfile('NAND-patched.bin'): 29 | doexit('NAND-patched.bin was found.\n' 30 | 'Please move or delete the patched NAND before patching another.', 31 | errcode=1) 32 | 33 | if not os.path.isfile('current.firm'): 34 | doexit('current.firm not found.', errcode=1) 35 | 36 | if not os.path.isfile('boot9strap.firm'): 37 | doexit('boot9strap.firm not found.', errcode=1) 38 | 39 | if not os.path.isfile('boot9strap.firm.sha'): 40 | doexit('boot9strap.firm.sha not found.', errcode=1) 41 | 42 | print('Verifying boot9strap.firm.') 43 | with open('boot9strap.firm.sha', 'rb') as f: 44 | b9s_hash = f.read(0x20) 45 | with open('boot9strap.firm', 'rb') as f: 46 | if hashlib.sha256(f.read(0x400000)).digest() != b9s_hash: 47 | doexit('boot9strap.firm hash check failed.', errcode=1) 48 | print('boot9strap.firm hash check passed.') 49 | 50 | readsize = 0x100000 # must be divisible by 0x3AF00000 and 0x4D800000 51 | 52 | shutil.rmtree('work', ignore_errors=True) 53 | os.makedirs('work', exist_ok=True) 54 | 55 | 56 | def runcommand(cmdargs): 57 | proc = subprocess.Popen(cmdargs, stdout=subprocess.PIPE, 58 | stderr=subprocess.PIPE) 59 | proc.wait() 60 | procoutput = proc.communicate()[0] 61 | # return(procoutput) 62 | if proc.returncode != 0: 63 | print('{} had an error.'.format(cmdargs[0])) 64 | print('Full command: {}'.format(' '.join(cmdargs))) 65 | print('Output:') 66 | print(procoutput) 67 | 68 | 69 | overall_time = time.time() 70 | print('Trying to open NAND.bin...') 71 | with open('NAND.bin', 'rb+') as nand: 72 | print('Backing up FIRM0FIRM1 to firm0firm1.bin...') 73 | nand.seek(0xB130000) 74 | start_time = time.time() 75 | with open('firm0firm1.bak', 'wb') as f: 76 | for curr in range(0x800000 // readsize): 77 | f.write(nand.read(readsize)) 78 | print('Reading {:06X} ({:>5.1f}%)'.format((curr + 1) * readsize, 79 | (((curr + 1) * readsize) / 0x800000) * 100), end='\r') 80 | print('\nReading finished in {:>.2f} seconds.\n'.format( 81 | time.time() - start_time)) 82 | 83 | print('Creating FIRMs to xor from boot9strap.firm.') 84 | start_time = time.time() 85 | with open('current.firm', 'rb') as f: 86 | with open('work/current_pad.bin', 'wb') as b9s: 87 | b9s.write(f.read(0x400000).ljust(0x400000, b'\0') * 2) 88 | with open('boot9strap.firm', 'rb') as f: 89 | with open('work/boot9strap_pad.bin', 'wb') as b9s: 90 | b9s.write(f.read(0x400000).ljust(0x400000, b'\0') * 2) 91 | print('Creation finished in {:>.2f} seconds.\n'.format( 92 | time.time() - start_time)) 93 | 94 | print('XORing FIRM0FIRM1 with current.firm.') 95 | start_time = time.time() 96 | runcommand(['tools/lazyxor-' + sys.platform, 'firm0firm1.bak', 97 | 'work/current_pad.bin', 'work/xored.bin']) 98 | print('XORing finished in {:>.2f} seconds.\n'.format( 99 | time.time() - start_time)) 100 | 101 | print('XORing FIRM0FIRM1 with boot9strap.firm.') 102 | start_time = time.time() 103 | runcommand(['tools/lazyxor-' + sys.platform, 'work/xored.bin', 104 | 'work/boot9strap_pad.bin', 'work/final.bin']) 105 | print('XORing finished in {:>.2f} seconds.\n'.format( 106 | time.time() - start_time)) 107 | 108 | print('Writing final FIRMs to NAND.bin.') 109 | with open('work/final.bin', 'rb') as f: 110 | firm_final = f.read(0x800000) 111 | nand.seek(0xB130000) 112 | start_time = time.time() 113 | for curr in range(0x800000 // readsize): 114 | print('Writing {:06X} ({:>5.1f}%)'.format((curr + 1) * readsize, 115 | (((curr + 1) * readsize) / 0x800000) * 100), end='\r') 116 | nand.write(bytes(firm_final[curr * readsize:(curr + 1) * readsize])) 117 | print('\nWriting finished in {:>.2f} seconds.'.format( 118 | time.time() - start_time)) 119 | 120 | os.rename('NAND.bin', 'NAND-patched.bin') 121 | 122 | doexit('boot9strap install process finished in {:>.2f} seconds.'.format( 123 | time.time() - overall_time)) 124 | --------------------------------------------------------------------------------