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

1
Wii.py
View File

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

View File

@ -1,454 +1,320 @@
from common import *
from title import *
import zlib
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.
The f parameter is either the source folder to pack, or the source file to unpack."""
class U8Header(Struct):
__endian__ = Struct.BE
def __format__(self):
self.tag = Struct.string(4)
self.rootnode_offset = Struct.uint32
self.header_size = Struct.uint32
self.data_offset = Struct.uint32
self.zeroes = Struct.string(16)
class U8Node(Struct):
__endian__ = Struct.BE
def __format__(self):
self.type = Struct.uint16
self.name_offset = Struct.uint16
self.data_offset = Struct.uint32
self.size = Struct.uint32
def __init__(self):
self.files = []
def _dump(self):
header = self.U8Header()
rootnode = self.U8Node()
# constants
header.tag = "U\xAA8-"
header.rootnode_offset = 0x20
header.zeroes = "\x00" * 16
rootnode.type = 0x0100
nodes = []
strings = "\x00"
data = ''
for item, value in self.files:
node = self.U8Node()
recursion = item.count('/')
if(recursion < 0):
recursion = 0
name = item[item.rfind('/') + 1:]
node.name_offset = len(strings)
strings += name + '\x00'
if(value == None):
node.type = 0x0100
node.data_offset = recursion
node.size = len(nodes)
for one, two in self.files:
if(one[:len(item)] == item): # find nodes in the folder
node.size += 1
node.size += 1
else:
sz = len(value)
node.data_offset = len(data)
data += value + "\x00" * (align(sz, 32) - sz) # 32 seems to work best for fuzzyness? I'm still really not sure
node.size = sz
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)
rootnode.size = len(nodes) + 1
for i in range(len(nodes)):
if(nodes[i].type == 0x0000):
nodes[i].data_offset += header.data_offset
fd = ''
fd += header.pack()
fd += rootnode.pack()
for nodeobj in nodes:
fd += nodeobj.pack()
fd += strings
fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
fd += data
return fd
def _dumpDir(self, dir):
if(not os.path.isdir(dir)):
os.mkdir(dir)
old = os.getcwd()
os.chdir(dir)
for item, data in self.files:
if(data == None):
if(not os.path.isdir(item)):
os.mkdir(item)
else:
open(item, "wb").write(data)
os.chdir(old)
def _loadDir(self, dir):
try:
self._tmpPath += ''
except:
self._tmpPath = ''
old = os.getcwd()
os.chdir(dir)
entries = os.listdir(".")
for entry in entries:
if(os.path.isdir(entry)):
self.files.append((self._tmpPath + entry, None))
self._tmpPath += entry + '/'
self._loadDir(entry)
elif(os.path.isfile(entry)):
data = open(entry, "rb").read()
self.files.append((self._tmpPath + entry, data))
os.chdir(old)
self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
def _load(self, data):
offset = 0
header = self.U8Header()
header.unpack(data[offset:offset + len(header)])
offset += len(header)
assert header.tag == "U\xAA8-"
offset = header.rootnode_offset
rootnode = self.U8Node()
rootnode.unpack(data[offset:offset + len(rootnode)])
offset += len(rootnode)
nodes = []
for i in range(rootnode.size - 1):
node = self.U8Node()
node.unpack(data[offset:offset + len(node)])
offset += len(node)
nodes.append(node)
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
offset += len(strings)
recursion = [rootnode.size]
recursiondir = []
counter = 0
for node in nodes:
counter += 1
name = strings[node.name_offset:].split('\0', 1)[0]
if(node.type == 0x0100): # folder
recursion.append(node.size)
recursiondir.append(name)
assert len(recursion) == node.data_offset + 2 # haxx
self.files.append(('/'.join(recursiondir), None))
elif(node.type == 0): # file
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
offset += node.size
else: # unknown type -- wtf?
pass
sz = recursion.pop()
if(sz != counter + 1):
recursion.append(sz)
else:
recursiondir.pop()
def __str__(self):
ret = ''
for key, value in self.files:
name = key[key.rfind('/') + 1:]
recursion = key.count('/')
ret += ' ' * recursion
if(value == None):
ret += '[' + name + ']'
else:
ret += name
ret += '\n'
return ret
def __getitem__(self, key):
for item, val in self.files:
if(item == key):
if(val != None):
return val
else:
ret = []
for item2, val2 in self.files:
if(item2.find(item) == 0):
ret.append(item2[len(item) + 1:])
return ret[1:]
raise KeyError
def __setitem__(self, key, val):
for i in range(len(self.files)):
if(self.files[i][0] == key):
self.files[i] = (self.files[i][0], val)
return
self.files.append((key, val))
class WAD(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):
origdir = os.getcwd()
os.chdir(dir)
contents = self.tmd.getContents()
for i in range(len(contents)):
open("%08x.app" % i, "wb").write(self.contents[i])
self.tmd.dumpFile("tmd")
self.tik.dumpFile("tik")
open("cert", "wb").write(self.cert)
os.chdir(origdir)
def _dump(self, fakesign = True):
titlekey = self.tik.getTitleKey()
contents = self.tmd.getContents()
apppack = ""
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 __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
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")
from common import *
import zlib
class U8(WiiArchive):
class U8Header(Struct):
__endian__ = Struct.BE
def __format__(self):
self.tag = Struct.string(4)
self.rootnode_offset = Struct.uint32
self.header_size = Struct.uint32
self.data_offset = Struct.uint32
self.zeroes = Struct.string(16)
class U8Node(Struct):
__endian__ = Struct.BE
def __format__(self):
self.type = Struct.uint16
self.name_offset = Struct.uint16
self.data_offset = Struct.uint32
self.size = Struct.uint32
def __init__(self):
self.files = []
def _dump(self):
header = self.U8Header()
rootnode = self.U8Node()
# constants
header.tag = "U\xAA8-"
header.rootnode_offset = 0x20
header.zeroes = "\x00" * 16
rootnode.type = 0x0100
nodes = []
strings = '\x00'
data = ''
for item, value in self.files:
node = self.U8Node()
recursion = item.count('/')
if(recursion < 0):
recursion = 0
name = item[item.rfind('/') + 1:]
node.name_offset = len(strings)
strings += name + '\x00'
if(value == None): # directory
node.type = 0x0100
node.data_offset = recursion
node.size = len(nodes) + 1
for one, two in self.files:
if(one[:len(item)] == item): # find nodes in the folder
node.size += 1
else: # file
node.type = 0x0000
node.data_offset = len(data)
#print "before: " + str(len(data))
data += value + ('\x00' * (align(len(value), 32) - len(value))) # 32 seems to work best for fuzzyness? I'm still really not sure
#print "after: " + str(len(data))
node.size = len(value)
#print "sz: " + str(len(value))
nodes.append(node)
header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings)
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):
nodes[i].data_offset += header.data_offset
fd = ''
fd += header.pack()
fd += rootnode.pack()
for node in nodes:
fd += node.pack()
fd += strings
fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
fd += data
return fd
def _dumpDir(self, dir):
if(not os.path.isdir(dir)):
os.mkdir(dir)
old = os.getcwd()
os.chdir(dir)
for item, data in self.files:
if(data == None):
if(not os.path.isdir(item)):
os.mkdir(item)
else:
open(item, "wb").write(data)
os.chdir(old)
def _loadDir(self, dir):
try:
self._tmpPath += ''
except:
self._tmpPath = ''
old = os.getcwd()
os.chdir(dir)
entries = os.listdir(".")
for entry in entries:
if(os.path.isdir(entry)):
self.files.append((self._tmpPath + entry, None))
self._tmpPath += entry + '/'
self._loadDir(entry)
elif(os.path.isfile(entry)):
data = open(entry, "rb").read()
self.files.append((self._tmpPath + entry, data))
os.chdir(old)
self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
def _load(self, data):
offset = 0
for i in range(len(data)):
header = self.U8Header()
header.unpack(data[offset:offset + len(header)])
if(header.tag == "U\xAA8-"):
break
data = data[1:]
offset += len(header)
offset = header.rootnode_offset
#print header.rootnode_offset
#print header.header_size
#print header.data_offset
rootnode = self.U8Node()
rootnode.unpack(data[offset:offset + len(rootnode)])
offset += len(rootnode)
nodes = []
for i in range(rootnode.size - 1):
node = self.U8Node()
node.unpack(data[offset:offset + len(node)])
offset += len(node)
nodes.append(node)
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
offset += len(strings)
recursion = [rootnode.size]
recursiondir = []
counter = 0
for node in nodes:
counter += 1
name = strings[node.name_offset:].split('\0', 1)[0]
if(node.type == 0x0100): # folder
recursion.append(node.size)
recursiondir.append(name)
assert len(recursion) == node.data_offset + 2 # haxx
self.files.append(('/'.join(recursiondir), None))
#print "Dir: " + name
elif(node.type == 0): # file
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
offset += node.size
#print "File: " + name
else: # unknown type -- wtf?
pass
#print "Data Offset: " + str(node.data_offset)
#print "Size: " + str(node.size)
#print "Name Offset: " + str(node.name_offset)
#print ""
sz = recursion.pop()
if(sz != counter + 1):
recursion.append(sz)
else:
recursiondir.pop()
def __str__(self):
ret = ''
for key, value in self.files:
name = key[key.rfind('/') + 1:]
recursion = key.count('/')
ret += ' ' * recursion
if(value == None):
ret += '[' + name + ']'
else:
ret += name
ret += '\n'
return ret
def __getitem__(self, key):
for item, val in self.files:
if(item == key):
if(val != None):
return val
else:
ret = []
for item2, val2 in self.files:
if(item2.find(item) == 0):
ret.append(item2[len(item) + 1:])
return ret[1:]
raise KeyError
def __setitem__(self, key, val):
for i in range(len(self.files)):
if(self.files[i][0] == key):
self.files[i] = (self.files[i][0], val)
return
self.files.append((key, val))
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)

287
common.py
View File

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

714
disc.py
View File

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

View File

@ -251,3 +251,100 @@ class Savegame():
def getFilesCount(self):
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
from common import *
from title import *
class locDat:
class locHeader(Struct):
def __format__(self):
self.magic = Struct.string(4)
self.md5 = Struct.string(16)
def __init__(self, f):
self.sdKey = '\xab\x01\xb9\xd8\xe1\x62\x2b\x08\xaf\xba\xd8\x4d\xbf\xc2\xa5\x5d'
self.sdIv = '\x21\x67\x12\xe6\xaa\x1f\x68\x9f\x95\xc5\xa2\x23\x24\xdc\x6a\x98'
self.titles = []
self.usedBlocks = 0
self.freeBlocks = 0
try:
self.fp = open(f, 'r+')
except:
raise Exception('File not found')
plainBuffer = Crypto().decryptData(self.sdKey, self.sdIv, self.fp.read(), False)
self.hdr = self.locHeader().unpack(plainBuffer[:0x14])
for x in range(240):
self.titles.append(plainBuffer[0x14 + x * 4:0x14 + (x + 1) * 4])
if self.titles[x] == '\x00\x00\x00\x00':
self.freeBlocks += 1
self.usedBlocks = 240 - self.freeBlocks
def __str__(self):
out = ''
out += 'Used %i blocks out of 240\n\n' % self.usedBlocks
for x in range(240):
if self.titles[x] == '\x00\x00\x00\x00':
out += 'Block %i on page %i is empty\n' % (x, x / 12)
else:
out += 'Block %i on page %i hold title %s\n' % (x, x / 12, self.titles[x])
return out
def getFreeBlocks(self):
return self.freeBlocks
def getUsedBlocks(self):
return self.usedBlocks
def isBlockFree(self, x, y, page):
if self.titles[((x + (y * 4) + (page * 12)))] == '\x00\x00\x00\x00':
return 1
return 0
def isTitleInList(self, title):
try:
return self.titles.index(title.upper())
except:
return -1
def getPageTitles(self, page):
if page > 19:
raise Exception('Out of bounds')
return self.titles[12 * page:12 * (page + 1)]
def getTitle(self, x, y, page):
if x > 3 or y > 2 or page > 19:
raise Exception('Out of bounds')
return self.titles[((x + (y * 4) + (page * 12)))]
def setTitle(self, x, y, page, element):
if x > 3 or y > 2 or page > 19 or len(element) > 4:
raise Exception('Out of bounds')
self.titles[((x + (y * 4) + (page * 12)))] = element.upper()
titles = ''
titles += self.hdr.magic
titles += self.hdr.md5
for x in range(240):
titles += self.titles[x]
titles += '\x00' * 12
titles = titles[:0x4] + Crypto().createMD5Hash(titles) + titles[0x14:]
self.fp.seek(0)
self.fp.write(Crypto().encryptData(self.sdKey, self.sdIv, titles))
def delTitle(self, x, y, page):
self.setTitle(x, y, page, '\x00\x00\x00\x00')
class CONF:
"""This class deals with setting.txt which holds some important information like region and serial number """
def __init__(self, f):

View File

@ -1,44 +1,29 @@
from common import *
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):
__endian__ = Struct.BE
def __format__(self):
self.tag = Struct.string(4)
self.size = Struct.uint32
self.zeroes = Struct.uint8[8]
self.zeroes = Struct.string(8)
self.crypto = Struct.string(16)
def add(self):
data = self.data
imd5 = self.IMD5Header()
imd5.tag = "IMD5"
imd5.size = len(data)
for i in range(8):
imd5.zeroes[i] = 0x00
imd5.crypto = str(Crypto().createMD5Hash(data))
data = imd5.pack() + data
return data
imd5.size = len(self.data)
imd5.zeroes = '\x00' * 8
imd5.crypto = str(Crypto.createMD5Hash(self.data))
self.data = imd5.pack() + self.data
return self.data
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()
if(data[:4] != "IMD5"):
if(fn != ""):
open(fn, "wb").write(data)
return fn
else:
return self.f
data = data[len(imd5):]
return data
if(self.data[:4] != "IMD5"):
return self.data
self.data = self.data[len(imd5):]
return self.data
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):
__endian__ = Struct.BE
def __format__(self):
@ -50,14 +35,11 @@ class IMET(WiiHeader):
self.names = Struct.string(84, encoding = "utf-16-be", stripNulls = True)[7]
self.zeroes2 = Struct.uint8[840]
self.hash = Struct.string(16)
def add(self, iconsz, bannersz, soundsz, name = "", langs = [], fn = ""):
"""This function adds an IMET header to the file specified with f in the initializer. The file will be output to fn if it is not empty, otherwise it will overwrite the input file. You must specify the size of banner.bin in bannersz, and respectivly for iconsz and soundsz. langs is an optional arguement that is a list of different langauge channel titles. name is the english name that is copied everywhere in langs that there is an empty string. Returns the output filename."""
data = self.data
def add(self, iconsz, bannersz, soundsz, name = '', langs = []):
imet = self.IMETHeader()
for i in imet.zeroes:
for i in range(len(imet.zeroes)):
imet.zeroes[i] = 0x00
imet.tag = "IMET"
imet.tag = 'IMET'
imet.unk = 0x0000060000000003
imet.sizes[0] = iconsz
imet.sizes[1] = bannersz
@ -68,45 +50,40 @@ class IMET(WiiHeader):
imet.names[i] = langs[i]
else:
imet.names[i] = name
for i in imet.zeroes2:
for i in range(len(imet.zeroes2)):
imet.zeroes2[i] = 0x00
imet.hash = "\x00" * 16
imet.hash = '\x00' * 16
tmp = imet.pack()
imet.hash = Crypto().createMD5Hash(tmp[0x40:0x640]) #0x00 or 0x40?
data = imet.pack() + data
return data
imet.hash = Crypto.createMD5Hash(tmp[0x40:0x640])
self.data = imet.pack() + self.data
print "testing %x %x %x %x %x" % (len(imet), 128, 840, 0x1A << 1, 84)
return self.data
def remove(self):
data = self.data
if(data[0x80:0x84] == "IMET"):
if(data[0x80:0x84] == 'IMET'):
data = data[0x640:]
elif(data[0x40:0x44] == "IMET"):
elif(data[0x40:0x44] == 'IMET'):
data = data[0x640:]
return data
def getTitle(self):
imet = self.IMETHeader()
data = self.data
if(data[0x40:0x44] == "IMET"):
if(data[0x40:0x44] == 'IMET'):
pass
elif(data[0x80:0x84] == "IMET"):
elif(data[0x80:0x84] == 'IMET'):
data = data[0x40:]
else:
raise ValueError("No IMET header found!")
imet.unpack(data[:len(imet)])
name = imet.names[1]
topop = []
for i in range(len(name)):
if(name[i] == "\x00"):
if(name[i] == '\x00'):
topop.append(i)
name = list(name)
popped = 0 #don't ask me why I did this
for pop in topop:
name.pop(pop - popped)
popped += 1
name = ''.join(name)
return name

View File

@ -1,6 +1,4 @@
#!/usr/bin/env python
from common import *
import wx
def flatten(myTuple):
if (len(myTuple) == 4):
@ -31,7 +29,7 @@ def avg(w0, w1, c0, c1):
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.
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
def getSizes(self):
"""This returns a tuple containing the width and height of the TPL image filename in the class initializer. Will only return the size of single textured TPL images."""
data = open(self.file, "rb").read()
if(self.file):
data = open(self.file, "rb").read()
else:
data = self.data
header = self.TPLHeader()
textures = []
@ -506,9 +507,6 @@ class TPL():
h = tex.height
return (w, h)
def toScreen(self): #single texture only
"""This will draw a simple window with the TPL image displayed on it. It uses WxPython for the window creation and management. The window has a minimum width and height of 300 x 200. Does not return a value.
Again, only a single texture is supported."""
import wx
class imp(wx.Dialog):
def __init__(self, title, im):

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.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."""
self.ES.AddTitleStart(tmd, None, None, is_decrypted, result_decrypted, use_version = True)
self.ES.AddTitleTMD(tmd)
self.ES.AddTicket(tik)
contents = tmd.getContents()
for i in range(tmd.tmd.numcontents):
self.ES.AddContentStart(tmd.tmd.titleid, contents[i].cid)
fp = open(prefix + "/%08x.app" % contents[i].cid, "rb")
data = fp.read()
fp.close()
self.ES.AddContentData(contents[i].cid, data)
self.ES.AddContentFinish(contents[i].cid)
self.ES.AddTitleStart(title.tmd, None, None, True, result_decrypted, use_version = use_version)
self.ES.AddTitleTMD(title.tmd)
self.ES.AddTicket(title.tik)
contents = title.tmd.getContents()
for i, content in enumerate(contents):
self.ES.AddContentStart(title.tmd.titleid, content.cid)
data = title[content.index]
self.ES.AddContentData(content.cid, data)
self.ES.AddContentFinish(content.cid)
self.ES.AddTitleFinish()
if(add_to_menu == True):
if(((tmd.tmd.titleid >> 32) != 0x00010008) and ((tmd.tmd.titleid >> 32) != 0x00000001)):
self.addTitleToMenu(tmd.tmd.titleid)
def createWADFromTitle(self, title, cert, output, version=0):
def getTitle(self, title_id, version = None, fakesign = True):
title = Title()
tmdpth = self.f + "/title/%08x/%08x/content/title.tmd" % (title >> 32, title & 0xFFFFFFFF)
if(version != 0):
tmdpth += ".%d" % version
tmd = TMD.loadFile(tmdpth)
if(not os.path.isdir("export")):
os.mkdir("export")
tmd.fakesign()
tmd.dumpFile("export/tmd")
tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
tik.fakesign()
tik.dumpFile("export/tik")
contents = tmd.getContents()
for i in range(tmd.tmd.numcontents):
title.tmd = TMD.loadFile(tmdpth)
if(fakesign):
title.tmd.fakesign()
title.tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
if(fakesign):
title.tik.fakesign()
contents = title.tmd.getContents()
for i in range(len(contents)):
path = ""
if(contents[i].type == 0x0001):
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")
data = fp.read()
fp.close()
fp = open("export/%08x.app" % contents[i].index, "wb")
fp.write(data)
fp.close()
title.contents.append(data)
fp = open(cert, "rb")
data = fp.read()
fp.close()
fp = open("export/cert", "wb")
fp.write(data)
fp.close()
WAD("export").pack(output)
for i in range(tmd.tmd.numcontents):
os.remove("export/%08x.app" % contents[i].index)
os.remove("export/tmd")
os.remove("export/tik")
os.remove("export/cert")
os.rmdir("export")
title.cert = data
class ISFSClass:
"""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."""
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:
"""This class can download titles from NUS, or Nintendo Update Server. The titleid parameter is the long integer version of the title to download. The version parameter is optional and specifies the version to download. If version is not given, it is assumed to be the latest version on NUS."""
def __init__(self, titleid, version = None):
self.titleid = titleid
self.baseurl = "http://nus.cdn.shop.wii.com/ccs/download/%08x%08x/" % (titleid >> 32, titleid & 0xFFFFFFFF)
self.version = version
def download(self, fn = "", decrypt = True, useidx = True):
"""This will download a title from NUS into a directory either specified by fn (if it is not empty) or a directory created by the title id in hex form. If decrypt is true, it will decrypt the contents, otherwise it will not. A certs file is always created to enable easy WAD Packing. The parameter useidx specifies wheither to use the index or the content id for the file naming (default is index)."""
if(fn == ""):
fn = "%08x%08x" % (self.titleid >> 32, self.titleid & 0xFFFFFFFF)
try:
os.mkdir(fn)
except:
pass
os.chdir(fn)
url = "http://nus.cdn.shop.wii.com/ccs/download/"
def download(self, titleid, version = None):
certs = ""
rawtmd = urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/tmd.289").read()
rawtik = urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/cetk").read()
tmd = TMD.load(urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/tmd.289").read())
tik = Ticket.load(urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/cetk").read())
certs += rawtik[0x2A4:0x2A4 + 0x300] #XS
certs += rawtik[0x2A4 + 0x300:] #CA (tik)
certs += rawtmd[0x328:0x328 + 0x300] #CP
certs += tik.dump()[0x2A4:0x2A4 + 0x300] #XS
certs += tik.dump()[0x2A4 + 0x300:] #CA (tik)
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.")
open("cert", "wb").write(certs)
if(self.version == None):
versionstring = ""
else:
versionstring = ".%u" % self.version
titleurl = self.url + "%08x%08x/" % (titleid >> 32, titleid & 0xFFFFFFFF)
urllib.urlretrieve(self.baseurl + "tmd" + versionstring, "tmd")
tmd = TMD.loadFile("tmd")
tmd.dumpFile("tmd") # strip certs
tmd = TMD.load(urllib.urlopen(titleurl + "tmd" + versionstring).read())
tik = Ticket.load(urllib.urlopen(titleurl + "cetk").read())
urllib.urlretrieve(self.baseurl + "cetk", "tik")
tik = Ticket.loadFile("tik")
tik.dumpFile("tik") # strip certs
if(decrypt):
titlekey = tik.getTitleKey()
title = Title()
title.tmd = tmd
title.tik = tik
title.cert = certs
contents = tmd.getContents()
for content in contents:
output = content.cid
if(useidx):
output = content.index
urllib.urlretrieve(self.baseurl + ("%08x" % content.cid), "%08x.app" % output)
if(decrypt):
data = open("%08x.app" % output, "rb").read(content.size)
tmpdata = Crypto().decryptContent(titlekey, content.index, data)
if(Crypto().validateSHAHash(tmpdata, content.hash) == 0):
raise ValueError("Decryption failed! SHA1 mismatch.")
open("%08x.app" % output, "wb").write(tmpdata)
os.chdir("..")
encdata = urllib.urlopen(titleurl + ("%08x" % content.cid)).read(content.size)
decdata = Crypto.decryptContent(titlekey, content.index, encdata)
if(Crypto.validateSHAHash(decdata, content.hash) == 0):
raise ValueError("Decryption failed! SHA1 mismatch.")
title.contents.append(decdata)
download = classmethod(download)

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)