#!/usr/bin/env python import sys, lz4, hashlib from struct import unpack as up, pack as pk def lz4_compress(data): try: import lz4.block as block except ImportError: block = lz4.LZ4_compress return block.compress(data, 'high_compression', store_size=False) def read_file(fn): with open(fn, 'rb') as f: return f.read() def pad(data, size): assert len(data) <= size return (data + '\x00' * size)[:size] def get_overlay(program, i): return program[0x2B000 + 0x14000 * i:0x2B000 + 0x14000 * (i+1)] KIP_NAMES = ['Loader', 'NCM', 'ProcessManager', 'sm', 'boot', 'spl', 'ams_mitm'] def get_kips(): emummc = read_file('../../../../emummc/emummc.kip') loader = read_file('../../../../stratosphere/loader/loader.kip') ncm = read_file('../../../../stratosphere/ncm/ncm.kip') pm = read_file('../../../../stratosphere/pm/pm.kip') sm = read_file('../../../../stratosphere/sm/sm.kip') boot = read_file('../../../../stratosphere/boot/boot.kip') spl = read_file('../../../../stratosphere/spl/spl.kip') ams_mitm = read_file('../../../../stratosphere/ams_mitm/ams_mitm.kip') return (emummc, { 'Loader' : loader, 'NCM' : ncm, 'ProcessManager' : pm, 'sm' : sm, 'boot' : boot, 'spl' : spl, 'ams_mitm' : ams_mitm, }) def write_kip_meta(f, kip, ofs): # Write program id f.write(kip[0x10:0x18]) # Write offset, size f.write(pk('<II', ofs, len(kip))) # Write hash f.write(hashlib.sha256(kip).digest()) def write_header(f, all_kips): # Unpack kips emummc, kips = all_kips # Write reserved0 (previously entrypoint) as infinite loop instruction. f.write(pk('<I', 0xEAFFFFFE)) # Write metadata offset = 0x10 f.write(pk('<I', 0x20)) # Write flags f.write(pk('<I', 0x00000000)) # Write num_kips f.write(pk('<I', len(KIP_NAMES))) # Write reserved2 f.write(b'\xCC' * 0x10) # Write magic f.write('FSS0') # Write total size f.write(pk('<I', 0x800000)) # Write reserved3 f.write(pk('<I', 0xCCCCCCCC)) # Write content_header_offset f.write(pk('<I', 0x40)) # Write num_content_headers; f.write(pk('<I', 8 + len(KIP_NAMES))) # Write supported_hos_version; f.write(pk('<I', 0xCCCCCCCC)) # TODO # Write release_version; f.write(pk('<I', 0xCCCCCCCC)) # TODO # Write git_revision; f.write(pk('<I', 0xCCCCCCCC)) # TODO # Write content metas f.write(pk('<IIBBBBI16s', 0x000800, 0x001800, 2, 0, 0, 0, 0xCCCCCCCC, 'warmboot')) f.write(pk('<IIBBBBI16s', 0x002000, 0x002000, 12, 0, 0, 0, 0xCCCCCCCC, 'tsec_keygen')) f.write(pk('<IIBBBBI16s', 0x004000, 0x01C000, 11, 0, 0, 0, 0xCCCCCCCC, 'exosphere_fatal')) f.write(pk('<IIBBBBI16s', 0x048000, 0x00E000, 1, 0, 0, 0, 0xCCCCCCCC, 'exosphere')) f.write(pk('<IIBBBBI16s', 0x056000, 0x0AA000, 10, 0, 0, 0, 0xCCCCCCCC, 'mesosphere')) f.write(pk('<IIBBBBI16s', 0x7C0000, 0x020000, 0, 0, 0, 0, 0xCCCCCCCC, 'fusee')) f.write(pk('<IIBBBBI16s', 0x7E0000, 0x001000, 3, 0, 0, 0, 0xCCCCCCCC, 'rebootstub')) f.write(pk('<IIBBBBI16s', 0x100000, len(emummc), 8, 0, 0, 0, 0xCCCCCCCC, 'emummc')) ofs = 0x100000 + len(emummc) for kip_name in KIP_NAMES: kip_data = kips[kip_name] f.write(pk('<IIBBBBI16s', ofs, len(kip_data), 6, 0, 0, 0, 0xCCCCCCCC, kip_name)) ofs += len(kip_data) # Pad to kip metas. f.write(b'\xCC' * (0x400 - 0x40 - (0x20 * (8 + len(KIP_NAMES))))) # Write emummc_meta. */ write_kip_meta(f, emummc, 0x100000) # Write kip metas ofs = 0x100000 + len(emummc) for kip_name in KIP_NAMES: kip_data = kips[kip_name] write_kip_meta(f, kip_data, ofs) ofs += len(kip_data) # Pad to end of header f.write(b'\xCC' * (0x800 - (0x400 + (1 + len(KIP_NAMES)) * 0x30))) def write_kips(f, all_kips): # Unpack kips emummc, kips = all_kips # Write emummc f.write(emummc) # Write kips tot = len(emummc) for kip_name in KIP_NAMES: kip_data = kips[kip_name] f.write(kip_data) tot += len(kip_data) # Pad to 3 MB f.write(b'\xCC' * (0x300000 - tot)) def main(argc, argv): if argc == 2: target = argv[1] if len(target) == 0: target = '' elif argc == 1: target = '' else: print('Usage: %s target' % argv[0]) return 1 all_kips = get_kips() with open('../../program%s.bin' % target, 'rb') as f: data = f.read() erista_mtc = get_overlay(data, 1) mariko_mtc = get_overlay(data, 2) erista_hsh = hashlib.sha256(erista_mtc[:-4]).digest()[:4] mariko_hsh = hashlib.sha256(mariko_mtc[:-4]).digest()[:4] fusee_program = lz4_compress(data[:0x2B000 - 8] + erista_hsh + mariko_hsh + get_overlay(data, 0)[:0x11000]) with open('../../program%s.lz4' % target, 'wb') as f: f.write(fusee_program) with open('../../fusee-boogaloo%s.bin' % target, 'wb') as f: # Write header write_header(f, all_kips) # Write warmboot f.write(pad(read_file('../../../../exosphere/warmboot%s.bin' % target), 0x1800)) # Write TSEC Keygen f.write(pad(read_file('../../tsec_keygen/tsec_keygen.bin'), 0x2000)) # Write Mariko Fatal f.write(pad(read_file('../../../../exosphere/mariko_fatal%s.bin' % target), 0x1C000)) # Write Erista MTC f.write(erista_mtc[:-4] + erista_hsh) # Write Mariko MTC f.write(mariko_mtc[:-4] + mariko_hsh) # Write exosphere f.write(pad(read_file('../../../../exosphere/exosphere%s.bin' % target), 0xE000)) # Write mesosphere f.write(pad(read_file('../../../../mesosphere/mesosphere%s.bin' % target), 0xAA000)) # Write kips write_kips(f, all_kips) # Write Splash Screen f.write(pad(read_file('../../splash_screen/splash_screen.bin'), 0x3C0000)) # Write fusee f.write(pad(fusee_program, 0x20000)) # Write rebootstub f.write(pad(read_file('../../../../exosphere/program/rebootstub/rebootstub%s.bin' % target), 0x1000)) # Pad to 8 MB f.write(b'\xCC' * (0x800000 - 0x7E1000)) return 0 if __name__ == '__main__': sys.exit(main(len(sys.argv), sys.argv))