Make-Forwarder-Dsi/patch_ndsheader_dsiware.py
2019-02-13 20:18:36 +01:00

457 lines
13 KiB
Python

# -*- 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]')
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('--digest-block', type=file, help='dsi digest block table') #Not yet implemented
parser.add_argument('--digest-sector', type=file, help='dsi digest sector table') #Not yet implemented
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= '\x03',
)
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= 0x00000000
)
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="<III"
file = open(fname, 'rb')
arm9FooterAddr=srlHeader.arm9RomOffset + srlHeader.arm9Size
file.read(arm9FooterAddr)
data=file.read(12)
arm9Footer=ARM9Footer._make(unpack_from(ARM9FooterFormat, data))
if args.verbose:
print "footer addr "+hex(arm9FooterAddr)
if arm9Footer.nitrocode == 0xDEC00621:
if args.verbose or args.read:
print "ARM9 footer found."
print "no patch needed"
print "nitrocode "+hex(arm9Footer.nitrocode)
print "versionInfo "+hex(arm9Footer.versionInfo)
print "reserved "+hex(arm9Footer.reserved)
print "\n"
else:
if args.verbose or args.read:
print "ARM9 footer not found.\n"
arm9FooterPatched=arm9Footer._replace(
nitrocode= 0xDEC00621,
versionInfo= 0xad8,
reserved= 0
)
data4=pack(*[ARM9FooterFormat]+arm9FooterPatched._asdict().values())
file.close()
if not args.read:
# write the file
if args.out is not None:
filew = open(args.out, "wb")
else:
filew = open(fname+".tmp", "wb")
filew.write(data1)
filew.write(data2)
filew.write(data3[0:0xC80])
filew.write('\xff'*16*8)
writeBlankuntilAddress(filew,0x1000,0x4000)
if arm9Footer.nitrocode != 0xDEC00621:
# patch ARM9 footer
skipUntilAddress(filer,filew,caddr,arm9FooterAddr)
filew.write(data4)
filer.read(12)
caddr=arm9FooterAddr+12
skipUntilAddress(filer,filew,caddr,srlTwlExtHeader.twlRomSize)
filew.close()
filer.close()
if args.out is None:
if os.path.exists(fname+".orig.nds"):
os.remove(fname+".orig.nds")
os.rename(fname,fname+".orig.nds")
os.rename(fname+".tmp",fname)
print "file patched"