mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 14:55:35 -04:00
lots of accumulated fixes. BNS now is included in main wii.py :D
This commit is contained in:
parent
20e8c665a6
commit
c65d1e23b7
3
Wii.py
3
Wii.py
@ -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")
|
||||
|
254
archive.py
254
archive.py
@ -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
618
bns.py
Normal 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()
|
||||
|
84
common.py
84
common.py
@ -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)
|
||||
|
||||
|
67
headers.py
67
headers.py
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user