pushed Xuzz's 'fork'

This commit is contained in:
Omega 2009-08-20 13:22:59 -04:00
parent cb6b94a323
commit 969ecaee9c
15 changed files with 2378 additions and 1462 deletions

564
Struct.py
View File

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

1
Wii.py
View File

@ -1,6 +1,7 @@
__all__ = [] __all__ = []
from common import * from common import *
from formats import * from formats import *
from title import * from title import *
from disc import * from disc import *

View File

@ -1,454 +1,320 @@
from common import * from common import *
from title import * import zlib
import zlib
class U8(WiiArchive): class U8(WiiArchive):
"""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. class U8Header(Struct):
__endian__ = Struct.BE
The f parameter is either the source folder to pack, or the source file to unpack.""" def __format__(self):
class U8Header(Struct): self.tag = Struct.string(4)
__endian__ = Struct.BE self.rootnode_offset = Struct.uint32
def __format__(self): self.header_size = Struct.uint32
self.tag = Struct.string(4) self.data_offset = Struct.uint32
self.rootnode_offset = Struct.uint32 self.zeroes = Struct.string(16)
self.header_size = Struct.uint32 class U8Node(Struct):
self.data_offset = Struct.uint32 __endian__ = Struct.BE
self.zeroes = Struct.string(16) def __format__(self):
class U8Node(Struct): self.type = Struct.uint16
__endian__ = Struct.BE self.name_offset = Struct.uint16
def __format__(self): self.data_offset = Struct.uint32
self.type = Struct.uint16 self.size = Struct.uint32
self.name_offset = Struct.uint16 def __init__(self):
self.data_offset = Struct.uint32 self.files = []
self.size = Struct.uint32 def _dump(self):
def __init__(self): header = self.U8Header()
self.files = [] rootnode = self.U8Node()
def _dump(self):
header = self.U8Header() # constants
rootnode = self.U8Node() header.tag = "U\xAA8-"
header.rootnode_offset = 0x20
# constants header.zeroes = "\x00" * 16
header.tag = "U\xAA8-" rootnode.type = 0x0100
header.rootnode_offset = 0x20
header.zeroes = "\x00" * 16 nodes = []
rootnode.type = 0x0100 strings = '\x00'
data = ''
nodes = []
strings = "\x00" for item, value in self.files:
data = '' node = self.U8Node()
for item, value in self.files: recursion = item.count('/')
node = self.U8Node() if(recursion < 0):
recursion = 0
recursion = item.count('/') name = item[item.rfind('/') + 1:]
if(recursion < 0):
recursion = 0 node.name_offset = len(strings)
name = item[item.rfind('/') + 1:] strings += name + '\x00'
node.name_offset = len(strings) if(value == None): # directory
strings += name + '\x00' node.type = 0x0100
node.data_offset = recursion
if(value == None):
node.type = 0x0100 node.size = len(nodes) + 1
node.data_offset = recursion for one, two in self.files:
if(one[:len(item)] == item): # find nodes in the folder
node.size = len(nodes) node.size += 1
for one, two in self.files: else: # file
if(one[:len(item)] == item): # find nodes in the folder node.type = 0x0000
node.size += 1 node.data_offset = len(data)
node.size += 1 #print "before: " + str(len(data))
else: data += value + ('\x00' * (align(len(value), 32) - len(value))) # 32 seems to work best for fuzzyness? I'm still really not sure
sz = len(value) #print "after: " + str(len(data))
node.data_offset = len(data) node.size = len(value)
data += value + "\x00" * (align(sz, 32) - sz) # 32 seems to work best for fuzzyness? I'm still really not sure #print "sz: " + str(len(value))
node.size = sz nodes.append(node)
node.type = 0x0000
nodes.append(node) header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings)
header.data_offset = align(header.header_size + header.rootnode_offset, 64)
header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings) rootnode.size = len(nodes) + 1
header.data_offset = align(header.header_size + header.rootnode_offset, 64)
rootnode.size = len(nodes) + 1 for i in range(len(nodes)):
if(nodes[i].type == 0x0000):
for i in range(len(nodes)): nodes[i].data_offset += header.data_offset
if(nodes[i].type == 0x0000):
nodes[i].data_offset += header.data_offset fd = ''
fd += header.pack()
fd = '' fd += rootnode.pack()
fd += header.pack() for node in nodes:
fd += rootnode.pack() fd += node.pack()
for nodeobj in nodes: fd += strings
fd += nodeobj.pack() fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
fd += strings fd += data
fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
fd += data return fd
def _dumpDir(self, dir):
return fd if(not os.path.isdir(dir)):
def _dumpDir(self, dir): os.mkdir(dir)
if(not os.path.isdir(dir)): old = os.getcwd()
os.mkdir(dir) os.chdir(dir)
old = os.getcwd() for item, data in self.files:
os.chdir(dir) if(data == None):
for item, data in self.files: if(not os.path.isdir(item)):
if(data == None): os.mkdir(item)
if(not os.path.isdir(item)): else:
os.mkdir(item) open(item, "wb").write(data)
else: os.chdir(old)
open(item, "wb").write(data) def _loadDir(self, dir):
os.chdir(old) try:
def _loadDir(self, dir): self._tmpPath += ''
try: except:
self._tmpPath += '' self._tmpPath = ''
except: old = os.getcwd()
self._tmpPath = '' os.chdir(dir)
old = os.getcwd() entries = os.listdir(".")
os.chdir(dir) for entry in entries:
entries = os.listdir(".") if(os.path.isdir(entry)):
for entry in entries: self.files.append((self._tmpPath + entry, None))
if(os.path.isdir(entry)): self._tmpPath += entry + '/'
self.files.append((self._tmpPath + entry, None)) self._loadDir(entry)
self._tmpPath += entry + '/' elif(os.path.isfile(entry)):
self._loadDir(entry) data = open(entry, "rb").read()
elif(os.path.isfile(entry)): self.files.append((self._tmpPath + entry, data))
data = open(entry, "rb").read() os.chdir(old)
self.files.append((self._tmpPath + entry, data)) self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
os.chdir(old) def _load(self, data):
self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1] offset = 0
def _load(self, data):
offset = 0 for i in range(len(data)):
header = self.U8Header()
header = self.U8Header() header.unpack(data[offset:offset + len(header)])
header.unpack(data[offset:offset + len(header)]) if(header.tag == "U\xAA8-"):
offset += len(header) break
data = data[1:]
assert header.tag == "U\xAA8-" offset += len(header)
offset = header.rootnode_offset offset = header.rootnode_offset
rootnode = self.U8Node() #print header.rootnode_offset
rootnode.unpack(data[offset:offset + len(rootnode)]) #print header.header_size
offset += len(rootnode) #print header.data_offset
nodes = [] rootnode = self.U8Node()
for i in range(rootnode.size - 1): rootnode.unpack(data[offset:offset + len(rootnode)])
node = self.U8Node() offset += len(rootnode)
node.unpack(data[offset:offset + len(node)])
offset += len(node) nodes = []
nodes.append(node) for i in range(rootnode.size - 1):
node = self.U8Node()
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)] node.unpack(data[offset:offset + len(node)])
offset += len(strings) offset += len(node)
nodes.append(node)
recursion = [rootnode.size]
recursiondir = [] strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
counter = 0 offset += len(strings)
for node in nodes:
counter += 1 recursion = [rootnode.size]
name = strings[node.name_offset:].split('\0', 1)[0] recursiondir = []
counter = 0
if(node.type == 0x0100): # folder for node in nodes:
recursion.append(node.size) counter += 1
recursiondir.append(name) name = strings[node.name_offset:].split('\0', 1)[0]
assert len(recursion) == node.data_offset + 2 # haxx
self.files.append(('/'.join(recursiondir), None)) if(node.type == 0x0100): # folder
elif(node.type == 0): # file recursion.append(node.size)
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size])) recursiondir.append(name)
offset += node.size assert len(recursion) == node.data_offset + 2 # haxx
else: # unknown type -- wtf? self.files.append(('/'.join(recursiondir), None))
pass
#print "Dir: " + name
sz = recursion.pop() elif(node.type == 0): # file
if(sz != counter + 1): self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
recursion.append(sz) offset += node.size
else:
recursiondir.pop() #print "File: " + name
def __str__(self): else: # unknown type -- wtf?
ret = '' pass
for key, value in self.files:
name = key[key.rfind('/') + 1:] #print "Data Offset: " + str(node.data_offset)
recursion = key.count('/') #print "Size: " + str(node.size)
ret += ' ' * recursion #print "Name Offset: " + str(node.name_offset)
if(value == None): #print ""
ret += '[' + name + ']'
else: sz = recursion.pop()
ret += name if(sz != counter + 1):
ret += '\n' recursion.append(sz)
return ret else:
def __getitem__(self, key): recursiondir.pop()
for item, val in self.files: def __str__(self):
if(item == key): ret = ''
if(val != None): for key, value in self.files:
return val name = key[key.rfind('/') + 1:]
else: recursion = key.count('/')
ret = [] ret += ' ' * recursion
for item2, val2 in self.files: if(value == None):
if(item2.find(item) == 0): ret += '[' + name + ']'
ret.append(item2[len(item) + 1:]) else:
return ret[1:] ret += name
raise KeyError ret += '\n'
def __setitem__(self, key, val): return ret
for i in range(len(self.files)): def __getitem__(self, key):
if(self.files[i][0] == key): for item, val in self.files:
self.files[i] = (self.files[i][0], val) if(item == key):
return if(val != None):
self.files.append((key, val)) return val
else:
ret = []
class WAD(WiiArchive): for item2, val2 in self.files:
def __init__(self, boot2 = False): if(item2.find(item) == 0):
self.tmd = TMD() ret.append(item2[len(item) + 1:])
self.tik = Ticket() return ret[1:]
self.contents = [] raise KeyError
self.boot2 = False def __setitem__(self, key, val):
self.cert = "" for i in range(len(self.files)):
def _load(self, data): if(self.files[i][0] == key):
if(self.boot2 != True): self.files[i] = (self.files[i][0], val)
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding = struct.unpack('>I4s6I32s', data[:64]) return
pos = 64 self.files.append((key, val))
else:
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', data[:32])
pos = 32
class CCF:
rawcert = data[pos:pos + certsize] class CCFHeader(Struct):
pos += certsize __endian__ = Struct.LE
if(self.boot2 != True): def __format__(self):
if(certsize % 64 != 0): self.magic = Struct.string(4)
pos += 64 - (certsize % 64) self.zeroes12 = Struct.string(12)
self.cert = rawcert self.rootOffset = Struct.uint32
self.filesCount = Struct.uint32
rawtik = data[pos:pos + tiksize] self.zeroes8 = Struct.string(8)
pos += tiksize
if(self.boot2 != True): class CCFFile(Struct):
if(tiksize % 64 != 0): __endian__ = Struct.LE
pos += 64 - (tiksize % 64) def __format__(self):
self.tik = Ticket.load(rawtik) self.fileName = Struct.string(20)
self.fileOffset = Struct.uint32
rawtmd = data[pos:pos + tmdsize] self.fileSize = Struct.uint32
pos += tmdsize self.fileSizeDecompressed = Struct.uint32
if(self.boot2 == True):
pos = data_offset def __init__(self, fileName):
else: self.fileName = fileName
pos += 64 - (tmdsize % 64) self.fd = open(fileName, 'r+b')
self.tmd = TMD.load(rawtmd)
def compress(self, folder):
titlekey = self.tik.getTitleKey() fileList = []
contents = self.tmd.getContents()
for i in range(0, len(contents)): fileHdr = self.CCFHeader()
tmpsize = contents[i].size
if(tmpsize % 16 != 0): files = os.listdir(folder)
tmpsize += 16 - (tmpsize % 16)
encdata = data[pos:pos + tmpsize] fileHdr.magic = "\x43\x43\x46\x00"
pos += tmpsize fileHdr.zeroes12 = '\x00' * 12
decdata = Crypto().decryptContent(titlekey, contents[i].index, encdata) fileHdr.rootOffset = 0x20
self.contents.append(decdata) fileHdr.zeroes8 = '\x00' * 8
if(tmpsize % 64 != 0):
pos += 64 - (tmpsize % 64) currentOffset = len(fileHdr)
def _loadDir(self, dir): packedFiles = 0
origdir = os.getcwd() previousFileEndOffset = 0
os.chdir(dir)
for file in files:
self.tmd = TMD.loadFile("tmd") if os.path.isdir(folder + '/' + file) or file == '.DS_Store':
self.tik = Ticket.loadFile("tik") continue
self.cert = open("cert", "rb").read() else:
fileList.append(file)
contents = self.tmd.getContents()
for i in range(len(contents)): fileHdr.filesCount = len(fileList)
self.contents.append(open("%08x.app" % i, "rb").read()) self.fd.write(fileHdr.pack())
os.chdir(origdir) self.fd.write('\x00' * (fileHdr.filesCount * len(self.CCFFile())))
def _dumpDir(self, dir):
origdir = os.getcwd() for fileNumber in range(len(fileList)):
os.chdir(dir)
fileEntry = self.CCFFile()
contents = self.tmd.getContents()
for i in range(len(contents)): compressedBuffer = zlib.compress(open(folder + '/' + fileList[fileNumber]).read())
open("%08x.app" % i, "wb").write(self.contents[i])
self.tmd.dumpFile("tmd") fileEntry.fileName = fileList[fileNumber]
self.tik.dumpFile("tik") fileEntry.fileSize = len(compressedBuffer)
open("cert", "wb").write(self.cert) fileEntry.fileSizeDecompressed = os.stat(folder + '/' + fileList[fileNumber]).st_size
fileEntry.fileOffset = align(self.fd.tell(), 32) / 32
os.chdir(origdir)
def _dump(self, fakesign = True): print 'File {0} ({1}Kb) placed at offset 0x{2:X}'.format(fileEntry.fileName, fileEntry.fileSize / 1024, fileEntry.fileOffset * 32)
titlekey = self.tik.getTitleKey()
contents = self.tmd.getContents() self.fd.seek(len(fileHdr) + fileNumber * len(self.CCFFile()))
self.fd.write(fileEntry.pack())
apppack = "" self.fd.seek(fileEntry.fileOffset * 32)
for i, content in enumerate(contents): self.fd.write(compressedBuffer)
if(fakesign):
content.hash = str(Crypto().createSHAHash(self.contents[content.index])) self.fd.close()
content.size = len(self.contents[content.index])
def decompress(self):
encdata = Crypto().encryptContent(titlekey, content.index, self.contents[content.index]) fileHdr = self.CCFHeader()
hdrData = self.fd.read(len(fileHdr))
apppack += encdata fileHdr.unpack(hdrData)
if(len(encdata) % 64 != 0):
apppack += "\x00" * (64 - (len(encdata) % 64)) print 'Found {0} file/s and root node at 0x{1:X}'.format(fileHdr.filesCount, fileHdr.rootOffset)
if(fakesign): if fileHdr.magic != "\x43\x43\x46\x00":
self.tmd.setContents(contents) raise ValueError("Wrong magic, 0x{0}".format(fileHdr.magic))
self.tmd.fakesign()
self.tik.fakesign() try:
os.mkdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
rawtmd = self.tmd.dump() except:
rawcert = self.cert pass
rawtik = self.tik.dump()
os.chdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
sz = 0
for i in range(len(contents)): currentOffset = len(fileHdr)
sz += contents[i].size
if(sz % 64 != 0): for x in range(fileHdr.filesCount):
sz += 64 - (contents[i].size % 64) self.fd.seek(currentOffset)
if(self.boot2 != True): fileEntry = self.CCFFile()
pack = struct.pack('>I4s6I', 32, "Is\x00\x00", len(rawcert), 0, len(rawtik), len(rawtmd), sz, 0) fileData = self.fd.read(len(fileEntry))
pack += "\x00" * 32 fileEntry.unpack(fileData)
else:
pack = struct.pack('>IIIII12s', 32, align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40), len(rawcert), len(rawtik), len(rawtmd), "\x00" * 12) fileEntry.fileOffset = fileEntry.fileOffset * 32
pack += rawcert print 'File {0} at offset 0x{1:X}'.format(fileEntry.fileName, fileEntry.fileOffset)
if(len(rawcert) % 64 != 0 and self.boot2 != True): print 'File size {0}Kb ({1}Kb decompressed)'.format(fileEntry.fileSize / 1024, fileEntry.fileSizeDecompressed / 1024)
pack += "\x00" * (64 - (len(rawcert) % 64))
pack += rawtik output = open(fileEntry.fileName.rstrip('\0'), 'w+b')
if(len(rawtik) % 64 != 0 and self.boot2 != True):
pack += "\x00" * (64 - (len(rawtik) % 64)) self.fd.seek(fileEntry.fileOffset)
pack += rawtmd if fileEntry.fileSize == fileEntry.fileSizeDecompressed:
if(len(rawtmd) % 64 != 0 and self.boot2 != True): print 'The file is stored uncompressed'
pack += "\x00" * (64 - (len(rawtmd) % 64)) output.write(self.fd.read(fileEntry.fileSize))
else:
if(self.boot2 == True): print 'The file is stored compressed..decompressing'
pack += "\x00" * (align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40) - (len(rawcert) + len(rawtik) + len(rawtmd))) decompressedBuffer = zlib.decompress(self.fd.read(fileEntry.fileSize))
output.write(decompressedBuffer)
pack += apppack output.close()
return pack
def __getitem__(self, idx): currentOffset += len(fileEntry)
return self.contents[idx]
def __setitem__(self, idx, value):
self.contents[idx] = value
def __str__(self):
out = ""
out += "Wii WAD:\n"
out += str(self.tmd)
out += str(self.tik)
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)
if(__name__ == '__main__'):
wad = WAD.loadFile("testing.wad")
print wad
wad.dumpDir("outdir")
wad.dumpFile("interesting.wad", fakesign = False) #keyword arguements work as expected when calling _dump(). awesome.
wad2 = WAD.loadDir("outdir")
print wad2
wad3 = WAD.loadFile("interesting.wad")
print wad3
wad3.dumpDir("outdir2")

