more pywii cleanup :/

This commit is contained in:
Xuzz 2009-07-02 09:59:03 -07:00
parent e1a5087296
commit 126d9f5913
12 changed files with 0 additions and 4540 deletions

View File

@ -1,282 +0,0 @@
import struct, sys
class StructType(tuple):
def __getitem__(self, value):
return [self] * value
def __call__(self, value, endian='<'):
if isinstance(value, str):
return struct.unpack(endian + tuple.__getitem__(self, 0), value[:tuple.__getitem__(self, 1)])[0]
else:
return struct.pack(endian + tuple.__getitem__(self, 0), value)
class StructException(Exception):
pass
class Struct(object):
__slots__ = ('__attrs__', '__baked__', '__defs__', '__endian__', '__next__', '__sizes__', '__values__')
int8 = StructType(('b', 1))
uint8 = StructType(('B', 1))
int16 = StructType(('h', 2))
uint16 = StructType(('H', 2))
int32 = StructType(('l', 4))
uint32 = StructType(('L', 4))
int64 = StructType(('q', 8))
uint64 = StructType(('Q', 8))
float = StructType(('f', 4))
@classmethod
def string(cls, len, offset=0, encoding=None, stripNulls=False, value=''):
return StructType(('string', (len, offset, encoding, stripNulls, value)))
LE = '<'
BE = '>'
__endian__ = '<'
def __init__(self, func=None, unpack=None, **kwargs):
self.__defs__ = []
self.__sizes__ = []
self.__attrs__ = []
self.__values__ = {}
self.__next__ = True
self.__baked__ = False
if func == None:
self.__format__()
else:
sys.settrace(self.__trace__)
func()
for name in func.func_code.co_varnames:
value = self.__frame__.f_locals[name]
self.__setattr__(name, value)
self.__baked__ = True
if unpack != None:
if isinstance(unpack, tuple):
self.unpack(*unpack)
else:
self.unpack(unpack)
if len(kwargs):
for name in kwargs:
self.__values__[name] = kwargs[name]
def __trace__(self, frame, event, arg):
self.__frame__ = frame
sys.settrace(None)
def __setattr__(self, name, value):
if name in self.__slots__:
return object.__setattr__(self, name, value)
if self.__baked__ == False:
if not isinstance(value, list):
value = [value]
attrname = name
else:
attrname = '*' + name
self.__values__[name] = None
for sub in value:
if isinstance(sub, Struct):
sub = sub.__class__
try:
if issubclass(sub, Struct):
sub = ('struct', sub)
except TypeError:
pass
type_, size = tuple(sub)
if type_ == 'string':
self.__defs__.append(Struct.string)
self.__sizes__.append(size)
self.__attrs__.append(attrname)
self.__next__ = True
if attrname[0] != '*':
self.__values__[name] = size[3]
elif self.__values__[name] == None:
self.__values__[name] = [size[3] for val in value]
elif type_ == 'struct':
self.__defs__.append(Struct)
self.__sizes__.append(size)
self.__attrs__.append(attrname)
self.__next__ = True
if attrname[0] != '*':
self.__values__[name] = size()
elif self.__values__[name] == None:
self.__values__[name] = [size() for val in value]
else:
if self.__next__:
self.__defs__.append('')
self.__sizes__.append(0)
self.__attrs__.append([])
self.__next__ = False
self.__defs__[-1] += type_
self.__sizes__[-1] += size
self.__attrs__[-1].append(attrname)
if attrname[0] != '*':
self.__values__[name] = 0
elif self.__values__[name] == None:
self.__values__[name] = [0 for val in value]
else:
try:
self.__values__[name] = value
except KeyError:
raise AttributeError(name)
def __getattr__(self, name):
if self.__baked__ == False:
return name
else:
try:
return self.__values__[name]
except KeyError:
raise AttributeError(name)
def __len__(self):
ret = 0
arraypos, arrayname = None, None
for i in range(len(self.__defs__)):
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
if sdef == Struct.string:
size, offset, encoding, stripNulls, value = size
if isinstance(size, str):
size = self.__values__[size] + offset
elif sdef == Struct:
if attrs[0] == '*':
if arrayname != attrs:
arrayname = attrs
arraypos = 0
size = len(self.__values__[attrs[1:]][arraypos])
size = len(self.__values__[attrs])
ret += size
return ret
def unpack(self, data, pos=0):
for name in self.__values__:
if not isinstance(self.__values__[name], Struct):
self.__values__[name] = None
elif self.__values__[name].__class__ == list and len(self.__values__[name]) != 0:
if not isinstance(self.__values__[name][0], Struct):
self.__values__[name] = None
arraypos, arrayname = None, None
for i in range(len(self.__defs__)):
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
if sdef == Struct.string:
size, offset, encoding, stripNulls, value = size
if isinstance(size, str):
size = self.__values__[size] + offset
temp = data[pos:pos+size]
if len(temp) != size:
raise StructException('Expected %i byte string, got %i' % (size, len(temp)))
if encoding != None:
temp = temp.decode(encoding)
if stripNulls:
temp = temp.rstrip('\0')
if attrs[0] == '*':
name = attrs[1:]
if self.__values__[name] == None:
self.__values__[name] = []
self.__values__[name].append(temp)
else:
self.__values__[attrs] = temp
pos += size
elif sdef == Struct:
if attrs[0] == '*':
if arrayname != attrs:
arrayname = attrs
arraypos = 0
name = attrs[1:]
self.__values__[attrs][arraypos].unpack(data, pos)
pos += len(self.__values__[attrs][arraypos])
arraypos += 1
else:
self.__values__[attrs].unpack(data, pos)
pos += len(self.__values__[attrs])
else:
values = struct.unpack(self.__endian__+sdef, data[pos:pos+size])
pos += size
j = 0
for name in attrs:
if name[0] == '*':
name = name[1:]
if self.__values__[name] == None:
self.__values__[name] = []
self.__values__[name].append(values[j])
else:
self.__values__[name] = values[j]
j += 1
return self
def pack(self):
arraypos, arrayname = None, None
ret = ''
for i in range(len(self.__defs__)):
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
if sdef == Struct.string:
size, offset, encoding, stripNulls, value = size
if isinstance(size, str):
size = self.__values__[size]+offset
if attrs[0] == '*':
if arrayname != attrs:
arraypos = 0
arrayname = attrs
temp = self.__values__[attrs[1:]][arraypos]
arraypos += 1
else:
temp = self.__values__[attrs]
if encoding != None:
temp = temp.encode(encoding)
temp = temp[:size]
ret += temp + ('\0' * (size - len(temp)))
elif sdef == Struct:
if attrs[0] == '*':
if arrayname != attrs:
arraypos = 0
arrayname = attrs
ret += self.__values__[attrs[1:]][arraypos].pack()
arraypos += 1
else:
ret += self.__values__[attrs].pack()
else:
values = []
for name in attrs:
if name[0] == '*':
if arrayname != name:
arraypos = 0
arrayname = name
values.append(self.__values__[name[1:]][arraypos])
arraypos += 1
else:
values.append(self.__values__[name])
ret += struct.pack(self.__endian__+sdef, *values)
return ret
def __getitem__(self, value):
return [('struct', self.__class__)] * value

View File

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

View File

@ -1,501 +0,0 @@
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().loadFile("tik")
tmd = TMD().loadFile("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().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):
out = ""
out += "Wii WAD:\n"
fd = open(self.f, 'rb')
if(self.boot2 != True):
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding= struct.unpack('>I4s6I32s', fd.read(64))
else:
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', fd.read(32))
rawcert = fd.read(certsize)
if(certsize % 64 != 0):
fd.seek(64 - (certsize % 64), 1)
rawtik = fd.read(tiksize)
if(self.boot2 != True):
if(tiksize % 64 != 0):
fd.seek(64 - (tiksize % 64), 1)
rawtmd = fd.read(tmdsize)
if(self.boot2 != True):
out += " Header %02x Type '%s' Certs %x Tiket %x TMD %x Data %x Footer %x\n" % (headersize, wadtype, certsize, tiksize, tmdsize, datasize, footersize)
else:
out += " Header %02x Type 'boot2' Certs %x Tiket %x TMD %x Data @ %x\n" % (headersize, certsize, tiksize, tmdsize, data_offset)
out += str(Ticket().load(rawtik))
out += str(TMD().load(rawtmd))
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,64 +0,0 @@
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):
x += (x + boundary) - (x % boundary)
return x
def hexdump(s, sep=" "):
return sep.join(map(lambda x: "%02x" % ord(x), s))
class Crypto:
"""This is a Cryptographic/hash class used to abstract away things (to make changes easier)"""
def __init__(self):
self.align = 64
return
def decryptData(self, key, iv, data, align = True):
"""Decrypts some data (aligns to 64 bytes, if needed)."""
if((len(data) % self.align) != 0 and align):
return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
else:
return AES.new(key, AES.MODE_CBC, iv).decrypt(data)
def encryptData(self, key, iv, data, align = True):
"""Encrypts some data (aligns to 64 bytes, if needed)."""
if((len(data) % self.align) != 0 and align):
return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
else:
return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
def decryptContent(self, titlekey, idx, data):
"""Decrypts a Content."""
iv = struct.pack(">H", idx) + "\x00" * 14
return self.decryptData(titlekey, iv, data)
def decryptTitleKey(self, commonkey, tid, enckey):
"""Decrypts a Content."""
iv = struct.pack(">Q", tid) + "\x00" * 8
return self.decryptData(commonkey, iv, enckey, False)
def encryptContent(self, titlekey, idx, data):
"""Encrypts a Content."""
iv = struct.pack(">H", idx) + "\x00" * 14
return self.encryptData(titlekey, iv, data)
def createSHAHash(self, data): #tested WORKING (without padding)
return hashlib.sha1(data).digest()
def createSHAHashHex(self, data):
return hashlib.sha1(data).hexdigest()
def createMD5HashHex(self, data):
return hashlib.md5(data).hexdigest()
def createMD5Hash(self, data):
return hashlib.md5(data).digest()
def validateSHAHash(self, data, hash):
"""Validates a hash. Not checking currently because we have some...issues with hashes."""
return 1 #hack
if((len(data) % self.align) != 0):
datax = data + ("\x00" * (self.align - (len(data) % align)))
else:
datax = data
# if(hashlib.sha1(datax).hexdigest() == hexdump(hash, "")):
# return 1
# else:
# return 0

View File

