mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 23:05:48 -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 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")
|
||||||
|
250
archive.py
250
archive.py
@ -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()
|
||||||
|
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
|
||||||
|
|
||||||
WAD packing support currently creates WAD files that return -4100 on install."""
|
rawcert = data[pos:pos + certsize]
|
||||||
def __init__(self, f, boot2 = False):
|
pos += certsize
|
||||||
self.f = f
|
if(self.boot2 != True):
|
||||||
self.boot2 = boot2
|
if(certsize % 64 != 0):
|
||||||
def pack(self, fn = "", fakesign = True, decrypted = True):
|
pos += 64 - (certsize % 64)
|
||||||
"""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."""
|
self.cert = rawcert
|
||||||
os.chdir(self.f)
|
|
||||||
|
|
||||||
tik = Ticket.loadFile("tik")
|
rawtik = data[pos:pos + tiksize]
|
||||||
tmd = TMD.loadFile("tmd")
|
pos += tiksize
|
||||||
titlekey = tik.getTitleKey()
|
if(self.boot2 != True):
|
||||||
contents = tmd.getContents()
|
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)
|
||||||
|
|
||||||
|
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 = ""
|
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
|
||||||
|
|
||||||
|
|
||||||
@ -460,3 +442,13 @@ class CCF():
|
|||||||
|
|
||||||
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
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 Crypto.Cipher import AES
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@ -10,71 +11,106 @@ def align(x, boundary):
|
|||||||
x += (x + boundary) - (x % boundary)
|
x += (x + boundary) - (x % boundary)
|
||||||
return x
|
return x
|
||||||
|
|
||||||
def hexdump(s, sep=" "):
|
def clamp(var, min, max):
|
||||||
|
if var < min: var = min
|
||||||
|
if var > max: var = max
|
||||||
|
return var
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
class Crypto:
|
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(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)
|
||||||
|
|
||||||
|
67
headers.py
67
headers.py
@ -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]
|
||||||
|
2
image.py
2
image.py
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user