lots of accumulated fixes. BNS now is included in main wii.py :D

This commit is contained in:
Xuzz 2009-07-25 13:08:38 -07:00
parent 20e8c665a6
commit c65d1e23b7
6 changed files with 827 additions and 201 deletions

3
Wii.py
View File

@ -10,6 +10,7 @@ from export import *
from compression import *
from nand import *
from headers import *
from bns import *
if (__name__ == "__main__"):
Crypto()
@ -18,4 +19,4 @@ if (__name__ == "__main__"):
#insert non-dependant check code here
print ("\nAll Wii.py components loaded sucessfully!\n")
print("\nAll Wii.py components loaded sucessfully!\n")

View File

@ -24,15 +24,14 @@ class U8(WiiArchive):
def __init__(self):
self.files = []
def _dump(self):
"""This function will pack a folder into a U8 archive. The output file name is specified in the parameter fn. If fn is an empty string, the filename is deduced from the input folder name. Returns the output filename.
This creates valid U8 archives for all purposes."""
header = self.U8Header()
rootnode = self.U8Node()
# constants
header.tag = "U\xAA8-"
header.rootnode_offset = 0x20
header.zeroes = "\x00" * 16
rootnode.type = 0x0100
nodes = []
strings = "\x00"
@ -40,37 +39,35 @@ class U8(WiiArchive):
for item, value in self.files:
node = self.U8Node()
node.name_offset = len(strings)
recursion = item.count('/')
if(recursion < 0):
recursion = 0
name = item[item.rfind('/') + 1:]
node.name_offset = len(strings)
strings += name + '\x00'
if(value == None):
node.type = 0x0100
node.data_offset = recursion
this_length = 0
node.size = len(nodes)
for one, two in self.files:
subdirs = one
if(subdirs.find(item) != -1):
this_length += 1
node.size = len(nodes) + this_length + 1
if(one[:len(item)] == item): # find nodes in the folder
node.size += 1
node.size += 1
else:
sz = len(value)
value += "\x00" * (align(sz, 32) - sz) #32 seems to work best for fuzzyness? I'm still really not sure
node.data_offset = len(data)
data += value
data += value + "\x00" * (align(sz, 32) - sz) # 32 seems to work best for fuzzyness? I'm still really not sure
node.size = sz
node.type = 0x0000
nodes.append(node)
header.header_size = (len(nodes) + 1) * len(rootnode) + len(strings)
header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings)
header.data_offset = align(header.header_size + header.rootnode_offset, 64)
rootnode.size = len(nodes) + 1
rootnode.type = 0x0100
for i in range(len(nodes)):
if(nodes[i].type == 0x0000):
@ -155,7 +152,7 @@ class U8(WiiArchive):
elif(node.type == 0): # file
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
offset += node.size
else: # unknown
else: # unknown type -- wtf?
pass
sz = recursion.pop()
@ -178,7 +175,14 @@ class U8(WiiArchive):
def __getitem__(self, key):
for item, val in self.files:
if(item == key):
return val
if(val != None):
return val
else:
ret = []
for item2, val2 in self.files:
if(item2.find(item) == 0):
ret.append(item2[len(item) + 1:])
return ret[1:]
raise KeyError
def __setitem__(self, key, val):
for i in range(len(self.files)):
@ -188,49 +192,103 @@ class U8(WiiArchive):
self.files.append((key, val))
class WAD:
"""This class is to pack and unpack WAD files, which store a single title. You pass the input filename or input directory name to the parameter f.
WAD packing support currently creates WAD files that return -4100 on install."""
def __init__(self, f, boot2 = False):
self.f = f
self.boot2 = boot2
def pack(self, fn = "", fakesign = True, decrypted = True):
"""Packs a WAD into the filename specified by fn, if it is not empty. If it is empty, it packs into a filename generated from the folder's name. If fakesign is True, it will fakesign the Ticket and TMD, and update them as needed. If decrypted is true, it will assume the contents are already decrypted. For now, fakesign can not be True if decrypted is False, however fakesign can be False if decrypted is True. Title ID is a long integer of the destination title id."""
os.chdir(self.f)
class WAD(WiiArchive):
def __init__(self, boot2 = False):
self.tmd = TMD()
self.tik = Ticket()
self.contents = []
self.boot2 = False
self.cert = ""
def _load(self, data):
if(self.boot2 != True):
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding = struct.unpack('>I4s6I32s', data[:64])
pos = 64
else:
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', data[:32])
pos = 32
rawcert = data[pos:pos + certsize]
pos += certsize
if(self.boot2 != True):
if(certsize % 64 != 0):
pos += 64 - (certsize % 64)
self.cert = rawcert
rawtik = data[pos:pos + tiksize]
pos += tiksize
if(self.boot2 != True):
if(tiksize % 64 != 0):
pos += 64 - (tiksize % 64)
self.tik = Ticket.load(rawtik)
rawtmd = data[pos:pos + tmdsize]
pos += tmdsize
if(self.boot2 == True):
pos = data_offset
else:
pos += 64 - (tmdsize % 64)
self.tmd = TMD.load(rawtmd)
tik = Ticket.loadFile("tik")
tmd = TMD.loadFile("tmd")
titlekey = tik.getTitleKey()
contents = tmd.getContents()
titlekey = self.tik.getTitleKey()
contents = self.tmd.getContents()
for i in range(0, len(contents)):
tmpsize = contents[i].size
if(tmpsize % 16 != 0):
tmpsize += 16 - (tmpsize % 16)
encdata = data[pos:pos + tmpsize]
pos += tmpsize
decdata = Crypto().decryptContent(titlekey, contents[i].index, encdata)
self.contents.append(decdata)
if(tmpsize % 64 != 0):
pos += 64 - (tmpsize % 64)
def _loadDir(self, dir):
origdir = os.getcwd()
os.chdir(dir)
self.tmd = TMD.loadFile("tmd")
self.tik = Ticket.loadFile("tik")
self.cert = open("cert", "rb").read()
contents = self.tmd.getContents()
for i in range(len(contents)):
self.contents.append(open("%08x.app" % i, "rb").read())
os.chdir(origdir)
def _dumpDir(self, dir):
origdir = os.getcwd()
os.chdir(dir)
contents = self.tmd.getContents()
for i in range(len(contents)):
open("%08x.app" % i, "wb").write(self.contents[i])
self.tmd.dumpFile("tmd")
self.tik.dumpFile("tik")
open("cert", "wb").write(self.cert)
os.chdir(origdir)
def _dump(self, fakesign = True):
titlekey = self.tik.getTitleKey()
contents = self.tmd.getContents()
apppack = ""
for content in contents:
tmpdata = open("%08x.app" % content.index, "rb").read()
for i, content in enumerate(contents):
if(fakesign):
content.hash = str(Crypto().createSHAHash(self.contents[content.index]))
content.size = len(self.contents[content.index])
if(decrypted):
if(fakesign):
content.hash = str(Crypto().createSHAHash(tmpdata))
content.size = len(tmpdata)
encdata = Crypto().encryptContent(titlekey, content.index, tmpdata)
else:
encdata = tmpdata
encdata = Crypto().encryptContent(titlekey, content.index, self.contents[content.index])
apppack += encdata
if(len(encdata) % 64 != 0):
apppack += "\x00" * (64 - (len(encdata) % 64))
if(fakesign):
tmd.setContents(contents)
tmd.fakesign()
tik.fakesign()
tmd.dumpFile("tmd")
tik.dumpFile("tik")
self.tmd.setContents(contents)
self.tmd.fakesign()
self.tik.fakesign()
rawtmd = open("tmd", "rb").read()
rawcert = open("cert", "rb").read()
rawtik = open("tik", "rb").read()
rawtmd = self.tmd.dump()
rawcert = self.cert
rawtik = self.tik.dump()
sz = 0
for i in range(len(contents)):
@ -258,92 +316,16 @@ class WAD:
pack += "\x00" * (align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40) - (len(rawcert) + len(rawtik) + len(rawtmd)))
pack += apppack
os.chdir('..')
if(fn == ""):
if(self.f[len(self.f) - 4:] == "_out"):
fn = os.path.dirname(self.f) + "/" + os.path.basename(self.f)[:len(os.path.basename(self.f)) - 4].replace("_", ".")
else:
fn = self.f
open(fn, "wb").write(pack)
return fn
def unpack(self, fn = ""):
"""Unpacks the WAD from the parameter f in the initializer to either the value of fn, if there is one, or a folder created with this formula: `filename_extension_out`. Certs are put in the file "cert", TMD in the file "tmd", ticket in the file "tik", and contents are put in the files based on index and with ".app" added to the end."""
fd = open(self.f, 'rb')
if(self.boot2 != True):
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding= struct.unpack('>I4s6I32s', fd.read(64))
else:
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', fd.read(32))
try:
if(fn == ""):
fn = self.f.replace(".", "_") + "_out"
os.mkdir(fn)
except OSError:
pass
os.chdir(fn)
rawcert = fd.read(certsize)
if(self.boot2 != True):
if(certsize % 64 != 0):
fd.seek(64 - (certsize % 64), 1)
open('cert', 'wb').write(rawcert)
rawtik = fd.read(tiksize)
if(self.boot2 != True):
if(tiksize % 64 != 0):
fd.seek(64 - (tiksize % 64), 1)
open('tik', 'wb').write(rawtik)
rawtmd = fd.read(tmdsize)
if(self.boot2 == True):
fd.seek(data_offset)
else:
fd.seek(64 - (tmdsize % 64), 1)
open('tmd', 'wb').write(rawtmd)
titlekey = Ticket.loadFile("tik").getTitleKey()
contents = TMD.loadFile("tmd").getContents()
for i in range(0, len(contents)):
tmpsize = contents[i].size
if(tmpsize % 16 != 0):
tmpsize += 16 - (tmpsize % 16)
tmptmpdata = fd.read(tmpsize)
tmpdata = Crypto().decryptContent(titlekey, contents[i].index, tmptmpdata)
open("%08x.app" % contents[i].index, "wb").write(tmpdata)
if(tmpsize % 64 != 0):
fd.seek(64 - (tmpsize % 64), 1)
fd.close()
os.chdir('..')
return fn
return pack
def __getitem__(self, idx):
return self.contents[idx]
def __setitem__(self, idx, value):
self.contents[idx] = value
def __str__(self):
out = ""
out += "Wii WAD:\n"
fd = open(self.f, 'rb')
if(self.boot2 != True):
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding= struct.unpack('>I4s6I32s', fd.read(64))
else:
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', fd.read(32))
rawcert = fd.read(certsize)
if(certsize % 64 != 0):
fd.seek(64 - (certsize % 64), 1)
rawtik = fd.read(tiksize)
if(self.boot2 != True):
if(tiksize % 64 != 0):
fd.seek(64 - (tiksize % 64), 1)
rawtmd = fd.read(tmdsize)
if(self.boot2 != True):
out += " Header %02x Type '%s' Certs %x Tiket %x TMD %x Data %x Footer %x\n" % (headersize, wadtype, certsize, tiksize, tmdsize, datasize, footersize)
else:
out += " Header %02x Type 'boot2' Certs %x Tiket %x TMD %x Data @ %x\n" % (headersize, certsize, tiksize, tmdsize, data_offset)
out += str(Ticket.load(rawtik))
out += str(TMD.load(rawtmd))
out += str(self.tmd)
out += str(self.tik)
return out
@ -459,4 +441,14 @@ class CCF():
output.close()
currentOffset += len(fileEntry)
if(__name__ == '__main__'):
wad = WAD.loadFile("testing.wad")
print wad
wad.dumpDir("outdir")
wad.dumpFile("interesting.wad", fakesign = False) #keyword arguements work as expected when calling _dump(). awesome.
wad2 = WAD.loadDir("outdir")
print wad2
wad3 = WAD.loadFile("interesting.wad")
print wad3
wad3.dumpDir("outdir2")