@ -1,75 +0,0 @@
from common import *
class LZ77():
class WiiLZ77: #class by marcan
TYPE_LZ77 = 1
def __init__(self, file, offset):
self.file = file
self.offset = offset
self.file.seek(self.offset)
hdr = struct.unpack("<I",self.file.read(4))[0]
self.uncompressed_length = hdr>>8
self.compression_type = hdr>>4 & 0xF
if self.compression_type != self.TYPE_LZ77:
raise ValueError("Unsupported compression method %d"%self.compression_type)
def uncompress(self):
dout = ""
self.file.seek(self.offset + 0x4)
while len(dout) < self.uncompressed_length:
flags = struct.unpack("<B",self.file.read(1))[0]
for i in range(8):
if flags & 0x80:
info = struct.unpack(">H",self.file.read(2))[0]
num = 3 + ((info>>12)&0xF)
disp = info & 0xFFF
ptr = len(dout) - (info & 0xFFF) - 1
for i in range(num):
dout += dout[ptr]
ptr+=1
if len(dout) >= self.uncompressed_length:
break
else:
dout += self.file.read(1)
flags <<= 1
if len(dout) >= self.uncompressed_length:
break
self.data = dout
return self.data
def __init__(self, f):
self.f = f
def decompress(self, fn = ""):
"""This uncompresses a LZ77 compressed file, specified in f in the initializer. It outputs the file in either fn, if it isn't empty, or overwrites the input if it is. Returns the output filename."""
file = open(self.f, "rb")
hdr = file.read(4)
if hdr != "LZ77":
if(fn == ""):
return self.f
else:
data = open(self.f, "rb").read()
open(fn, "wb").write(data)
unc = self.WiiLZ77(file, file.tell())
data = unc.uncompress()
file.close()
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
open(self.f, "wb").write(data)
return self.f
def compress(self, fn = ""):
"""This will eventually add LZ77 compression to a file. Does nothing for now."""
if(fn != ""):
#subprocess.call(["./gbalzss", self.f, fn, "-pack"])
return fn
else:
#subprocess.call(["./gbalzss", self.f, self.f, "-pack"])
return self.f

View File

@ -1,357 +0,0 @@
from common import *
from title import *
class WOD: #WiiOpticalDisc
class discHeader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.discId = Struct.string(1)
self.gameCode = Struct.string(2)
self.region = Struct.string(1)
self.makerCode = Struct.uint8[2]
self.h = Struct.uint8
self.version = Struct.uint8
self.audioStreaming = Struct.uint8
self.streamingBufSize = Struct.uint8
self.unused = Struct.uint8[14]
self.magic = Struct.uint32
self.title = Struct.string(64)
self.hashVerify = Struct.uint8
self.h3verify = Struct.uint8
def __str__(self):
ret = ''
ret += '%s [%s%s%s]\n' % (self.title, self.discId, self.gameCode, self.region)
if self.region == 'P':
ret += 'Region : PAL\n'
elif self.region == 'E':
ret += 'Region : NTSC\n'
elif self.region == 'J':
ret += 'Region : JPN\n'
ret += 'Version 0x%x Maker %i%i Audio streaming %x\n' % (self.version, self.makerCode[0], self.makerCode[1], self.audioStreaming)
ret += 'Hash verify flag 0x%x H3 verify flag : 0x%x\n' % (self.hashVerify, self.h3verify)
return ret
# Many many thanks to Wiipower
class Apploader(Struct):
__endian__ = Struct.BE
def __format__(self):
self.buildDate = Struct.string(16)
self.entryPoint = Struct.uint32
self.size = Struct.uint32
self.trailingSize = Struct.uint32
self.padding = Struct.uint8[4]
def __str__(self):
ret = ''
ret += 'Apploader built on %s\n' % self.buildDate
ret += 'Entry point 0x%x\n' % self.entryPoint
ret += 'Size %i (%i of them are trailing)\n' % (self.size, self.trailingSize)
return ret
def __str__(self):
ret = ''
ret += '%s\n' % self.discHdr
ret += 'Found %i partitions (table at 0x%x)\n' % (self.partitionCount, self.partsTableOffset)
ret += 'Found %i channels (table at 0x%x)\n' % (self.channelsCount, self.chansTableOffset)
ret += '\n'
ret += 'Partition %i opened (type 0x%x) at 0x%x\n' % (self.partitionOpen, self.partitionType, self.partitionOffset)
ret += 'Partition name : %s' % self.partitionHdr
ret += 'Partition key : %s\n' % hexdump(self.partitionKey)
ret += 'Partition IOS : IOS%i\n' % self.partitionIos
ret += 'Partition tmd : 0x%x (%x)\n' % (self.tmdOffset, self.tmdSize)
ret += 'Partition main.dol : 0x%x (%x)\n' % (self.dolOffset, self.dolSize)
ret += 'Partition FST : 0x%x (%x)\n' % (self.fstSize, self.fstOffset)
ret += '%s\n' % (self.appLdr)
return ret
def __init__(self, f):
self.f = f
self.fp = open(f, 'rb')
self.discHdr = self.discHeader().unpack(self.fp.read(0x400))
if self.discHdr.magic != 0x5D1C9EA3:
raise Exception('Wrong disc magic')
self.fp.seek(0x40000)
self.partitionCount = 1 + struct.unpack(">I", self.fp.read(4))[0]
self.partsTableOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
self.channelsCount = struct.unpack(">I", self.fp.read(4))[0]
self.chansTableOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
self.markedBlocks = []
self.partitionOpen = -1
self.partitionOffset = -1
self.partitionType = -1
def markContent(self, offset, size):
blockStart = offset / 0x7C00
blockLen = (align(size, 0x7C00)) / 0x7C00
for x in range(blockStart, blockStart + blockLen):
try:
self.markedBlocks.index(blockStart + x)
except:
self.markedBlocks.append(blockStart + x)
def decryptBlock(self, block):
if len(block) != 0x8000:
raise Exception('Block size too big/small')
blockIV = block[0x3d0:0x3e0]
#print 'IV %s (len %i)\n' % (hexdump(blockIV), len(blockIV))
blockData = block[0x0400:0x8000]
return Crypto().decryptData(self.partitionKey, blockIV, blockData, True)
def readBlock(self, blockNumber):
self.fp.seek(self.partitionOffset + 0x20000 + (0x8000 * blockNumber))
return self.decryptBlock(self.fp.read(0x8000))
def readPartition(self, offset, size):
readStart = offset / 0x7C00
readLen = (align(size, 0x7C00)) / 0x7C00
blob = ''
#print 'Read at 0x%x (Start on %i block, ends at %i block) for %i bytes' % (offset, readStart, readStart + readLen, size)
self.fp.seek(self.partitionOffset + 0x20000 + (0x8000 * readStart))
for x in range(readLen + 1):
blob += self.decryptBlock(self.fp.read(0x8000))
self.markContent(offset, size)
#print 'Read from 0x%x to 0x%x' % (offset, offset + size)
offset -= readStart * 0x7C00
return blob[offset:offset + size]
def readUnencrypted(self, offset, size):
if offset + size > 0x20000:
raise Exception('This read is on encrypted data')
# FIXMII : Needs testing, extracting the tmd cause to have 10 null bytes in the end instead of 10 useful bytes at start :|
self.fp.seek(self.partitionOffset + 0x2A4 + offset)
return self.fp.read(size)
class fstObject(object):
#TODO: add ability to extract file by path
def __init__(self, name, iso=None):
''' do init stuff here '''
self.parent = None
self.type = 1 #directory: 1, file:0
self.name = name
self.nameOff = 0
self.fileOffset = 0
self.size = 0
self.children = []
self.iso = iso
def addChild(self, child):
if self.type == 0:
raise Exception('I am not a directory.')
child.parent = self
self.children.append(child)
def getISO(self):
if(self.parent == None):
return self.iso
return self.parent.getISO()
def getList(self, pad=0):
if self.type == 0:
return ("\t" * pad) + self.getPath() + "\n"
str = "%s[%s]\n" % ("\t" * (pad), self.getPath())
for child in self.children:
str += child.getList(pad+1)
return str
def count(self):
if self.type == 0:
return 1
i = 0
for child in self.children:
i += child.count()
return i
def getPath(self):
if(self.parent == None):
return "/"
if(self.type == 1):
return self.parent.getPath() + self.name + "/"
return self.parent.getPath() + self.name
def write(self, cwd):
if(self.type==0):
print cwd + self.getPath()
#print self.nameOff
open(cwd + self.getPath(), 'w+b').write(self.getISO().readPartition(self.fileOffset, self.size))
if(self.type==1):
if(self.parent != None):
try:
os.makedirs(cwd + self.getPath())
except:
j = None
for child in self.children:
child.write(cwd)
def parseFst(self, fst, names, i, fstDir):
size = struct.unpack(">I", fst[(12*i + 8):(12*i + 8) + 4])[0]
nameOff = struct.unpack(">I", fst[(12*i):(12*i) + 4])[0] & 0x00ffffff
fileName = names[nameOff:]
fileName = fileName[:fileName.find('\0')]
if i == 0:
j = 1
while(j<size):
j = self.parseFst(fst, names, j, fstDir)
return size
if fst[12 * i] == '\x01':
newDir = self.fstObject(fileName)
j = i+1
while(j<size):
j = self.parseFst(fst, names, j, newDir)
fstDir.addChild(newDir)
return size
else:
fileOffset = 4 * struct.unpack(">I", fst[(12*i + 4):(12*i + 4) + 4])[0]
newFile = self.fstObject(fileName)
newFile.type = 0
newFile.fileOffset = fileOffset
newFile.size = size
newFile.nameOff = nameOff
fstDir.addChild(newFile)
self.markContent(fileOffset, size)
return i+1
def openPartition(self, index):
if index+1 > self.partitionCount:
raise ValueError('Partition index too big')
self.partitionOpen = index
self.partitionOffset = self.partsTableOffset + (8 * self.partitionOpen)
self.fp.seek(self.partsTableOffset + (8 * self.partitionOpen))
self.partitionOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
self.partitionType = struct.unpack(">I", self.fp.read(4))[0]
self.fp.seek(self.partitionOffset)
self.tikData = self.fp.read(0x2A4)
self.partitionKey = Ticket().load(self.tikData).getTitleKey()
self.tmdSize = struct.unpack(">I", self.fp.read(4))[0]
self.tmdOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
self.certsSize = struct.unpack(">I", self.fp.read(4))[0]
self.certsOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
self.H3TableOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
self.dataOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
self.dataSize = struct.unpack(">I", self.fp.read(4))[0] >> 2
self.fstOffset = 4 * struct.unpack(">I", self.readPartition (0x424, 4))[0]
self.fstSize = 4 * struct.unpack(">I", self.readPartition (0x428, 4))[0]
self.dolOffset = 4 * struct.unpack(">I", self.readPartition (0x420, 4))[0]
self.dolSize = self.fstOffset - self.dolOffset
self.appLdr = self.Apploader().unpack(self.readPartition (0x2440, 32))
self.partitionHdr = self.discHeader().unpack(self.readPartition (0x0, 0x400))
self.partitionIos = TMD().load(self.getPartitionTmd()).getIOSVersion() & 0x0fffffff
def getFst(self):
fstBuf = self.readPartition(self.fstOffset, self.fstSize)
return fstBuf
def getIsoBootmode(self):
if self.discHdr.discId == 'R' or self.discHdr.discId == '_':
return 2
elif self.discHdr.discId == '0':
return 1
def getOpenedPartition(self):
return self.partitionOpen
def getOpenedPartitionOffset(self):
return self.partitionOffset
def getOpenedPartitionType(self):
return self.partitionType
def getPartitionsCount(self):
return self.partitionCount
def getChannelsCount(self):
return self.channelsCount
def getPartitionCerts(self):
return self.readUnencrypted(self.certsOffset, self.certsSize)
def getPartitionH3Table(self):
return self.readUnencrypted(self.H3TableOffset, 0x18000)
def getPartitionTmd(self):
return self.readUnencrypted(self.tmdOffset, self.tmdSize)
def getPartitionTik(self):
self.fp.seek(self.partitionOffset)
return self.fp.read(0x2A4)
def getPartitionApploader(self):
return self.readPartition (0x2440, self.appLdr.size + self.appLdr.trailingSize + 32)
def getPartitionMainDol(self):
return self.readPartition (self.dolOffset, self.dolSize)
def dumpPartition(self, fn):
rawPartition = open(fn, 'w+b')
print 'Partition useful data %i Mb' % (align(len(self.markedBlocks) * 0x7C00, 1024) / 1024 / 1024)
self.fp.seek(self.partitionOffset)
rawPartition.write(self.fp.read(0x2A4)) # Write teh TIK
rawPartition.write(self.readUnencrypted(0, 0x20000 - 0x2A4)) # Write the TMD and other stuff
for x in range(len(self.markedBlocks)):
rawPartition.write(self.readBlock(self.markedBlocks[x])) # Write each decrypted block
class updateInf():
def __init__(self, f):
self.buffer = open(f, 'r+b').read()
def __str__(self):
out = ''
self.buildDate = self.buffer[:0x10]
self.fileCount = struct.unpack('>L', self.buffer[0x10:0x14])[0]
out += 'This update partition was built on %s and has %i files\n\n' % (self.buildDate, self.fileCount)
for x in range(self.fileCount):
updateEntry = self.buffer[0x20 + x * 0x200:0x20 + (x + 1) * 0x200]
titleType = struct.unpack('>L', updateEntry[:0x4])[0]
titleAttr = struct.unpack('>L', updateEntry[0x4:0x8])[0]
titleUnk1 = struct.unpack('>L', updateEntry[0x8:0xC])[0]
titleType2 = struct.unpack('>L', updateEntry[0xC:0x10])[0]
titleFile = updateEntry[0x10:0x50]
titleFile = titleFile[:titleFile.find('\x00')]
titleID = struct.unpack('>Q', updateEntry[0x50:0x58])[0]
titleMajor = struct.unpack('>B', updateEntry[0x58:0x59])[0]
titleMinor = struct.unpack('>B', updateEntry[0x59:0x5A])[0]
titleName = updateEntry[0x60:0xA0]
titleName = titleName[:titleName.find('\x00')]
titleInfo = updateEntry[0xA0:0xE0]
titleInfo = titleInfo[:titleInfo.find('\x00')]
out += 'Update type : 0x%x\n' % titleType
out += 'Update flag : %i (0 means critical, 1 means need reboot)\n' % titleAttr
out += 'Update file : %s\n' % titleFile
out += 'Update ID : %lu\n' % titleID
out += 'Update version : %i.%i\n' % (titleMajor, titleMinor)
out += 'Update name : %s\n' % titleName
out += 'Update info : %s\n' % titleInfo
out += '\n'
return out