287
common.py
View File

@ -1,137 +1,150 @@
import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array, time, sys, tempfile, wave import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array, time, sys, tempfile, wave
from cStringIO import StringIO from binascii import *
from cStringIO import StringIO
from Crypto.Cipher import AES
from PIL import Image from Crypto.Cipher import AES
from PIL import Image
from Struct import Struct
from Struct import Struct
def align(x, boundary):
if(x % boundary): def align(x, boundary):
x += (x + boundary) - (x % boundary) while x % boundary != 0:
return x x += 1
return x
def clamp(var, min, max):
if var < min: var = min def clamp(var, min, max):
if var > max: var = max if var < min: var = min
return var if var > max: var = max
return var
def abs(var):
if var < 0: def abs(var):
var = var + (2 * var) if var < 0:
return var var = var + (2 * var)
return var
def hexdump(s, sep=" "): # just dumps hex values
return sep.join(map(lambda x: "%02x" % ord(x), s)) def hexdump(s, sep=" "): # just dumps hex values
return sep.join(map(lambda x: "%02x" % ord(x), s))
def hexdump2(src, length = 16): # dumps to a "hex editor" style output
result = [] def hexdump2(src, length = 16): # dumps to a "hex editor" style output
for i in xrange(0, len(src), length): result = []
s = src[i:i + length] for i in xrange(0, len(src), length):
if(len(s) % 4 == 0): s = src[i:i + length]
mod = 0 if(len(s) % 4 == 0):
else: mod = 0
mod = 1 else:
hexa = '' mod = 1
for j in range((len(s) / 4) + mod): hexa = ''
hexa += ' '.join(["%02X" % ord(x) for x in s[j * 4:j * 4 + 4]]) for j in range((len(s) / 4) + mod):
if(j != ((len(s) / 4) + mod) - 1): hexa += ' '.join(["%02X" % ord(x) for x in s[j * 4:j * 4 + 4]])
hexa += ' ' if(j != ((len(s) / 4) + mod) - 1):
printable = s.translate(''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])) hexa += ' '
result.append("0x%04X %-*s %s\n" % (i, (length * 3) + 2, hexa, printable)) printable = s.translate(''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]))
return ''.join(result) result.append("0x%04X %-*s %s\n" % (i, (length * 3) + 2, hexa, printable))
return ''.join(result)
class Crypto(object):
"""This is a Cryptographic/hash class used to abstract away things (to make changes easier)""" class Crypto(object):
align = 64 """This is a Cryptographic/hash class used to abstract away things (to make changes easier)"""
@classmethod align = 64
def decryptData(self, key, iv, data, align = True):
"""Decrypts some data (aligns to 64 bytes, if needed).""" def decryptData(self, key, iv, data, align = True):
if((len(data) % self.align) != 0 and align): """Decrypts some data (aligns to 64 bytes, if needed)."""
return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align)))) if((len(data) % self.align) != 0 and align):
else: return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
return AES.new(key, AES.MODE_CBC, iv).decrypt(data) else:
@classmethod return AES.new(key, AES.MODE_CBC, iv).decrypt(data)
def encryptData(self, key, iv, data, align = True): decryptData = classmethod(decryptData)
"""Encrypts some data (aligns to 64 bytes, if needed)."""
if((len(data) % self.align) != 0 and align): def encryptData(self, key, iv, data, align = True):
return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align)))) """Encrypts some data (aligns to 64 bytes, if needed)."""
else: if((len(data) % self.align) != 0 and align):
return AES.new(key, AES.MODE_CBC, iv).encrypt(data) return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
@classmethod else:
def decryptContent(self, titlekey, idx, data): return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
"""Decrypts a Content.""" encryptData = classmethod(encryptData)
iv = struct.pack(">H", idx) + "\x00" * 14
return self.decryptData(titlekey, iv, data) def decryptContent(self, titlekey, idx, data):
@classmethod """Decrypts a Content."""
def decryptTitleKey(self, commonkey, tid, enckey): iv = struct.pack(">H", idx) + "\x00" * 14
"""Decrypts a Content.""" return self.decryptData(titlekey, iv, data)
iv = struct.pack(">Q", tid) + "\x00" * 8 decryptContent = classmethod(decryptContent)
return self.decryptData(commonkey, iv, enckey, False)
@classmethod def decryptTitleKey(self, commonkey, tid, enckey):
def encryptContent(self, titlekey, idx, data): """Decrypts a Content."""
"""Encrypts a Content.""" iv = struct.pack(">Q", tid) + "\x00" * 8
iv = struct.pack(">H", idx) + "\x00" * 14 return self.decryptData(commonkey, iv, enckey, False)
return self.encryptData(titlekey, iv, data) decryptTitleKey = classmethod(decryptTitleKey)
@classmethod
def createSHAHash(self, data): #tested WORKING (without padding) def encryptContent(self, titlekey, idx, data):
return hashlib.sha1(data).digest() """Encrypts a Content."""
@classmethod iv = struct.pack(">H", idx) + "\x00" * 14
def createSHAHashHex(self, data): return self.encryptData(titlekey, iv, data)
return hashlib.sha1(data).hexdigest() encryptContent = classmethod(encryptContent)
@classmethod
def createMD5HashHex(self, data): def createSHAHash(self, data): #tested WORKING (without padding)
return hashlib.md5(data).hexdigest() return hashlib.sha1(data).digest()
@classmethod createSHAHash = classmethod(createSHAHash)
def createMD5Hash(self, data):
return hashlib.md5(data).digest() def createSHAHashHex(self, data):
@classmethod return hashlib.sha1(data).hexdigest()
def validateSHAHash(self, data, hash): createSHAHashHex = classmethod(createSHAHashHex)
contentHash = hashlib.sha1(data).digest()
return 1 def createMD5HashHex(self, data):
if (contentHash == hash): return hashlib.md5(data).hexdigest()
return 1 createMD5HashHex = classmethod(createMD5HashHex)
else:
#raise ValueError('Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash)) + 'Expected : %s : len %i' % (hexdump(hash), len(hash))) def createMD5Hash(self, data):
return 0 return hashlib.md5(data).digest()
createMD5Hash = classmethod(createMD5Hash)
class WiiObject(object):
@classmethod def validateSHAHash(self, data, hash):
def load(cls, data, *args, **kwargs): contentHash = hashlib.sha1(data).digest()
self = cls() return 1
self._load(data, *args, **kwargs) if (contentHash == hash):
return self return 1
@classmethod else:
def loadFile(cls, filename, *args, **kwargs): #raise ValueError('Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash)) + 'Expected : %s : len %i' % (hexdump(hash), len(hash)))
return cls.load(open(filename, "rb").read(), *args, **kwargs) return 0
validateSHAHash = classmethod(validateSHAHash)
def dump(self, *args, **kwargs):
return self._dump(*args, **kwargs) class WiiObject(object):
def dumpFile(self, filename, *args, **kwargs): def load(cls, data, *args, **kwargs):
open(filename, "wb").write(self.dump(*args, **kwargs)) self = cls()
return filename self._load(data, *args, **kwargs)
return self
class WiiArchive(WiiObject): load = classmethod(load)
@classmethod
def loadDir(cls, dirname): def loadFile(cls, filename, *args, **kwargs):
self = cls() return cls.load(open(filename, "rb").read(), *args, **kwargs)
self._loadDir(dirname) loadFile = classmethod(loadFile)
return self
def dump(self, *args, **kwargs):
def dumpDir(self, dirname): return self._dump(*args, **kwargs)
if(not os.path.isdir(dirname)): def dumpFile(self, filename, *args, **kwargs):
os.mkdir(dirname) open(filename, "wb").write(self.dump(*args, **kwargs))
self._dumpDir(dirname) return filename
return dirname
class WiiArchive(WiiObject):
class WiiHeader(object): def loadDir(cls, dirname):
def __init__(self, data): self = cls()
self.data = data self._loadDir(dirname)
def addFile(self, filename): return self
open(filename, "wb").write(self.add()) loadDir = classmethod(loadDir)
def removeFile(self, filename):
open(filename, "wb").write(self.remove()) def dumpDir(self, dirname):
@classmethod if(not os.path.isdir(dirname)):
def loadFile(cls, filename, *args, **kwargs): os.mkdir(dirname)
return cls(open(filename, "rb").read(), *args, **kwargs) self._dumpDir(dirname)
return dirname
class WiiHeader(object):
def __init__(self, data):
self.data = data
def addFile(self, filename):
open(filename, "wb").write(self.add())
def removeFile(self, filename):
open(filename, "wb").write(self.remove())
def loadFile(cls, filename, *args, **kwargs):
return cls(open(filename, "rb").read(), *args, **kwargs)
loadFile = classmethod(loadFile)

