mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 14:55:35 -04:00
more pywii cleanup :/
This commit is contained in:
parent
e1a5087296
commit
126d9f5913
282
pywii/Struct.py
282
pywii/Struct.py
@ -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
|
21
pywii/Wii.py
21
pywii/Wii.py
@ -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")
|
501
pywii/archive.py
501
pywii/archive.py
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
357
pywii/disc.py
357
pywii/disc.py
@ -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
|
||||
|
||||
|
253
pywii/export.py
253
pywii/export.py
@ -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
|
887
pywii/formats.py
887
pywii/formats.py
@ -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)
|
145
pywii/headers.py
145
pywii/headers.py
@ -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
|
796
pywii/image.py
796
pywii/image.py
@ -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)
|
816
pywii/nand.py
816
pywii/nand.py
@ -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
|
343
pywii/title.py
343
pywii/title.py
@ -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("..")
|
Loading…
Reference in New Issue
Block a user