View File

@ -1,253 +0,0 @@
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,887 +0,0 @@
from binascii import *
import struct
from common import *
from title import *
class locDat:
class locHeader(Struct):
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'
self.titles = []
self.usedBlocks = 0
self.freeBlocks = 0
try:
self.fp = open(f, 'r+')
except:
raise Exception('File not found')
plainBuffer = Crypto().decryptData(self.sdKey, self.sdIv, self.fp.read(), False)
self.hdr = self.locHeader().unpack(plainBuffer[:0x14])
for x in range(240):
self.titles.append(plainBuffer[0x14 + x * 4:0x14 + (x + 1) * 4])
if self.titles[x] == '\x00\x00\x00\x00':
self.freeBlocks += 1
self.usedBlocks = 240 - self.freeBlocks
def __str__(self):
out = ''
out += 'Used %i blocks out of 240\n\n' % self.usedBlocks
for x in range(240):
if self.titles[x] == '\x00\x00\x00\x00':
out += 'Block %i on page %i is empty\n' % (x, x / 12)
else:
out += 'Block %i on page %i hold title %s\n' % (x, x / 12, self.titles[x])
return out
def getFreeBlocks(self):
return self.freeBlocks
def getUsedBlocks(self):
return self.usedBlocks
def isBlockFree(self, x, y, page):
if self.titles[((x + (y * 4) + (page * 12)))] == '\x00\x00\x00\x00':
return 1
return 0
def isTitleInList(self, title):
try:
return self.titles.index(title.upper())
except:
return -1
def getPageTitles(self, page):
if page > 19:
raise Exception('Out of bounds')
return self.titles[12 * page:12 * (page + 1)]
def getTitle(self, x, y, page):
if x > 3 or y > 2 or page > 19:
raise Exception('Out of bounds')
return self.titles[((x + (y * 4) + (page * 12)))]
def setTitle(self, x, y, page, element):
if x > 3 or y > 2 or page > 19 or len(element) > 4:
raise Exception('Out of bounds')
self.titles[((x + (y * 4) + (page * 12)))] = element.upper()
titles = ''
titles += self.hdr.magic
titles += self.hdr.md5
for x in range(240):
titles += self.titles[x]
titles += '\x00' * 12
titles = titles[:0x4] + Crypto().createMD5Hash(titles) + titles[0x14:]
self.fp.seek(0)
self.fp.write(Crypto().encryptData(self.sdKey, self.sdIv, titles))
def delTitle(self, x, y, page):
self.setTitle(x, y, page, '\x00\x00\x00\x00')
class CONF:
"""This class deals with setting.txt which holds some important information like region and serial number """
def __init__(self, f):
self.conf = ''
self.keys = {}
self.lastKeyOffset = 0
self.totalKeys = 0
try:
self.fp = open(f, 'r+b')
except:
self.fp = open(f, 'w+b')
return
self.conf = self.fp.read(0x100)
self.conf = self.XORConf(self.conf)
self.fp.seek(0)
keys = self.conf.split('\r\n')
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
self.totalKeys = len(keys) - 1
for x in range(self.totalKeys):
keyName = keys[x].split('=')[0]
keyVal = keys[x].split('=')[1]
self.keys[keyName] = keyVal
def getKeysCount(self):
"""Gets how many keys exist."""
return self.totalKeys
def getKeysName(self):
"""Returns the list of key names."""
return self.keys.keys()
def getKeyValue(self, key):
"""Returns the value of the key ``key''."""
try:
return self.keys[key.upper()]
except KeyError:
return 'Key not found'
def setKeyValue(self, key, value):
"""Sets the value of key ``key'' to ``value''."""
if(self.keyExist(key)):
self.keys[key.upper()] = value.upper()
self.conf = ''
for key in self.keys:
self.conf += key
self.conf += '='
self.conf += self.keys[key]
self.conf += '\r\n'
self.fp.seek(0)
self.fp.write(self.XORConf(self.conf))
self.fp.write('\x00' * (0x100 - len(self.conf)))
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
def keyExist(self, key):
return self.keys.has_key(key.upper())
def addKey(self, key, value):
"""Adds key ``key'' with value ``value'' to the list."""
if self.lastKeyOffset + len(key) + 1 + len(value) + 2 > 0x100:
return -1
if not self.keyExist(key):
return -2
self.keys[key.upper()] = value.upper()
self.totalKeys +=1
self.conf = self.conf[:self.lastKeyOffset] + key.upper() + '=' + value.upper() + '\r\n'
self.lastKeyOffset += len(key) + 1 + len(value) + 2
self.fp.seek(0)
self.fp.write(self.XORConf(self.conf))
def XORConf(self, conf):
"""Encrypts/decrypts the setting.txt file."""
XORKey = 0x73B5DBFA
out = ''
for x in range(len(conf)):
out += chr(ord(conf[x]) ^ XORKey & 0xFF)
XORKey = (XORKey << 1) | (XORKey >> 31)
return out
def deleteKey(self, key):
"""Deletes the key ``key''."""
try:
del self.keys[key.upper()]
self.totalKeys -=1
self.conf = ''
for key in self.keys:
self.conf += key
self.conf += '='
self.conf += self.keys[key]
self.conf += '\r\n'
self.fp.seek(0)
self.fp.write(self.XORConf(self.conf))
self.fp.write('\x00' * (0x100 - len(self.conf)))
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
except KeyError:
return 'Key not found'
# This function is fucking dangerous. It deletes all keys with that value. Really not a good idea.
def deleteKeyByValue(self, value):
"""Deletes all keys with value ``value''. WATCH OUT, YOU MIGHT ACCIDENTALLY DELETE WRONG KEYS."""
try:
for key in self.keys.keys():
if self.keys.get(key) == value:
del self.keys[key]
self.totalKeys -=1
self.conf = ''
for key in self.keys:
self.conf += key
self.conf += '='
self.conf += self.keys[key]
self.conf += '\r\n'
self.fp.seek(0)
self.fp.write(self.XORConf(self.conf))
self.fp.write('\x00' * (0x100 - len(self.conf)))
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
except KeyError:
return 'Key not found'
def getRegion(self):
"""gets the Region key. (Shortcut for getKeyValue("GAME"))"""
return self.getKeyValue("GAME")
def getArea(self):
"""gets the Area key. (Shortcut for getKeyValue("AREA"))"""
return self.getKeyValue("AREA")
def getVideoMode(self):
"""gets the Video Mode key. (Shortcut for getKeyValue("VIDEO"))"""
return self.getKeyValue("VIDEO")
def getSerialCode(self):
"""gets the Serial Code key. (Shortcut for getKeyValue("CODE"))"""
return self.getKeyValue("CODE")
def getDVDModel(self): # Might not be model =/
"""gets the DVD Model (?) key. (Shortcut for getKeyValue("DVD"))"""
return self.getKeyValue("DVD")
def getHardwareModel(self):
"""gets the Hardware Model key. (Shortcut for getKeyValue("MODEL"))"""
return self.getKeyValue("MODEL")
def getSerialNumber(self):
"""gets the Serial Number key. (Shortcut for getKeyValue("SERNO"))"""
return self.getKeyValue("SERNO")
def setRegion(self, value):
"""sets the Region key. (Shortcut for setKeyValue("GAME", value))"""
return self.setKeyValue("GAME", value)
def setArea(self, value):
"""sets the Area key. (Shortcut for setKeyValue("AREA", value))"""
return self.setKeyValue("AREA", value)
def setVideoMode(self, value):
"""sets the Video Mode key. (Shortcut for setKeyValue("VIDEO", value))"""
return self.setKeyValue("VIDEO", value)
def setSerialCode(self, value):
"""sets the Serial Code key. (Shortcut for setKeyValue("CODE", value))"""
return self.setKeyValue("CODE", value)
def setDVDModel(self, value): # Might not be model =/
"""sets the DVD Model (?) key. (Shortcut for setKeyValue("DVD", value))"""
return self.setKeyValue("DVD", value)
def setHardwareModel(self, value):
"""sets the Hardware Model key. (Shortcut for setKeyValue("MODEL", value))"""
return self.setKeyValue("MODEL", value)
def setSerialNumber(self, value):
"""sets the Serial Number key. (Shortcut for setKeyValue("SERNO", value))"""
return self.setKeyValue("SERNO", value)
class netConfig:
"""This class performs network configuration. The file is located on the NAND at /shared2/sys/net/02/config.dat."""
class configEntry(Struct):
__endian__ = Struct.BE
def __format__(self):
self.selected = Struct.uint8
self.padding_1 = Struct.string(1987)
self.ssid = Struct.string(32)
self.padding_2 = Struct.uint8
self.ssid_len = Struct.uint8
self.padding_3 = Struct.string(2)
self.padding_4 = Struct.uint8
self.encryption = Struct.uint8 # OPEN: 0x00, WEP: 0x01, WPA-PSK (TKIP): 0x04, WPA2-PSK (AES): 0x05, WPA-PSK (AES): 0x06
self.padding_5 = Struct.string(2)
self.padding_6 = Struct.uint8
self.key_len = Struct.uint8
self.padding_7 = Struct.string(2)
self.key = Struct.string(64)
self.padding_3 = Struct.string(236)
def __init__(self, conf):
self.f = conf
if(not os.path.isfile(self.f)):
fp = open(self.f, "wb")
fp.write("\x00\x00\x00\x00\x01\x07\x00\x00")
fp.write("\x00" * 0x91C * 3)
fp.close()
fp = open(self.f, "rb")
head = fp.read(8)
if(head != "\x00\x00\x00\x00\x01\x07\x00\x00"):
print("Config file is invalid!\n")
def getNotBlank(self, config):
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config))
sel = struct.unpack(">B", fp.read(1))[0]
fp.close()
if(sel & 0x20):
return 1
return 0
def getIPType(self, config):
if(not self.getNotBlank(config)):
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config))
sel = struct.unpack(">B", fp.read(1))[0]
if(sel & 0x04):
return 0
else:
return 1
fp.close()
return sel
def getWireType(self, config):
if(not self.getNotBlank(config)):
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config))
sel = struct.unpack(">B", fp.read(1))[0]
if(sel & 0x02):
return 0
else:
return 1
fp.close()
def getSSID(self, config):
if(not self.getNotBlank(config)):
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config) + 2021)
len = struct.unpack(">B", fp.read(1))[0]
fp.seek(8 + (0x91C * config) + 1988)
ssid = fp.read(len)
fp.close()
return ssid
def getEncryptionType(self, config):
if(not self.getNotBlank(config)):
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config) + 2025)
crypt = struct.unpack(">B", fp.read(1))[0]
type = ""
if(crypt == 0):
type = "OPEN"
elif(crypt == 1):
type = "WEP"
elif(crypt == 4):
type = "WPA (TKIP)"
elif(crypt == 5):
type = "WPA2"
elif(crypt == 6):
type = "WPA (AES)"
else:
print("Invalid crypto type %02X. Valid types are: ``OPEN'', ``WEP'', ``WPA (TKIP)'', ``WPA2'', or ``WPA (AES)''\n" % crypt)
fp.close()
return None
fp.close()
return type
def getEncryptionKey(self, config):
if(not self.getNotBlank(config)):
return None
fp = open(self.f, "rb")
fp.seek(8 + (0x91C * config) + 2025)
crypt = struct.unpack(">B", fp.read(1))[0]
type = ""
if(crypt == 0):
type = "OPEN"
elif(crypt == 1):
type = "WEP"
elif(crypt == 4):
type = "WPA (TKIP)"
elif(crypt == 5):
type = "WPA2"
elif(crypt == 6):
type = "WPA (AES)"
else:
print("Invalid crypto type %02X. Valid types are: ``OPEN'', ``WEP'', ``WPA (TKIP)'', ``WPA2'', or ``WPA (AES)''\n" % crypt)
fp.close()
return None
if(crypt != "\x00"):
fp.seek(8 + (0x91C * config) + 2029)
keylen = struct.unpack(">B", fp.read(1))[0]
fp.seek(8 + (0x91C * config) + 2032)
key = fp.read(keylen)
fp.close()
return key
fp.close()
return None
def clearConfig(self, config):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config))
sel = struct.unpack(">B", fp.read(1))[0]
sel &= 0xDF
fp.seek(8 + (0x91C * config))
fp.write(struct.pack(">B", sel))
fp.close()
def setNotBlank(self, config):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config))
sel = struct.unpack(">B", fp.read(1))[0]
sel |= 0x20
fp.seek(8 + (0x91C * config))
fp.write(struct.pack(">B", sel))
fp.close()
def setIPType(self, config, static):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config))
sel = struct.unpack(">B", fp.read(1))[0]
if(not static):
sel |= 0x04
else:
sel &= 0xFB
fp.seek(8 + (0x91C * config))
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 = struct.unpack(">B", fp.read(1))[0]
if(not wired):
sel |= 0x02
else:
sel &= 0xFD
fp.seek(8 + (0x91C * config))
fp.write(struct.pack(">B", sel))
fp.close()
self.setNotBlank(config)
def setSSID(self, config, ssid):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config) + 1988)
fp.write(ssid)
fp.seek(8 + (0x91C * config) + 2021)
fp.write(a2b_hex("%02X" % len(ssid)))
fp.close()
self.setNotBlank(config)
def setEncryption(self, config, crypt, key):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * config) + 2025)
if(crypt == "OPEN"):
fp.write("\x00")
elif(crypt == "WEP"):
fp.write("\x01")
elif(crypt == "WPA (TKIP)"):
fp.write("\x04")
elif(crypt == "WPA2"):
fp.write("\x05")
elif(crypt == "WPA (AES)"):
fp.write("\x06")
else:
print("Invalid crypto type. Valid types are: ``OPEN'', ``WEP'', ``WPA (TKIP)'', ``WPA2'', or ``WPA (AES)''\n")
fp.close()
return
if(crypt != "OPEN"):
fp.seek(8 + (0x91C * config) + 2029)
fp.write(a2b_hex("%02X" % len(key)))
fp.seek(8 + (0x91C * config) + 2032)
fp.write(key)
fp.close()
self.setNotBlank(config)
def selectConfig(self, config):
fp = open(self.f, "rb+")
fp.seek(8 + (0x91C * 0))
sel = struct.unpack(">B", fp.read(1))[0]
if(config == 0):
sel |= 0x80
else:
sel &= 0x7F
fp.seek(8 + (0x91C * 0))
fp.write(struct.pack(">B", sel))
fp.seek(8 + (0x91C * 1))
sel = struct.unpack(">B", fp.read(1))[0]
if(config == 1):
sel |= 0x80
else:
sel &= 0x7F
fp.seek(8 + (0x91C * 1))
fp.write(struct.pack(">B", sel))
fp.seek(8 + (0x91C * 2))
sel = struct.unpack(">B", fp.read(1))[0]
if(config == 2):
sel |= 0x80
else:
sel &= 0x7F
fp.seek(8 + (0x91C * 2))
fp.write(struct.pack(">B", sel))
self.setNotBlank(config)
fp.close()
class ContentMap:
"""This class performs all content.map related actions. Has functions to add contents, and find contents by hash.
The ``map'' parameter is the location of the content.map file."""
def __init__(self, map):
self.f = map
if(not os.path.isfile(map)):
open(map, "wb").close()
def contentByHash(self, hash):
"""When passed a sha1 hash (string of length 20), this will return the filename of the shared content (/shared1/%08x.app, no NAND prefix) specified by the hash in content.map. Note that if the content is not found, it will return False - not an empty string."""
cmfp = open(self.f, "rb")
cmdict = {}
num = len(cmfp.read()) / 28
cmfp.seek(0)
for z in range(num):
name = cmfp.read(8)
hash = cmfp.read(20)
cmdict[name] = hash
for key, value in cmdict.iteritems():
if(value == hash):
return "/shared1/%s.app" % key
return False #not found
def addContentToMap(self, contentid, hash):
"""Adds a content to the content.map file for the contentid and hash.
Returns the content id."""
cmfp = open(self.f, "rb")
cmdict = {}
num = len(cmfp.read()) / 28
cmfp.seek(0)
for z in range(num):
name = cmfp.read(8)
hash = cmfp.read(20)
cmdict[name] = hash
cmdict["%08x" % contentid] = hash
cmfp.close()
cmfp = open(self.f, "wb")
for key, value in cmdict.iteritems():
cmfp.write(key)
cmfp.write(value)
cmfp.close()
return contentid
def addHashToMap(self, hash):
"""Adds a content to the content.map file for the hash (uses next unavailable content id)
Returns the content id."""
cmfp = open(self.f, "rb")
cmdict = {}
cnt = 0
num = len(cmfp.read()) / 28
cmfp.seek(0)
for z in range(num):
name = cmfp.read(8)
hasho = cmfp.read(20)
cmdict[name] = hasho
cnt += 1
cmdict["%08x" % cnt] = hash
cmfp.close()
cmfp = open(self.f, "wb")
for key, value in cmdict.iteritems():
cmfp.write(key)
cmfp.write(value)
cmfp.close()
return cnt
def contentCount(self):
cmfp = open(self.f, "rb")
cmdict = {}
cnt = 0
num = len(cmfp.read()) / 28
cmfp.seek(0)
for z in range(num):
name = cmfp.read(8)
hash = cmfp.read(20)
cmdict[name] = hash
cnt += 1
cmfp.close()
return cnt
def contentHashes(self, count):
cmfp = open(self.f, "rb")
num = len(cmfp.read()) / 28
if(num > count):
num = count
cmfp.seek(0)
hashout = ""
for z in range(num):
name = cmfp.read(8)
hashout += cmfp.read(20)
cmfp.close()
return hashout
class uidsys:
"""This class performs all uid.sys related actions. It includes functions to add titles and find titles from the uid.sys file.
The ``uid'' parameter is the location of the uid.sys file."""
class UIDSYSStruct(Struct):
__endian__ = Struct.BE
def __format__(self):
self.titleid = Struct.uint64
self.padding = Struct.uint16
self.uid = Struct.uint16
def __init__(self, uid):
self.f = uid
if(not os.path.isfile(uid)):
uidfp = open(uid, "wb")
uiddat = self.UIDSYSStruct()
uiddat.titleid = 0x0000000100000002
uiddat.padding = 0
uiddat.uid = 0x1000
uidfp.write(uiddat.pack())
uidfp.close()
if((os.path.isfile(uid)) and (len(open(uid, "rb").read()) == 0)):
uidfp = open(uid, "wb")
uiddat = self.UIDSYSStruct()
uiddat.titleid = 0x0000000100000002
uiddat.padding = 0
uiddat.uid = 0x1000
uidfp.write(uiddat.pack())
uidfp.close()
def getUIDForTitle(self, title):
uidfp = open(self.f, "rb")
uiddat = uidfp.read()
cnt = len(uiddat) / 12
uidfp.seek(0)
uidstr = self.UIDSYSStruct()
uidict = {}
for i in range(cnt):
uidstr.titleid = uidfp.read(8)
uidstr.padding = uidfp.read(2)
uidstr.uid = uidfp.read(2)
uidict[uidstr.titleid] = uidstr.uid
for key, value in uidict.iteritems():
if(hexdump(key, "") == ("%016X" % title)):
return value
return self.addTitle(title)
def getTitle(self, uid):
uidfp = open(self.f, "rb")
uiddat = uidfp.read()
cnt = len(uiddat) / 12
uidfp.seek(0)
uidstr = self.UIDSYSStruct()
uidict = {}
for i in range(cnt):
uidstr.titleid = uidfp.read(8)
uidstr.padding = uidfp.read(2)
uidstr.uid = uidfp.read(2)
uidict[uidstr.titleid] = uidstr.uid
for key, value in uidict.iteritems():
if(hexdump(value, "") == ("%04X" % uid)):
return key
return None
def addTitle(self, title):
uidfp = open(self.f, "rb")
uiddat = uidfp.read()
cnt = len(uiddat) / 12
uidfp.seek(0)
uidstr = self.UIDSYSStruct()
uidict = {}
enduid = "\x10\x01"
for i in range(cnt):
uidstr.titleid = uidfp.read(8)
uidstr.padding = uidfp.read(2)
uidstr.uid = uidfp.read(2)
if(hexdump(uidstr.titleid, "") == ("%016X" % title)):
uidfp.close()
return uidstr.uid
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()
uidfp = open(self.f, "wb")
for key, value in uidict.iteritems():
uidfp.write(key)
uidfp.write("\0\0")
uidfp.write(value)
uidfp.close()
return enduid
class iplsave:
"""This class performs all iplsave.bin related things. It includes functions to add a title to the list, remove a title based upon position or title, and move a title from one position to another."""
class IPLSAVE_Entry(Struct):
__endian__ = Struct.BE
def __format__(self):
self.type1 = Struct.uint8
self.type2 = Struct.uint8
self.unk = Struct.uint32
self.flags = Struct.uint16
self.titleid = Struct.uint64
class IPLSAVE_Header(Struct):
__endian__ = Struct.BE
def __format__(self):
self.magic = Struct.string(4)
self.filesize = Struct.uint32
self.unk = Struct.uint64
# 0x30 Entries go here.
self.unk2 = Struct.string(0x20)
self.md5 = Struct.string(0x10)
def __init__(self, f, nand = False):
self.f = f
if(not os.path.isfile(f)):
if(nand != False):
nand.newFile("/title/00000001/00000002/data/iplsave.bin", "rw----", 0x0001, 0x0000000100000002)
baseipl_h = self.IPLSAVE_Header
baseipl_ent = self.IPLSAVE_Entry
baseipl_ent.type1 = 0
baseipl_ent.type2 = 0
baseipl_ent.unk = 0
baseipl_ent.flags = 0
baseipl_ent.titleid = 0
baseipl_h.magic = "RIPL"
baseipl_h.filesize = 0x340
baseipl_h.unk = 0x0000000200000000
baseipl_h.unk2 = "\0" * 0x20
fp = open(f, "wb")
fp.write(baseipl_h.magic)
fp.write(a2b_hex("%08X" % baseipl_h.filesize))
fp.write(a2b_hex("%016X" % baseipl_h.unk))
i = 0
for i in range(0x30):
fp.write(a2b_hex("%02X" % baseipl_ent.type1))
fp.write(a2b_hex("%02X" % baseipl_ent.type2))
fp.write(a2b_hex("%08X" % baseipl_ent.unk))
fp.write(a2b_hex("%04X" % baseipl_ent.flags))
fp.write(a2b_hex("%016X" % baseipl_ent.titleid))
fp.write(baseipl_h.unk2)
fp.close()
self.updateMD5()
def updateMD5(self):
"""Updates the MD5 hash in the iplsave.bin file. Used by other functions here."""
fp = open(self.f, "rb")
data = fp.read()
fp.close()
md5 = Crypto().createMD5Hash(data)
fp = open(self.f, "wb")
fp.write(data)
fp.write(md5)
fp.close()
def slotUsed(self, x, y, page):
"""Returns whether or not the slot at (x,y) on page ``page'' is used."""
if((x + (y * 4) + (page * 12)) >= 0x30):
print "Too far!"
return None
fp = open(self.f, "rb")
data = fp.read()
fp.seek(16 + ((x + (y * 4) + (page * 12))) * 16)
baseipl_ent = self.IPLSAVE_Entry
baseipl_ent.type1 = fp.read(1)
baseipl_ent.type2 = fp.read(1)
baseipl_ent.unk = fp.read(4)
baseipl_ent.flags = fp.read(2)
baseipl_ent.titleid = fp.read(8)
fp.close()
if(baseipl_ent.type1 == "\0"):
return 0
return baseipl_ent.titleid
def addTitleBase(self, x, y, page, tid, movable, type, overwrite, clear, isdisc):
"""A base addTitle function that is used by others. Don't use this."""
if((x + (y * 4) + (page * 12)) >= 0x30):
print "Too far!"
return None
fp = open(self.f, "rb")
data = fp.read()
fp.seek(16 + ((x + (y * 4) + (page * 12))) * 16)
baseipl_ent = self.IPLSAVE_Entry
baseipl_ent.type1 = fp.read(1)
fp.close()
if((self.slotUsed(x, y, page)) and (not overwrite)):
return self.addTitleBase(x + 1, y, page, tid, movable, type, overwrite, clear, isdisc)
fp = open(self.f, "wb")
fp.write(data)
fp.seek(16 + ((x + (y * 4) + (page * 12))) * 16)
if((not clear) and (not isdisc)):
baseipl_ent.type1 = 3
baseipl_ent.type2 = type
baseipl_ent.unk = 0
baseipl_ent.flags = (movable ^ 1) + 0x0E
baseipl_ent.titleid = tid
if((clear) and (not isdisc)):
baseipl_ent.type1 = 0
baseipl_ent.type2 = 0
baseipl_ent.unk = 0
baseipl_ent.flags = 0
baseipl_ent.titleid = 0
if(isdisc):
baseipl_ent.type1 = 1
baseipl_ent.type2 = 1
baseipl_ent.unk = 0
baseipl_ent.flags = (movable ^ 1) + 0x0E
baseipl_ent.titleid = 0
fp.write(a2b_hex("%02X" % baseipl_ent.type1))
fp.write(a2b_hex("%02X" % baseipl_ent.type2))
fp.write(a2b_hex("%08X" % baseipl_ent.unk))
fp.write(a2b_hex("%04X" % baseipl_ent.flags))
fp.write(a2b_hex("%016X" % baseipl_ent.titleid))
fp.close()
self.updateMD5()
return (x + (y * 4) + (page * 12))
def addTitle(self, x, y, page, tid, movable, type):
"""Adds a title with title ID ``tid'' at location (x,y) on page ``page''. ``movable'' specifies whether the title is movable, and ``type'' specifies the type of title (00 for most titles.)"""
return self.addTitleBase(x, y, page, tid, movable, type, 0, 0, 0)
def addDisc(self, x, y, page, movable):
"""Adds the Disc Channel at location (x,y) on page ``page''. ``movable'' specifies whether it can be moved."""
return self.addTitleBase(x, y, page, 0, movable, 0, 0, 0, 1)
def deletePosition(self, x, y, page):
"""Deletes the title at (x,y) on page ``page''"""
return self.addTitleBase(x, y, page, 0, 0, 0, 1, 1, 0)
def deleteTitle(self, tid):
"""Deletes the title with title ID ``tid''"""
fp = open(self.f, "rb")
baseipl_ent = self.IPLSAVE_Entry
for i in range(0x30):
fp.seek(16 + (i * 16))
baseipl_ent.type1 = fp.read(1)
baseipl_ent.type2 = fp.read(1)
baseipl_ent.unk = fp.read(4)
baseipl_ent.flags = fp.read(2)
baseipl_ent.titleid = fp.read(8)
if(baseipl_ent.titleid == a2b_hex("%016X" % tid)):
self.deletePosition(i, 0, 0)
fp.close()
def moveTitle(self, x1, y1, page1, x2, y2, page2):
"""Moves a title from (x1,y1) on page ``page1'' to (x2,y2) on page ``page2''"""
fp = open(self.f, "rb")
baseipl_ent = self.IPLSAVE_Entry
fp.seek(16 + ((x1 + (y1 * 4) + (page1 * 12)) * 16))
baseipl_ent.type1 = fp.read(1)
baseipl_ent.type2 = fp.read(1)
baseipl_ent.unk = fp.read(4)
baseipl_ent.flags = fp.read(2)
baseipl_ent.titleid = fp.read(8)
fp.close()
self.deletePosition(x1, y1, page1)
return self.addTitle(x2, y2, page2, baseipl_ent.titleid, (baseipl_ent.flags - 0xE) ^ 1, baseipl_ent.type2)