618
bns.py Normal file
View File

@ -0,0 +1,618 @@
#!/usr/bin/python
from common import *
class SoundFile:
def __init__(self, signal, filename, samplerate=32000):
self.actual_file = StringIO()
self.file = wave.open(filename, 'wb')
self.signal = signal
self.sr = samplerate
def write(self):
self.file.setparams((2, 2, self.sr, self.sr*4, 'NONE', 'noncompressed'))
self.file.writeframes(self.signal)
self.actual_file.seek(0)
self.file.close()
class BNS_data(object):
def __init__(self):
self.magic = "DATA"
self.size = 0x0004d000
def eat(self, buffer, offset):
self.magic, self.size = struct.unpack('>4sI', buffer[offset:offset+8])
return offset + 8
def show(self):
print "Magic: %s" % self.magic
print "Length: %08x" % self.size
return
def write(self, file):
file.write(self.magic)
file.write(struct.pack('>I', self.size))
file.write(self.data)
return
class BNS_info(object):
def __init__(self):
self.magic = "INFO"
self.size = 0x000000a0
self.codec = 0x00
self.has_loop = 0x00
self.chan_cnt = 0x02
self.zero = 0x00
self.samplerate = 0xac44
self.pad0 = 0x0000
self.loop_start = 0x00000000
self.loop_end = 0x00000000
self.offset_to_chan_starts = 0x00000018
self.pad2 = 0x00000000
self.channel1_start_offset = 0x00000020
self.channel2_start_offset = 0x0000002C
self.chan1_start = 0x00000000
self.coefficients1_offset = 0x0000038
self.pad1 = 0x00000000
self.chan2_start = 0x00000000
self.coefficients2_offset = 0x00000068
self.pad3 = 0x00000000
self.coefficients1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
self.chan1_gain = 0x0000
self.chan1_predictive_scale = 0x0000
self.chan1_previous_value = 0x0000
self.chan1_next_previous_value = 0x0000
self.chan1_loop_predictive_scale = 0x0000
self.chan1_loop_previous_value = 0x0000
self.chan1_loop_next_previous_value = 0x0000
self.chan1_loop_padding = 0x0000
self.coefficients2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
self.chan2_gain = 0x0000
self.chan2_predictive_scale = 0x0000
self.chan2_previous_value = 0x0000
self.chan2_next_previous_value = 0x0000
self.chan2_loop_predictive_scale = 0x0000
self.chan2_loop_previous_value = 0x0000
self.chan2_loop_next_previous_value = 0x0000
self.chan2_loop_padding = 0x0000
def eat(self, buffer, offset):
self.magic, self.size = struct.unpack('>4sI', buffer[offset+0:offset+8])
self.codec, self.has_loop = struct.unpack('>BB', buffer[offset+8:offset+10])
self.chan_cnt, self.zero = struct.unpack('>BB', buffer[offset+10:offset+12])
self.samplerate, self.pad0 = struct.unpack('>HH', buffer[offset+12:offset+16])
assert self.samplerate <= 48000
assert self.samplerate > 32000
self.loop_start, self.loop_end = struct.unpack('>II', buffer[offset+16:offset+24])
co = offset + 24
self.offset_to_chan_starts = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.pad2 = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.channel1_start_offset = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.channel2_start_offset = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.chan1_start = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.coefficients1_offset = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
if self.chan_cnt == 2:
self.pad1 = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.chan2_start = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.coefficients2_offset = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
self.pad3 = Struct.uint32(buffer[co:co+4], endian='>')
co += 4
for x in xrange(16):
self.coefficients1[x] = Struct.int16(buffer[co:co+2], endian='>')
co += 2
self.chan1_gain = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_predictive_scale = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_next_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_predictive_scale = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_next_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_padding = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
for x in xrange(16):
self.coefficients2[x] = Struct.int16(buffer[co:co+2], endian='>')
co += 2
self.chan2_gain = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan2_predictive_scale = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan2_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan2_next_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan2_loop_predictive_scale = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan2_loop_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan2_loop_next_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan2_loop_padding = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
elif self.chan_cnt == 1:
for x in xrange(16):
self.coefficients1[x] = Struct.int16(buffer[co:co+2], endian='>')
co += 2
self.chan1_gain = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_predictive_scale = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_next_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_predictive_scale = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_next_previous_value = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
self.chan1_loop_padding = Struct.uint16(buffer[co:co+2], endian='>')
co += 2
return co
def show(self):
print "Magic: %s" % self.magic
print "Length: %08x" % self.size
print "Codec: %02x " % self.codec,
if self.codec == 0: print "ADPCM"
else: print "Unknown (Maybe >_>, please contact megazig)"
print "Loop Flag: %02x " % self.has_loop,
if self.has_loop == 0: print "One shot"
else: print "Looping"
print "Channel Count: %02x" % self.chan_cnt
print "Zero: %02x" % self.zero
print "Samplerate: %04x %d" % ( self.samplerate , self.samplerate )
print "Padding: %04x" % self.pad0
print "Loop Start: %08x" % self.loop_start
print "Loop End: %08x" % self.loop_end
print "Channels Starts Offsets: %08x" % self.offset_to_chan_starts
print "Padding: %08x" % self.pad2
print "Channel 1 Start Offset: %08x" % self.channel1_start_offset
print "Channel 2 Start Offset: %08x" % self.channel2_start_offset
print "Channel 1 Start: %08x" % self.chan1_start
print "Coefficients 1 Offset: %08x" % self.coefficients1_offset
if self.chan_cnt == 2:
print "Padding: %08x" % self.pad1
print "Channel 2 Start: %08x" % self.chan2_start
print "Coefficients 2 Offset: %08x" % self.coefficients2_offset
print "Padding: %08x" % self.pad3
for x in xrange(16):
print "\t\tCoefficients 1: %2d - %04x - %d" % ( x , self.coefficients1[x], self.coefficients1[x] )
print "\tGain: %04x" % self.chan1_gain
print "\tPredictive Scale: %04x" % self.chan1_predictive_scale
print "\tPrevious Value: %04x" % self.chan1_previous_value
print "\tNext Previous Value: %04x" % self.chan1_next_previous_value
print "\tLoop Predictive Scale: %04x" % self.chan1_loop_predictive_scale
print "\tLoop Previous Value: %04x" % self.chan1_loop_previous_value
print "\tLoop Next Previous Value: %04x" % self.chan1_loop_next_previous_value
print "\tPadding: %04x" % self.chan1_loop_padding
for x in xrange(16):
print "\t\tCoefficients 2: %2d - %04x - %d" % ( x , self.coefficients2[x], self.coefficients2[x] )
print "\tGain: %04x" % self.chan2_gain
print "\tPredictive Scale: %04x" % self.chan2_predictive_scale
print "\tPrevious Value: %04x" % self.chan2_previous_value
print "\tNext Previous Value: %04x" % self.chan2_next_previous_value
print "\tLoop Predictive Scale: %04x" % self.chan2_loop_predictive_scale
print "\tLoop Previous Value: %04x" % self.chan2_loop_previous_value
print "\tLoop Next Previous Value: %04x" % self.chan2_loop_next_previous_value
print "\tPadding: %04x" % self.chan2_loop_padding
elif self.chan_cnt == 1:
for x in xrange(16):
print "\t\tCoefficients 1: %2d - %04x - %d" % ( x , self.coefficients1[x], self.coefficients1[x] )
print "\tGain: %04x" % self.chan1_gain
print "\tPredictive Scale: %04x" % self.chan1_predictive_scale
print "\tPrevious Value: %04x" % self.chan1_previous_value
print "\tNext Previous Value: %04x" % self.chan1_next_previous_value
print "\tLoop Predictive Scale: %04x" % self.chan1_loop_predictive_scale
print "\tLoop Previous Value: %04x" % self.chan1_loop_previous_value
print "\tLoop Next Previous Value: %04x" % self.chan1_loop_next_previous_value
print "\tPadding: %04x" % self.chan1_loop_padding
return
def write(self, file):
file.write(self.magic)
file.write(struct.pack('>I', self.size))
file.write(struct.pack('>B', self.codec))
file.write(struct.pack('>B', self.has_loop))
file.write(struct.pack('>B', self.chan_cnt))
file.write(struct.pack('>B', self.zero))
file.write(struct.pack('>H', self.samplerate))
file.write(struct.pack('>H', self.pad0))
file.write(struct.pack('>I', self.loop_start))
file.write(struct.pack('>I', self.loop_end))
file.write(struct.pack('>I', self.offset_to_chan_starts))
file.write(struct.pack('>I', self.pad2))
file.write(struct.pack('>I', self.channel1_start_offset))
file.write(struct.pack('>I', self.channel2_start_offset))
file.write(struct.pack('>I', self.chan1_start))
file.write(struct.pack('>I', self.coefficients1_offset))
if self.chan_cnt == 2:
file.write(struct.pack('>I', self.pad1))
file.write(struct.pack('>I', self.chan2_start))
file.write(struct.pack('>I', self.coefficients2_offset))
file.write(struct.pack('>I', self.pad3))
for x in xrange(16):
file.write(struct.pack('>h', self.coefficients1[x]))
file.write(struct.pack('>H', self.chan1_gain))
file.write(struct.pack('>H', self.chan1_predictive_scale))
file.write(struct.pack('>H', self.chan1_previous_value))
file.write(struct.pack('>H', self.chan1_next_previous_value))
file.write(struct.pack('>H', self.chan1_loop_predictive_scale))
file.write(struct.pack('>H', self.chan1_loop_previous_value))
file.write(struct.pack('>H', self.chan1_loop_next_previous_value))
file.write(struct.pack('>H', self.chan1_loop_padding))
for x in xrange(16):
file.write(struct.pack('>h', self.coefficients2[x]))
file.write(struct.pack('>H', self.chan2_gain))
file.write(struct.pack('>H', self.chan2_predictive_scale))
file.write(struct.pack('>H', self.chan2_previous_value))
file.write(struct.pack('>H', self.chan2_next_previous_value))
file.write(struct.pack('>H', self.chan2_loop_predictive_scale))
file.write(struct.pack('>H', self.chan2_loop_previous_value))
file.write(struct.pack('>H', self.chan2_loop_next_previous_value))
file.write(struct.pack('>H', self.chan2_loop_padding))
elif self.chan_cnt == 1:
for x in xrange(16):
file.write(struct.pack('>h', self.coefficients1[x]))
file.write(struct.pack('>H', self.chan1_gain))
file.write(struct.pack('>H', self.chan1_predictive_scale))
file.write(struct.pack('>H', self.chan1_previous_value))
file.write(struct.pack('>H', self.chan1_next_previous_value))
file.write(struct.pack('>H', self.chan1_loop_predictive_scale))
file.write(struct.pack('>H', self.chan1_loop_previous_value))
file.write(struct.pack('>H', self.chan1_loop_next_previous_value))
file.write(struct.pack('>H', self.chan1_loop_padding))
return
class BNS_header(object):
def __init__(self):
self.magic = "BNS "
self.flags = 0xfeff0100
self.filesize = 0x0004d0c0
self.size = 0x0020
self.chunk_cnt = 0x0002
self.info_off = 0x00000020
self.info_len = 0x000000a0
self.data_off = 0x000000c0
self.data_len = 0x0004d000
def eat(self, buffer, offset):
if struct.unpack('>4s', buffer[offset:offset+4])[0] != "BNS ":
offset += 0x20
self.magic, self.flags = struct.unpack('>4sI', buffer[offset+0:offset+8])
self.filesize, self.size, self.chunk_cnt = struct.unpack('>IHH', buffer[offset+8:offset+16])
self.info_off, self.info_len = struct.unpack('>II', buffer[offset+16:offset+24])
self.data_off, self.data_len = struct.unpack('>II', buffer[offset+24:offset+32])
assert self.magic == "BNS "
assert self.info_off < self.filesize
assert self.data_off < self.filesize
return offset + 32
def show(self):
print "Magic: %s" % self.magic
print "Flags: %08x" % self.flags
print "Length: %08x" % self.filesize
print "Header Size: %04x" % self.size
print "Chunk Count: %04x" % self.chunk_cnt
print "Info Offset: %08x" % self.info_off
print "Info Length: %08x" % self.info_len
print "Data Offset: %08x" % self.data_off
print "Data Length: %08x" % self.data_len
return
def write(self, file):
file.write(self.magic)
file.write(struct.pack('>I', self.flags))
file.write(struct.pack('>I', self.filesize))
file.write(struct.pack('>H', self.size))
file.write(struct.pack('>H', self.chunk_cnt))
file.write(struct.pack('>I', self.info_off))
file.write(struct.pack('>I', self.info_len))
file.write(struct.pack('>I', self.data_off))
file.write(struct.pack('>I', self.data_len))
return
class BNS(object):
def __init__(self):
self.header = BNS_header()
self.info = BNS_info()
self.data = BNS_data()
self.buffered_data = ""
self.lsamps = [ [ 0 , 0 ] , [ 0 , 0 ] ]
self.rlsamps = [ [ 0 , 0 ] , [ 0 , 0 ] ]
self.tlsamps = [ 0 , 0 ]
self.hbc_deftbl = [ 674 , 1040,
3598, -1738,
2270, -583,
3967, -1969,
1516, 381,
3453, -1468,
2606, -617,
3795, -1759 ]
self.deftbl = [ 1820 , -856 ,
3238 , -1514 ,
2333 , -550 ,
3336 , -1376 ,
2444 , -949 ,
3666 , -1764 ,
2654 , -701 ,
3420 , -1398 ]
self.phist1 = [ 0 , 0 ]
self.phist2 = [ 0 , 0 ]
self.errors = 0
def find_exp(self, residual):
exp = 0
while residual>7.5 or residual<-8.5:
exp += 1
residual /= 2.0
return exp
def determine_std_exponent(self, idx, table, index, inbuf):
elsamps = [ 0 , 0 ]
max_res = 0
factor1 = table[2*index+0]
factor2 = table[2*index+1]
for x in xrange(2):
elsamps[x] = self.rlsamps[idx][x]
for i in xrange(14):
predictor = (elsamps[1]*factor1 + elsamps[0]*factor2) >> 11
residual = inbuf[i] - predictor
if residual>max_res:
max_res = residual
elsamps[0] = elsamps[1]
elsamps[1] = inbuf[i]
return self.find_exp(max_res)
def compress_adpcm(self, idx, table, tblidx, inbuf):
data = [0 for i in range(8)]
error = 0
factor1 = table[2*tblidx+0]
factor2 = table[2*tblidx+1]
exp = self.determine_std_exponent(idx, table, tblidx, inbuf)
while exp<=15:
error = 0
data[0] = exp | (tblidx << 4)
for x in xrange(2):
self.tlsamps[x] = self.rlsamps[idx][x]
j = 0
for i in xrange(14):
predictor = (self.tlsamps[1]*factor1 + self.tlsamps[0]*factor2) >> 11
residual = inbuf[i] - predictor
residual = residual >> exp
if residual>7 or residual<-8:
exp += 1
break
nibble = clamp(residual, -8, 7)
if i&1:
data[i/2+1] = data[i/2+1] | (nibble & 0xf)
else:
data[i/2+1] = nibble << 4
predictor = predictor + (nibble << exp)
self.tlsamps[0] = self.tlsamps[1]
self.tlsamps[1] = clamp(predictor, -32768, 32767)
error = error + ((self.tlsamps[1] - inbuf[i]) ** 2)
else:
j = 14
if j == 14:
break
return error, data
def repack_adpcm(self, idx, table, inbuf):
data = [0 for i in range(8)]
blsamps = [ 0 , 0 ]
bestidx = -1
besterror = 999999999.0
for tblidx in xrange(8):
error, testdata = self.compress_adpcm(idx, table, tblidx, inbuf)
if error < besterror:
besterror = error
for x in xrange(8):
data[x] = testdata[x]
for x in xrange(2):
blsamps[x] = self.tlsamps[x]
bestidx = tblidx
for x in xrange(2):
self.rlsamps[idx][x] = blsamps[x]
return data
def encode(self, buffer, offset=0):
sampsbuf = [0 for i in range(14)]
templen = len(buffer)
templen = templen / 4
modlen = templen % 14
for x in xrange(14-modlen):
buffer = buffer + '\x00'
buffer = buffer + '\x00'
buffer = buffer + '\x00'
buffer = buffer + '\x00'
num_samps = len(buffer) / 4
blocks = (num_samps + 13) / 14
snddatal = []
snddatar = []
co = offset
temp = 0
for x in xrange(num_samps):
snddatal.append(Struct.int16(buffer[co:co+2]))
co += 2
snddatar.append(Struct.int16(buffer[co:co+2]))
co += 2
data = [0 for i in range(blocks*16)]
data1_off = 0
data2_off = blocks * 8
self.info.chan2_start = data2_off
for i in xrange(blocks):
for j in xrange(14):
sampsbuf[j] = snddatal[i*14+j]
out_buf = self.repack_adpcm(0, self.deftbl, sampsbuf)
for k in xrange(8):
data[data1_off+k] = out_buf[k]
for j in xrange(14):
sampsbuf[j] = snddatar[i*14+j]
out_buf = self.repack_adpcm(1, self.deftbl, sampsbuf)
for k in xrange(8):
data[data2_off+k] = out_buf[k]
data1_off += 8
data2_off += 8
self.info.loop_end = blocks * 7
return data
def create_bns(self, inbuf, samplerate=44100, channels=2):
self.info.chan_cnt = channels
self.info.samplerate = samplerate
assert samplerate >=32000
self.data.data = ''.join(Struct.int8(p) for p in self.encode(inbuf))
self.data.size = len(self.data.data)
self.header.data_len = self.data.size
self.header.filesize = self.info.size + self.data.size + 8 + self.header.size
self.info.loop_end = self.data.size - (self.data.size / 7)
for x in xrange(16):
self.info.coefficients1[x] = self.deftbl[x]
if self.info.chan_cnt == 2:
for x in xrange(16): self.info.coefficients2[x] = self.deftbl[x]
return
def decode_adpcm(self, index, coefs, buffer):
outbuf = [0 for i in range(14)]
header = Struct.uint8(buffer[0:1], endian='>')
coef_index = (header >> 4) & 0x7
scale = 1 << (header & 0xf)
hist1 = self.phist1[index]
hist2 = self.phist2[index]
coef1 = coefs[coef_index * 2 + 0]
coef2 = coefs[coef_index * 2 + 1]
for x in xrange(14):
sample_byte = Struct.uint8(buffer[x/2+1:x/2+2], endian='>')
if x&1:
nibble = (sample_byte & 0xf0) >> 4
else:
nibble = (sample_byte & 0x0f) >> 0
if nibble >= 8:
nibble -= 16
sample_delta_11 = (scale * nibble) << 11
predicted_sample_11 = coef1*hist1 + coef2*hist2
sample_11 = predicted_sample_11 + sample_delta_11
sample_raw = (sample_11 + 1024) >> 11
sample_raw = clamp(sample_raw, -32768, 32767)
outbuf[x] = sample_raw
hist2 = hist1
hist1 = outbuf[x]
self.phist1[index] = hist1
self.phist2[index] = hist2
return outbuf
def decode(self, buffer, offset):
decoded_buffer = []
if self.info.chan_cnt == 2:
multi = 16
coeff0 = self.info.coefficients1
coeff1 = self.info.coefficients2
elif self.info.chan_cnt == 1:
multi = 8
coeff0 = self.info.coefficients1
coeff1 = self.info.coefficients1
blocks = self.data.size / multi
data1_offset = offset
data2_offset = offset + blocks * 8
decoded_buffer_l = [0 for i in range(blocks * 14)]
decoded_buffer_r = [0 for i in range(blocks * 14)]
for x in xrange(blocks):
out_buffer = self.decode_adpcm(0, coeff0, buffer[data1_offset:data1_offset+8])
for y in xrange(14):
decoded_buffer_l[x*14+y] = out_buffer[y]
out_buffer = self.decode_adpcm(1, coeff1, buffer[data2_offset:data2_offset+8])
for y in xrange(14):
decoded_buffer_r[x*14+y] = out_buffer[y]
data1_offset += 8
data2_offset += 8
for x in xrange(blocks * 14):
decoded_buffer.append(decoded_buffer_l[x])
decoded_buffer.append(decoded_buffer_r[x])
return decoded_buffer
def eat(self, buffer, offset, decode=False):
co = self.header.eat(buffer, offset)
co = self.info.eat(buffer, co)
co = self.data.eat(buffer, co)
self.data.data = buffer[co:]
if decode == True:
buffer_out = self.decode(buffer, co)
return buffer_out
return
def show(self):
self.header.show()
self.info.show()
self.data.show()
return
def write(self, filename):
file = open(filename, 'wb')
if file:
self.header.write(file)
self.info.write(file)
self.data.write(file)
file.close()
else:
print "Could not open file for writing"
return
def main():
if sys.argv[1] == "-d":
file = open(sys.argv[2], 'rb')
if file:
buffer = file.read()
file.close()
else:
print "Could not open file"
sys.exit(2)
bns = BNS()
wavbuffer = bns.eat(buffer, 0x00, True)
wavstring = ''.join(Struct.int16(p) for p in wavbuffer)
f = SoundFile(wavstring, sys.argv[3], bns.info.samplerate)
f.write()
elif sys.argv[1] == "-e":
f = wave.open(sys.argv[2], 'rb')
num_chans = f.getnchannels()
samplerate = f.getframerate()
assert samplerate >= 32000
assert samplerate <= 48000
buffer = f.readframes(f.getnframes())
f.close()
bns = BNS()
bns.create_bns(buffer, samplerate, num_chans)
bns.write(sys.argv[3])
elif sys.argv[1] == "-s":
file = open(sys.argv[2], 'rb')
if file:
buffer = file.read()
file.close()
else:
print "Could not open file"
sys.exit(2)
bns = BNS()
bns.eat(buffer, 0x00, False)
bns.show()
else:
print "Unknown second argument. possiblities are -d and -e"
print "Usage: python bns.py -d <sound.bin> <output.wav>"
print " == OR == "
print " python bns.py -e <input.wav> <sound.bin> "
print " == OR == "
print " python bns.py -s <sound.bin> "
sys.exit(1)
if __name__ == "__main__":
# Import Psyco if available
try:
import psyco
psyco.full()
except ImportError:
print "no psycho import"
if len(sys.argv) == 1:
print "Usage: python bns.py -d <sound.bin> <output.wav>"
print " == OR == "
print " python bns.py -e <input.wav> <sound.bin> "
print " == OR == "
print " python bns.py -s <sound.bin> "
sys.exit(1)
main()