View File

@ -1,5 +1,31 @@
from common import * from common import *
# Copyright (c) 2008 Hector Martin <marcan@marcansoft.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * The name of the author may not be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
class LZ77(WiiHeader): class LZ77(WiiHeader):
class WiiLZ77: # class by marcan, used under scope of BSD license class WiiLZ77: # class by marcan, used under scope of BSD license
TYPE_LZ77 = 1 TYPE_LZ77 = 1
@ -46,7 +72,7 @@ class LZ77(WiiHeader):
hdr = self.data[:4] hdr = self.data[:4]
if hdr != "LZ77": if hdr != "LZ77":
return self.data return self.data
file = StringIO.StringIO(self.data) file = StringIO(self.data)
file.seek(4) file.seek(4)
unc = self.WiiLZ77(file, file.tell()) unc = self.WiiLZ77(file, file.tell())
data = unc.uncompress() data = unc.uncompress()

714
disc.py
View File

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

View File

@ -251,3 +251,100 @@ class Savegame():
def getFilesCount(self): def getFilesCount(self):
return self.bkHdr.filesCount return self.bkHdr.filesCount
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')

View File

@ -1,104 +1,8 @@
from binascii import *
import struct import struct
from common import * from common import *
from title 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: class CONF:
"""This class deals with setting.txt which holds some important information like region and serial number """ """This class deals with setting.txt which holds some important information like region and serial number """
def __init__(self, f): def __init__(self, f):

View File

