diff --git a/Makefile b/Makefile index 67cfc3c..c59ca59 100644 --- a/Makefile +++ b/Makefile @@ -132,6 +132,7 @@ $(TARGET).nds: $(TARGET).arm7 $(TARGET).arm9 ndstool -c $(TARGET).nds -7 $(TARGET).arm7.elf -9 $(TARGET).arm9.elf \ -b icon.bmp "GodMode9i;RocketRobz" \ -g HGMA 01 "GODMODE9I" -z 80040000 -u 00030004 + python fix_ndsheader.py $(CURDIR)/$(TARGET).nds $(TARGET).arm7: arm7/$(TARGET).elf cp arm7/$(TARGET).elf $(TARGET).arm7.elf diff --git a/fix_ndsheader.py b/fix_ndsheader.py new file mode 100644 index 0000000..fdbe6d1 --- /dev/null +++ b/fix_ndsheader.py @@ -0,0 +1,463 @@ +# -*- coding: utf8 -*- +# Patch an .nds (works with homebrew and ds demo only) to make it ready for make_cia +# +# 2016-02-28, Ahezard +# +# inspired by +# Apache Thunder .nds edited files and comments +# https://github.com/Relys/Project_CTR/blob/master/makerom/srl.h +# https://dsibrew.org/wiki/DSi_Cartridge_Header +# if the header size of the input nds file is 0x200 (homebrew) +# the header size of the output nds file will be patched to 0x4000 (normal ds/dsi header), 0x3E00 offset + +from struct import * +from collections import namedtuple +from collections import OrderedDict +from pprint import pprint +import os, sys +import binascii +import argparse + + +parser = argparse.ArgumentParser(description='Patch an nds in order to be ready cia conversion via make_cia --srl=.') +parser.add_argument('file', metavar='file.nds', type=file, help='nds file to patch') +parser.add_argument('--verbose', help='verbose mode', action="store_true") +parser.add_argument('--out', help='output file [optionnal]') +parser.add_argument('--read', help='print only the header content, do not patch', action="store_true") +parser.add_argument('--extract', help='extract the content of the rom : header.bin,arm9.bin,arm7.bin,icon.bin,arm9i.bin,arm7i.bin, do not patch', action="store_true") #Not yet implemented +parser.add_argument('--title', help='Game title') +parser.add_argument('--code', help='Game code') +parser.add_argument('--maker', help='Maker code') +parser.add_argument('--mode', help='target mode, default mode is ds [ds|dsi|dsinogba|nitrohax]') +parser.add_argument('--arm9', type=file, help='swap the ds arm9 binary by the one provided') +parser.add_argument('--arm7', type=file, help='swap the ds arm7 binary by the one provided') +parser.add_argument('--arm9EntryAddress', help='arm9 ram address of the binary provided') +parser.add_argument('--arm7EntryAddress', help='arm7 ram address of the binary provided') +parser.add_argument('--arm9i', type=file, help='add a dsi arm9i binary to the file, not needed for homebrew so far') +parser.add_argument('--arm7i', type=file, help='add a dsi arm7i binary to the file, not needed for homebrew so far') +parser.add_argument('--accessControl', help='access control field') +args = parser.parse_args() + +if args.mode is None: + args.mode = "dsi" + +# +# CRC16 MODULE +# +# includes CRC16 and CRC16 MODBUS +# + +from ctypes import c_ushort + +# from https://github.com/cristianav/PyCRC/blob/master/demo.py +class CRC16(object): + crc16_tab = [] + + # The CRC's are computed using polynomials. Here is the most used + # coefficient for CRC16 + crc16_constant = 0xA001 # 40961 + + def __init__(self, modbus_flag=False): + # initialize the precalculated tables + if not len(self.crc16_tab): + self.init_crc16() + self.mdflag = bool(modbus_flag) + + def calculate(self, input_data=None): + try: + is_string = isinstance(input_data, str) + is_bytes = isinstance(input_data, (bytes, bytearray)) + + if not is_string and not is_bytes: + raise Exception("Please provide a string or a byte sequence " + "as argument for calculation.") + + crc_value = 0x0000 if not self.mdflag else 0xffff + + for c in input_data: + d = ord(c) if is_string else c + tmp = crc_value ^ d + rotated = crc_value >> 8 + crc_value = rotated ^ self.crc16_tab[(tmp & 0x00ff)] + + return crc_value + except Exception as e: + print("EXCEPTION(calculate): {}".format(e)) + + def init_crc16(self): + """The algorithm uses tables with precalculated values""" + for i in range(0, 256): + crc = c_ushort(i).value + for j in range(0, 8): + if crc & 0x0001: + crc = c_ushort(crc >> 1).value ^ self.crc16_constant + else: + crc = c_ushort(crc >> 1).value + self.crc16_tab.append(crc) + +def getSize(fileobject): + current = fileobject.tell() + fileobject.seek(0,2) # move the cursor to the end of the file + size = fileobject.tell() + fileobject.seek(current,0) + return size + +def skipUntilAddress(f_in,f_out, caddr, taddr): + chunk = f_in.read(taddr-caddr) + f_out.write(chunk) + +def writeBlankuntilAddress(f_out, caddr, taddr): + f_out.write("\x00"*(taddr-caddr)) + +fname=args.file.name +args.file.close() + +if not args.read: + print "Patching file : "+fname +else: + print "Reading header of file : "+fname + +#offset of 0x4600 created + +# File size compute +file = open(fname, 'rb') +fsize=getSize(file) +file.close() + +#CRC header compute "CRC-16 (Modbus)" +file = open(fname, 'rb') +#0x15E from https://github.com/devkitPro/ndstool/ ... source/header.cpp +hdr = file.read(0x15E) +hdrCrc=CRC16(modbus_flag=True).calculate(hdr) +if args.verbose: + print("{:10s} {:20X}".format('HDR CRC-16 ModBus', hdrCrc)) +#print "origin header cr c"+hdr[0x15E:0x15F] +#filew = open(fname+".hdr", "wb") +#filew.write(hdr); +#filew.close() +file.close() + +if args.arm9 is not None: + arm9Fname=args.arm9.name + args.arm9.close() + arm9File = open(arm9Fname, 'rb') + arm9FileSize=getSize(arm9File) + dataArm9=arm9File.read(arm9FileSize) + arm9File.close() + +if args.arm7 is not None: + arm7Fname=args.arm7.name + args.arm7.close() + arm7File = open(arm7Fname, 'rb') + arm7FileSize=getSize(arm7File) + dataArm7=arm7File.read(arm7FileSize) + arm7File.close() + +filer = open(fname, 'rb') +data = filer.read(0x180) +caddr=0x180 + +#DS Data 180 bytes +SrlHeader = namedtuple('SrlHeader', + "gameTitle " + "gameCode " + "makerCode " + "unitCode " + "encryptionSeedSelect " + "deviceCapacity " + "reserved0 " + "dsiflags " + "romVersion " + "internalFlag " + "arm9RomOffset " + "arm9EntryAddress " + "arm9RamAddress " + "arm9Size " + "arm7RomOffset " + "arm7EntryAddress " + "arm7RamAddress " + "arm7Size " + "fntOffset " + "fntSize " + "fatOffset " + "fatSize " + "arm9OverlayOffset " + "arm9OverlaySize " + "arm7OverlayOffset " + "arm7OverlaySize " + "normalCardControlRegSettings " + "secureCardControlRegSettings " + "icon_bannerOffset " + "secureAreaCrc " + "secure_transfer_timeout " + "arm9Autoload " + "arm7Autoload " + "secureDisable " + "ntrRomSize " + "headerSize " + "reserved1 " + "nintendoLogo " + "nintendoLogoCrc " + "headerCrc " + "debugReserved ") +srlHeaderFormat='<12s4s2scbb7s2sbcIIIIIIIIIIIIIIIIIIIHHII8sII56s156s2sH32s' +srlHeader=SrlHeader._make(unpack_from(srlHeaderFormat, data)) +if args.verbose: + print "origin header crc "+hex(srlHeader.headerCrc) + print "origin secure crc "+hex(srlHeader.secureAreaCrc) + +#SecureArea CRC compute "CRC-16 (Modbus)" +file = open(fname, 'rb') +#0x15E from https://github.com/devkitPro/ndstool/ ... source/header.cpp +file.read(0x200) +sec = file.read(0x4000) +secCrc=CRC16(modbus_flag=True).calculate(sec) +if args.verbose: + print("{:10s} {:20X}".format('SEC CRC-16 ModBus', secCrc)) +file.close() + +if srlHeader.arm7EntryAddress>0x2400000 and not args.read and args.arm7 is None: + print "WARNING: .nds arm7EntryAddress greater than 0x2400000 will not boot as cia" + print "you need to recompile or swap the arm7 binary with a precompiled one with --arm7 and --arm7EntryAddress" + +if "dsi" in args.mode : + srlHeaderPatched=srlHeader._replace( + dsiflags= '\x01\x00', #disable modcrypt but enable twl + unitCode= '\x02', + ) + +data1=pack(*[srlHeaderFormat]+srlHeaderPatched._asdict().values()) +newHdrCrc=CRC16(modbus_flag=True).calculate(data1[0:0x15E]) +srlHeaderPatched=srlHeaderPatched._replace(headerCrc=newHdrCrc) + +if args.verbose: + print "new header crc "+hex(newHdrCrc) +if not args.read : + if args.verbose: + pprint(dict(srlHeaderPatched._asdict())) +else: + pprint(dict(srlHeader._asdict())) + +data1=pack(*[srlHeaderFormat]+srlHeaderPatched._asdict().values()) + +arm9isize=0 +arm7isize=0 + +#TWL Only Data 384 bytes +SrlTwlExtHeader = namedtuple('SrlTwlExtHeader', + "MBK_1_5_Settings " + "MBK_6_8_Settings_ARM9 " + "MBK_6_8_Settings_ARM7 " + "global_MBK_9_Setting " + "regionFlags " + "accessControl " + "arm7ScfgExtMask " + "reserved_flags " + "arm9iRomOffset " + "reserved2 " + "arm9iLoadAddress " + "arm9iSize " + "arm7iRomOffset " + "struct_param_baseAddress " + "arm7iLoadAddress " + "arm7iSize " + "digest_ntrRegionOffset " + "digest_ntrRegionSize " + "digest_twlRegionOffset " + "digest_twlRegionSize " + "digestSectorHashtableOffset " + "digestSectorHashtableSize " + "digest_blockHashtableOffset " + "digest_blockHashtableSize " + "digestSectorSize " + "digest_blockSectorcount " + "iconSize " #usually 0x23C0 or 2112 in homebrew + "unknown1 " + "twlRomSize " + "unknown2 " + "modcryptArea1Offset " + "modcryptArea1Size " + "modcryptArea2Offset " + "modcryptArea2Size " + "title_id " + "pubSaveDataSize " + "privSaveDataSize " + "reserved4 " + "parentalControl ") +srlTwlExtHeaderFormat="<20s12s12s4s4sIIII4sIIIIIIIIIIIIIIIII4sI12sIIII8sII176s16s" +if srlHeader.headerSize<0x300: + #homebrew + srlTwlExtHeader=SrlTwlExtHeader._make(unpack_from(srlTwlExtHeaderFormat, "\x00" * (0x300-0x180))) +else: + data = filer.read(0x300-0x180) + srlTwlExtHeader=SrlTwlExtHeader._make(unpack_from(srlTwlExtHeaderFormat, data)) + caddr=0x300 + +#pprint(dict(srlTwlExtHeader._asdict())) + +if not args.read: + # Fix srlTwlExtHeader + if "dsi" in args.mode: + arm7iRomOffset=srlHeaderPatched.arm7RomOffset + arm9iRomOffset=srlHeaderPatched.arm9RomOffset + arm7isize=srlHeaderPatched.arm7Size + arm9isize=srlHeaderPatched.arm9Size + totaldsisize=0 + arm7iname = None + arm9iname = None + + if args.arm9i is not None: + arm9iname = args.arm9i.name + arm9isize = getSize(args.arm9i) + arm9iRomOffset=srlHeaderPatched.ntrRomSize + if args.verbose: + print "arm9isize : "+hex(arm9isize) + print "arm9ioffset : "+hex(srlHeaderPatched.ntrRomSize) + args.arm9i.close() + totaldsisize=arm9isize + + if args.arm7i is not None: + arm7iname = args.arm7i.name + arm7isize = getSize(args.arm7i) + arm7iRomOffset=srlHeaderPatched.ntrRomSize+arm9isize + if args.verbose: + print "arm7isize : "+hex(arm7isize) + print "arm9ioffset : "+hex(srlHeaderPatched.ntrRomSize+arm9isize) + args.arm7i.close() + totaldsisize=arm9isize+arm7isize + + + + srlTwlExtHeader=srlTwlExtHeader._replace( + accessControl= 0x00000138, + arm7ScfgExtMask= 0x80040000, + reserved_flags= 0x01000000 + ) + + if args.accessControl is not None: + srlTwlExtHeader=srlTwlExtHeader._replace( + accessControl= int(args.accessControl,0), + ) + +if args.verbose or args.read: + pprint(dict(srlTwlExtHeader._asdict())) + +data2=pack(*[srlTwlExtHeaderFormat]+srlTwlExtHeader._asdict().values()) + +#TWL and Signed NTR 3328 bytes +SrlSignedHeader = namedtuple('SrlSignedHeader', + "arm9WithSecAreaSha1Hmac " + "arm7Sha1Hmac " + "digestMasterSha1Hmac " + "bannerSha1Hmac " + "arm9iSha1Hmac " + "arm7iSha1Hmac " + "reserved5 " + "arm9Sha1Hmac " + "reserved6 " + "reserved7 " + "signature " + ) +srlSignedHeaderFormat="<20s20s20s20s20s20s40s20s2636s384s128s" +if srlHeader.headerSize<0x1100: + #homebrew + srlSignedHeader=SrlSignedHeader._make(unpack_from(srlSignedHeaderFormat, "\x00" * (3328))) +else: + data = filer.read(3328) + srlSignedHeader=SrlSignedHeader._make(unpack_from(srlSignedHeaderFormat, data)) + caddr=0x300+3328 + filer.read(0x4000-caddr) + caddr=0x4000 + +#pprint(dict(srlSignedHeader._asdict())) + +# Fix srlSignedHeader +if not args.read: + srlSignedHeader=srlSignedHeader._replace( + arm7Sha1Hmac= '\xff'*20, + arm9WithSecAreaSha1Hmac= '\xff'*20, + bannerSha1Hmac= '\xff'*20, + signature= '\xff'*128 + ) + if "dsi" in args.mode : + srlSignedHeader=srlSignedHeader._replace( + arm7Sha1Hmac= '\xff'*20, + arm7iSha1Hmac= '\xff'*20, + arm9Sha1Hmac= '\xff'*20, + arm9WithSecAreaSha1Hmac= '\xff'*20, + arm9iSha1Hmac= '\xff'*20, + bannerSha1Hmac= '\xff'*20, + digestMasterSha1Hmac= '\xff'*20, + signature= '\xff'*128 + ) +if args.verbose or args.read: + pprint(dict(srlSignedHeader._asdict())) + +data3=pack(*[srlSignedHeaderFormat]+srlSignedHeader._asdict().values()) + +# ARM9 footer +# from https://github.com/devkitPro/ndstool/ ... source/header.cpp +# ARM9 footer size = 3*4 +ARM9Footer = namedtuple('ARM9Footer', + "nitrocode " #0xDEC00621 + "versionInfo " + "reserved " + ) +ARM9FooterFormat="