View File

@ -1,4 +1,5 @@
import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array, time, sys, tempfile
import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array, time, sys, tempfile, wave
from cStringIO import StringIO
from Crypto.Cipher import AES
from PIL import Image
@ -9,72 +10,107 @@ def align(x, boundary):
if(x % boundary):
x += (x + boundary) - (x % boundary)
return x
def clamp(var, min, max):
if var < min: var = min
if var > max: var = max
return var
def hexdump(s, sep=" "):
def abs(var):
if var < 0:
var = var + (2 * var)
return var
def hexdump(s, sep=" "): # just dumps hex values
return sep.join(map(lambda x: "%02x" % ord(x), s))
def hexdump2(src, length = 16): # dumps to a "hex editor" style output
result = []
for i in xrange(0, len(src), length):
s = src[i:i + length]
if(len(s) % 4 == 0):
mod = 0
else:
mod = 1
hexa = ''
for j in range((len(s) / 4) + mod):
hexa += ' '.join(["%02X" % ord(x) for x in s[j * 4:j * 4 + 4]])
if(j != ((len(s) / 4) + mod) - 1):
hexa += ' '
printable = s.translate(''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]))
result.append("0x%04X %-*s %s\n" % (i, (length * 3) + 2, hexa, printable))
return ''.join(result)
print hexdump2("RANDOM STRING \x01 TESTING \x214 TEST OF STrasneljkasdhfleasdklhglkaje;shadlkghehaosehlgasdlkfhe;lakhsdglhaelksejdfffffffjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjasdfsadf")
class Crypto:
class Crypto(object):
"""This is a Cryptographic/hash class used to abstract away things (to make changes easier)"""
def __init__(self):
self.align = 64
return
align = 64
@classmethod
def decryptData(self, key, iv, data, align = True):
"""Decrypts some data (aligns to 64 bytes, if needed)."""
if((len(data) % self.align) != 0 and align):
return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
else:
return AES.new(key, AES.MODE_CBC, iv).decrypt(data)
@classmethod
def encryptData(self, key, iv, data, align = True):
"""Encrypts some data (aligns to 64 bytes, if needed)."""
if((len(data) % self.align) != 0 and align):
return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
else:
return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
@classmethod
def decryptContent(self, titlekey, idx, data):
"""Decrypts a Content."""
iv = struct.pack(">H", idx) + "\x00" * 14
return self.decryptData(titlekey, iv, data)
@classmethod
def decryptTitleKey(self, commonkey, tid, enckey):
"""Decrypts a Content."""
iv = struct.pack(">Q", tid) + "\x00" * 8
return self.decryptData(commonkey, iv, enckey, False)
@classmethod
def encryptContent(self, titlekey, idx, data):
"""Encrypts a Content."""
iv = struct.pack(">H", idx) + "\x00" * 14
return self.encryptData(titlekey, iv, data)
@classmethod
def createSHAHash(self, data): #tested WORKING (without padding)
return hashlib.sha1(data).digest()
@classmethod
def createSHAHashHex(self, data):
return hashlib.sha1(data).hexdigest()
@classmethod
def createMD5HashHex(self, data):
return hashlib.md5(data).hexdigest()
@classmethod
def createMD5Hash(self, data):
return hashlib.md5(data).digest()
@classmethod
def validateSHAHash(self, data, hash):
contentHash = hashlib.sha1(data).digest()
print 'Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash))
print 'Expected : %s : len %i' % (hexdump(hash), len(hash))
return 1
if (contentHash == hash):
return 1
else:
#raise ValueError('Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash)) + 'Expected : %s : len %i' % (hexdump(hash), len(hash)))
return 0
class WiiObject(object):
@classmethod
def load(cls, data):
def load(cls, data, *args, **kwargs):
self = cls()
self._load(data)
self._load(data, *args, **kwargs)
return self
@classmethod
def loadFile(cls, filename):
return cls.load(open(filename, "rb").read())
def loadFile(cls, filename, *args, **kwargs):
return cls.load(open(filename, "rb").read(), *args, **kwargs)
def dump(self):
return self._dump()
def dumpFile(self, filename):
open(filename, "wb").write(self.dump())
def dump(self, *args, **kwargs):
return self._dump(*args, **kwargs)
def dumpFile(self, filename, *args, **kwargs):
open(filename, "wb").write(self.dump(*args, **kwargs))
return filename
class WiiArchive(WiiObject):
@ -89,3 +125,15 @@ class WiiArchive(WiiObject):
os.mkdir(dirname)
self._dumpDir(dirname)
return dirname
class WiiHeader(object):
def __init__(self, data):
self.data = data
def addFile(self, filename):
open(filename, "wb").write(self.add())
def removeFile(self, filename):
open(filename, "wb").write(self.remove())
@classmethod
def loadFile(cls, filename, *args, **kwargs):
return cls(open(filename, "rb").read(), *args, **kwargs)

