restructured layout of all code. archive.py is for all "archive" files, or files that contain other files. export.py is for stuff the wii spits onto SD (savegames, content.bin, etc). image.py is just TPL for now, but it can be expanded. headers.py has IMD5 and IMET. title.py is just NUS, Ticket, and TMD now. formats.py contains general other file formats. compression.py is JUST for compression (LZ77, ASH0, yaz0, yay0, etc). Ticket and TMD now use the "new" handling code for load-and-dumpable files - you do TMD() for a blank tmd, TMD().load(data) to load from data, TMD.loadFile(filename) to load from a filename. I think thats all

This commit is contained in:
Xuzz 2009-06-30 00:25:23 -07:00
parent 9b8368270a
commit 1ca063a8fc
12 changed files with 1887 additions and 461 deletions

25
Wii.py
View File

@ -1,14 +1,21 @@
__all__ = []
from compression import *
from savedata import *
from banner import *
from disc import *
from nand import *
from common import *
from formats import *
from banner import *
from title import *
from TPL import *
from U8 import *
from disc import *
from image import *
from archive import *
from export import *
from compression import *
from nand import *
if (__name__ == "__main__"):
print ("\nAll components loaded sucessfully!\n\n")
Crypto()
TMD()
Ticket()
#insert non-dependant check code here
print ("\nAll Wii.py components loaded sucessfully!\n")

501
archive.py Normal file
View File