@ -1,44 +1,29 @@
from common import * from common import *
class IMD5(WiiHeader): class IMD5(WiiHeader):
"""This class can add and remove IMD5 headers to files. The parameter f is the file to use for the addition or removal of the header. IMD5 headers are found in banner.bin, icon.bin, and sound.bin."""
class IMD5Header(Struct): class IMD5Header(Struct):
__endian__ = Struct.BE __endian__ = Struct.BE
def __format__(self): def __format__(self):
self.tag = Struct.string(4) self.tag = Struct.string(4)
self.size = Struct.uint32 self.size = Struct.uint32
self.zeroes = Struct.uint8[8] self.zeroes = Struct.string(8)
self.crypto = Struct.string(16) self.crypto = Struct.string(16)
def add(self): def add(self):
data = self.data
imd5 = self.IMD5Header() imd5 = self.IMD5Header()
imd5.tag = "IMD5" imd5.tag = "IMD5"
imd5.size = len(data) imd5.size = len(self.data)
for i in range(8): imd5.zeroes = '\x00' * 8
imd5.zeroes[i] = 0x00 imd5.crypto = str(Crypto.createMD5Hash(self.data))
imd5.crypto = str(Crypto().createMD5Hash(data)) self.data = imd5.pack() + self.data
data = imd5.pack() + data return self.data
return data
def remove(self): def remove(self):
"""This will remove an IMD5 header from the file specified in f, if one exists. If there is no IMD5 header, it will output the file as it is. It will output in the parameter fn if available, otherwise it will overwrite the source. Returns the output filename."""
data = self.data
imd5 = self.IMD5Header() imd5 = self.IMD5Header()
if(data[:4] != "IMD5"): if(self.data[:4] != "IMD5"):
if(fn != ""): return self.data
open(fn, "wb").write(data) self.data = self.data[len(imd5):]
return fn return self.data
else:
return self.f
data = data[len(imd5):]
return data
class IMET(WiiHeader): class IMET(WiiHeader):
"""IMET headers are found in Opening.bnr and 0000000.app files. They contain the channel titles and more metadata about channels. They are in two different formats with different amounts of padding before the start of the IMET header. This class suports both.
The parameter f is used to specify the input file name."""
class IMETHeader(Struct): class IMETHeader(Struct):
__endian__ = Struct.BE __endian__ = Struct.BE
def __format__(self): def __format__(self):
@ -50,14 +35,11 @@ class IMET(WiiHeader):
self.names = Struct.string(84, encoding = "utf-16-be", stripNulls = True)[7] self.names = Struct.string(84, encoding = "utf-16-be", stripNulls = True)[7]
self.zeroes2 = Struct.uint8[840] self.zeroes2 = Struct.uint8[840]
self.hash = Struct.string(16) self.hash = Struct.string(16)
def add(self, iconsz, bannersz, soundsz, name = "", langs = [], fn = ""): def add(self, iconsz, bannersz, soundsz, name = '', langs = []):
"""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 = self.data
imet = self.IMETHeader() imet = self.IMETHeader()
for i in range(len(imet.zeroes)):
for i in imet.zeroes:
imet.zeroes[i] = 0x00 imet.zeroes[i] = 0x00
imet.tag = "IMET" imet.tag = 'IMET'
imet.unk = 0x0000060000000003 imet.unk = 0x0000060000000003
imet.sizes[0] = iconsz imet.sizes[0] = iconsz
imet.sizes[1] = bannersz imet.sizes[1] = bannersz
@ -68,45 +50,40 @@ class IMET(WiiHeader):
imet.names[i] = langs[i] imet.names[i] = langs[i]
else: else:
imet.names[i] = name imet.names[i] = name
for i in imet.zeroes2: for i in range(len(imet.zeroes2)):
imet.zeroes2[i] = 0x00 imet.zeroes2[i] = 0x00
imet.hash = "\x00" * 16 imet.hash = '\x00' * 16
tmp = imet.pack() tmp = imet.pack()
imet.hash = Crypto().createMD5Hash(tmp[0x40:0x640]) #0x00 or 0x40? imet.hash = Crypto.createMD5Hash(tmp[0x40:0x640])
self.data = imet.pack() + self.data
data = imet.pack() + data print "testing %x %x %x %x %x" % (len(imet), 128, 840, 0x1A << 1, 84)
return self.data
return data
def remove(self): def remove(self):
data = self.data data = self.data
if(data[0x80:0x84] == "IMET"): if(data[0x80:0x84] == 'IMET'):
data = data[0x640:] data = data[0x640:]
elif(data[0x40:0x44] == "IMET"): elif(data[0x40:0x44] == 'IMET'):
data = data[0x640:] data = data[0x640:]
return data return data
def getTitle(self): def getTitle(self):
imet = self.IMETHeader() imet = self.IMETHeader()
data = self.data data = self.data
if(data[0x40:0x44] == 'IMET'):
if(data[0x40:0x44] == "IMET"):
pass pass
elif(data[0x80:0x84] == "IMET"): elif(data[0x80:0x84] == 'IMET'):
data = data[0x40:] data = data[0x40:]
else: else:
raise ValueError("No IMET header found!") raise ValueError("No IMET header found!")
imet.unpack(data[:len(imet)]) imet.unpack(data[:len(imet)])
name = imet.names[1] name = imet.names[1]
topop = [] topop = []
for i in range(len(name)): for i in range(len(name)):
if(name[i] == "\x00"): if(name[i] == '\x00'):
topop.append(i) topop.append(i)
name = list(name) name = list(name)
popped = 0 #don't ask me why I did this popped = 0 #don't ask me why I did this
for pop in topop: for pop in topop:
name.pop(pop - popped) name.pop(pop - popped)
popped += 1 popped += 1
name = ''.join(name) name = ''.join(name)
return name return name

View File

@ -1,6 +1,4 @@
#!/usr/bin/env python
from common import * from common import *
import wx
def flatten(myTuple): def flatten(myTuple):
if (len(myTuple) == 4): if (len(myTuple) == 4):
@ -31,7 +29,7 @@ def avg(w0, w1, c0, c1):
return c return c
class TPL(): 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. """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 are the following formats to convert from TPL (all formats): RGBA8, RGB565, RGB5A3, I4, IA4, I8, IA8, CI4, CI8, CMP, CI14X2.
@ -483,7 +481,10 @@ class TPL():
return outfile return outfile
def getSizes(self): 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.""" """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() if(self.file):
data = open(self.file, "rb").read()
else:
data = self.data
header = self.TPLHeader() header = self.TPLHeader()
textures = [] textures = []
@ -506,9 +507,6 @@ class TPL():
h = tex.height h = tex.height
return (w, h) return (w, h)
def toScreen(self): #single texture only 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 import wx
class imp(wx.Dialog): class imp(wx.Dialog):
def __init__(self, title, im): def __init__(self, title, im):

187
mymenu.py Normal file
View File