View File

@ -1,6 +1,6 @@
from common import *
class IMD5():
class IMD5(WiiHeader):
"""This class can add and remove IMD5 headers to files. The parameter f is the file to use for the addition or removal of the header. IMD5 headers are found in banner.bin, icon.bin, and sound.bin."""
class IMD5Header(Struct):
__endian__ = Struct.BE
@ -9,29 +9,21 @@ class IMD5():
self.size = Struct.uint32
self.zeroes = Struct.uint8[8]
self.crypto = Struct.string(16)
def __init__(self, f):
self.f = f
def add(self, fn = ""):
"""This function adds an IMD5 header to the file specified by f in the initializer. The output file is specified with fn, if it is empty, it will overwrite the input file. If the file already has an IMD5 header, it will now have two. Returns the output filename."""
data = open(self.f, "rb").read()
def add(self):
data = self.data
imd5 = self.IMD5Header()
for i in range(8):
imd5.zeroes[i] = 0x00
imd5.tag = "IMD5"
imd5.size = len(data)
for i in range(8):
imd5.zeroes[i] = 0x00
imd5.crypto = str(Crypto().createMD5Hash(data))
data = imd5.pack() + data
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
open(self.f, "wb").write(data)
return self.f
def remove(self, fn = ""):
return data
def remove(self):
"""This will remove an IMD5 header from the file specified in f, if one exists. If there is no IMD5 header, it will output the file as it is. It will output in the parameter fn if available, otherwise it will overwrite the source. Returns the output filename."""
data = open(self.f, "rb").read()
data = self.data
imd5 = self.IMD5Header()
if(data[:4] != "IMD5"):
if(fn != ""):
@ -41,14 +33,9 @@ class IMD5():
return self.f
data = data[len(imd5):]
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
open(self.f, "wb").write(data)
return self.f
return data
class IMET():
class IMET(WiiHeader):
"""IMET headers are found in Opening.bnr and 0000000.app files. They contain the channel titles and more metadata about channels. They are in two different formats with different amounts of padding before the start of the IMET header. This class suports both.
The parameter f is used to specify the input file name."""
@ -63,11 +50,9 @@ class IMET():
self.names = Struct.string(84, encoding = "utf-16-be", stripNulls = True)[7]
self.zeroes2 = Struct.uint8[840]
self.hash = Struct.string(16)
def __init__(self, f):
self.f = f
def add(self, iconsz, bannersz, soundsz, name = "", langs = [], fn = ""):
"""This function adds an IMET header to the file specified with f in the initializer. The file will be output to fn if it is not empty, otherwise it will overwrite the input file. You must specify the size of banner.bin in bannersz, and respectivly for iconsz and soundsz. langs is an optional arguement that is a list of different langauge channel titles. name is the english name that is copied everywhere in langs that there is an empty string. Returns the output filename."""
data = open(self.f, "rb").read()
data = self.data
imet = self.IMETHeader()
for i in imet.zeroes:
@ -92,42 +77,24 @@ class IMET():
data = imet.pack() + data
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
open(self.f, "wb").write(data)
return self.f
def remove(self, fn = ""):
"""This method removes an IMET header from a file specified with f in the initializer. fn is the output file name if it isn't an empty string, if it is, it will overwrite the input. If the input has no IMD5 header, it is output as is. Returns the output filename."""
data = open(self.f, "rb").read()
return data
def remove(self):
data = self.data
if(data[0x80:0x84] == "IMET"):
data = data[0x640:]
elif(data[0x40:0x44] == "IMET"):
data = data[0x640:]
else:
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
return self.f
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
open(self.f, "wb").write(data)
return self.f
return data
def getTitle(self):
imet = self.IMETHeader()
data = open(self.f, "rb").read()
data = self.data
if(data[0x40:0x44] == "IMET"):
pass
elif(data[0x80:0x84] == "IMET"):
data = data[0x40:]
else:
return ""
raise ValueError("No IMET header found!")
imet.unpack(data[:len(imet)])
name = imet.names[1]

View File

@ -73,7 +73,7 @@ class TPL():
self.format = Struct.uint32
self.offset = Struct.uint32
def __init__(self, file):
if(os.path.isfile(file)):
if(not ("\x00" in file) and os.path.isfile(file)):
self.file = file
self.data = None
else: