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 compression import *
from nand import * from nand import *
from headers import * from headers import *
from bns import *
if (__name__ == "__main__"): if (__name__ == "__main__"):
Crypto() Crypto()
@ -18,4 +19,4 @@ if (__name__ == "__main__"):
#insert non-dependant check code here #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): def __init__(self):
self.files = [] self.files = []
def _dump(self): 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() header = self.U8Header()
rootnode = self.U8Node() rootnode = self.U8Node()
# constants
header.tag = "U\xAA8-" header.tag = "U\xAA8-"
header.rootnode_offset = 0x20 header.rootnode_offset = 0x20
header.zeroes = "\x00" * 16 header.zeroes = "\x00" * 16
rootnode.type = 0x0100
nodes = [] nodes = []
strings = "\x00" strings = "\x00"
@ -40,37 +39,35 @@ class U8(WiiArchive):
for item, value in self.files: for item, value in self.files:
node = self.U8Node() node = self.U8Node()
node.name_offset = len(strings)
recursion = item.count('/') recursion = item.count('/')
if(recursion < 0): if(recursion < 0):
recursion = 0 recursion = 0
name = item[item.rfind('/') + 1:] name = item[item.rfind('/') + 1:]
node.name_offset = len(strings)
strings += name + '\x00' strings += name + '\x00'
if(value == None): if(value == None):
node.type = 0x0100 node.type = 0x0100
node.data_offset = recursion node.data_offset = recursion
this_length = 0 node.size = len(nodes)
for one, two in self.files: for one, two in self.files:
subdirs = one if(one[:len(item)] == item): # find nodes in the folder
if(subdirs.find(item) != -1): node.size += 1
this_length += 1 node.size += 1
node.size = len(nodes) + this_length + 1
else: else:
sz = len(value) 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) 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.size = sz
node.type = 0x0000 node.type = 0x0000
nodes.append(node) 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) header.data_offset = align(header.header_size + header.rootnode_offset, 64)
rootnode.size = len(nodes) + 1 rootnode.size = len(nodes) + 1
rootnode.type = 0x0100
for i in range(len(nodes)): for i in range(len(nodes)):
if(nodes[i].type == 0x0000): if(nodes[i].type == 0x0000):
@ -155,7 +152,7 @@ class U8(WiiArchive):
elif(node.type == 0): # file elif(node.type == 0): # file
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size])) self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
offset += node.size offset += node.size
else: # unknown else: # unknown type -- wtf?
pass pass
sz = recursion.pop() sz = recursion.pop()
@ -178,7 +175,14 @@ class U8(WiiArchive):
def __getitem__(self, key): def __getitem__(self, key):
for item, val in self.files: for item, val in self.files:
if(item == key): 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 raise KeyError
def __setitem__(self, key, val): def __setitem__(self, key, val):
for i in range(len(self.files)): for i in range(len(self.files)):
@ -188,49 +192,103 @@ class U8(WiiArchive):
self.files.append((key, val)) self.files.append((key, val))
class WAD: class WAD(WiiArchive):
"""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. def __init__(self, boot2 = False):
self.tmd = TMD()
WAD packing support currently creates WAD files that return -4100 on install.""" self.tik = Ticket()
def __init__(self, f, boot2 = False): self.contents = []
self.f = f self.boot2 = False
self.boot2 = boot2 self.cert = ""
def pack(self, fn = "", fakesign = True, decrypted = True): def _load(self, data):
"""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.""" if(self.boot2 != True):
os.chdir(self.f) 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") titlekey = self.tik.getTitleKey()
tmd = TMD.loadFile("tmd") contents = self.tmd.getContents()
titlekey = tik.getTitleKey() for i in range(0, len(contents)):
contents = tmd.getContents() 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 = "" apppack = ""
for content in contents: for i, content in enumerate(contents):
tmpdata = open("%08x.app" % content.index, "rb").read() if(fakesign):
content.hash = str(Crypto().createSHAHash(self.contents[content.index]))
content.size = len(self.contents[content.index])
if(decrypted): encdata = Crypto().encryptContent(titlekey, content.index, self.contents[content.index])
if(fakesign):
content.hash = str(Crypto().createSHAHash(tmpdata))
content.size = len(tmpdata)
encdata = Crypto().encryptContent(titlekey, content.index, tmpdata)
else:
encdata = tmpdata
apppack += encdata apppack += encdata
if(len(encdata) % 64 != 0): if(len(encdata) % 64 != 0):
apppack += "\x00" * (64 - (len(encdata) % 64)) apppack += "\x00" * (64 - (len(encdata) % 64))
if(fakesign): if(fakesign):
tmd.setContents(contents) self.tmd.setContents(contents)
tmd.fakesign() self.tmd.fakesign()
tik.fakesign() self.tik.fakesign()
tmd.dumpFile("tmd")
tik.dumpFile("tik")
rawtmd = open("tmd", "rb").read() rawtmd = self.tmd.dump()
rawcert = open("cert", "rb").read() rawcert = self.cert
rawtik = open("tik", "rb").read() rawtik = self.tik.dump()
sz = 0 sz = 0
for i in range(len(contents)): 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 += "\x00" * (align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40) - (len(rawcert) + len(rawtik) + len(rawtmd)))
pack += apppack pack += apppack
return pack
os.chdir('..') def __getitem__(self, idx):
if(fn == ""): return self.contents[idx]
if(self.f[len(self.f) - 4:] == "_out"): def __setitem__(self, idx, value):
fn = os.path.dirname(self.f) + "/" + os.path.basename(self.f)[:len(os.path.basename(self.f)) - 4].replace("_", ".") self.contents[idx] = value
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
def __str__(self): def __str__(self):
out = "" out = ""
out += "Wii WAD:\n" out += "Wii WAD:\n"
fd = open(self.f, 'rb') out += str(self.tmd)
out += str(self.tik)
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))
return out return out
@ -459,4 +441,14 @@ class CCF():
output.close() output.close()
currentOffset += len(fileEntry) 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 Crypto.Cipher import AES
from PIL import Image from PIL import Image
@ -9,72 +10,107 @@ def align(x, boundary):
if(x % boundary): if(x % boundary):
x += (x + boundary) - (x % boundary) x += (x + boundary) - (x % boundary)
return x 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)) 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)""" """This is a Cryptographic/hash class used to abstract away things (to make changes easier)"""
def __init__(self): align = 64
self.align = 64 @classmethod
return
def decryptData(self, key, iv, data, align = True): def decryptData(self, key, iv, data, align = True):
"""Decrypts some data (aligns to 64 bytes, if needed).""" """Decrypts some data (aligns to 64 bytes, if needed)."""
if((len(data) % self.align) != 0 and align): 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)))) return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
else: else:
return AES.new(key, AES.MODE_CBC, iv).decrypt(data) return AES.new(key, AES.MODE_CBC, iv).decrypt(data)
@classmethod
def encryptData(self, key, iv, data, align = True): def encryptData(self, key, iv, data, align = True):
"""Encrypts some data (aligns to 64 bytes, if needed).""" """Encrypts some data (aligns to 64 bytes, if needed)."""
if((len(data) % self.align) != 0 and align): 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)))) return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
else: else:
return AES.new(key, AES.MODE_CBC, iv).encrypt(data) return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
@classmethod
def decryptContent(self, titlekey, idx, data): def decryptContent(self, titlekey, idx, data):
"""Decrypts a Content.""" """Decrypts a Content."""
iv = struct.pack(">H", idx) + "\x00" * 14 iv = struct.pack(">H", idx) + "\x00" * 14
return self.decryptData(titlekey, iv, data) return self.decryptData(titlekey, iv, data)
@classmethod
def decryptTitleKey(self, commonkey, tid, enckey): def decryptTitleKey(self, commonkey, tid, enckey):
"""Decrypts a Content.""" """Decrypts a Content."""
iv = struct.pack(">Q", tid) + "\x00" * 8 iv = struct.pack(">Q", tid) + "\x00" * 8
return self.decryptData(commonkey, iv, enckey, False) return self.decryptData(commonkey, iv, enckey, False)
@classmethod
def encryptContent(self, titlekey, idx, data): def encryptContent(self, titlekey, idx, data):
"""Encrypts a Content.""" """Encrypts a Content."""
iv = struct.pack(">H", idx) + "\x00" * 14 iv = struct.pack(">H", idx) + "\x00" * 14
return self.encryptData(titlekey, iv, data) return self.encryptData(titlekey, iv, data)
@classmethod
def createSHAHash(self, data): #tested WORKING (without padding) def createSHAHash(self, data): #tested WORKING (without padding)
return hashlib.sha1(data).digest() return hashlib.sha1(data).digest()
@classmethod
def createSHAHashHex(self, data): def createSHAHashHex(self, data):
return hashlib.sha1(data).hexdigest() return hashlib.sha1(data).hexdigest()
@classmethod
def createMD5HashHex(self, data): def createMD5HashHex(self, data):
return hashlib.md5(data).hexdigest() return hashlib.md5(data).hexdigest()
@classmethod
def createMD5Hash(self, data): def createMD5Hash(self, data):
return hashlib.md5(data).digest() return hashlib.md5(data).digest()
@classmethod
def validateSHAHash(self, data, hash): def validateSHAHash(self, data, hash):
contentHash = hashlib.sha1(data).digest() contentHash = hashlib.sha1(data).digest()
return 1
print 'Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash))
print 'Expected : %s : len %i' % (hexdump(hash), len(hash))
if (contentHash == hash): if (contentHash == hash):
return 1 return 1
else: else:
#raise ValueError('Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash)) + 'Expected : %s : len %i' % (hexdump(hash), len(hash)))
return 0 return 0
class WiiObject(object): class WiiObject(object):
@classmethod @classmethod
def load(cls, data): def load(cls, data, *args, **kwargs):
self = cls() self = cls()
self._load(data) self._load(data, *args, **kwargs)
return self return self
@classmethod @classmethod
def loadFile(cls, filename): def loadFile(cls, filename, *args, **kwargs):
return cls.load(open(filename, "rb").read()) return cls.load(open(filename, "rb").read(), *args, **kwargs)
def dump(self): def dump(self, *args, **kwargs):
return self._dump() return self._dump(*args, **kwargs)
def dumpFile(self, filename): def dumpFile(self, filename, *args, **kwargs):
open(filename, "wb").write(self.dump()) open(filename, "wb").write(self.dump(*args, **kwargs))
return filename return filename
class WiiArchive(WiiObject): class WiiArchive(WiiObject):
@ -89,3 +125,15 @@ class WiiArchive(WiiObject):
os.mkdir(dirname) os.mkdir(dirname)
self._dumpDir(dirname) self._dumpDir(dirname)
return 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 * 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.""" """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): class IMD5Header(Struct):
__endian__ = Struct.BE __endian__ = Struct.BE
@ -9,29 +9,21 @@ class IMD5():
self.size = Struct.uint32 self.size = Struct.uint32
self.zeroes = Struct.uint8[8] self.zeroes = Struct.uint8[8]
self.crypto = Struct.string(16) self.crypto = Struct.string(16)
def __init__(self, f): def add(self):
self.f = f data = self.data
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()
imd5 = self.IMD5Header() imd5 = self.IMD5Header()
for i in range(8):
imd5.zeroes[i] = 0x00
imd5.tag = "IMD5" imd5.tag = "IMD5"
imd5.size = len(data) imd5.size = len(data)
for i in range(8):
imd5.zeroes[i] = 0x00
imd5.crypto = str(Crypto().createMD5Hash(data)) imd5.crypto = str(Crypto().createMD5Hash(data))
data = imd5.pack() + data data = imd5.pack() + data
if(fn != ""): return data
open(fn, "wb").write(data) def remove(self):
return fn
else:
open(self.f, "wb").write(data)
return self.f
def remove(self, fn = ""):
"""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.""" """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() imd5 = self.IMD5Header()
if(data[:4] != "IMD5"): if(data[:4] != "IMD5"):
if(fn != ""): if(fn != ""):
@ -41,14 +33,9 @@ class IMD5():
return self.f return self.f
data = data[len(imd5):] data = data[len(imd5):]
if(fn != ""): return data
open(fn, "wb").write(data)
return fn
else:
open(self.f, "wb").write(data)
return self.f
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. """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.""" 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.names = Struct.string(84, encoding = "utf-16-be", stripNulls = True)[7]
self.zeroes2 = Struct.uint8[840] self.zeroes2 = Struct.uint8[840]
self.hash = Struct.string(16) self.hash = Struct.string(16)
def __init__(self, f):
self.f = f
def add(self, iconsz, bannersz, soundsz, name = "", langs = [], fn = ""): 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.""" """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() imet = self.IMETHeader()
for i in imet.zeroes: for i in imet.zeroes:
@ -92,42 +77,24 @@ class IMET():
data = imet.pack() + data data = imet.pack() + data
if(fn != ""): return data
open(fn, "wb").write(data) def remove(self):
return fn data = self.data
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()
if(data[0x80:0x84] == "IMET"): if(data[0x80:0x84] == "IMET"):
data = data[0x640:] data = data[0x640:]
elif(data[0x40:0x44] == "IMET"): elif(data[0x40:0x44] == "IMET"):
data = data[0x640:] data = data[0x640:]
else: return data
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
def getTitle(self): def getTitle(self):
imet = self.IMETHeader() imet = self.IMETHeader()
data = open(self.f, "rb").read() data = self.data
if(data[0x40:0x44] == "IMET"): if(data[0x40:0x44] == "IMET"):
pass pass
elif(data[0x80:0x84] == "IMET"): elif(data[0x80:0x84] == "IMET"):
data = data[0x40:] data = data[0x40:]
else: else:
return "" raise ValueError("No IMET header found!")
imet.unpack(data[:len(imet)]) imet.unpack(data[:len(imet)])
name = imet.names[1] name = imet.names[1]

View File

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