@ -0,0 +1,501 @@
from common import *
from title import *
import zlib
class U8():
"""This class can unpack and pack U8 archives, which are used all over the Wii. They are often used in Banners and contents in Downloadable Titles. Please remove all headers and compression first, kthx.
The f parameter is either the source folder to pack, or the source file to unpack."""
class U8Header(Struct):
__endian__ = Struct.BE
def __format__(self):
self.tag = Struct.string(4)
self.rootnode_offset = Struct.uint32
self.header_size = Struct.uint32
self.data_offset = Struct.uint32
self.zeroes = Struct.string(16)
class U8Node(Struct):
__endian__ = Struct.BE
def __format__(self):
self.type = Struct.uint16
self.name_offset = Struct.uint16
self.data_offset = Struct.uint32
self.size = Struct.uint32
def __init__(self, f):
self.f = f
def _pack(self, file, recursion, is_root = 0): #internal
node = self.U8Node()
node.name_offset = len(self.strings)
if(is_root != 1):
self.strings += (file)
self.strings += ("\x00")
if(os.path.isdir(file)):
node.type = 0x0100
node.data_offset = recursion - 1
recursion += 1
files = sorted(os.listdir(file))
#if(sorted(files) == ["banner.bin", "icon.bin", "sound.bin"]):
# files = ["icon.bin", "banner.bin", "sound.bin"]
oldsz = len(self.nodes)
if(is_root != 1):
self.nodes.append(node)
os.chdir(file)
for entry in files:
if(entry != ".DS_Store" and entry[len(entry) - 4:] != "_out"):
self._pack(entry, recursion)
os.chdir("..")
self.nodes[oldsz].size = len(self.nodes) + 1
else:
f = open(file, "rb")
data = f.read()
f.close()
sz = len(data)
data += "\x00" * (align(sz, 32) - sz) #32 seems to work best for fuzzyness? I'm still really not sure
node.data_offset = len(self.data)
self.data += data
node.size = sz
node.type = 0x0000
if(is_root != 1):
self.nodes.append(node)
def pack(self, fn = ""):
"""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()
header.tag = "U\xAA8-"
header.rootnode_offset = 0x20
header.zeroes = "\x00" * 16
self.nodes = []
self.strings = "\x00"
self.data = ""
origdir = os.getcwd()
os.chdir(self.f)
self._pack(".", 0, 1)
os.chdir(origdir)
header.header_size = (len(self.nodes) + 1) * len(rootnode) + len(self.strings)
header.data_offset = align(header.header_size + header.rootnode_offset, 64)
rootnode.size = len(self.nodes) + 1
rootnode.type = 0x0100
for i in range(len(self.nodes)):
if(self.nodes[i].type == 0x0000):
self.nodes[i].data_offset += header.data_offset
if(fn == ""):
if(self.f[len(self.f) - 4:] == "_out"):
fn = os.path.dirname(self.f) + "/" + os.path.basename(self.f)[:-4].replace("_", ".")
else:
fn = self.f
fd = open(fn, "wb")
fd.write(header.pack())
fd.write(rootnode.pack())
for node in self.nodes:
fd.write(node.pack())
fd.write(self.strings)
fd.write("\x00" * (header.data_offset - header.rootnode_offset - header.header_size))
fd.write(self.data)
fd.close()
return fn
def unpack(self, fn = ""):
"""This will unpack the U8 archive specified by the initialization parameter into either the folder specified by the parameter fn, or into a folder created with this formula:
``filename_extension_out``
This will recreate the directory structure, including the initial root folder in the U8 archive (such as "arc" or "meta"). Returns the output directory name."""
data = open(self.f, "rb").read()
offset = 0
header = self.U8Header()
header.unpack(data[offset:offset + len(header)])
offset += len(header)
if(header.tag != "U\xAA8-"):
raise NameError("Bad U8 Tag")
offset = header.rootnode_offset
rootnode = self.U8Node()
rootnode.unpack(data[offset:offset + len(rootnode)])
offset += len(rootnode)
nodes = []
for i in range(rootnode.size - 1):
node = self.U8Node()
node.unpack(data[offset:offset + len(node)])
offset += len(node)
nodes.append(node)
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
offset += len(strings)
if(fn == ""):
fn = os.path.dirname(self.f) + "/" + os.path.basename(self.f).replace(".", "_") + "_out"
try:
origdir = os.getcwd()
os.mkdir(fn)
except:
pass
os.chdir(fn)
recursion = [rootnode.size]
counter = 0
for node in nodes:
counter += 1
name = strings[node.name_offset:].split('\0', 1)[0]
if(node.type == 0x0100): #folder
recursion.append(node.size)
try:
os.mkdir(name)
except:
pass
os.chdir(name)
continue
elif(node.type == 0): #file
file = open(name, "wb")
file.write(data[node.data_offset:node.data_offset + node.size])
offset += node.size
else: #unknown
pass #ignore
sz = recursion.pop()
if(sz == counter + 1):
os.chdir("..")
else:
recursion.append(sz)
os.chdir("..")
os.chdir(origdir)
return fn
def __str__(self):
data = open(self.f, "rb").read()
offset = 0
header = self.U8Header()
header.unpack(data[offset:offset + len(header)])
offset += len(header)
if(header.tag != "U\xAA8-"):
raise NameError("Bad U8 Tag")
offset = header.rootnode_offset
rootnode = self.U8Node()
rootnode.unpack(data[offset:offset + len(rootnode)])
offset += len(rootnode)
nodes = []
for i in xrange(rootnode.size - 1):
node = self.U8Node()
node.unpack(data[offset:offset + len(node)])
offset += len(node)
nodes.append(node)
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
offset += len(strings)
out = "[root]\n"
recursion = [rootnode.size]
counter = 0
for node in nodes:
counter += 1
name = strings[node.name_offset:].split('\0', 1)[0]
out += " " * len(recursion)
if(node.type == 0x0100): #folder
recursion.append(node.size)
out += "[%s]\n" % name
continue
elif(node.type == 0): #file
out += "%s\n" % name
offset += node.size
else: #unknown, ignore
pass
sz = recursion.pop()
if(sz == counter + 1):
pass
else:
recursion.append(sz)
return out
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)
tik = Ticket("tik")
tmd = TMD("tmd")
titlekey = tik.getTitleKey()
contents = tmd.getContents()
apppack = ""
for content in contents:
tmpdata = open("%08x.app" % content.index, "rb").read()
if(decrypted):
if(fakesign):
content.hash = str(Crypto().createSHAHash(tmpdata))
content.size = len(tmpdata)
encdata = Crypto().encryptContent(titlekey, content.index, tmpdata)
else:
encdata = tmpdata
apppack += encdata
if(len(encdata) % 64 != 0):
apppack += "\x00" * (64 - (len(encdata) % 64))
if(fakesign):
tmd.setContents(contents)
tmd.dump()
tik.dump()
rawtmd = open("tmd", "rb").read()
rawcert = open("cert", "rb").read()
rawtik = open("tik", "rb").read()
sz = 0
for i in range(len(contents)):
sz += contents[i].size
if(sz % 64 != 0):
sz += 64 - (contents[i].size % 64)
if(self.boot2 != True):
pack = struct.pack('>I4s6I', 32, "Is\x00\x00", len(rawcert), 0, len(rawtik), len(rawtmd), sz, 0)
pack += "\x00" * 32
else:
pack = struct.pack('>IIIII12s', 32, align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40), len(rawcert), len(rawtik), len(rawtmd), "\x00" * 12)
pack += rawcert
if(len(rawcert) % 64 != 0 and self.boot2 != True):
pack += "\x00" * (64 - (len(rawcert) % 64))
pack += rawtik
if(len(rawtik) % 64 != 0 and self.boot2 != True):
pack += "\x00" * (64 - (len(rawtik) % 64))
pack += rawtmd
if(len(rawtmd) % 64 != 0 and self.boot2 != True):
pack += "\x00" * (64 - (len(rawtmd) % 64))
if(self.boot2 == True):
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("tik").getTitleKey()
contents = TMD("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):
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(rawtik))
out += str(TMD(rawtmd))
return out
class CCF():
class CCFHeader(Struct):
__endian__ = Struct.LE
def __format__(self):
self.magic = Struct.string(4)
self.zeroes12 = Struct.string(12)
self.rootOffset = Struct.uint32
self.filesCount = Struct.uint32
self.zeroes8 = Struct.string(8)
class CCFFile(Struct):
__endian__ = Struct.LE
def __format__(self):
self.fileName = Struct.string(20)
self.fileOffset = Struct.uint32
self.fileSize = Struct.uint32
self.fileSizeDecompressed = Struct.uint32
def __init__(self, fileName):
self.fileName = fileName
self.fd = open(fileName, 'r+b')
def compress(self, folder):
fileList = []
fileHdr = self.CCFHeader()
files = os.listdir(folder)
fileHdr.magic = "\x43\x43\x46\x00"
fileHdr.zeroes12 = '\x00' * 12
fileHdr.rootOffset = 0x20
fileHdr.zeroes8 = '\x00' * 8
currentOffset = len(fileHdr)
packedFiles = 0
previousFileEndOffset = 0
for file in files:
if os.path.isdir(folder + '/' + file) or file == '.DS_Store':
continue
else:
fileList.append(file)
fileHdr.filesCount = len(fileList)
self.fd.write(fileHdr.pack())
self.fd.write('\x00' * (fileHdr.filesCount * len(self.CCFFile())))
for fileNumber in range(len(fileList)):
fileEntry = self.CCFFile()
compressedBuffer = zlib.compress(open(folder + '/' + fileList[fileNumber]).read())
fileEntry.fileName = fileList[fileNumber]
fileEntry.fileSize = len(compressedBuffer)
fileEntry.fileSizeDecompressed = os.stat(folder + '/' + fileList[fileNumber]).st_size
fileEntry.fileOffset = align(self.fd.tell(), 32) / 32
print 'File {0} ({1}Kb) placed at offset 0x{2:X}'.format(fileEntry.fileName, fileEntry.fileSize / 1024, fileEntry.fileOffset * 32)
self.fd.seek(len(fileHdr) + fileNumber * len(self.CCFFile()))
self.fd.write(fileEntry.pack())
self.fd.seek(fileEntry.fileOffset * 32)
self.fd.write(compressedBuffer)
self.fd.close()
def decompress(self):
fileHdr = self.CCFHeader()
hdrData = self.fd.read(len(fileHdr))
fileHdr.unpack(hdrData)
print 'Found {0} file/s and root node at 0x{1:X}'.format(fileHdr.filesCount, fileHdr.rootOffset)
if fileHdr.magic != "\x43\x43\x46\x00":
raise ValueError("Wrong magic, 0x{0}".format(fileHdr.magic))
try:
os.mkdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
except:
pass
os.chdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
currentOffset = len(fileHdr)
for x in range(fileHdr.filesCount):
self.fd.seek(currentOffset)
fileEntry = self.CCFFile()
fileData = self.fd.read(len(fileEntry))
fileEntry.unpack(fileData)
fileEntry.fileOffset = fileEntry.fileOffset * 32
print 'File {0} at offset 0x{1:X}'.format(fileEntry.fileName, fileEntry.fileOffset)
print 'File size {0}Kb ({1}Kb decompressed)'.format(fileEntry.fileSize / 1024, fileEntry.fileSizeDecompressed / 1024)
output = open(fileEntry.fileName.rstrip('\0'), 'w+b')
self.fd.seek(fileEntry.fileOffset)
if fileEntry.fileSize == fileEntry.fileSizeDecompressed:
print 'The file is stored uncompressed'
output.write(self.fd.read(fileEntry.fileSize))
else:
print 'The file is stored compressed..decompressing'
decompressedBuffer = zlib.decompress(self.fd.read(fileEntry.fileSize))
output.write(decompressedBuffer)
output.close()
currentOffset += len(fileEntry)

View File

@ -1,14 +1,14 @@
import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array
import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array, time, sys, tempfile
from Crypto.Cipher import AES
from PIL import Image
from Struct import Struct
def align(x, boundary):
if(x % boundary == 0):
return x
else:
return (x + boundary) - (x % boundary)
if(x % boundary):
x += (x + boundary) - (x % boundary)
return x
def hexdump(s, sep=" "):
return sep.join(map(lambda x: "%02x" % ord(x), s))

View File

@ -1,123 +1,4 @@
import os, struct, subprocess, fnmatch, shutil, urllib, array
import zlib
from Struct import Struct
from common import *
class CCF():
class CCFHeader(Struct):
__endian__ = Struct.LE
def __format__(self):
self.magic = Struct.string(4)
self.zeroes12 = Struct.string(12)
self.rootOffset = Struct.uint32
self.filesCount = Struct.uint32
self.zeroes8 = Struct.string(8)
class CCFFile(Struct):
__endian__ = Struct.LE
def __format__(self):
self.fileName = Struct.string(20)
self.fileOffset = Struct.uint32
self.fileSize = Struct.uint32
self.fileSizeDecompressed = Struct.uint32
def __init__(self, fileName):
self.fileName = fileName
self.fd = open(fileName, 'r+b')
def compress(self, folder):
fileList = []
fileHdr = self.CCFHeader()
files = os.listdir(folder)
fileHdr.magic = "\x43\x43\x46\x00"
fileHdr.zeroes12 = '\x00' * 12
fileHdr.rootOffset = 0x20
fileHdr.zeroes8 = '\x00' * 8
currentOffset = len(fileHdr)
packedFiles = 0
previousFileEndOffset = 0
for file in files:
if os.path.isdir(folder + '/' + file) or file == '.DS_Store':
continue
else:
fileList.append(file)
fileHdr.filesCount = len(fileList)
self.fd.write(fileHdr.pack())
self.fd.write('\x00' * (fileHdr.filesCount * len(self.CCFFile())))
for fileNumber in range(len(fileList)):
fileEntry = self.CCFFile()
compressedBuffer = zlib.compress(open(folder + '/' + fileList[fileNumber]).read())
fileEntry.fileName = fileList[fileNumber]
fileEntry.fileSize = len(compressedBuffer)
fileEntry.fileSizeDecompressed = os.stat(folder + '/' + fileList[fileNumber]).st_size
fileEntry.fileOffset = align(self.fd.tell(), 32) / 32
print 'File {0} ({1}Kb) placed at offset 0x{2:X}'.format(fileEntry.fileName, fileEntry.fileSize / 1024, fileEntry.fileOffset * 32)
self.fd.seek(len(fileHdr) + fileNumber * len(self.CCFFile()))
self.fd.write(fileEntry.pack())
self.fd.seek(fileEntry.fileOffset * 32)
self.fd.write(compressedBuffer)
self.fd.close()
def decompress(self):
fileHdr = self.CCFHeader()
hdrData = self.fd.read(len(fileHdr))
fileHdr.unpack(hdrData)
print 'Found {0} file/s and root node at 0x{1:X}'.format(fileHdr.filesCount, fileHdr.rootOffset)
if fileHdr.magic != "\x43\x43\x46\x00":
raise ValueError("Wrong magic, 0x{0}".format(fileHdr.magic))
try:
os.mkdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
except:
pass
os.chdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
currentOffset = len(fileHdr)
for x in range(fileHdr.filesCount):
self.fd.seek(currentOffset)
fileEntry = self.CCFFile()
fileData = self.fd.read(len(fileEntry))
fileEntry.unpack(fileData)
fileEntry.fileOffset = fileEntry.fileOffset * 32
print 'File {0} at offset 0x{1:X}'.format(fileEntry.fileName, fileEntry.fileOffset)
print 'File size {0}Kb ({1}Kb decompressed)'.format(fileEntry.fileSize / 1024, fileEntry.fileSizeDecompressed / 1024)
output = open(fileEntry.fileName.rstrip('\0'), 'w+b')
self.fd.seek(fileEntry.fileOffset)
if fileEntry.fileSize == fileEntry.fileSizeDecompressed:
print 'The file is stored uncompressed'
output.write(self.fd.read(fileEntry.fileSize))
else:
print 'The file is stored compressed..decompressing'
decompressedBuffer = zlib.decompress(self.fd.read(fileEntry.fileSize))
output.write(decompressedBuffer)
output.close()
currentOffset += len(fileEntry)
class LZ77():
class WiiLZ77: #class by marcan

View File

@ -1,11 +1,5 @@
import os, struct, subprocess, fnmatch, shutil, urllib, array
import time
from title import *
from Struct import Struct
from common import *
from title import *
class WOD: #WiiOpticalDisc
class discHeader(Struct):

View File

@ -15,6 +15,7 @@ class BREFF(object):
class BREFF_REFF_Section2(Struct):
__endian__ = Struct.BE
def __format__(self):
self.unknown00 = Struct.uint32
self.unknown01 = Struct.uint32
self.unknown02 = Struct.uint32
self.unk03p1 = Struct.uint16
@ -23,7 +24,7 @@ class BREFF(object):
self.unk04p2 = Struct.uint16
self.unknown05 = Struct.uint32
self.unknown06 = Struct.float
self.unknown07 = Struct.uint32
self.unknown07 = Struct.float
self.unknown08 = Struct.uint32
self.unknown09 = Struct.uint32
self.unknown10 = Struct.float
@ -98,19 +99,19 @@ class BREFF(object):
self.unknown78 = Struct.uint32
self.unknown79 = Struct.uint32
self.unknown80 = Struct.uint32
self.unknown81 = Struct.uint32
self.unknown82 = Struct.uint32
self.unknown83 = Struct.uint32
self.unknown84 = Struct.uint32
self.unknown81 = Struct.float
self.unknown82 = Struct.float
self.unknown83 = Struct.float
self.unknown84 = Struct.float
self.unknown85 = Struct.uint32
self.unknown86 = Struct.uint32
self.unknown87 = Struct.uint32
self.unknown88 = Struct.uint32
self.unknown89 = Struct.uint32
self.unknown90 = Struct.uint32
self.unknown91 = Struct.uint32
self.unknown92 = Struct.uint32
self.unknown93 = Struct.uint32
self.unknown88 = Struct.float
self.unknown89 = Struct.float
self.unknown90 = Struct.float
self.unknown91 = Struct.float
self.unknown92 = Struct.float
self.unknown93 = Struct.float
self.unknown94 = Struct.uint32
self.unknown95 = Struct.uint32
self.unknown96 = Struct.uint32
@ -129,49 +130,49 @@ class BREFF(object):
self.unknownA9 = Struct.uint32
self.unknownB0 = Struct.uint32
self.unknownB1 = Struct.uint32
self.unknownB2 = Struct.uint32
def __str__(self):
return_string = "Unknown01: %08x\talways 00000128 ?\n" % self.unknown01
return_string = "Unknown00: %08x\n" % self.unknown00
return_string += "Unknown01: %08x\talways 00000128 ?\n" % self.unknown01
return_string += "Unknown02: %08x\talways 80000xxx ?\n" % self.unknown02
return_string += "Unknown03: %04x\t%04x\n" % (self.unk03p1 , self.unk03p2)
return_string += "Unknown04: %.2d\t%04x\n" % (self.unk04p1 , self.unk04p2)
return_string += "Unknown05: %08x\n" % self.unknown05
return_string += "Unknown06: %.9f\n" % self.unknown06
return_string += "Unknown07: %08x\n" % self.unknown07
return_string += "Unknown07: %f\n" % self.unknown07
return_string += "Unknown08: %08x\n" % self.unknown08
return_string += "Unknown09: %08x\n" % self.unknown09
return_string += "Unknown10: %.9f\n" % self.unknown10
return_string += "Unknown11: %.9f\n" % self.unknown11
return_string += "Unknown12: %08x\n" % self.unknown12
return_string += "Unknown13: %.9f\n" % self.unknown13
return_string += "Size Outer Radius X: %.9f\n" % self.unknown10
return_string += "Size Outer Radius Y: %.9f\n" % self.unknown11
return_string += "Size Outer Radius Z: %.9f\n" % self.unknown12
return_string += "Inner Radius: %.9f\n" % self.unknown13
return_string += "Unknown14: %08x\n" % self.unknown14
return_string += "Unknown15: %08x\n" % self.unknown15
return_string += "Unknown16: %04x\t%04x\n" % (self.unk16p1, self.unk16p2)
return_string += "Unknown17: %.9f\n" % self.unknown17
return_string += "Unknown18: %.9f\n" % self.unknown18
return_string += "Unknown19: %08x\n" % self.unknown19
return_string += "Unknown20: %.9f\n" % self.unknown20
return_string += "All Direction Speed: %.9f\n" % self.unknown17
return_string += "Y Axis Difuse Speed: %.9f\n" % self.unknown18
return_string += "Random Direction Speed: %.9f\n" % self.unknown19
return_string += "Normal Direction Speed: %.9f\n" % self.unknown20
return_string += "Unknown21: %.9f\n" % self.unknown21
return_string += "Unknown22: %08x\n" % self.unknown22
return_string += "Move to Specific Direction: %.9f\n" % self.unknown22
return_string += "Unknown23: %08x\n" % self.unknown23
return_string += "Unknown24: %.20f\n" % self.unknown24
return_string += "Unknown25: %.9f\n" % self.unknown25
return_string += "Unknown26: %08x\n" % self.unknown26
return_string += "Unknown27: %08x\n" % self.unknown27
return_string += "Unknown28: %08x\n" % self.unknown28
return_string += "Unknown29: %08x\n" % self.unknown29
return_string += "Unknown27: %.9f\n" % self.unknown27
return_string += "Unknown28: %.9f\n" % self.unknown28
return_string += "Unknown29: %.9f\n" % self.unknown29
return_string += "Unknown30: %08x\n" % self.unknown30
return_string += "Unknown31: %.9f\n" % self.unknown31
return_string += "Unknown32: %08x\n" % self.unknown32
return_string += "Unknown33: %08x\n" % self.unknown33
return_string += "Four Bytes: %08x\n" % self.unknown33
return_string += "Unknown34: %08x\n" % self.unknown34
return_string += "Unknown35: %08x\n" % self.unknown35
return_string += "Unknown36: %08x\n" % self.unknown36
return_string += "Unknown37: %.9f\n" % self.unknown37
return_string += "Unknown38: %.9f\n" % self.unknown38
return_string += "Unknown39: %.9f\n" % self.unknown39
return_string += "Unknown40: %.9f\n" % self.unknown40
return_string += "Unknown41: %.9f\n" % self.unknown41
return_string += "Transform Scale X: %.9f\n" % self.unknown37
return_string += "Transform Scale Y: %.9f\n" % self.unknown38
return_string += "Transform Scale Z: %.9f\n" % self.unknown39
return_string += "Center of Particle SRT Horiz: %.9f\n" % self.unknown40
return_string += "Center of Particle SRT Vert: %.9f\n" % self.unknown41
return_string += "Unknown42: %08x\n" % self.unknown42
return_string += "Unknown43: %08x\n" % self.unknown43
return_string += "Unknown44: %.9f\n" % self.unknown44
@ -211,19 +212,19 @@ class BREFF(object):
return_string += "Unknown78: %08x\n" % self.unknown78
return_string += "Unknown79: %08x\n" % self.unknown79
return_string += "Unknown80: %08x\n" % self.unknown80
return_string += "Unknown81: %08x\n" % self.unknown81
return_string += "Unknown82: %08x\n" % self.unknown82
return_string += "Unknown83: %08x\n" % self.unknown83
return_string += "Unknown84: %08x\n" % self.unknown84
return_string += "Unknown81: %.9f\n" % self.unknown81
return_string += "Unknown82: %.9f\n" % self.unknown82
return_string += "Unknown83: %.9f\n" % self.unknown83
return_string += "Unknown84: %.9f\n" % self.unknown84
return_string += "Unknown85: %08x\n" % self.unknown85
return_string += "Unknown86: %08x\n" % self.unknown86
return_string += "Unknown87: %08x\n" % self.unknown87
return_string += "Unknown88: %08x\n" % self.unknown88
return_string += "Unknown89: %08x\n" % self.unknown89
return_string += "Unknown90: %08x\n" % self.unknown90
return_string += "Unknown91: %08x\n" % self.unknown91
return_string += "Unknown92: %08x\n" % self.unknown92
return_string += "Unknown93: %08x\n" % self.unknown93
return_string += "Unknown88: %.9f\n" % self.unknown88
return_string += "Unknown89: %.9f\n" % self.unknown89
return_string += "Unknown90: %.9f\n" % self.unknown90
return_string += "Unknown91: %.9f\n" % self.unknown91
return_string += "Unknown92: %.9f\n" % self.unknown92
return_string += "Unknown93: %.9f\n" % self.unknown93
return_string += "Unknown94: %08x\n" % self.unknown94
return_string += "Unknown95: %08x\n" % self.unknown95
return_string += "Unknown96: %08x\n" % self.unknown96
@ -242,7 +243,6 @@ class BREFF(object):
return_string += "UnknownA9: %08x\n" % self.unknownA9
return_string += "UnknownB0: %08x\n" % self.unknownB0
return_string += "UnknownB1: %08x\n" % self.unknownB1
return_string += "UnknownB2: %08x\n" % self.unknownB2
return return_string
class BREFF_REFF_StringSection1(Struct):
@ -260,11 +260,9 @@ class BREFF(object):
def __format__(self):
self.magic = Struct.string(4)
self.length = Struct.uint32
self.chunk_cnt = Struct.uint32
def __str__(self):
return_string = "Magic: %s\n" % self.magic
return_string += "Length: %08x\n" % self.length
return_string += "Chunk Count: %08x\n" % self.chunk_cnt
return return_string
class BREFF_Header(Struct):
@ -302,6 +300,10 @@ class BREFF(object):
print reff
assert reff.magic == "REFF"
temp = pos
project = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Header Size: %08x" % project
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown: %08x" % unknown
@ -315,12 +317,23 @@ class BREFF(object):
pos += 2
print "Unknown: %04x" % unknown
string = data[pos:pos+str_length-1]
pos += str_length + (2 - (str_length % 2))
pos += str_length
print "String: %s\n" % string
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Offset or Length: %08x" % unknown
while pos % 2:
unknown = Struct.uint8(data[pos:pos+1])
pos += 1
print "Padding: %02x" % unknown
print "\n%08x\n" % pos
temp = pos
unknown = Struct.uint16(data[pos:pos+2], endian='>')
pos += 2
print "Offset: %04x" % unknown
unknown = Struct.uint16(data[pos:pos+2], endian='>')
pos += 2
print "Length: %04x" % unknown
print "\n%08x\n" % pos
@ -349,16 +362,16 @@ class BREFF(object):
print "\n%08x\n" % pos
while pos % 0x10:
while pos % 0x04:
padding = Struct.uint8(data[pos:pos+1])
pos += 1
print "Padding: %02x" % padding
print "\n%08x\n" % pos
assert pos == string_groups[0].offset + 0x34
assert pos == string_groups[0].offset + temp
for x in xrange(string_cnt):
pos = 0x34 + string_groups[x].offset
pos = temp + string_groups[x].offset
reff_section2 = self.BREFF_REFF_Section2()
reff_section2.unpack(data[pos:pos+len(reff_section2)])
pos += len(reff_section2)
@ -372,39 +385,39 @@ class BREFF(object):
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown02: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown03: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Unknown03: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown04: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Unknown04: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown05: %08x" % unknown
print "Unknown05: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown06: %f" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown07: %08x" % unknown
print "Unknown07: %f" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown08: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown09: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown10: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown11: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown12: %08x" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "PI TIMES 2 BITCHES: %.20f" % unknown
print "Size Outer Radius X: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Size Outer Radius Y: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Size Outer Radius Z: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Inner Radius: %f" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown14: %08x" % unknown
@ -414,24 +427,24 @@ class BREFF(object):
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown16: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown17: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "All Direction Speed: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown18: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Y Axis Difuse Speed: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown19: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Random Direction Speed: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown20: %08x" % unknown
print "Normal Direction Speed: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "PI DIVIDED BY 4 BITCHES: %.20f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown22: %f" % unknown
print "Move to specific direction: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown23: %f" % unknown
@ -444,15 +457,15 @@ class BREFF(object):
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown26: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown27: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Unknown27: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown28: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Unknown28: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown29: %08x" % unknown
print "Unknown29: %f" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown30: %08x" % unknown
@ -464,7 +477,7 @@ class BREFF(object):
print "Unknown32: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown33: %08x" % unknown
print "Bytes: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown34: %08x" % unknown
@ -476,19 +489,19 @@ class BREFF(object):
print "Unknown36: %08x" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown37: %f" % unknown
print "Transform Scale X: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown38: %f" % unknown
print "Transform Scale Y: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown39: %f" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Transform Scale Z: %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown40: %08x" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
print "Center of Particle SRT Horizontal(x): %f" % unknown
unknown = Struct.float(data[pos:pos+4], endian='>')
pos += 4
print "Unknown41: %08x" % unknown
print "Center of Particle SRT Verticle(y): %f" % unknown
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown42: %08x" % unknown
@ -512,7 +525,7 @@ class BREFF(object):
print "Unknown48: %08x" % unknown
LARGE TEST DATA '''
#''' BEGIN TEST DATA
''' BEGIN TEST DATA
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown: %08x" % unknown
@ -543,7 +556,7 @@ class BREFF(object):
unknown = Struct.uint32(data[pos:pos+4], endian='>')
pos += 4
print "Unknown: %08x" % unknown
#END TEST DATA '''
END TEST DATA '''
def main():
if len(sys.argv) != 2:

253
export.py Normal file
View File

@ -0,0 +1,253 @@
from common import *
from title import *
from image import *
class Savegame():
class savegameHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.savegameId = Struct.uint32[2]
self.bannerSize = Struct.uint32
self.permissions = Struct.uint8
self.unknown1 = Struct.uint8
self.md5hash = Struct.string(16)
self.unknown2 = Struct.uint16
class savegameBanner(Struct):
__endian__ = Struct.BE
def __format__(self):
self.magic = Struct.string(4)
self.reserved = Struct.uint8[4]
self.flags = Struct.uint32
self.reserved = Struct.uint32[5]
self.gameTitle = Struct.string(64)
self.gameSubTitle = Struct.string(64)
self.banner = Struct.string(24576)
self.icon0 = Struct.string(4608)
self.icon1 = Struct.string(4608)
self.icon2 = Struct.string(4608)
self.icon3 = Struct.string(4608)
self.icon4 = Struct.string(4608)
self.icon5 = Struct.string(4608)
self.icon6 = Struct.string(4608)
self.icon7 = Struct.string(4608)
class backupHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.hdrSize = Struct.uint32
self.magic = Struct.string(2)
self.version = Struct.uint16
self.NGid = Struct.uint32
self.filesCount = Struct.uint32
self.filesSize = Struct.uint32
self.unknown1 = Struct.uint32
self.unknown2 = Struct.uint32
self.totalSize = Struct.uint32
self.unknown3 = Struct.uint8[64]
self.unknown4 = Struct.uint32
self.gameId = Struct.string(4)
self.wiiMacAddr = Struct.uint8[6]
self.unknown6 = Struct.uint16
self.padding = Struct.uint32[4]
class fileHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.magic = Struct.uint32
self.size = Struct.uint32
self.permissions = Struct.uint8
self.attribute = Struct.uint8
self.type = Struct.uint8
self.nameIV = Struct.string(0x45)
def __init__(self, f):
self.f = f
try:
self.fd = open(f, 'r+b')
except:
raise Exception('Cannot open input')
self.sdKey = '\xab\x01\xb9\xd8\xe1\x62\x2b\x08\xaf\xba\xd8\x4d\xbf\xc2\xa5\x5d'
self.sdIv = '\x21\x67\x12\xe6\xaa\x1f\x68\x9f\x95\xc5\xa2\x23\x24\xdc\x6a\x98'
self.iconCount = 1
def __str__(self):
ret = ''
ret += '\nSavegame header \n'
ret += 'Savegame ID : 0x%x%x\n' % (self.hdr.savegameId[0], self.hdr.savegameId[1])
ret += 'Banner size : 0x%x\n' % self.hdr.bannerSize
ret += 'Permissions : 0x%x\n' % self.hdr.permissions
ret += 'Unknown : 0x%x\n' % self.hdr.unknown1
ret += 'MD5 hash : %s\n' % hexdump(self.hdr.md5hash)
ret += 'Unknown : 0x%x\n' % self.hdr.unknown2
ret += '\nBanner header \n'
ret += 'Flags : 0x%x\n' % self.bnr.flags
ret += 'Game title : %s\n' % self.bnr.gameTitle
ret += 'Game subtitle : %s\n' % self.bnr.gameSubTitle
ret += 'Icons found : %i\n' % self.iconCount
ret += '\nBackup header \n'
ret += 'Header size : 0x%x (+ 0x10 of padding) version 0x%x\n' % (self.bkHdr.hdrSize, self.bkHdr.version)
if self.bkHdr.gameId[3] == 'P':
ret += 'Region : PAL\n'
elif self.bkHdr.gameId[3] == 'E':
ret += 'Region : NTSC\n'
elif self.bkHdr.gameId[3] == 'J':
ret += 'Region : JAP\n'
ret += 'Game ID : %s\n' % self.bkHdr.gameId
ret += 'Wii unique ID : 0x%x\n' % self.bkHdr.NGid
ret += 'Wii MAC address %02x:%02x:%02x:%02x:%02x:%02x\n' % (self.bkHdr.wiiMacAddr[0], self.bkHdr.wiiMacAddr[1], self.bkHdr.wiiMacAddr[2], self.bkHdr.wiiMacAddr[3], self.bkHdr.wiiMacAddr[4], self.bkHdr.wiiMacAddr[5])
ret += 'Found %i files for %i bytes\n' % (self.bkHdr.filesCount, self.bkHdr.filesSize)
ret += 'Total size : %i bytes\n' % self.bkHdr.totalSize
ret += 'This save is %i blocks wise' % (self.bkHdr.totalSize / 0x20000)
return ret
def extractFiles(self):
try:
os.mkdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId)
except:
pass
os.chdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId)
self.fd.seek(self.fileStartOffset)
for i in range(self.bkHdr.filesCount):
fileHdr = self.fd.read(0x80)
fileHdr = self.fileHeader().unpack(fileHdr)
if fileHdr.magic != 0x03adf17e:
raise Exception('Wrong file magic')
fileHdr.size = align(fileHdr.size, 64)
ivpos = 0
name = ""
i = 0
for char in list(fileHdr.nameIV):
if(char == "\x00"):
i -= 1
ivpos = i
break
else:
name += char
i += 1
fileIV = fileHdr.nameIV[ivpos:ivpos + 16]
if len(fileIV) != 16:
raise Exception('IV alignment issue')
if fileHdr.type == 1:
print 'Extracted %s (%ib)' % (name, fileHdr.size)
fileBuffer = self.fd.read(fileHdr.size)
fileBuffer = Crypto().decryptData(self.sdKey, fileIV, fileBuffer, True)
try:
open(name, 'w+b').write(fileBuffer)
except:
raise Exception('Cannot write the output')
elif fileHdr.type == 2:
print 'Extracted folder %s' % name
try:
os.mkdir(name)
except:
pass
print 'Attribute %i Permission %i' % (fileHdr.attribute, fileHdr.permissions)
print 'File IV : 0x%s' % hexdump(fileIV, '')
os.chdir('..')
def analyzeHeader(self):
headerBuffer = self.fd.read(0xF0C0)
headerBuffer = Crypto().decryptData(self.sdKey, self.sdIv, headerBuffer, True)
self.hdr = self.savegameHeader().unpack(headerBuffer[:0x20])
#headerBuffer.replace(self.hdr.md5hash, '\x0e\x65\x37\x81\x99\xbe\x45\x17\xab\x06\xec\x22\x45\x1a\x57\x93')
#print 'Reashed md5 : %s' % hexdump(Crypto().createMD5Hash(headerBuffer))
self.bnr = self.savegameBanner().unpack(headerBuffer[0x20:])
if self.bnr.magic != 'WIBN':
raise Exception ('Wrong magic, should be WIBN')
if self.hdr.bannerSize != 0x72A0:
self.iconCount += 7
bkHdrBuffer = self.fd.read(0x80)
self.bkHdr = self.backupHeader().unpack(bkHdrBuffer)
if self.bkHdr.magic != 'Bk' or self.bkHdr.hdrSize != 0x70:
raise Exception ('Bk header error')
self.fileStartOffset = self.fd.tell()
def eraseWiiMac(self):
self.fd.seek(0xF128)
print self.fd.write('\x00' * 6)
def getBanner(self):
try:
os.mkdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId)
except:
pass
os.chdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId)
return Image.fromstring("RGBA", (192, 64), TPL('').RGB5A3((192, 64), self.bnr.banner)).save('banner', 'png')
def getIcon(self, index):
if index < 0 or index > 7 or index > self.iconCount:
return -1
try:
os.mkdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId)
except:
pass
os.chdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId)
if index == 0:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon0)).save('icon' + str(index), 'png')
if index == 1:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon1)).save('icon' + str(index), 'png')
if index == 2:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon2)).save('icon' + str(index), 'png')
if index == 3:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon3)).save('icon' + str(index), 'png')
if index == 4:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon4)).save('icon' + str(index), 'png')
if index == 5:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon5)).save('icon' + str(index), 'png')
if index == 6:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon6)).save('icon' + str(index), 'png')
if index == 7:
return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon7)).save('icon' + str(index), 'png')
def getIconsCount(self):
return self.iconCount
def getSaveString(self, string):
if string == 'id':
return self.bkHdr.gameId
elif string == 'title':
return self.bnr.gameTitle
elif string == 'subtitle':
return self.bnr.gameSubTitle
elif string == 'mac':
return self.bkHdr.wiiMacAddr[0] + ':' + self.bkHdr.wiiMacAddr[1] + ':' + self.bkHdr.wiiMacAddr[2] + ':' + self.bkHdr.wiiMacAddr[3] + ':' + self.bkHdr.wiiMacAddr[4] + ':' + self.bkHdr.wiiMacAddr[5]
def getFilesCount(self):
return self.bkHdr.filesCount

View File

@ -1,8 +1,5 @@
import os, struct, subprocess, fnmatch, shutil, urllib, array
from binascii import *
from Struct import Struct
from struct import *
import struct
from common import *
from title import *
@ -12,7 +9,7 @@ class locDat:
def __format__(self):
self.magic = Struct.string(4)
self.md5 = Struct.string(16)
def __init__(self, f):
self.sdKey = '\xab\x01\xb9\xd8\xe1\x62\x2b\x08\xaf\xba\xd8\x4d\xbf\xc2\xa5\x5d'
self.sdIv = '\x21\x67\x12\xe6\xaa\x1f\x68\x9f\x95\xc5\xa2\x23\x24\xdc\x6a\x98'
@ -335,7 +332,7 @@ class netConfig:
def getNotBlank(self, config):
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
fp.close()
if(sel & 0x20):
return 1
@ -347,7 +344,7 @@ class netConfig:
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
if(sel & 0x04):
return 0
else:
@ -360,7 +357,7 @@ class netConfig:
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
if(sel & 0x02):
return 0
else:
@ -372,7 +369,7 @@ class netConfig:
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config) + 2021)
len = unpack(">B", fp.read(1))[0]
len = struct.unpack(">B", fp.read(1))[0]
fp.seek(8 + (0x91C * config) + 1988)
ssid = fp.read(len)
fp.close()
@ -383,7 +380,7 @@ class netConfig:
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config) + 2025)
crypt = unpack(">B", fp.read(1))[0]
crypt = struct.unpack(">B", fp.read(1))[0]
type = ""
if(crypt == 0):
type = "OPEN"
@ -407,7 +404,7 @@ class netConfig:
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config) + 2025)
crypt = unpack(">B", fp.read(1))[0]
crypt = struct.unpack(">B", fp.read(1))[0]
type = ""
if(crypt == 0):
type = "OPEN"
@ -425,7 +422,7 @@ class netConfig:
return None
if(crypt != "\x00"):
fp.seek(8 + (0x91C * config) + 2029)
keylen = unpack(">B", fp.read(1))[0]
keylen = struct.unpack(">B", fp.read(1))[0]
fp.seek(8 + (0x91C * config) + 2032)
key = fp.read(keylen)
fp.close()
@ -436,44 +433,44 @@ class netConfig:
def clearConfig(self, config):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
sel &= 0xDF
fp.seek(8 + (0x91C * config))
fp.write(pack(">B", sel))
fp.write(struct.pack(">B", sel))
fp.close()
def setNotBlank(self, config):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
sel |= 0x20
fp.seek(8 + (0x91C * config))
fp.write(pack(">B", sel))
fp.write(struct.pack(">B", sel))
fp.close()
def setIPType(self, config, static):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
if(not static):
sel |= 0x04
else:
sel &= 0xFB
fp.seek(8 + (0x91C * config))
fp.write(pack(">B", sel))
fp.write(struct.pack(">B", sel))
fp.close()
self.setNotBlank(config)
def setWireType(self, config, wired):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
if(not wired):
sel |= 0x02
else:
sel &= 0xFD
fp.seek(8 + (0x91C * config))
fp.write(pack(">B", sel))
fp.write(struct.pack(">B", sel))
fp.close()
self.setNotBlank(config)
@ -514,29 +511,29 @@ class netConfig:
def selectConfig(self, config):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * 0))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
if(config == 0):
sel |= 0x80
else:
sel &= 0x7F
fp.seek(8 + (0x91C * 0))
fp.write(pack(">B", sel))
fp.write(struct.pack(">B", sel))
fp.seek(8 + (0x91C * 1))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
if(config == 1):
sel |= 0x80
else:
sel &= 0x7F
fp.seek(8 + (0x91C * 1))
fp.write(pack(">B", sel))
fp.write(struct.pack(">B", sel))
fp.seek(8 + (0x91C * 2))
sel = unpack(">B", fp.read(1))[0]
sel = struct.unpack(">B", fp.read(1))[0]
if(config == 2):
sel |= 0x80
else:
sel &= 0x7F
fp.seek(8 + (0x91C * 2))
fp.write(pack(">B", sel))
fp.write(struct.pack(">B", sel))
self.setNotBlank(config)
fp.close()
@ -710,8 +707,8 @@ class uidsys:
if(hexdump(uidstr.titleid, "") == ("%016X" % title)):
uidfp.close()
return uidstr.uid
if(unpack(">H", uidstr.uid) >= unpack(">H", enduid)):
enduid = a2b_hex("%04X" % (unpack(">H", uidstr.uid)[0] + 1))
if(struct.unpack(">H", uidstr.uid) >= struct.unpack(">H", enduid)):
enduid = a2b_hex("%04X" % (struct.unpack(">H", uidstr.uid)[0] + 1))
uidict[uidstr.titleid] = uidstr.uid
uidict[a2b_hex("%016X" % title)] = enduid
uidfp.close()

145
headers.py Normal file
View File

@ -0,0 +1,145 @@
from common import *
class IMD5():
"""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
def __format__(self):
self.tag = Struct.string(4)
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()
imd5 = self.IMD5Header()
for i in range(8):
imd5.zeroes[i] = 0x00
imd5.tag = "IMD5"
imd5.size = len(data)
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 = ""):
"""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()
imd5 = self.IMD5Header()
if(data[:4] != "IMD5"):
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
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
class IMET():
"""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."""
class IMETHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.zeroes = Struct.uint8[128]
self.tag = Struct.string(4)
self.unk = Struct.uint64
self.sizes = Struct.uint32[3] #icon, banner, sound
self.unk2 = Struct.uint32
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()
imet = self.IMETHeader()
for i in imet.zeroes:
imet.zeroes[i] = 0x00
imet.tag = "IMET"
imet.unk = 0x0000060000000003
imet.sizes[0] = iconsz
imet.sizes[1] = bannersz
imet.sizes[2] = soundsz
imet.unk2 = 0
for i in range(len(imet.names)):
if(len(langs) > 0 and langs[i] != ""):
imet.names[i] = langs[i]
else:
imet.names[i] = name
for i in imet.zeroes2:
imet.zeroes2[i] = 0x00
imet.hash = "\x00" * 16
tmp = imet.pack()
imet.hash = Crypto().createMD5Hash(tmp[0x40:0x640]) #0x00 or 0x40?
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()
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
def getTitle(self):
imet = self.IMETHeader()
data = open(self.f, "rb").read()
if(data[0x40:0x44] == "IMET"):
pass
elif(data[0x80:0x84] == "IMET"):
data = data[0x40:]
else:
return ""
imet.unpack(data[:len(imet)])
name = imet.names[1]
topop = []
for i in range(len(name)):
if(name[i] == "\x00"):
topop.append(i)
name = list(name)
popped = 0 #don't ask me why I did this
for pop in topop:
name.pop(pop - popped)
popped += 1
name = ''.join(name)
return name

796
image.py Normal file
View File

@ -0,0 +1,796 @@
from common import *
def flatten(myTuple):
if (len(myTuple) == 4):
return myTuple[0] << 0 | myTuple[1] << 8 | myTuple[2] << 16 | myTuple[3] << 24
else:
return myTuple[0] << 0 | myTuple[1] << 8 | myTuple[2] << 16 | 0xff << 24
def round_up(x, n):
left = x % n
return x + left
def avg(w0, w1, c0, c1):
a0 = c0 >> 11
a1 = c1 >> 11
a = (w0*a0 + w1*a1) / (w0 + w1)
c = (a << 11) & 0xffff
a0 = (c0 >> 5) & 63
a1 = (c1 >> 5) & 63
a = (w0*a0 + w1*a1) / (w0 + w1)
c = c | ((a << 5) & 0xffff)
a0 = c0 & 31
a1 = c1 & 31
a = (w0*a0 + w1*a1) / (w0 + w1)
c = c | a
return c
class TPL():
"""This is the class to generate TPL texutres from PNG images, and to convert TPL textures to PNG images. The parameter file specifies the filename of the source, either a PNG image or a TPL image.
Currently supported are the following formats to convert from TPL (all formats): RGBA8, RGB565, RGB5A3, I4, IA4, I8, IA8, CI4, CI8, CMP, CI14X2.
Currently supported to convert to TPL: I4, I8, IA4, IA8, RBG565, RBGA8, RGB5A3. Currently not supported are CI4, CI8, CMP, CI14X2."""
class TPLHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.magic = Struct.uint32
self.ntextures = Struct.uint32
self.header_size = Struct.uint32
class TPLTexture(Struct):
__endian__ = Struct.BE
def __format__(self):
self.header_offset = Struct.uint32
self.palette_offset = Struct.uint32
class TPLTextureHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.height = Struct.uint16
self.width = Struct.uint16
self.format = Struct.uint32
self.data_off = Struct.uint32
self.wrap = Struct.uint32[2]
self.filter = Struct.uint32[2]
self.lod_bias = Struct.float
self.edge_lod = Struct.uint8
self.min_lod = Struct.uint8
self.max_lod = Struct.uint8
self.unpacked = Struct.uint8
class TPLPaletteHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.nitems = Struct.uint16
self.unpacked = Struct.uint8
self.pad = Struct.uint8
self.format = Struct.uint32
self.offset = Struct.uint32
def __init__(self, file):
if(os.path.isfile(file)):
self.file = file
self.data = None
else:
self.file = None
self.data = file
def toTPL(self, outfile, (width, height) = (None, None), format = "RGBA8"): #single texture only
"""This converts an image into a TPL. The image is specified as the file parameter to the class initializer, while the output filename is specified here as the parameter outfile. Width and height are optional parameters and specify the size to resize the image to, if needed. Returns the output filename.
This only can create TPL images with a single texture."""
head = self.TPLHeader()
head.magic = 0x0020AF30
head.ntextures = 1
head.header_size = 0x0C
tex = self.TPLTexture()
tex.header_offset = 0x14
tex.pallete_offset = 0
img = Image.open(self.file)
theWidth, theHeight = img.size
if(width != None and height != None and (width != theWidth or height != theHeight)):
img = img.resize((width, height), Image.ANTIALIAS)
w, h = img.size
texhead = self.TPLTextureHeader()
texhead.height = h
texhead.width = w
if format == "I4":
texhead.format = 0
tpldata = self.toI4((w, h), img)
elif format == "I8":
texhead.format = 1
tpldata = self.toI8((w, h), img)
elif format == "IA4":
texhead.format = 2
tpldata = self.toIA4((w, h), img)
elif format == "IA8":
texhead.format = 3
tpldata = self.toIA8((w, h), img)
elif format == "RGB565":
texhead.format = 4
tpldata = self.toRGB565((w, h), img)
elif format == "RGB5A3":
texhead.format = 5
tpldata = self.toRGB5A3((w, h), img)
elif format == "RGBA8":
texhead.format = 6
tpldata = self.toRGBA8((w, h), img)
elif format == "CI4":
texhead.format = 8
''' ADD toCI4 '''
raise Exception("toCI4 not done")
#tpldata = self.toCI4((w, h), img)
elif format == "CI8":
texhead.format = 9
''' ADD toCI8 '''
raise Exception("toCI8 not done")
#tpldata = self.toCI8((w, h), img)
elif format == "CI14X2":
texhead.format = 10
''' ADD toCI14X2 '''
raise Exception("toCI14X2 not done")
#tpldata = self.toCI14X2((w, h), img)
elif format == "CMP":
texhead.format = 14
''' ADD toCMP '''
raise Exception("toCMP not done")
#tpldata = self.toCMP((w, h), img)
texhead.data_off = 0x14 + len(texhead)
texhead.wrap = [0, 0]
texhead.filter = [1, 1]
texhead.lod_bias = 0
texhead.edge_lod = 0
texhead.min_lod = 0
texhead.max_lod = 0
texhead.unpacked = 0
f = open(outfile, "wb")
f.write(head.pack())
f.write(tex.pack())
f.write(texhead.pack())
if format == "I4":
f.write(struct.pack(">" + str(align(w,4) * align(h,4) / 2) + "B", *tpldata))
if format == "I8":
f.write(struct.pack(">" + str(align(w,4) * align(h,4) * 1) + "B", *tpldata))
if format == "IA4":
f.write(struct.pack(">" + str(align(w,4) * align(h,4) * 1) + "B", *tpldata))
if format == "IA8":
f.write(struct.pack(">" + str(align(w,4) * align(h,4) * 1) + "H", *tpldata))
if format == "RGB565":
f.write(struct.pack(">" + str(align(w,4) * align(h,4) * 1) + "H", *tpldata))
if format == "RGB5A3":
f.write(struct.pack(">" + str(align(w,4) * align(h,4) * 1) + "H", *tpldata))
if format == "RGBA8":
f.write(struct.pack(">" + str(align(w,4) * align(h,4) * 4) + "B", *tpldata))
if format == "CI4":
''' ADD toCI4 '''
#f.write(struct.pack(">"+ str(align(w,4) * align(h,4) * 4) + "B", *tpldata))
if format == "CI8":
''' ADD toCI8 '''
#f.write(struct.pack(">"+ str(align(w,4) * align(h,4) * 4) + "B", *tpldata))
if format == "CI14X2":
''' ADD toCI14X2 '''
#f.write(struct.pack(">"+ str(align(w,4) * align(h,4) * 4) + "B", *tpldata))
if format == "CMP":
''' ADD toCMP '''
#f.write(struct.pack(">"+ str(align(w,4) * align(h,4) * 4) + "B", *tpldata))
f.close()
return outfile
def toI4(self, (w, h), img):
out = [0 for i in range(align(w, 4) * align(h, 4) / 2)]
outp = 0
inp = list(img.getdata())
for y1 in range(0, h, 8):
for x1 in range(0, w, 8):
for y in range(y1, y1+8, 1):
for x in range(x1, x1+8, 2):
if x>=w or y>=h:
newpixel = 0
else:
rgba = flatten(inp[x+y*w])
r = (rgba >> 0) & 0xff
g = (rgba >> 8) & 0xff
b = (rgba >> 16) & 0xff
i1 = ((r + g + b) / 3) & 0xff
rgba = flatten(inp[x+1+y*w])
r = (rgba >> 0) & 0xff
g = (rgba >> 8) & 0xff
b = (rgba >> 16) & 0xff
i2 = ((r + g + b) / 3) & 0xff
newpixel = (((i1 * 15) / 255) << 4)
newpixel |= (((i2 * 15) / 255) & 0xf)
out[outp] = newpixel
outp += 1
return out
def toI8(self, (w, h), img):
out = [0 for i in range(align(w, 4) * align(h, 4))]
outp = 0
inp = list(img.getdata())
for y1 in range(0, h, 4):
for x1 in range(0, w, 8):
for y in range(y1, y1+4, 1):
for x in range(x1, x1+8, 1):
rgba = flatten(inp[x + (y * w)])
if x>= w or y>=h:
i1 = 0
else:
r = (rgba >> 0) & 0xff
g = (rgba >> 8) & 0xff
b = (rgba >> 16) & 0xff
i1 = ((r + g + b) / 3) & 0xff
out[outp] = i1
outp += 1
return out
def toIA4(self, (w, h), img):
out = [0 for i in range(align(w, 4) * align(h, 4))]
outp = 0
inp = list(img.getdata())
for y1 in range(0, h, 4):
for x1 in range(0, w, 8):
for y in range(y1, y1+4, 1):
for x in range(x1, x1+8, 1):
if x>=w or y>=h:
newpixel = 0
else:
rgba = flatten(inp[x + (y * w)])
r = (rgba >> 0) & 0xff
g = (rgba >> 8) & 0xff
b = (rgba >> 16) & 0xff
i1 = ((r + g + b) / 3) & 0xff
a1 = (rgba >> 24) & 0xff
newpixel = (((i1 * 15) / 255) & 0xf)
newpixel = newpixel | (((a1 * 15) / 255) << 4)
out[outp] = newpixel
outp += 1
return out
def toIA8(self, (w, h), img):
out = [0 for i in range(align(w, 4) * align(h, 4))]
outp = 0
inp = list(img.getdata())
for y1 in range(0, h, 4):
for x1 in range(0, w, 4):
for y in range(y1, y1+4, 1):
for x in range(x1, x1+4, 1):
if x>=w or y>=h:
newpixel = 0
else:
rgba = flatten(inp[x + (y * w)])
r = (rgba >> 0) & 0xff
g = (rgba >> 8) & 0xff
b = (rgba >> 16) & 0xff
i1 = ((r + g + b) / 3) & 0xff
a1 = (rgba >> 24) & 0xff
newpixel = i1 << 8
newpixel = newpixel | a1
out[outp] = newpixel
outp += 1
return out
def toRGB565(self, (w, h), img):
out = [0 for i in range(align(w, 4) * align(h, 4))]
outp = 0
inp = img.getdata()
for y1 in range(0, h, 4):
for x1 in range(0, w, 4):
for y in range(y1, y1+4, 1):
for x in range(x1, x1+4, 1):
newpixel = 0
if x>=w or y>=h:
newpixel = 0
else:
rgba = flatten(inp[x+y*w])
r = (rgba >> 0) & 0xff
g = (rgba >> 8) & 0xff
b = (rgba >> 16) & 0xff
newpixel = ((b >>3) << 11) | ((g >>2) << 5) | ((r >>3) << 0)
out[outp] = newpixel
outp += 1
return out
def toRGB5A3(self, (w, h), img):
out = [0 for i in range(align(w, 4) * align(h, 4))]
outp = 0
inp = list(img.getdata())
for y1 in range(0, h, 4):
for x1 in range(0, w, 4):
for y in range(y1, y1+4, 1):
for x in range(x1, x1+4, 1):
newpixel = 0
if x>=w or y>=h:
newpixel = 0
else:
rgba = flatten(inp[x + (y * h)])
r = (rgba >> 0) & 0xff
g = (rgba >> 8) & 0xff
b = (rgba >> 16) & 0xff
a = (rgba >> 24) & 0xff
if (a <= 0xda):
newpixel &= ~(1 << 15)
r = ((r * 15) / 255) & 0xf
g = ((g * 15) / 255) & 0xf
b = ((b * 15) / 255) & 0xf
a = ((a * 7) / 255) & 0x7
#newpixel |= r << 12
#newpixel |= g << 8
#newpixel |= b << 4
#newpixel |= a << 0
newpixel |= a << 12
newpixel |= b << 8
newpixel |= g << 4
newpixel |= r << 0
else:
newpixel |= (1 << 15)
r = ((r * 31) / 255) & 0x1f
g = ((g * 31) / 255) & 0x1f
b = ((b * 31) / 255) & 0x1f
newpixel |= b << 10
newpixel |= g << 5
newpixel |= r << 0
out[outp] = newpixel
outp += 1
return out
def toRGBA8(self, (w, h), img):
out = [0 for i in range(align(w, 4) * align(h, 4) * 4)]
inp = list(img.getdata())
iv = 0
z = 0
lr = [0 for i in range(32)]
lg = [0 for i in range(32)]
lb = [0 for i in range(32)]
la = [0 for i in range(32)]
for y1 in range(0, h, 4):
for x1 in range(0, w, 4):
for y in range(y1, y1 + 4, 1):
for x in range(x1, x1 + 4, 1):
if(y >= h or x >= w):
lr[z] = 0
lg[z] = 0
lb[z] = 0
la[z] = 0
else:
rgba = flatten(inp[x + (y * w)])
lr[z] = (rgba >> 0) & 0xff
lg[z] = (rgba >> 8) & 0xff
lb[z] = (rgba >> 16) & 0xff
la[z] = (rgba >> 24) & 0xff
z += 1
if(z == 16):
for i in range(16):
out[iv] = la[i] & 0xff
iv += 1
out[iv] = lr[i] & 0xff
iv += 1
for i in range(16):
out[iv] = lg[i] & 0xff
iv += 1
out[iv] = lb[i] & 0xff
iv += 1
z = 0
return out
def toImage(self, outfile):
"""This converts a TPL texture to a PNG image. You specify the input TPL filename in the initializer, and you specify the output filename in the outfile parameter to this method. Returns the output filename.
This only supports single textured TPL images."""
if(self.file):
data = open(self.file, "rb").read()
else:
data = self.data
header = self.TPLHeader()
textures = []
pos = 0
header.unpack(data[pos:pos + len(header)])
pos += len(header)
palette_offsets = []
for i in range(header.ntextures):
tmp = self.TPLTexture()
tmp.unpack(data[pos:pos + len(tmp)])
textures.append(tmp)
pos += len(tmp)
if(tmp.palette_offset > 0):
palette_offsets.append(tmp.palette_offset)
if(header.ntextures > 1):
raise ValueError("Only one texture supported. Don't touch me!")
for i in range(header.ntextures):
head = textures[i]
tex = self.TPLTextureHeader()
tex.unpack(data[head.header_offset:head.header_offset + len(tex)])
w = tex.width
h = tex.height
if(tex.format == 0): #I4, 4-bit
tpldata = struct.unpack(">" + str((w * h) / 2) + "B", data[tex.data_off:tex.data_off + ((w * h) / 2)])
rgbdata = self.I4((w, h), tpldata)
elif(tex.format == 1): #I8, 8-bit
tpldata = struct.unpack(">" + str(w * h) + "B", data[tex.data_off:tex.data_off + (w * h * 1)])
rgbdata = self.I8((w, h), tpldata)
elif(tex.format == 2): #IA4, 8-bit
tpldata = struct.unpack(">" + str(w * h) + "B", data[tex.data_off:tex.data_off + (w * h * 1)])
rgbdata = self.IA4((w, h), tpldata)
elif(tex.format == 4): #RGB565, 16-bit
tpldata = data[tex.data_off:]
rgbdata = self.RGB565((w, h), tpldata)
elif(tex.format == 5): #RGB5A3, 16-bit
tpldata = data[tex.data_off:]
rgbdata = self.RGB5A3((w, h), tpldata)
elif(tex.format == 3): #IA8, 16-bit
tpldata = data[tex.data_off:]
rgbdata = self.IA8((w, h), tpldata)
elif(tex.format == 6): #RGBA8, 32-bit, but for easyness's sake lets do it with 16-bit
tpldata = data[tex.data_off:]
rgbdata = self.RGBA8((w, h), tpldata)
elif(tex.format == 8 or tex.format == 9 or tex.format == 10):
palhead = self.TPLPaletteHeader()
offs = palette_offsets.pop(0)
palhead.unpack(data[offs:offs + len(palhead)])
tpldata = struct.unpack(">" + str(palhead.nitems) + "H", data[palhead.offset:palhead.offset + (palhead.nitems * 2)])
if(palhead.format == 0):
palette_data = self.IA8((palhead.nitems, 1), tpldata)[0]
elif(palhead.format == 1):
palette_data = self.RGB565((palhead.nitems, 1), tpldata)[0]
elif(palhead.format == 2):
palette_data = self.RGB5A3((palhead.nitems, 1), tpldata)[0]
paldata = []
for i in range(0, palhead.nitems * 4, 4):
tmp = 0
tmp |= palette_data[i + 0] << 24
tmp |= palette_data[i + 1] << 16
tmp |= palette_data[i + 2] << 8
tmp |= palette_data[i + 3] << 0
paldata.append(tmp)
if(tex.format == 8):
tpldata = struct.unpack(">" + str((w * h) / 2) + "B", data[tex.data_off:tex.data_off + ((w * h) / 2)])
rgbdata = self.CI4((w, h), tpldata, paldata)
if(tex.format == 9):
tpldata = struct.unpack(">" + str(w * h) + "B", data[tex.data_off:tex.data_off + (w * h * 1)])
rgbdata = self.CI8((w, h), tpldata, paldata)
if(tex.format == 10):
tpldata = struct.unpack(">" + str(w * h) + "H", data[tex.data_off:tex.data_off + (w * h * 2)])
rgbdata = self.CI14X2((w, h), tpldata, paldata)
elif(tex.format == 14):
tpldata = ''.join(data[tex.data_off:])
rgbdata = self.CMP((w, h), tpldata)
else:
raise TypeError("Unsupported TPL Format: " + str(tex.format))
output = Image.fromstring("RGBA", (w, h), rgbdata)
ext = outfile[outfile.rfind(".")+1:]
output.save(outfile, ext)
return outfile
def getSizes(self):
"""This returns a tuple containing the width and height of the TPL image filename in the class initializer. Will only return the size of single textured TPL images."""
data = open(self.file, "rb").read()
header = self.TPLHeader()
textures = []
pos = 0
header.unpack(data[pos:pos + len(header)])
pos += len(header)
for i in range(header.ntextures):
tmp = self.TPLTexture()
tmp.unpack(data[pos:pos + len(tmp)])
textures.append(tmp)
pos += len(tmp)
for i in range(header.ntextures):
head = textures[i]
tex = self.TPLTextureHeader()
tex.unpack(data[head.header_offset:head.header_offset + len(tex)])
w = tex.width
h = tex.height
return (w, h)
def toScreen(self): #single texture only
"""This will draw a simple window with the TPL image displayed on it. It uses WxPython for the window creation and management. The window has a minimum width and height of 300 x 200. Does not return a value.
Again, only a single texture is supported."""
import wx
class imp(wx.Dialog):
def __init__(self, title, im):
w = img.GetWidth()
h = img.GetHeight()
wx.Dialog.__init__(self, None, -1, title, size = (max(w, 300), max(h, 200)))
wx.StaticBitmap(self, -1, im, ( ((max(w, 300) - w) / 2), ((max(h, 200) - h) / 2) ), (w, h))
self.toImage("tmp.png")
img = wx.Image("tmp.png", wx.BITMAP_TYPE_ANY).ConvertToBitmap()
w = img.GetWidth()
h = img.GetHeight()
dialog = imp("TPL (" + str(w) + ", " + str(h) + ")", img)
dialog.ShowModal()
dialog.Destroy()
os.unlink("tmp.png")
def RGBA8(self, (x, y), data):
out = [0 for i in range(x * y)]
inp = 0
for i in xrange(0, y, 4):
for j in xrange(0, x, 4):
for k in xrange(2):
for l in xrange(i, i + 4, 1):
for m in xrange(j, j + 4, 1):
texel = Struct.uint16(data[inp * 2:inp * 2 + 2], endian = '>')
inp += 1
if (m >= x) or (l >= y):
continue
if k == 0:
a = (texel >> 8) & 0xff
r = (texel >> 0) & 0xff
out[m + (l * x)] = out[m + (l * x)] | ((r<<0) | (a<<24))
else:
g = (texel >> 8) & 0xff
b = (texel >> 0) & 0xff
out[m + (l * x)] = out[m + (l * x)] | ((g<<8) | (b<<16))
return ''.join(Struct.uint32(p) for p in out)
def RGB5A3(self, (w, h), jar):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 4):
for x in range(0, w, 4):
for y1 in range(y, y + 4):
for x1 in range(x, x + 4):
if(y1 >= h or x1 >= w):
continue
pixel = Struct.uint16(jar[i * 2:i * 2 + 2], endian='>')
i += 1
if(pixel & (1 << 15)): #RGB555
b = (((pixel >> 10) & 0x1F) * 255) / 31
g = (((pixel >> 5) & 0x1F) * 255) / 31
r = (((pixel >> 0) & 0x1F) * 255) / 31
a = 255
else: #RGB4A3
a = (((pixel >> 12) & 0x07) * 255) / 7
b = (((pixel >> 8) & 0x0F) * 255) / 15
g = (((pixel >> 4) & 0x0F) * 255) / 15
r = (((pixel >> 0) & 0x0F) * 255)/ 15
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[(y1 * w) + x1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def RGB565(self, (w, h), jar):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 4):
for x in range(0, w, 4):
for y1 in range(y, y + 4):
for x1 in range(x, x + 4):
if(y1 >= h or x1 >= w):
continue
pixel = Struct.uint16(jar[i * 2:i * 2 + 2], endian='>')
i += 1
b = (((pixel >> 11) & 0x1F) << 3) & 0xff
g = (((pixel >> 5) & 0x3F) << 2) & 0xff
r = (((pixel >> 0) & 0x1F) << 3) & 0xff
a = 255
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def I4(self, (w, h), jar):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 8):
for x in range(0, w, 8):
for y1 in range(y, y + 8):
for x1 in range(x, x + 8, 2):
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
r = (pixel >> 4) * 255 / 15
g = (pixel >> 4) * 255 / 15
b = (pixel >> 4) * 255 / 15
a = 255
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
i += 1
r = (pixel & 0x0F) * 255 / 15
g = (pixel & 0x0F) * 255 / 15
b = (pixel & 0x0F) * 255 / 15
a = 255
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1 + 1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def IA4(self, (w, h), jar):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 4):
for x in range(0, w, 8):
for y1 in range(y, y + 4):
for x1 in range(x, x + 8):
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
i += 1
r = ((pixel & 0x0F) * 255 / 15) & 0xff
g = ((pixel & 0x0F) * 255 / 15) & 0xff
b = ((pixel & 0x0F) * 255 / 15) & 0xff
a = (((pixel >> 4) * 255) / 15) & 0xff
rgba = ( r<< 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def I8(self, (w, h), jar):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 4):
for x in range(0, w, 8):
for y1 in range(y, y + 4):
for x1 in range(x, x + 8):
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
i += 1
r = pixel
g = pixel
b = pixel
a = 255
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def IA8(self, (w, h), jar):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 4):
for x in range(0, w, 4):
for y1 in range(y, y + 4):
for x1 in range(x, x + 4):
if(y1 >= h or x1 >= w):
continue
pixel = Struct.uint16(jar[i * 2:i * 2 + 2], endian='>')
i += 1
r = (pixel >> 8) & 0xff
g = (pixel >> 8) & 0xff
b = (pixel >> 8) & 0xff
a = pixel & 0xff
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def CI4(self, (w, h), jar, pal):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 8):
for x in range(0, w, 8):
for y1 in range(y, y + 8):
for x1 in range(x, x + 8, 2):
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
r = (pal[pixel] & 0xFF000000) >> 24
g = (pal[pixel] & 0x00FF0000) >> 16
b = (pal[pixel] & 0x0000FF00) >> 8
a = (pal[pixel] & 0x000000FF) >> 0
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
i += 1
r = (pal[pixel] & 0xFF000000) >> 24
g = (pal[pixel] & 0x00FF0000) >> 16
b = (pal[pixel] & 0x0000FF00) >> 8
a = (pal[pixel] & 0x000000FF) >> 0
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1 + 1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def CI8(self, (w, h), jar, pal):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 4):
for x in range(0, w, 8):
for y1 in range(y, y + 4):
for x1 in range(x, x + 8):
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
i += 1
r = (pal[pixel] & 0xFF000000) >> 24
g = (pal[pixel] & 0x00FF0000) >> 16
b = (pal[pixel] & 0x0000FF00) >> 8
a = (pal[pixel] & 0x000000FF) >> 0
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
return ''.join(Struct.uint32(p) for p in out)
def CMP(self, (w, h), data):
temp = [0 for i in range(w * h)]
pix = [ 0 , 0 , 0 ]
c = [ 0 , 0 , 0 , 0 ]
outp = 0
for y in xrange(h):
for x in xrange(w):
ww = round_up(w, 8)
x0 = x & 0x03
x1 = (x >> 2) & 0x01
x2 = x >> 3
y0 = y & 0x03
y1 = (y >> 2) & 0x01
y2 = y >> 3
off = (8 * x1) + (16 * y1) + (32 * x2) + (4 * ww * y2)
c[0] = Struct.uint16(data[off + 0:off + 2], endian='>')
c[1] = Struct.uint16(data[off + 2:off + 4], endian='>')
if(c[0] > c[1]):
c[2] = avg(2, 1, c[0], c[1])
c[3] = avg(1, 2, c[0], c[1])
else:
c[2] = avg(1, 1, c[0], c[1])
c[3] = 0
px = Struct.uint32(data[off+4:off + 8], endian='>')
ix = x0 + ( 4 * y0 )
raw = c[(px >> (30 - (2 * ix))) & 0x03]
pix[0] = (raw >> 8) & 0xf8
pix[1] = (raw >> 3) & 0xf8
pix[2] = (raw << 3) & 0xf8
temp[outp] = (pix[0] <<0) | (pix[1] << 8) | (pix[2] << 16) | (255 << 24)
outp += 1
return ''.join(Struct.uint32(p) for p in temp)
def CI14X2(self, (w, h), jar):
out = [0 for i in range(w * h)]
i = 0
for y in range(0, h, 4):
for x in range(0, w, 4):
for y1 in range(y, y + 4):
for x1 in range(x, x + 4):
if(y1 >= h or x1 >= w):
continue
pixel = jar[i]
i += 1
r = (pal[pixel & 0x3FFF] & 0xFF000000) >> 24
g = (pal[pixel & 0x3FFF] & 0x00FF0000) >> 16
b = (pal[pixel & 0x3FFF] & 0x0000FF00) >> 8
a = (pal[pixel & 0x3FFF] & 0x000000FF) >> 0
rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24)
out[y1 * w + x1] = rgba
return ''.join(Struct.uint32(p) for p in out)

View File

@ -1,14 +1,10 @@
import os, struct, subprocess, fnmatch, shutil, urllib, array
from binascii import *
from Struct import Struct
from struct import *
from common import *
from title import *
from formats import *
class NAND:
"""This class performs all NAND related things. It includes functions to copy a title (given the TMD) into the correct structure as the Wii does, and has an entire ES-like system. Parameter f to the initializer is the folder that will be used as the NAND root."""
def __init__(self, f):

229
title.py
View File

@ -1,7 +1,3 @@
import os, struct, subprocess, fnmatch, shutil, urllib, array
from Struct import Struct
from common import *
class TicketView:
@ -41,7 +37,6 @@ class TicketView:
return out
class Ticket:
"""Creates a ticket from the filename defined in f. This may take a longer amount of time than expected, as it also decrypts the title key. Now supports Korean tickets (but their title keys stay Korean on dump)."""
class TicketStruct(Struct):
@ -65,21 +60,26 @@ class Ticket:
self.unk3 = Struct.uint16
self.limits = Struct.string(96)
self.unk4 = Struct.uint8
def __init__(self, f):
isf = False
try:
isf = os.path.isfile(f)
except:
pass
if(isf):
self.f = f
data = open(f, "rb").read()
else:
self.f = "tik"
data = f
self.size = len(data)
def __init__(self):
self.tik = self.TicketStruct()
self.tik.rsaexp = 0x10001
self.tik.rsamod = "\x00" * 256
self.tik.padding1 = "\x00" * 60
self.tik.rsaid = "\x00" * 64
self.tik.padding2 = "\x00" * 63
self.tik.enctitlekey = "\x00" * 16
self.tik.titleid = 0x0000000100000000
self.tik.reserved = "\x00" * 80
self.tik.limits = "\x00" * 96
commonkey = "\xEB\xE4\x2A\x22\x5E\x85\x93\xE4\x48\xD9\xC5\x45\x73\x81\xAA\xF7"
koreankey = "\x63\xB8\x2B\xB4\xF4\x61\x4E\x2E\x13\xF2\xFE\xFB\xBA\x4C\x9B\x7E"
if(self.tik.commonkey_index == 1): #korean, kekekekek!
commonkey = koreankey
self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey)
def load(self, data):
self.tik.unpack(data[:len(self.tik)])
commonkey = "\xEB\xE4\x2A\x22\x5E\x85\x93\xE4\x48\xD9\xC5\x45\x73\x81\xAA\xF7"
@ -89,6 +89,9 @@ class Ticket:
commonkey = koreankey
self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey)
return self
def loadFile(self, filename):
return self.load(open(filename, "rb").read())
def getTitleKey(self):
"""Returns a string containing the title key."""
return self.titlekey
@ -98,6 +101,12 @@ class Ticket:
def setTitleID(self, titleid):
"""Sets the title id of the ticket from the long integer passed in titleid."""
self.tik.titleid = titleid
commonkey = "\xEB\xE4\x2A\x22\x5E\x85\x93\xE4\x48\xD9\xC5\x45\x73\x81\xAA\xF7"
koreankey = "\x63\xB8\x2B\xB4\xF4\x61\x4E\x2E\x13\xF2\xFE\xFB\xBA\x4C\x9B\x7E"
if(self.tik.commonkey_index == 1): #korean, kekekekek!
commonkey = koreankey
self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey) #This changes the decrypted title key!
def __str__(self):
out = ""
out += " Ticket:\n"
@ -116,7 +125,6 @@ class Ticket:
out += "\n"
return out
def dump(self, fn = ""):
"""Fakesigns (or Trucha signs) and dumps the ticket to either fn, if not empty, or overwriting the source if empty. Returns the output filename."""
self.rsamod = self.rsamod = "\x00" * 256
@ -173,30 +181,21 @@ class TMD:
self.boot_index = Struct.uint16
self.padding2 = Struct.uint16
#contents follow this
def __init__(self, f):
isf = False
try:
isf = os.path.isfile(f)
except:
pass
if(isf):
self.f = f
data = open(f, "rb").read()
else:
self.f = "tmd"
data = f
self.tmd = self.TMDStruct()
def load(self, data):
self.tmd.unpack(data[:len(self.tmd)])
self.contents = []
pos = len(self.tmd)
for i in range(self.tmd.numcontents):
cont = self.TMDContent()
cont.unpack(data[pos:pos + len(cont)])
pos += len(cont)
self.contents.append(cont)
return self
def loadFile(self, filename):
return self.load(open(filename, "rb").read())
def __init__(self):
self.tmd = self.TMDStruct()
self.tmd.titleid = 0x0000000100000000
self.contents = []
def getContents(self):
"""Returns a list of contents. Each content is an object with the members "size", the size of the content's decrypted data; "cid", the content id; "type", the type of the content (0x8001 for shared, 0x0001 for standard, more possible), and a 20 byte string called "hash"."""
return self.contents
@ -342,159 +341,3 @@ class NUS:
open("%08x.app" % output, "wb").write(tmpdata)
os.chdir("..")
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)
tik = Ticket("tik")
tmd = TMD("tmd")
titlekey = tik.getTitleKey()
contents = tmd.getContents()
apppack = ""
for content in contents:
tmpdata = open("%08x.app" % content.index, "rb").read()
if(decrypted):
if(fakesign):
content.hash = str(Crypto().createSHAHash(tmpdata))
content.size = len(tmpdata)
encdata = Crypto().encryptContent(titlekey, content.index, tmpdata)
else:
encdata = tmpdata
apppack += encdata
if(len(encdata) % 64 != 0):
apppack += "\x00" * (64 - (len(encdata) % 64))
if(fakesign):
tmd.setContents(contents)
tmd.dump()
tik.dump()
rawtmd = open("tmd", "rb").read()
rawcert = open("cert", "rb").read()
rawtik = open("tik", "rb").read()
sz = 0
for i in range(len(contents)):
sz += contents[i].size
if(sz % 64 != 0):
sz += 64 - (contents[i].size % 64)
if(self.boot2 != True):
pack = struct.pack('>I4s6I', 32, "Is\x00\x00", len(rawcert), 0, len(rawtik), len(rawtmd), sz, 0)
pack += "\x00" * 32
else:
pack = struct.pack('>IIIII12s', 32, align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40), len(rawcert), len(rawtik), len(rawtmd), "\x00" * 12)
pack += rawcert
if(len(rawcert) % 64 != 0 and self.boot2 != True):
pack += "\x00" * (64 - (len(rawcert) % 64))
pack += rawtik
if(len(rawtik) % 64 != 0 and self.boot2 != True):
pack += "\x00" * (64 - (len(rawtik) % 64))
pack += rawtmd
if(len(rawtmd) % 64 != 0 and self.boot2 != True):
pack += "\x00" * (64 - (len(rawtmd) % 64))
if(self.boot2 == True):
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("tik").getTitleKey()
contents = TMD("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):
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(rawtik))
out += str(TMD(rawtmd))
return out