@ -0,0 +1,187 @@
import os, sys, ConfigParser, zipfile
import wx
import Wii
queue = []
logdata = ''
def log(text):
logdata.append(text + '\n')
def debug(text):
log("[DEBUG] " + text)
def error(text):
log("[ERROR] " + text)
wx.MessageBox(text, 'Error', wx.OK | wx.ICON_ERROR)
class MyMenu(wx.App):
def OnInit(self):
self.completed = 0
self.selected = -1
frame = wx.Frame(None, -1, "MyMenu 1.5", (300, 200), (400, 250))
panel = wx.Panel(frame)
panel.Show(True)
wx.StaticText(panel, -1, "MyMenu (c) 2009 Xuzz. Powered by the Wii.py framework.", (15, 230))
wx.StaticText(panel, -1, "Source:", (5, 10), (60, 27))
self.src = wx.TextCtrl(panel, -1, "", (65, 5), (245, 30))
browsebtn = wx.Button(panel, -1, "Browse", (315, 5), (80, 30))
self.Bind(wx.EVT_BUTTON, self.browse, browsebtn)
addbtn = wx.Button(panel, -1, "Add", (315, 45), (80, 30))
self.Bind(wx.EVT_BUTTON, self.add, addbtn)
rmbtn = wx.Button(panel, -1, "Remove", (315, 80), (80, 30))
self.Bind(wx.EVT_BUTTON, self.remove, rmbtn)
upbtn = wx.Button(panel, -1, "Up", (315, 115), (80, 30))
self.Bind(wx.EVT_BUTTON, self.up, upbtn)
downbtn = wx.Button(panel, -1, "Down", (315, 150), (80, 30))
self.Bind(wx.EVT_BUTTON, self.down, downbtn)
self.list = wx.ListCtrl(panel, -1, (5, 45), (300, 140), wx.LC_REPORT | wx.SUNKEN_BORDER)
self.list.Show(True)
self.list.InsertColumn(0, "MyMenu Scripts", 0, 290)
gobtn = wx.Button(panel, -1, "Create CSM", (5, 200), (90, 30))
self.Bind(wx.EVT_BUTTON, self.go, gobtn)
self.progress = wx.Gauge(panel, -1, 100, (100, 200), (295, 30))
self.progress.SetValue(0)
frame.Show(True)
log("GUI Started...")
return True
def progress(self, val):
self.progress.SetValue(self.completed * 100 + val + 50)
def doMyMenu(self, arc, mym):
self.progress(0)
try:
debug("Opening zip file %s..." % mym)
myZip = zipfile.ZipFile(mym, 'r')
debug("Loading INI...")
myScript = ConfigParser.ConfigParser()
myScript.readfp(myZip.open("mym.ini"))
except:
error("Invalid MyScript, skipping...")
debug("Loaded successfully.")
self.progress(10)
sections = myScript.sections()
def go(self, evt):
self.progress.SetRange(len(queue) * 100 + 50)
src = self.src.GetText()
try:
arc = Wii.U8.load(Wii.WAD.loadFile(src)[0])
except:
try:
arc = Wii.U8.loadFile(src)
except:
error("File selected is not a WAD or a U8 file.")
return
try: #basic sanity checking
assert arc['layout'] == None
assert arc['layout/common'] == None
except:
error("Invalid source selected!")
return
self.progress(0)
for elem in queue:
arc = self.doMyMenu(arc, elem)
self.completed += 1
dlg = wx.FileDialog(None, "Save Completed File...", "", "", "Custom System Menu Files (*.csm)|*.csm|All Files (*.*)|*.*", wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
dst = dlg.GetPath()
if(dst.find('.') == -1):
dst = dst + '.csm'
arc.dumpFile(dst)
dlg.Destroy()
def add(self, evt):
dlg = wx.FileDialog(None, "Browse for Source...", "", "", "MyMenu Scripts (*.mym)|*.mym|All Files (*.*)|*.*", wx.OPEN)
if dlg.ShowModal() == wx.ID_OK:
queue.append(dlg.GetPath())
log("Added " + dlg.GetPath() + " to queue.")
self.setList()
self.list.Select(len(queue) - 1)
dlg.Destroy()
def getSelected(self):
i = self.list.GetFirstSelected()
self.selected = i
return i
def setList(self):
self.list.DeleteAllItems()
for elem in queue:
self.list.InsertStringItem(0, elem)
def remove(self, evt):
if(self.getSelected() == -1):
return
log("Removing " + queue[self.selected] + " from queue.")
queue.pop(self.selected)
self.setList()
self.list.Select(min(len(queue) - 1, self.selected))
def browse(self, evt):
dlg = wx.FileDialog(None, "Browse for Source...", "", "", "Theme Bases (*.csm)|*.csm|Theme Bases (*.app)|*.app|Wii WAD files (*.wad)|*.wad|All Files (*.*)|*.*", wx.OPEN)
if dlg.ShowModal() == wx.ID_OK:
self.src.SetValue(dlg.GetPath())
log("Source selected: " + dlg.GetPath())
dlg.Destroy()
def up(self, evt):
global queue
if(self.getSelected() == -1):
return
if(self.selected == 0):
return
tmp1 = queue[self.selected - 1]
tmp2 = queue[self.selected]
queue[self.selected - 1] = tmp2
queue[self.selected] = tmp1
self.setList()
self.list.Select(self.selected - 1)
self.getSelected()
def down(self, evt):
global queue
if(self.getSelected() == -1):
return
if(self.selected == len(queue) - 1):
return
tmp1 = queue[self.selected + 1]
tmp2 = queue[self.selected]
queue[self.selected + 1] = tmp2
queue[self.selected] = tmp1
self.setList()
self.list.Select(self.selected + 1)
self.getSelected()
mymenu = MyMenu()
mymenu.MainLoop()
"""
cont = {}
numcont = 0
numelse = 0
for sec in sections:
if(sec[:4] == 'cont'):
numcont += 1
for sec in sections:
if(sec[:4] == 'sdta' or sec[:4] == 'simg' or sec[:4] == 'cdta' or sec[:4] == 'cimg'):
numelse += 1
thiscont = 0
for i, sec in enumerate(sections):
if(sec[:4] == 'cont'):
self.progress(10 + (20 / (numcont / thiscont))
thiscont += 1
self.progress(30)
for i, sec in enumerate(sections):
if(sec[:4] == 'sdta'):
elif(sec[:4] == 'simg'):
elif(sec[:4] == 'cdta'):
elif(sec[:4] == 'cimg'):
"""

59
nand.py
View File

@ -363,38 +363,35 @@ class NAND:
a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin", self) a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin", self)
a.deleteTitle(tid) a.deleteTitle(tid)
def importTitle(self, prefix, tmd, tik, add_to_menu = True, is_decrypted = False, result_decrypted = False): def importTitle(self, title, add_to_menu = True, result_decrypted = False, use_version = True):
"""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.""" """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.AddTitleStart(title.tmd, None, None, True, result_decrypted, use_version = use_version)
self.ES.AddTitleTMD(tmd) self.ES.AddTitleTMD(title.tmd)
self.ES.AddTicket(tik) self.ES.AddTicket(title.tik)
contents = tmd.getContents() contents = title.tmd.getContents()
for i in range(tmd.tmd.numcontents): for i, content in enumerate(contents):
self.ES.AddContentStart(tmd.tmd.titleid, contents[i].cid) self.ES.AddContentStart(title.tmd.titleid, content.cid)
fp = open(prefix + "/%08x.app" % contents[i].cid, "rb") data = title[content.index]
data = fp.read() self.ES.AddContentData(content.cid, data)
fp.close() self.ES.AddContentFinish(content.cid)
self.ES.AddContentData(contents[i].cid, data)
self.ES.AddContentFinish(contents[i].cid)
self.ES.AddTitleFinish() self.ES.AddTitleFinish()
if(add_to_menu == True): if(add_to_menu == True):
if(((tmd.tmd.titleid >> 32) != 0x00010008) and ((tmd.tmd.titleid >> 32) != 0x00000001)): if(((tmd.tmd.titleid >> 32) != 0x00010008) and ((tmd.tmd.titleid >> 32) != 0x00000001)):
self.addTitleToMenu(tmd.tmd.titleid) self.addTitleToMenu(tmd.tmd.titleid)
def createWADFromTitle(self, title, cert, output, version=0): def getTitle(self, title_id, version = None, fakesign = True):
title = Title()
tmdpth = self.f + "/title/%08x/%08x/content/title.tmd" % (title >> 32, title & 0xFFFFFFFF) tmdpth = self.f + "/title/%08x/%08x/content/title.tmd" % (title >> 32, title & 0xFFFFFFFF)
if(version != 0): if(version != 0):
tmdpth += ".%d" % version tmdpth += ".%d" % version
tmd = TMD.loadFile(tmdpth) title.tmd = TMD.loadFile(tmdpth)
if(not os.path.isdir("export")): if(fakesign):
os.mkdir("export") title.tmd.fakesign()
tmd.fakesign() title.tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
tmd.dumpFile("export/tmd") if(fakesign):
tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF)) title.tik.fakesign()
tik.fakesign() contents = title.tmd.getContents()
tik.dumpFile("export/tik") for i in range(len(contents)):
contents = tmd.getContents()
for i in range(tmd.tmd.numcontents):
path = "" path = ""
if(contents[i].type == 0x0001): if(contents[i].type == 0x0001):
path = self.f + "/title/%08x/%08x/content/%08x.app" % (title >> 32, title & 0xFFFFFFFF, contents[i].cid) path = self.f + "/title/%08x/%08x/content/%08x.app" % (title >> 32, title & 0xFFFFFFFF, contents[i].cid)
@ -403,23 +400,11 @@ class NAND:
fp = open(path, "rb") fp = open(path, "rb")
data = fp.read() data = fp.read()
fp.close() fp.close()
fp = open("export/%08x.app" % contents[i].index, "wb") title.contents.append(data)
fp.write(data)
fp.close()
fp = open(cert, "rb") fp = open(cert, "rb")
data = fp.read() data = fp.read()
fp.close() fp.close()
fp = open("export/cert", "wb") title.cert = data
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: class ISFSClass:
"""This class contains an interface to the NAND that simulates the permissions system and all other aspects of the ISFS. """This class contains an interface to the NAND that simulates the permissions system and all other aspects of the ISFS.

397
nus wadpacker.py Normal file
View File

@ -0,0 +1,397 @@
#----------------------------------------------------------------------
# NUS WAD Packer 1.0.1 - a (sorta) simple GUI for NUS downloading.
# (c) 2009 Xuzz (icefire) and Xuzz Productions.
#
# Wii.py (c) Xuzz, SquidMan, megazig, TheLemonMan, Omega|, and Matt_P.
#----------------------------------------------------------------------
import os, wx, shutil, sys, threading
import Wii
def readableTitleID(lower):
out = struct.unpack("4s", struct.pack(">I", lower))
return out[0]
def getName(titleid):
upper = (titleid >> 32)
lower = (titleid & 0xFFFFFFFF)
if(upper == 0x00000001):
if(lower > 0x02 and lower < 0x100):
return "IOS%d" % lower
elif(lower == 0x02):
return "SystemMenu"
elif(lower == 0x100):
return "BC"
elif(lower == 0x101):
return "MIOS"
else:
return "Unknown System Title (%08x)" % lower
elif(upper == 0x00010002 or upper == 0x00010008):
read = readableTitleID(lower)
if(read[3] == "K"):
rgn = "Korea"
elif(read[3] == "A"):
rgn = "All Regions"
elif(read[3] == "P"):
rgn = "Europe/PAL"
elif(read[3] == "E"):
rgn = "North America"
elif(read[3] == "J"):
rgn = "Japan"
else:
rgn = "Unknown Region"
if(read[:3] == "HAB"):
return "Wii Shop Channel (%s)" % rgn
if(read[:3] == "HAL"):
return "EULA (%s)" % rgn
if(read[:3] == "HAA"):
return "Photo Channel (%s)" % rgn
if(read[:3] == "HAC"):
return "Mii Channel (%s)" % rgn
if(read[:3] == "HAE"):
return "Wii Message Board (%s)" % rgn
if(read[:3] == "HAF"):
return "Weather Channel (%s)" % rgn
if(read[:3] == "HAG"):
return "News Channel (%s)" % rgn
if(read[:3] == "HAK"):
return "Region Select (%s)" % rgn
if(read[:3] == "HAY"):
return "Photo Channel 1.1 (%s)" % rgn
if(upper == 0x00010002):
return "Channel '%s'" % read
if(upper == 0x00010008):
return "Hidden Channel '%s'" % read
else:
return "Other (%08x-%08x)" % (upper, lower)
queue = []
class Downloader(wx.App):
def OnInit(self):
self.selected = -1
frame = wx.Frame(None, -1, "NUS WAD Packer", (150, 150), (600, 650))#, wx.SYSTEM_MENU | wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.CLOSE_BOX)
panel = wx.Panel(frame)
panel.Show(True)
wx.StaticText(panel, -1, "NUS WAD Packer (c) 2009 Xuzz. Powered by the Wii.py framework.", (5, 5))
wx.StaticText(panel, -1, "Title ID:", (5, 35))
self.titleid = wx.TextCtrl(panel, -1, "", (60, 30), (150, -1))
wx.StaticText(panel, -1, "Version:", (216, 35))
self.version = wx.TextCtrl(panel, -1, "", (270, 30), (50, -1))
wx.StaticText(panel, -1, "Output:", (5, 70))
self.out = wx.TextCtrl(panel, -1, "", (60, 65), (150, -1))
browsebtn = wx.Button(panel, -1, "Browse", (216, 65), (105, 27))
self.Bind(wx.EVT_BUTTON, self.browse, browsebtn)
wx.StaticText(panel, -1, "Output Format:", (330, 35))
selectbox = ['WAD', 'Enc Contents', 'Dec Contents']
self.outputtype = wx.ComboBox(panel, -1, "", (330, 65), (125, 27), choices = selectbox, style = wx.CB_READONLY)
self.outputtype.SetStringSelection(selectbox[0])
wx.StaticBox(panel, -1, "Scripting", (460, 5), (130, 86))
loadbtn = wx.Button(panel, -1, "Load Script", (470, 25), (110, 25))
self.Bind(wx.EVT_BUTTON, self.load, loadbtn)
savescriptbtn = wx.Button(panel, -1, "Save Script", (470, 55), (110, 25))
self.Bind(wx.EVT_BUTTON, self.savescript, savescriptbtn)
addbtn = wx.Button(panel, -1, "Add", (5, 100), (75, -1))
self.Bind(wx.EVT_BUTTON, self.add, addbtn)
savebtn = wx.Button(panel, -1, "Save", (250, 100), (75, -1))
self.Bind(wx.EVT_BUTTON, self.save, savebtn)
rmbtn = wx.Button(panel, -1, "Remove", (85, 100), (75, -1))
self.Bind(wx.EVT_BUTTON, self.remove, rmbtn)
upbtn = wx.Button(panel, -1, "Up", (440, 100), (75, -1))
self.Bind(wx.EVT_BUTTON, self.up, upbtn)
downbtn = wx.Button(panel, -1, "Down", (520, 100), (75, -1))
self.Bind(wx.EVT_BUTTON, self.down, downbtn)
self.list = wx.ListCtrl(panel, -1, (5, 140), (590, 250), wx.LC_REPORT | wx.SUNKEN_BORDER)
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.select, self.list)
self.list.Show(True)
self.list.InsertColumn(0, "Title ID")
self.list.InsertColumn(1, "Version")
self.list.InsertColumn(2, "Output")
self.list.InsertColumn(3, "Format")
wx.StaticText(panel, -1, "Message Log:", (5, 400))
self.output = wx.TextCtrl(panel, -1, "", (5, 420), (590, 170), wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP)
font = wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL)
self.output.SetFont(font)
self.output.AppendText("NUS WAD Packer started and ready...\n\n")
downloadbtn = wx.Button(panel, -1, "Download", (5, 600), (120, -1))
self.Bind(wx.EVT_BUTTON, self.go, downloadbtn)
self.progress = wx.Gauge(panel, -1, 100, (135, 605), (460, -1))
self.progress.SetValue(0)
frame.Show(True)
return True
def getSelected(self):
i = self.list.GetFirstSelected()
self.selected = i
return i
def select(self, evt):
if(self.getSelected() == -1):
return
item = queue[self.selected]
self.titleid.SetValue(item[0])
self.version.SetValue(item[1])
self.out.SetValue(item[2])
self.outputtype.SetValue(item[3])
def setList(self):
self.list.DeleteAllItems()
for i, elem in enumerate(queue):
self.list.InsertStringItem(i, elem[0])
self.list.SetStringItem(i, 1, elem[1])
self.list.SetStringItem(i, 2, elem[2])
self.list.SetStringItem(i, 3, elem[3])
def add(self, evt):
titleid = self.titleid.GetValue()
if(len(titleid) != 16 and not titleid.isdigit()):
self.dialog("Title ID must be 16 numbers long! No dashes.")
return
ver = self.version.GetValue()
if(not ver.isdigit() and ver != ""):
self.dialog("Version must be all numbers!")
return
out = self.out.GetValue()
fmt = self.outputtype.GetValue()
if(not os.path.isdir(os.path.dirname(out)) and fmt == "WAD"):
out = os.path.expanduser("~/" + getName(int(titleid, 16)))
if(ver != ""):
out += "v" + str(ver)
out += ".wad"
elif(not os.path.isdir(os.path.dirname(out))):
out = os.path.expanduser("~/" + getName(int(titleid, 16)))
if(ver != ""):
out += "v" + str(ver)
queue.append((titleid, ver, out, fmt))
self.setList()
self.list.Select(len(queue) - 1)
def dialog(self, message):
wx.MessageBox(message, "Error")
def save(self, evt):
if(self.getSelected() == -1):
return
titleid = self.titleid.GetValue()
if(len(titleid) != 16 and titleid.isdigit()):
self.dialog("Title ID must be 16 numbers long! No dashes.")
return
ver = self.version.GetValue()
if(ver.isdigit()):
self.dialog("Version must be all numbers!")
return
out = self.out.GetValue()
fmt = self.outputtype.GetValue()
if(not os.path.isdir(os.path.dirname(out)) and fmt == "WAD"):
out = os.path.expanduser("~/" + getName(int(titleid, 16)))
if(ver != ""):
out += "v" + str(ver)
out += ".wad"
elif(not os.path.isdir(os.path.dirname(out))):
out = os.path.expanduser("~/" + getName(int(titleid, 16)))
if(ver != ""):
out += + "v" + str(ver)
queue[self.selected] = (titleid, ver, out, fmt)
self.setList()
self.list.Select(self.selected)
def remove(self, evt):
if(self.getSelected() == -1):
return
queue.pop(self.selected)
self.setList()
self.list.Select(min(len(queue) - 1, self.selected))
def browse(self, evt):
if(self.outputtype.GetValue() == "WAD"):
dlg = wx.FileDialog(None, "Browse For Destination...", "", "", "Wii WAD files (*.wad)|*.wad|All Files (*.*)|*.*", wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
self.out.SetValue(dlg.GetPath())
dlg.Destroy()
else:
dlg = wx.DirDialog(None, "Browse For Destination...", "")
if dlg.ShowModal() == wx.ID_OK:
self.out.SetValue(dlg.GetPath())
dlg.Destroy()
def up(self, evt):
global queue
if(self.getSelected() == -1):
return
if(self.selected == 0):
return
tmp1 = queue[self.selected - 1]
tmp2 = queue[self.selected]
queue[self.selected - 1] = tmp2
queue[self.selected] = tmp1
self.setList()
self.list.Select(self.selected - 1)
self.getSelected()
def down(self, evt):
global queue
if(self.getSelected() == -1):
return
if(self.selected == len(queue) - 1):
return
tmp1 = queue[self.selected + 1]
tmp2 = queue[self.selected]
queue[self.selected + 1] = tmp2
queue[self.selected] = tmp1
self.setList()
self.list.Select(self.selected + 1)
self.getSelected()
def load(self, evt):
dlg = wx.FileDialog(None, "Open Script...", "", "", "Wii NUS Packer/Wiiimposter scripts (*.nus)|*.nus|All Files (*.*)|*.*", wx.OPEN)
if(dlg.ShowModal() == wx.ID_OK):
script = dlg.GetPath()
else:
dlg.Destroy()
return
dlg.Destroy()
stuff = open(script).read().split()
for i, arg in enumerate(stuff):
if(len(arg) == 16 and len(stuff) != i + 1 and stuff[i + 1].isdigit()):
tmp = (arg, str(int(stuff[i + 1], 16)), os.path.expanduser("~/" + getName(int(arg, 16)) + "v" + str(int(stuff[i + 1], 16)) + ".wad"), "WAD")
queue.append(tmp)
self.setList()
self.output.AppendText("Script loaded from %s.\n" % script)
def savescript(self, evt):
dlg = wx.FileDialog(None, "Save Script...", "", "", "Wii NUS Packer/Wiiimposter scripts (*.nus)|*.nus|All Files (*.*)|*.*", wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
script = dlg.GetPath()
else:
dlg.Destroy()
return
dlg.Destroy()
fd = open(script, "wb")
for i, elem in enumerate(queue):
fd.write("%08x%08x %04x\n" % (int(elem[0][:8], 16), int(elem[0][8:], 16), int(elem[1])))
self.output.AppendText("Script written to %s.\n" % script)
def go(self, evt):
self.progress.SetValue(0)
self.progress.SetRange(len(queue) * 3)
if(os.path.isdir("tmp")):
shutil.rmtree("tmp")
olddir = os.getcwd()
for i, elem in enumerate(queue):
os.chdir(olddir)
try:
titleid = int(elem[0], 16)
if(len(elem[1]) > 0):
ver = int(elem[1])
else:
ver = 0
except:
self.output.AppendText("Invalid title %s" % elem[0])
if(elem[1] != ""):
self.output.AppendText(" version %s" % elem[1])
self.output.AppendText(", skipping...\n\n")
continue
outfile = elem[2]
fmt = elem[3]
self.output.AppendText("Downloading %08x-%08x (%s)" % (titleid >> 32, titleid & 0xFFFFFFF, getName(titleid),))
if(ver != 0):
self.output.AppendText(" version %u" % ver)
self.output.AppendText("...")
self.progress.SetValue(i * 3 + 1)
if(fmt == "Dec Contents"):
dlthread = download(titleid, ver, True)
else:
dlthread = download(titleid, ver)
dlthread.start()
while(dlthread.is_alive()):
wx.Yield()
self.progress.SetValue(i * 3 + 2)
if(os.path.exists("tmp/tik") == False):
self.output.AppendText("not found!\n\n")
continue
self.output.AppendText("done!\n")
if(fmt == "WAD"):
if(os.path.isdir(os.path.dirname(outfile))):
self.output.AppendText("Packing WAD to %s..." % outfile)
dlthread = pack("tmp", outfile)
dlthread.start()
while(dlthread.is_alive()):
wx.Yield()
else:
if(os.path.isdir("/".join(outfile.split("/")[:-1]))):
if(not os.path.isdir(outfile)):
os.mkdir(outfile)
self.output.AppendText("Copying files to %s..." % outfile)
for file in os.listdir("tmp"):
shutil.copy("tmp/" + file, outfile)
wx.Yield()
self.output.AppendText("done!\n")
self.progress.SetValue(i * 3 + 3)
self.output.AppendText("Title Info:\n")
self.output.AppendText(str(Wii.TMD.loadFile("tmp/tmd")))
self.output.AppendText(str(Wii.Ticket.loadFile("tmp/tik")))
self.output.AppendText("\n")
os.chdir(olddir)
os.chdir(olddir)
self.output.AppendText("Queue Downloaded!\n\n")
class download(threading.Thread):
def __init__(self, titleid, ver, decrypt = True):
threading.Thread.__init__(self)
self.titleid = titleid
self.ver = ver
self.decrypt = decrypt
def run(self):
try:
if(self.ver != 0):
Wii.NUS.download(self.titleid, self.ver).dumpDir("tmp", decrypt = self.decrypt)
else:
Wii.NUS.download(self.titleid).dumpDir("tmp", decrypt = self.decrypt)
except:
pass
class pack(threading.Thread):
def __init__(self, dir, outfile):
threading.Thread.__init__(self)
self.outfile = outfile
self.dir = dir
def run(self):
try:
Wii.WAD.loadDir(self.dir).dumpFile(self.outfile, fakesign = False)
except:
pass
tb = ''
def excepthook(type, value, traceb):
import traceback
class dummy:
def write(self, text):
global tb
tb += text
dummyf = dummy()
traceback.print_exception(type, value, traceb, file=dummyf)
wx.MessageBox('NUS WAD Packer has encountered a fatal error. Please inform the author of the following traceback:\n\n%s' % tb, 'Fatal Error', wx.OK | wx.ICON_ERROR)
clean()
sys.exit(1)
if(__name__ == '__main__'):
dl = Downloader(redirect = False)
dl.MainLoop()
if(os.path.isdir(os.getcwd() + "/tmp")):
shutil.rmtree(os.getcwd() + "/tmp")

215
title.py
View File

@ -252,63 +252,190 @@ class TMD(WiiObject):
"""Sets the boot index of the TMD to the value of index.""" """Sets the boot index of the TMD to the value of index."""
self.tmd.boot_index = index self.tmd.boot_index = index
class Title(WiiArchive):
def __init__(self, boot2 = False):
self.tmd = TMD()
self.tik = Ticket()
self.contents = []
self.boot2 = False
self.cert = ""
def _load(self, data):
if(self.boot2 != True):
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding = struct.unpack('>I4s6I32s', data[:64])
pos = 64
else:
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', data[:32])
pos = 32
rawcert = data[pos:pos + certsize]
pos += certsize
if(self.boot2 != True):
if(certsize % 64 != 0):
pos += 64 - (certsize % 64)
self.cert = rawcert
rawtik = data[pos:pos + tiksize]
pos += tiksize
if(self.boot2 != True):
if(tiksize % 64 != 0):
pos += 64 - (tiksize % 64)
self.tik = Ticket.load(rawtik)
rawtmd = data[pos:pos + tmdsize]
pos += tmdsize
if(self.boot2 == True):
pos = data_offset
else:
pos += 64 - (tmdsize % 64)
self.tmd = TMD.load(rawtmd)
titlekey = self.tik.getTitleKey()
contents = self.tmd.getContents()
for i in range(0, len(contents)):
tmpsize = contents[i].size
if(tmpsize % 16 != 0):
tmpsize += 16 - (tmpsize % 16)
encdata = data[pos:pos + tmpsize]
pos += tmpsize
decdata = Crypto().decryptContent(titlekey, contents[i].index, encdata)
self.contents.append(decdata)
if(tmpsize % 64 != 0):
pos += 64 - (tmpsize % 64)
def _loadDir(self, dir):
origdir = os.getcwd()
os.chdir(dir)
self.tmd = TMD.loadFile("tmd")
self.tik = Ticket.loadFile("tik")
self.cert = open("cert", "rb").read()
contents = self.tmd.getContents()
for i in range(len(contents)):
self.contents.append(open("%08x.app" % i, "rb").read())
os.chdir(origdir)
def _dumpDir(self, dir, useidx = True, decrypt = True):
origdir = os.getcwd()
os.chdir(dir)
contents = self.tmd.getContents()
titlekey = self.tik.getTitleKey()
for i, content in enumerate(contents):
if(useidx == True):
output = content.index
else:
output = content.cid
if(decrypt == True):
open("%08x.app" % output, "wb").write(self.contents[i])
else:
open("%08x.app" % output, "wb").write(Crypto.encryptContent(titlekey, content.index, self.contents[content.index]))
self.tmd.dumpFile("tmd")
self.tik.dumpFile("tik")
open("cert", "wb").write(self.cert)
os.chdir(origdir)
def _dump(self, fakesign = True):
titlekey = self.tik.getTitleKey()
contents = self.tmd.getContents()
apppack = ""
for i, content in enumerate(contents):
if(fakesign):
content.hash = str(Crypto.createSHAHash(self.contents[content.index]))
content.size = len(self.contents[content.index])
encdata = Crypto.encryptContent(titlekey, content.index, self.contents[content.index])
apppack += encdata
if(len(encdata) % 64 != 0):
apppack += "\x00" * (64 - (len(encdata) % 64))
if(fakesign):
self.tmd.setContents(contents)
self.tmd.fakesign()
self.tik.fakesign()
rawtmd = self.tmd.dump()
rawcert = self.cert
rawtik = self.tik.dump()
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
return pack
def fakesign(self):
self.tik.fakesign()
self.tmd.fakesign()
def __getitem__(self, idx):
return self.contents[idx]
def __setitem__(self, idx, value):
self.contents[idx] = value
def __str__(self):
out = ""
out += "Wii WAD:\n"
out += str(self.tmd)
out += str(self.tik)
return out
WAD = Title
class NUS: 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.""" """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): url = "http://nus.cdn.shop.wii.com/ccs/download/"
self.titleid = titleid def download(self, titleid, version = None):
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 = "" certs = ""
rawtmd = urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/tmd.289").read() tmd = TMD.load(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() tik = Ticket.load(urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/cetk").read())
certs += rawtik[0x2A4:0x2A4 + 0x300] #XS certs += tik.dump()[0x2A4:0x2A4 + 0x300] #XS
certs += rawtik[0x2A4 + 0x300:] #CA (tik) certs += tik.dump()[0x2A4 + 0x300:] #CA (tik)
certs += rawtmd[0x328:0x328 + 0x300] #CP certs += tmd.dump()[0x328:0x328 + 0x300] #CP
if(Crypto().createMD5HashHex(certs) != "7ff50e2733f7a6be1677b6f6c9b625dd"): if(Crypto.createMD5HashHex(certs) != "7ff50e2733f7a6be1677b6f6c9b625dd"):
raise ValueError("Failed to create certs! MD5 mistatch.") raise ValueError("Failed to create certs! MD5 mistatch.")
open("cert", "wb").write(certs)
if(self.version == None): if(self.version == None):
versionstring = "" versionstring = ""
else: else:
versionstring = ".%u" % self.version versionstring = ".%u" % self.version
titleurl = self.url + "%08x%08x/" % (titleid >> 32, titleid & 0xFFFFFFFF)
urllib.urlretrieve(self.baseurl + "tmd" + versionstring, "tmd") tmd = TMD.load(urllib.urlopen(titleurl + "tmd" + versionstring).read())
tmd = TMD.loadFile("tmd") tik = Ticket.load(urllib.urlopen(titleurl + "cetk").read())
tmd.dumpFile("tmd") # strip certs
urllib.urlretrieve(self.baseurl + "cetk", "tik") title = Title()
tik = Ticket.loadFile("tik") title.tmd = tmd
tik.dumpFile("tik") # strip certs title.tik = tik
if(decrypt): title.cert = certs
titlekey = tik.getTitleKey()
contents = tmd.getContents() contents = tmd.getContents()
for content in contents: for content in contents:
output = content.cid encdata = urllib.urlopen(titleurl + ("%08x" % content.cid)).read(content.size)
if(useidx): decdata = Crypto.decryptContent(titlekey, content.index, encdata)
output = content.index if(Crypto.validateSHAHash(decdata, content.hash) == 0):
raise ValueError("Decryption failed! SHA1 mismatch.")
urllib.urlretrieve(self.baseurl + ("%08x" % content.cid), "%08x.app" % output) title.contents.append(decdata)
download = classmethod(download)
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("..")

338
wiimposter.py Normal file
View File

@ -0,0 +1,338 @@
import Wii as wii
import urllib2, struct, time, shutil, os, sys
class NUSID():
def __init__(self, titleid, version, size):
self.titleid = titleid
self.version = version
self.size = size
def __str__(self):
return "[soap] INFO: %08x-%08x %04x %u\n" % (self.titleid >> 32, self.titleid & 0xFFFFFFFF, self.version, self.size)
def rawstr(self):
return "%08x%08x %04x %u\n" % (self.titleid >> 32, self.titleid & 0xFFFFFFFF, self.version, self.size)
def getSOAP(region): # hardcoded device id for now
soaprequest = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><GetSystemUpdateRequest xmlns="urn:nus.wsapi.broadon.com"><Version>1.0</Version><MessageId>13198105123219138</MessageId><DeviceId>4362227770</DeviceId><RegionId>' + region + '</RegionId><CountryCode>' + region[:2] + '</CountryCode><TitleVersion><TitleId>0000000100000001</TitleId><Version>2</Version></TitleVersion><TitleVersion><TitleId>0000000100000002</TitleId><Version>33</Version></TitleVersion><TitleVersion><TitleId>0000000100000009</TitleId><Version>516</Version></TitleVersion><Attribute>2</Attribute><AuditData></AuditData></GetSystemUpdateRequest></soapenv:Body></soapenv:Envelope>'
headers = {"Content-type":"text/xml; charset=utf-8", "SOAPAction":'"urn:nus.wsapi.broadon.com/"', "User-agent":"wii libnup/1.0"}
request = urllib2.Request("http://nus.shop.wii.com/nus/services/NetUpdateSOAP", soaprequest, headers)
f = urllib2.urlopen(request)
data = f.read()
titles = []
data = data[data.find("</UncachedContentPrefixURL>") + len("</UncachedContentPrefixURL>"):data.find("<UploadAuditData>")]
for i in range(data.count("<TitleId>")):
title = data[data.find("<TitleVersion>"):data.find("</TitleVersion>") + len("</TitleVersion>")]
titleid = int(data[data.find("<TitleId>") + len("<TitleId>"):data.find("</TitleId>")], 16) #16 for hex
version = int(data[data.find("<Version>") + len("<Version>"):data.find("</Version>")])
size = int(data[data.find("<FsSize>") + len("<FsSize>"):data.find("</FsSize>")])
nus = NUSID(titleid, version, size)
titles.append(nus)
data = data[data.find("</TitleVersion>") + len("</TitleVersion>"):]
return titles
def readableTitleID(lower):
out = struct.unpack("4s", struct.pack(">I", lower))
return out[0]
def log(text):
sys.stdout.write(text)
sys.stdout.flush()
def getName(titleid):
upper = (titleid >> 32)
lower = (titleid & 0xFFFFFFFF)
if(upper == 0x00000001):
if(lower > 0x02 and lower < 0x100):
return "IOS%d" % lower
elif(lower == 0x02):
return "SystemMenu"
elif(lower == 0x100):
return "BC"
elif(lower == 0x101):
return "MIOS"
else:
return "Unknown System Title (%08x)" % lower
elif(upper == 0x00010002 or upper == 0x00010008):
read = readableTitleID(lower)
if(read[3] == "K"):
rgn = "Korea"
elif(read[3] == "A"):
rgn = "All Regions"
elif(read[3] == "P"):
rgn = "Europe/PAL"
elif(read[3] == "E"):
rgn = "North America"
elif(read[3] == "J"):
rgn = "Japan"
else:
rgn = "Unknown Region"
if(read[:3] == "HAB"):
return "Wii Shop Channel (%s)" % rgn
if(read[:3] == "HAL"):
return "EULA (%s)" % rgn
if(read[:3] == "HAA"):
return "Photo Channel (%s)" % rgn
if(read[:3] == "HAC"):
return "Mii Channel (%s)" % rgn
if(read[:3] == "HAE"):
return "Wii Message Board (%s)" % rgn
if(read[:3] == "HAF"):
return "Weather Channel (%s)" % rgn
if(read[:3] == "HAG"):
return "News Channel (%s)" % rgn
if(read[:3] == "HAK"):
return "Region Select (%s)" % rgn
if(read[:3] == "HAY"):
return "Photo Channel 1.1 (%s)" % rgn
if(upper == 0x00010002):
return "Channel '%s'" % read
if(upper == 0x00010008):
return "Hidden Channel '%s'" % read
else:
return "Other (%08x-%08x)" % (upper, lower)
def summary(report, item):
tmd = wii.TMD.loadFile("%08x%08x/tmd" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF)) #not tmp/ because we are already in tmp
report.write(getName(item.titleid) + "\n")
report.write(" Title ID: %08x-%08x\n" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF))
report.write(" Version: 0x%04x\n Size: %u\n" % (item.version, item.size))
shared = 0
contents = tmd.getContents()
for i in range(len(contents)):
if(contents[i].type & 0x8000):
shared += 1
report.write(" Contents: %u (of which %u are shared)\n\n" % (len(contents), shared))
def detailed(report, item):
tmd = wii.TMD.loadFile("tmp/%08x%08x/tmd" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF))
tik = wii.Ticket.loadFile("tmp/%08x%08x/tik" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF))
log("Importing %s to fake NAND (decrypted)..." % getName(item.titleid))
wii.NAND("nand").importTitle("tmp/%08x%08x" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF), tmd, tik, is_decrypted = False, result_decrypted = True)
log("Done!\n")
log("Importing %s to fake NAND (encrypted)..." % getName(item.titleid))
wii.NAND("encnand").importTitle("tmp/%08x%08x" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF), tmd, tik, is_decrypted = False, result_decrypted = False)
log("Done!\n")
shutil.copy("tmp/%08x%08x/cert" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF), "nand/sys/cert.sys")
shutil.copy("tmp/%08x%08x/cert" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF), "encnand/sys/cert.sys")
report.write(getName(item.titleid) + "\n")
report.write(" Title ID: %08x-%08x\n" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF))
report.write(" Version: 0x%04x\n Size: %u\n" % (item.version, item.size))
report.write(str(tmd))
#do TMD signature and certs. TODO!
report.write(str(tik))
#do Ticket signature and certs. TODO!
report.write("\n\n")
class nullFile:
def _null(self, *args, **kwds):
pass
def __getattr__(self, name):
return self._null
def changed(region, added, removed, modified, previous, no_log = False):
if(no_log):
report = nullFile()
else:
report = open("reports/%s/%s.log" % (region, time.strftime("%y%m%d-%I%M%S")), "wb")
report.write("************************************************************************\n*********************** Wii System Update Report ***********************\n************************************************************************\n\n")
report.write("Titles added:\t%u\n" % len(added))
report.write("Titles changed:\t%u\n" % len(modified))
report.write("Titles removed:\t%u\n" % len(removed))
report.write("\n************************** SUMMARY OF CHANGES **************************\n")
os.chdir("tmp")
if(len(added) > 0):
report.write("\n====== Titles Added ======\n\n")
for item in added:
log("Downloading %s..." % getName(item.titleid))
wii.NUS(item.titleid, item.version).download(useidx = False, decrypt = False)
log("Done!\n")
summary(report, item)
if(len(modified) > 0):
report.write("\n====== Titles Changed ======\n\n")
for item in modified:
log("Downloading %s..." % getName(item.titleid))
wii.NUS(item.titleid, item.version).download(useidx = False, decrypt = False)
log("Done!\n")
summary(report, item)
if(len(removed) > 0):
report.write("\n====== Titles Removed (showing info from last available version) ======\n\n")
for item in removed:
summary(report, item)
os.chdir("..")
report.write("\n**************************** DETAILED DUMPS ****************************\n")
if(len(added) > 0):
report.write("\n====== Titles Added ======\n\n")
for item in added:
detailed(report, item)
shutil.rmtree("tmp/%08x%08x" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF))
if(len(modified) > 0):
report.write("\n====== Titles Changed ======\n\n")
for item in modified:
detailed(report, item)
shutil.rmtree("tmp/%08x%08x" % (item.titleid >> 32, item.titleid & 0xFFFFFFFF))
if(len(removed) > 0):
report.write("\n====== Titles Removed (showing info from last available version) ======\n\n")
for item in removed:
detailed(report, item)
report.write("\n***************************** MESSAGE LOG *****************************\n")
if(no_log != True):
report.write(open("runlog.%s.txt" % region).read())
def imposter(regions):
for region in regions:
log("Wiimpostor invoked for region %s...\n" % region)
data = ""
data += "[check] INFO: Wiimposter: Check invoked for region %s\n" % region
try:
nodb = False
last = open("lastupdate.%s.txt" % region, "rb").read()
except:
data += "[titlelist] WARNING: TitleList: DB file lastupdate.%s.txt does not exist. Initializing to blank list.\n" % region
nodb = True
data += "[soap] INFO: Checking for updates...\n"
data += ("[soap] INFO: Title ID Version FsSize\n")
log("Getting list of titles from Nintendo's SOAP server...")
soap = getSOAP(region)
for entry in soap:
data += str(entry)
log("Done!\n")
if(nodb):
old = []
else:
old = last.split("\n")
topop = []
for i in range(len(old)):
elem = old[i]
if(elem == ""):
topop.append(i) # >_>
continue
elem = elem.split(" ")
titleid = int(elem[0], 16)
version = int(elem[1], 16)
sz = int(elem[2])
tmp = NUSID(titleid, version, sz) #is a tmp var needed?
old[i] = tmp
popped = 0 #don't ask me why I did this
for pop in topop:
old.pop(pop - popped)
popped += 1
#the code from here on down makes no sesne to me already. Dont ask.
added = []
removed = []
modified = []
same = []
previous = []
log("Checking for titles removed, modified, or added...")
for elem in old:
cookie_jar = len(soap)
for title in soap:
if(elem.titleid != title.titleid):
cookie_jar -= 1
elif(elem.version != title.version): #don't check sizes because nintendo randomly changes them
modified.append(title)
previous.append(elem)
else:
same.append(title)
if(cookie_jar == 0):
removed.append(elem)
for title in soap:
cookie_jar = len(old)
for elem in old:
if(elem.titleid != title.titleid):
cookie_jar -= 1
if(cookie_jar == 0):
added.append(title)
log("Done!\n")
#end code I don't get anymore
if(not os.path.isdir("reports")):
os.mkdir("reports")
if(not os.path.isdir("reports/USA")):
os.mkdir("reports/USA")
if(not os.path.isdir("reports/EUR")):
os.mkdir("reports/EUR")
if(not os.path.isdir("reports/JPN")):
os.mkdir("reports/JPN")
if(not os.path.isdir("reports/KOR")):
os.mkdir("reports/KOR")
if(not os.path.isdir("tmp")):
os.mkdir("tmp")
data += "[soap] INFO: Comparing update with previous...\n"
data += "[soap] INFO: %u added, %u removed, %u changed, %u unchanged" % (len(added), len(removed), len(modified), len(same))
open("runlog.%s.txt" % region, "wb").write(data)
if(len(added) != 0 or len(modified) != 0 or len(removed) != 0):
updatelog = ""
for title in soap:
updatelog += title.rawstr()
open("lastupdate.%s.txt" % region, "wb").write(updatelog)
changed(region, added, removed, modified, previous)
else:
log("No new update for this region!\n")
log("Complete for region %s!\n\n" % region)
if(__name__ == "__main__"):
available = ["USA", "EUR", "JPN", "KOR"]
script = 0
if(len(sys.argv) > 1):
script = 1
if(os.path.isfile(sys.argv[1])):
stuff = open(sys.argv[1]).read().split()
else:
stuff = sys.argv[1:]
for i, arg in enumerate(stuff):
if(arg in available):
imposter(args)
elif(len(arg) == 16 and len(stuff) != i + 1 and stuff[i + 1].isdigit()):
tmp = NUSID(int(arg, 16), int(stuff[i + 1]), 0)
changed("", [tmp], [], [], [], no_log = True)
if(script == 0):
imposter(available)