View File

@ -1,145 +0,0 @@
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

View File

@ -1,796 +0,0 @@
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,816 +0,0 @@
from binascii import *
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):
self.f = f
if(not os.path.isdir(f)):
os.mkdir(f)
self.perms = f + "/permission.txt"
if(not os.path.isfile(self.perms)):
open(self.perms, "wb").close()
self.newDirectory("/sys", "rwrw--", 0)
self.newFile("/sys/uid.sys", "rwrw--", 0)
self.UID = uidsys(self.f + "/sys/uid.sys")
self.newDirectory("/meta", "rwrwrw", 0x0001, 0x0000000100000002)
self.newDirectory("/import", "rwrw--", 0x0000)
self.newDirectory("/shared1", "rwrw--", 0x0000)
self.newDirectory("/shared2", "rwrwrw", 0x0000)
self.newFile("/sys/cc.sys", "rwrw--", 0x0000)
self.newFile("/sys/cert.sys", "rwrwr-", 0x0000)
self.newFile("/sys/space.sys", "rwrw--", 0x0000)
self.newDirectory("/ticket", "rwrw--", 0x0000)
self.newDirectory("/title", "rwrwr-", 0x0000)
self.newDirectory("/tmp", "rwrwrw", 0x0000)
self.ES = ESClass(self)
self.ISFS = ISFSClass(self)
self.ES._setisfs()
self.ISFS._setes()
self.contentmap = ContentMap(self.f + "/shared1/content.map")
def hasPermissionEntry(self, dir):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
return 1
def removePermissionEntry(self, dir):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = -1
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
data = pfp.read(newlineloc)
pfp.seek(endloc + 1)
data += pfp.read()
pfp.close()
pfp = open(self.perms, "wb")
pfp.write(data)
pfp.close()
return 1
def _getFilePermissionBase(self, dir, loc):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = 0
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
if(loc > 0):
loc *= 2
pfp.seek(newlineloc + 1 + loc)
pdata = pfp.read(2)
pfp.close()
return pdata
def getFilePermissionOwner(self, dir):
pdata = self._getFilePermissionBase(dir, 0)
pval = 0
if(pdata[0] == "r"):
pval += 1
if(pdata[1] == "w"):
pval += 2
return pval
def getFilePermissionGroup(self, dir):
pdata = self._getFilePermissionBase(dir, 1)
pval = 0
if(pdata[0] == "r"):
pval += 1
if(pdata[1] == "w"):
pval += 2
return pval
def getFilePermissionOthers(self, dir):
pdata = self._getFilePermissionBase(dir, 2)
pval = 0
if(pdata[0] == "r"):
pval += 1
if(pdata[1] == "w"):
pval += 2
return pval
def getFilePermissionPerms(self, dir):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = 0
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
pfp.seek(newlineloc + 1)
pdata = pfp.read(6)
pfp.close()
return pdata
def _setFilePermissionBase(self, dir, loc, val):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = 0
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
if(loc > 0):
loc *= 2
pfp.seek(newlineloc + 1 + loc)
pfp.write(val)
pfp.close()
def setFilePermissionOwner(self, dir, val):
out = ""
if(val & 1):
out += "r"
if(val & 2):
out += "w"
self._setFilePermissionBase(dir, 0, out)
def setFilePermissionGroup(self, dir):
out = ""
if(val & 1):
out += "r"
if(val & 2):
out += "w"
self._setFilePermissionBase(dir, 1, out)
def setFilePermissionOthers(self, dir):
out = ""
if(val & 1):
out += "r"
if(val & 2):
out += "w"
self._setFilePermissionBase(dir, 2, out)
def isFileDirectory(self, dir):
pdata = self._getFilePermissionBase(dir, -1)
pval = 0
if(pdata[0] == "d"):
pval += 1
return pval
def getFilePermissionUID(self, dir):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = -1
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
pfp.seek(newlineloc + 8)
uidata = pfp.read(4)
pfp.close()
return int(uidata, 16)
def getFilePermissionGID(self, dir):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = -1
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
pfp.seek(newlineloc + 13)
gidata = pfp.read(4)
pfp.close()
return int(gidata, 16)
def setFilePermissionUID(self, dir, val):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = -1
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
pfp.seek(newlineloc + 8)
uidata = pfp.write("%04X" % val)
pfp.close()
return int(uidata, 16)
def setFilePermissionGID(self, dir, val):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
ret = data.find(dir)
if(ret == -1):
return 0
newlineloc = -1
for i in range(ret):
if(data.startswith("\n", i)):
newlineloc = i + 1
endloc = data.find("\n", newlineloc)
pfp = open(self.perms, "rb")
pfp.seek(newlineloc + 13)
gidata = pfp.write("%04X" % val)
pfp.close()
return int(gidata, 16)
def addPermissionEntry(self, uid, permissions, dir, groupid):
pfp = open(self.perms, "rb")
data = pfp.read()
pfp.close()
data += "%s " % permissions
if(uid == None):
print "UID is None!\n"
try:
data += hexdump(uid, "")
data += " "
except:
try:
data += "%04X " % uid
except:
print "UID type couldn't be confirmed..."
return
try:
data += hexdump(groupid, "")
data += " "
except:
try:
data += "%04X " % groupid
except:
print "GID type couldn't be confirmed..."
return
data += "%s\n" % dir
pfp = open(self.perms, "wb")
pfp.write(data)
pfp.close()
def newDirectory(self, dir, perms, groupid, permtitle = 0):
"""Creates a new directory in the NAND filesystem and adds a permissions entry."""
if(not self.hasPermissionEntry(dir)):
if(permtitle == 0):
if(not os.path.isdir(self.f + dir)):
os.mkdir(self.f + dir)
self.addPermissionEntry(0, "d" + perms, dir, groupid)
else:
if(not os.path.isdir(self.f + dir)):
os.mkdir(self.f + dir)
self.addPermissionEntry(self.getUIDForTitleFromUIDSYS(permtitle), "d" + perms, dir, groupid)
def newFile(self, fil, perms, groupid, permtitle = 0):
"""Creates a new file in the NAND filesystem and adds a permissions entry."""
if(not self.hasPermissionEntry(fil)):
if(permtitle == 0):
if(not os.path.isfile(self.f + fil)):
open(self.f + fil, "wb").close()
self.addPermissionEntry(0, "-" + perms, fil, groupid)
else:
if(not os.path.isfile(self.f + fil)):
open(self.f + fil, "wb").close()
self.addPermissionEntry(self.getUIDForTitleFromUIDSYS(permtitle), "-" + perms, fil, groupid)
def removeFile(self, fil):
"""Deletes a file, and removes the permissions entry."""
os.remove(self.f + fil)
self.removePermissionEntry(fil)
def getContentByHashFromContentMap(self, hash):
"""Gets the filename of a shared content with SHA1 hash ``hash''. This includes the NAND prefix."""
return self.f + self.contentmap.contentByHash(hash)
def addContentToContentMap(self, contentid, hash):
"""Adds a content with content ID ``contentid'' and SHA1 hash ``hash'' to the content.map."""
return self.contentmap.addContentToMap(contentid, hash)
def addHashToContentMap(self, hash):
"""Adds a content with SHA1 hash ``hash'' to the content.map. It returns the content ID used."""
return self.contentmap.addHashToMap(hash)
def getContentCountFromContentMap(self):
"""Returns the number of contents in the content.map."""
return self.contentmap.contentCount()
def getContentHashesFromContentMap(self, count):
"""Returns the hashes of ``count'' contents in the content.map."""
return self.contentmap.contentHashes(count)
def addTitleToUIDSYS(self, title):
"""Adds the title with title ID ``title'' to the uid.sys file."""
return self.UID.addTitle(title)
def getTitleFromUIDSYS(self, uid):
"""Gets the title ID with UID ``uid'' from the uid.sys file."""
return self.UID.getTitle(uid)
def getUIDForTitleFromUIDSYS(self, title):
"""Gets the UID for title ID ``title'' from the uid.sys file."""
ret = self.UID.getUIDForTitle(title)
return ret
def addTitleToMenu(self, tid):
"""Adds a title to the System Menu."""
a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin", self)
type = 0
if(((tid & 0xFFFFFFFFFFFFFF00) == 0x0001000248414300) or ((tid & 0xFFFFFFFFFFFFFF00) == 0x0001000248414200)):
type = 1
a.addTitle(0,0, 0, tid, 1, type)
def addDiscChannelToMenu(self, x, y, page, movable):
"""Adds the disc channel to the System Menu."""
a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin", self)
a.addDisc(x, y, page, movable)
def deleteTitleFromMenu(self, tid):
"""Deletes a title from the System Menu."""
a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin", self)
a.deleteTitle(tid)
def importTitle(self, prefix, tmd, tik, add_to_menu = True, is_decrypted = False, result_decrypted = False):
"""When passed a prefix (the directory to obtain the .app files from, sorted by content id), a TMD instance, and a Ticket instance, this will add that title to the NAND base folder specified in the constructor. If add_to_menu is True, the title (if neccessary) will be added to the menu. The default is True. Unless is_decrypted is set, the contents are assumed to be encrypted. If result_decrypted is True, then the contents will not end up decrypted."""
self.ES.AddTitleStart(tmd, None, None, is_decrypted, result_decrypted, use_version = True)
self.ES.AddTitleTMD(tmd)
self.ES.AddTicket(tik)
contents = tmd.getContents()
for i in range(tmd.tmd.numcontents):
self.ES.AddContentStart(tmd.tmd.titleid, contents[i].cid)
fp = open(prefix + "/%08x.app" % contents[i].cid, "rb")
data = fp.read()
fp.close()
self.ES.AddContentData(contents[i].cid, data)
self.ES.AddContentFinish(contents[i].cid)
self.ES.AddTitleFinish()
if(add_to_menu == True):
if(((tmd.tmd.titleid >> 32) != 0x00010008) and ((tmd.tmd.titleid >> 32) != 0x00000001)):
self.addTitleToMenu(tmd.tmd.titleid)
def createWADFromTitle(self, title, cert, output, version=0):
tmdpth = self.f + "/title/%08x/%08x/content/title.tmd" % (title >> 32, title & 0xFFFFFFFF)
if(version != 0):
tmdpth += ".%d" % version
tmd = TMD().loadFile(tmdpth)
if(not os.path.isdir("export")):
os.mkdir("export")
tmd.dump("export/tmd")
tik = Ticket().loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
tik.dump("export/tik")
contents = tmd.getContents()
for i in range(tmd.tmd.numcontents):
path = ""
if(contents[i].type == 0x0001):
path = self.f + "/title/%08x/%08x/content/%08x.app" % (title >> 32, title & 0xFFFFFFFF, contents[i].cid)
elif(contents[i].type == 0x8001):
path = self.getContentByHashFromContentMap(contents[i].hash)
fp = open(path, "rb")
data = fp.read()
fp.close()
fp = open("export/%08x.app" % contents[i].index, "wb")
fp.write(data)
fp.close()
fp = open(cert, "rb")
data = fp.read()
fp.close()
fp = open("export/cert", "wb")
fp.write(data)
fp.close()
WAD("export").pack(output)
for i in range(tmd.tmd.numcontents):
os.remove("export/%08x.app" % contents[i].index)
os.remove("export/tmd")
os.remove("export/tik")
os.remove("export/cert")
os.rmdir("export")
class ISFSClass:
"""This class contains an interface to the NAND that simulates the permissions system and all other aspects of the ISFS.
The nand argument to the initializer is a NAND object."""
class ISFSFP:
def __init__(self, file, mode):
self.fp = open(file, mode)
self.loc = 0
self.size = len(self.fp.read())
self.fp.seek(0)
self.SEEK_SET = 0
self.SEEK_CUR = 1
self.SEEK_END = 2
def seek(self, where, whence = 0):
if(whence == self.SEEK_SET):
self.loc = where
if(whence == self.SEEK_CUR):
self.loc += where
if(whence == self.SEEK_END):
self.loc = self.size - where
self.fp.seek(self.loc)
return self.loc
def close(self):
self.fp.close()
self.loc = 0
self.size = 0
def write(self, data):
leng = self.fp.write(data)
self.loc += leng
return leng
def read(self, length=""):
if(length == ""):
self.loc = self.size
return self.fp.read()
self.loc += length
return self.fp.read(length)
def __init__(self, nand):
self.nand = nand
self.f = nand.f
self.ES = None
def _setes(self):
self.ES = self.nand.ES
def _checkPerms(self, mode, uid, gid, own, grp, oth):
if(uid == self.ES.title):
if(own & mode):
return 1
elif(gid == self.ES.group):
if(grp & mode):
return 1
elif(oth & mode):
return 1
else:
return 0
def Open(self, file, mode):
if(not os.path.isfile(self.f + file)):
return None
modev = 0
if(mode.find("r") != -1):
modev = 1
elif(mode.find("w") != -1):
modev = 2
if(mode.find("+") != -1):
modev = 3
uid = self.nand.getFilePermissionUID(file)
gid = self.nand.getFilePermissionGID(file)
own = self.nand.getFilePermissionOwner(file)
grp = self.nand.getFilePermissionGroup(file)
oth = self.nand.getFilePermissionOthers(file)
if(self._checkPerms(modev, uid, gid, own, grp, oth) == 0):
return -41
return self.ISFSFP(self.f + file, mode)
def Close(self, fp):
fp.close()
def Delete(self, file):
uid = self.nand.getFilePermissionUID(file)
gid = self.nand.getFilePermissionGID(file)
own = self.nand.getFilePermissionOwner(file)
grp = self.nand.getFilePermissionGroup(file)
oth = self.nand.getFilePermissionOthers(file)
if(self._checkPerms(2, uid, gid, own, grp, oth) == 0):
return -41
self.nand.removeFile(file)
return 0
def CreateFile(self, filename, perms):
dirabove = filename
uid = self.nand.getFilePermissionUID(dirabove)
gid = self.nand.getFilePermissionGID(dirabove)
own = self.nand.getFilePermissionOwner(dirabove)
grp = self.nand.getFilePermissionGroup(dirabove)
oth = self.nand.getFilePermissionOthers(dirabove)
if(self._checkPerms(2, uid, gid, own, grp, oth) == 0):
return -41
self.nand.newFile(filename, perms, self.ES.group, self.ES.title)
return 0
def Write(self, fp, data):
return fp.write(data)
def Read(self, fp, length=""):
return fp.read(length)
def Seek(self, fp, where, whence):
return fp.seek(where, whence)
def CreateDir(self, dirname, perms):
dirabove = dirname
uid = self.nand.getFilePermissionUID(dirabove)
gid = self.nand.getFilePermissionGID(dirabove)
own = self.nand.getFilePermissionOwner(dirabove)
grp = self.nand.getFilePermissionGroup(dirabove)
oth = self.nand.getFilePermissionOthers(dirabove)
if(self._checkPerms(2, uid, gid, own, grp, oth) == 0):
return -41
self.nand.newDirectory(dirname, perms, self.ES.group, self.ES.title)
return 0
def GetAttr(self, filename): # Wheeee, stupid haxx to put all the numbers into one return value!
ret = self.nand.getFilePermissionUID(filename)
ret += (self.nand.getFilePermissionGID(filename) << 16)
ret += (self.nand.getFilePermissionOwner(filename) << 32)
ret += (self.nand.getFilePermissionGroup(filename) << 34)
ret += (self.nand.getFilePermissionOthers(filename) << 36)
return ret
def splitAttrUID(self, attr):
return attr & 0xFFFF
def splitAttrGID(self, attr):
return (attr >> 16) & 0xFFFF
def splitAttrOwner(self, attr):
return (attr >> 32) & 0xFF
def splitAttrGroup(self, attr):
return (attr >> 34) & 0xFF
def splitAttrOthers(self, attr):
return (attr >> 36) & 0xFF
def Rename(self, fileold, filenew):
uid = self.nand.getFilePermissionUID(fileold)
gid = self.nand.getFilePermissionGID(fileold)
own = self.nand.getFilePermissionOwner(fileold)
grp = self.nand.getFilePermissionGroup(fileold)
oth = self.nand.getFilePermissionOthers(fileold)
if(self._checkPerms(2, uid, gid, own, grp, oth) == 0):
return -41
fld = self.nand.isFileDirectory(fileold)
if(fld):
print "Directory moving is busted ATM. Will fix laterz.\n"
return -40
fp = self.Open(fileold, "rb")
data = fp.Read()
fp.close()
perms = ""
if(own & 1):
perms += "r"
else:
perms += "-"
if(own & 2):
perms += "w"
else:
perms += "-"
if(grp & 1):
perms += "r"
else:
perms += "-"
if(grp & 2):
perms += "w"
else:
perms += "-"
if(oth & 1):
perms += "r"
else:
perms += "-"
if(oth & 2):
perms += "w"
else:
perms += "-"
self.CreateFile(filenew, perms)
fp = self.Open(filenew, "wb")
fp.write(data)
fp.close()
self.Delete(fileold)
return 0
def SetAttr(self, filename, uid, gid=0, owner=0, group=0, others=0):
uidx = self.nand.getFilePermissionUID(filename)
gidx = self.nand.getFilePermissionGID(filename)
own = self.nand.getFilePermissionOwner(filename)
grp = self.nand.getFilePermissionGroup(filename)
oth = self.nand.getFilePermissionOthers(filename)
if(self._checkPerms(2, uidx, gidx, own, grp, oth) == 0):
return -41
self.nand.setFilePermissionUID(filename, uid)
self.nand.setFilePermissionGID(filename, gid)
self.nand.setFilePermissionOwner(filename, owner)
self.nand.setFilePermissionGroup(filename, group)
self.nand.setFilePermissionOthers(filename, others)
return 0
class ESClass:
"""This class performs all services relating to titles installed on the Wii. It is a clone of the libogc ES interface.
The nand argument to the initializer is a NAND object."""
def __init__(self, nand):
self.title = 0x0000000100000002
self.group = 0x0001
self.ticketadded = 0
self.tmdadded = 0
self.workingcid = 0
self.workingcidcnt = 0
self.nand = nand
self.f = nand.f
self.ISFS = None
def _setisfs(self):
self.ISFS = self.nand.ISFS
def getContentIndexFromCID(self, tmd, cid):
"""Gets the content index from the content id cid referenced to in the TMD instance tmd."""
for i in range(tmd.tmd.numcontents):
if(cid == tmd.contents[i].cid):
return tmd.contents[i].index
return None
def Identify(self, id, version=0):
if(not os.path.isfile(self.f + "/ticket/%08x/%08x.tik" % (id >> 32, id & 0xFFFFFFFF))):
return None
tik = Ticket().loadFile(self.f + "/ticket/%08x/%08x.tik" % (id >> 32, id & 0xFFFFFFFF))
titleid = tik.titleid
path = "/title/%08x/%08x/content/title.tmd" % (titleid >> 32, titleid & 0xFFFFFFFF)
if(version):
path += ".%d" % version
if(not os.path.isfile(self.f + path)):
return None
tmd = TMD().loadFile(self.f + path)
self.title = titleid
self.group = tmd.tmd.group_id
return self.title
def GetTitleID(self):
return self.title
def GetDataDir(self, titleid):
"""When passed a titleid, it will get the Titles data directory. If there is no title associated with titleid, it will return None."""
if(not os.path.isdir(self.f + "/title/%08x/%08x/data" % (titleid >> 32, titleid & 0xFFFFFFFF))):
return None
return self.f + "/title/%08x/%08x/data" % (titleid >> 32, titleid & 0xFFFFFFFF)
def GetStoredTMD(self, titleid, version=0):
"""Gets the TMD for the specified titleid and version"""
path = "/title/%08x/%08x/content/title.tmd" % (titleid >> 32, titleid & 0xFFFFFFFF)
if(version):
path += ".%d" % version
if(not os.path.isfile(self.f + path)):
return None
return TMD().loadFile(self.f + path)
def GetTitleContentsCount(self, titleid, version=0):
"""Gets the number of contents the title with the specified titleid and version has."""
tmd = self.GetStoredTMD(titleid, version)
if(tmd == None):
return 0
return tmd.tmd.numcontents
def GetTitleContents(self, titleid, count, version=0):
"""Returns a list of content IDs for title id ``titleid'' and version ``version''. It will return, at maximum, ``count'' entries."""
tmd = self.GetStoredTMD(titleid, version)
if(tmd == None):
return 0
contents = tmd.getContents()
out = ""
for z in range(count):
out += a2b_hex("%08X" % contents[z].cid)
return out
def GetNumSharedContents(self):
"""Gets how many shared contents exist on the NAND"""
return self.nand.getContentCountFromContentMap()
def GetSharedContents(self, cnt):
"""Gets cnt amount of shared content hashes"""
return self.nand.getContentHashesFromContentMap(cnt)
def AddTitleStart(self, tmd, certs, crl, is_decrypted = False, result_decrypted = True, use_version = False):
self.nand.addTitleToUIDSYS(tmd.tmd.titleid)
self.nand.newDirectory("/title/%08x" % (tmd.tmd.titleid >> 32), "rwrwr-", 0x0000)
self.nand.newDirectory("/title/%08x/%08x" % (tmd.tmd.titleid >> 32, tmd.tmd.titleid & 0xFFFFFFFF), "rwrwr-", 0x0000)
self.nand.newDirectory("/title/%08x/%08x/content" % (tmd.tmd.titleid >> 32, tmd.tmd.titleid & 0xFFFFFFFF), "rwrw--", 0x0000)
self.nand.newDirectory("/title/%08x/%08x/data" % (tmd.tmd.titleid >> 32, tmd.tmd.titleid & 0xFFFFFFFF), "rw----", tmd.tmd.group_id, tmd.tmd.titleid)
self.nand.newDirectory("/ticket/%08x" % (tmd.tmd.titleid >> 32), "rwrw--", 0x0000)
self.workingcids = array.array('L')
self.wtitleid = tmd.tmd.titleid
self.is_decrypted = is_decrypted
self.result_decrypted = result_decrypted
self.use_version = use_version
return
def AddTicket(self, tik):
"""Adds ticket to the title being added."""
tik.rawdump(self.f + "/tmp/title.tik")
self.ticketadded = 1
def DeleteTicket(self, tikview):
"""Deletes the ticket relating to tikview
(UNIMPLEMENTED!)"""
return
def AddTitleTMD(self, tmd):
"""Adds TMD to the title being added."""
tmd.rawdump(self.f + "/tmp/title.tmd")
self.tmdadded = 1
def AddContentStart(self, titleid, cid):
"""Starts adding a content with content id cid to the title being added with ID titleid."""
if((self.workingcid != 0) and (self.workingcid != None)):
"Trying to start an already existing process"
return -41
if(self.tmdadded):
a = TMD().loadFile(self.f + "/tmp/title.tmd")
else:
a = TMD().loadFile(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version))
x = self.getContentIndexFromCID(a, cid)
if(x == None):
"Not a valid Content ID"
return -43
self.workingcid = cid
self.workingfp = open(self.f + "/tmp/%08x.app" % cid, "wb")
return 0
def AddContentData(self, cid, data):
"""Adds data to the content cid being added."""
if(cid != self.workingcid):
"Working on the not current CID"
return -40
self.workingfp.write(data);
return 0
def AddContentFinish(self, cid):
"""Finishes the content cid being added."""
if(cid != self.workingcid):
"Working on the not current CID"
return -40
self.workingfp.close()
self.workingcids.append(cid)
self.workingcidcnt += 1
self.workingcid = None
return 0
def AddTitleCancel(self):
"""Cancels adding a title (deletes the tmp files and resets status)."""
if(self.ticketadded):
self.nand.removeFile("/tmp/title.tik")
self.ticketadded = 0
if(self.tmdadded):
self.nand.removeFile("/tmp/title.tmd")
self.tmdadded = 0
for i in range(self.workingcidcnt):
self.nand.removeFile("/tmp/%08x.app" % self.workingcids[i])
self.workingcidcnt = 0
self.workingcid = None
def AddTitleFinish(self):
"""Finishes the adding of a title."""
if(self.ticketadded):
tik = Ticket().loadFile(self.f + "/tmp/title.tik")
else:
tik = Ticket().loadFile(self.f + "/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF))
if(self.tmdadded):
tmd = TMD().loadFile(self.f + "/tmp/title.tmd")
contents = tmd.getContents()
for i in range(self.workingcidcnt):
idx = self.getContentIndexFromCID(tmd, self.workingcids[i])
if(idx == None):
print "Content ID doesn't exist!"
return -42
fp = open(self.f + "/tmp/%08x.app" % self.workingcids[i], "rb")
if(contents[idx].type == 0x0001):
filestr = "/title/%08x/%08x/content/%08x.app" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, self.workingcids[i])
elif(contents[idx].type == 0x8001):
num = self.nand.addHashToContentMap(contents[idx].hash)
filestr = "/shared1/%08x.app" % num
self.nand.newFile(filestr, "rwrw--", 0x0000)
outfp = open(self.f + filestr, "wb")
data = fp.read()
titlekey = tik.getTitleKey()
if(self.is_decrypted):
tmpdata = data
else:
tmpdata = Crypto().decryptContent(titlekey, contents[idx].index, data)
if(Crypto().validateSHAHash(tmpdata, contents[idx].hash) == 0):
"Decryption failed! SHA1 mismatch."
return -44
if(self.result_decrypted != True):
if(self.is_decrypted):
tmpdata = Crypto().encryptContent(titlekey, contents[idx].index, data)
else:
tmpdata = data
fp.close()
outfp.write(tmpdata)
outfp.close()
if(self.tmdadded and self.use_version):
self.nand.newFile("/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version), "rwrw--", 0x0000)
tmd.rawdump(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version))
elif(self.tmdadded):
self.nand.newFile("/title/%08x/%08x/content/title.tmd" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF), "rwrw--", 0x0000)
tmd.rawdump(self.f + "/title/%08x/%08x/content/title.tmd" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF))
if(self.ticketadded):
self.nand.newFile("/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF), "rwrw--", 0x0000)
tik.rawdump(self.f + "/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF))
self.AddTitleCancel()
return 0

View File

@ -1,343 +0,0 @@
from common import *
class TicketView:
"""Creates a ticket view from the Ticket object ``tik''."""
class TikviewStruct(Struct):
__endian__ = Struct.BE
def __format__(self):
self.view = Struct.uint32
self.ticketid = Struct.uint64
self.devicetype = Struct.uint32
self.titleid = Struct.uint64
self.accessmask = Struct.uint16
self.reserved = Struct.string(0x3C)
self.cidxmask = Struct.string(0x40)
self.padding = Struct.uint16
self.limits = Struct.string(96)
def __init__(self, tik):
self.tikview = self.TikviewStruct()
self.tikview.view = 0
self.tikview.ticketid = tik.tik.tikid
self.tikview.devicetype = tik.tik.console
self.tikview.titleid = tik.getTitleID()
self.tikview.accessmask = 0xFFFF # This needs to be changed, I'm sure...
self.tikview.reserved = "\0" * 0x3C
self.tikview.cidxmask = "\xFF" * 0x40 # This needs to be changed, I'm sure...
self.tikview.padding = 0x0000
self.tikview.limits = "\0" * 96
def __str__(self):
out = ""
out += " Ticket View:\n"
out += " Title ID: %08X-%08X\n" % (self.tikview.titleid >> 32, self.tikview.titleid & 0xFFFFFFFF)
out += " Device type: %08X\n" % self.tikview.devicetype
out += " Ticket ID: %016X\n" % self.tikview.ticketid
out += " Access Mask: %04X\n" % self.tikview.accessmask
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):
__endian__ = Struct.BE
def __format__(self):
self.rsaexp = Struct.uint32
self.rsamod = Struct.string(256)
self.padding1 = Struct.string(60)
self.rsaid = Struct.string(64)
self.padding2 = Struct.string(63)
self.enctitlekey = Struct.string(16)
self.unk1 = Struct.uint8
self.tikid = Struct.uint64
self.console = Struct.uint32
self.titleid = Struct.uint64
self.unk2 = Struct.uint16
self.dlc = Struct.uint16
self.unk3 = Struct.uint64
self.commonkey_index = Struct.uint8
self.reserved = Struct.string(80)
self.unk3 = Struct.uint16
self.limits = Struct.string(96)
self.unk4 = Struct.uint8
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"
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)
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
def getTitleID(self):
"""Returns a long integer with the title id."""
return self.tik.titleid
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"
out += " Title ID: %08x-%08x\n" % (self.getTitleID() >> 32, self.getTitleID() & 0xFFFFFFFF)
out += " Title key IV: "
out += hexdump(struct.pack(">Q", self.getTitleID()) + "\x00\x00\x00\x00\x00\x00\x00\x00")
out += "\n"
out += " Title key (encrypted): "
out += hexdump(self.tik.enctitlekey)
out += "\n"
out += " Title key (decrypted): "
out += hexdump(self.getTitleKey())
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
for i in range(65536):
self.tik.unk2 = i
if(Crypto().createSHAHashHex(self.tik.pack())[:2] == "00"):
break
if(i == 65535):
raise ValueError("Failed to fakesign. Aborting...")
if(fn == ""):
open(self.f, "wb").write(self.tik.pack())
return self.f
else:
open(fn, "wb").write(self.tik.pack())
return fn
def rawdump(self, fn = ""):
"""Dumps the ticket to either fn, if not empty, or overwriting the source if empty. **Does not fakesign.** Returns the output filename."""
if(fn == ""):
open(self.f, "wb").write(self.tik.pack())
return self.f
else:
open(fn, "wb").write(self.tik.pack())
return fn
def __len__(self):
return len(self.tik)
class TMD:
"""This class allows you to edit TMDs. TMD (Title Metadata) files are used in many places to hold information about titles. The parameter f to the initialization is the filename to open and create a TMD from."""
class TMDContent(Struct):
__endian__ = Struct.BE
def __format__(self):
self.cid = Struct.uint32
self.index = Struct.uint16
self.type = Struct.uint16
self.size = Struct.uint64
self.hash = Struct.string(20)
class TMDStruct(Struct):
__endian__ = Struct.BE
def __format__(self):
self.rsaexp = Struct.uint32
self.rsamod = Struct.string(256)
self.padding1 = Struct.string(60)
self.rsaid = Struct.string(64)
self.version = Struct.uint8[4]
self.iosversion = Struct.uint64
self.titleid = Struct.uint64
self.title_type = Struct.uint32
self.group_id = Struct.uint16
self.reserved = Struct.string(62)
self.access_rights = Struct.uint32
self.title_version = Struct.uint16
self.numcontents = Struct.uint16
self.boot_index = Struct.uint16
self.padding2 = Struct.uint16
#contents follow this
def load(self, data):
self.tmd.unpack(data[:len(self.tmd)])
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
def setContents(self, contents):
"""This sets the contents in the TMD to the contents you provide in the contents parameter. Also updates the TMD to the appropraite amount of contents."""
self.contents = contents
self.tmd.numcontents = len(contents)
def __str__(self):
out = ""
out += " TMD:\n"
out += " Versions: (todo) %u, CA CRL (todo) %u, Signer CRL (todo) %u, System %u-%u\n" % (0, 0, 0, self.getIOSVersion() >> 32, self.getIOSVersion() & 0xFFFFFFFF)
out += " Title ID: %08x-%08x\n" % (self.getTitleID() >> 32, self.getTitleID() & 0xFFFFFFFF)
out += " Title Type: %u\n" % self.tmd.title_type
out += " Group ID: '%02u'\n" % self.tmd.group_id
out += " Access Rights: 0x%08x\n" % self.tmd.access_rights
out += " Title Version: 0x%04x\n" % self.tmd.title_version
out += " Boot Index: %u\n" % self.getBootIndex()
out += " Contents: \n"
out += " ID Index Type Size Hash\n"
contents = self.getContents()
for i in range(len(contents)):
out += " %08X %-4u 0x%04x %#-12x " % (contents[i].cid, contents[i].index, contents[i].type, contents[i].size)
out += hexdump(contents[i].hash)
out += "\n"
return out
def __len__(self):
contents = self.getContents()
sz = len(self.tmd)
for i in range(len(contents)):
sz += len(contents[i])
return sz
def dump(self, fn = ""):
"""Dumps the TMD to the filename specified in fn, if not empty. If that is empty, it overwrites the original. This fakesigns the TMD, but does not update the hashes and the sizes, that is left as a job for you. Returns output filename."""
for i in range(65536):
self.tmd.padding2 = i
data = "" #gotta reset it every time
data += self.tmd.pack()
for i in range(self.tmd.numcontents):
data += self.contents[i].pack()
if(Crypto().createSHAHashHex(data)[:2] == "00"):
break
if(i == 65535):
raise ValueError("Failed to fakesign! Aborting...")
if(fn == ""):
open(self.f, "wb").write(data)
return self.f
else:
open(fn, "wb").write(data)
return fn
def rawdump(self, fn = ""):
"""Same as the :dump: function, but does not fakesign the TMD. Also returns output filename."""
data = ""
data += self.tmd.pack()
for i in range(self.tmd.numcontents):
data += self.contents[i].pack()
if(fn == ""):
open(self.f, "wb").write(data)
return self.f
else:
open(fn, "wb").write(data)
return fn
def getTitleID(self):
"""Returns the long integer title id."""
return self.tmd.titleid
def setTitleID(self, titleid):
"""Sets the title id to the long integer specified in the parameter titleid."""
self.tmd.titleid = titleid
def getIOSVersion(self):
"""Returns the IOS version the title will run off of."""
return self.tmd.iosversion
def setIOSVersion(self, version):
"""Sets the IOS version the title will run off of to the arguement version."""
self.tmd.iosverison = version
def getBootIndex(self):
"""Returns the boot index of the TMD."""
return self.tmd.boot_index
def setBootIndex(self, index):
"""Sets the boot index of the TMD to the value of index."""
self.tmd.boot_index = index
class NUS:
"""This class can download titles from NUS, or Nintendo Update Server. The titleid parameter is the long integer version of the title to download. The version parameter is optional and specifies the version to download. If version is not given, it is assumed to be the latest version on NUS."""
def __init__(self, titleid, version = None):
self.titleid = titleid
self.baseurl = "http://nus.cdn.shop.wii.com/ccs/download/%08x%08x/" % (titleid >> 32, titleid & 0xFFFFFFFF)
self.version = version
def download(self, fn = "", decrypt = True, useidx = True):
"""This will download a title from NUS into a directory either specified by fn (if it is not empty) or a directory created by the title id in hex form. If decrypt is true, it will decrypt the contents, otherwise it will not. A certs file is always created to enable easy WAD Packing. The parameter useidx specifies wheither to use the index or the content id for the file naming (default is index)."""
if(fn == ""):
fn = "%08x%08x" % (self.titleid >> 32, self.titleid & 0xFFFFFFFF)
try:
os.mkdir(fn)
except:
pass
os.chdir(fn)
certs = ""
rawtmd = urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/tmd.289").read()
rawtik = urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/cetk").read()
certs += rawtik[0x2A4:0x2A4 + 0x300] #XS
certs += rawtik[0x2A4 + 0x300:] #CA (tik)
certs += rawtmd[0x328:0x328 + 0x300] #CP
if(Crypto().createMD5HashHex(certs) != "7ff50e2733f7a6be1677b6f6c9b625dd"):
raise ValueError("Failed to create certs! MD5 mistatch.")
open("cert", "wb").write(certs)
if(self.version == None):
versionstring = ""
else:
versionstring = ".%u" % self.version
urllib.urlretrieve(self.baseurl + "tmd" + versionstring, "tmd")
tmd = TMD().loadFile("tmd")
tmd.rawdump("tmd") #this is to strip off the certs, and this won't fakesign so it should work
urllib.urlretrieve(self.baseurl + "cetk", "tik")
tik = Ticket().loadFile("tik")
tik.rawdump("tik") #this is to strip off the certs, and this won't fakesign so it should work
if(decrypt):
titlekey = tik.getTitleKey()
contents = tmd.getContents()
for content in contents:
output = content.cid
if(useidx):
output = content.index
urllib.urlretrieve(self.baseurl + ("%08x" % content.cid), "%08x.app" % output)
if(decrypt):
data = open("%08x.app" % output, "rb").read(content.size)
tmpdata = Crypto().decryptContent(titlekey, content.index, data)
if(Crypto().validateSHAHash(tmpdata, content.hash) == 0):
raise ValueError("Decryption failed! SHA1 mismatch.")
open("%08x.app" % output, "wb").write(tmpdata)
os.chdir("..")