mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 06:45:46 -04:00
pushed Xuzz's 'fork'
This commit is contained in:
parent
cb6b94a323
commit
969ecaee9c
564
Struct.py
564
Struct.py
@ -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
1
Wii.py
@ -1,6 +1,7 @@
|
||||
__all__ = []
|
||||
|
||||
from common import *
|
||||
|
||||
from formats import *
|
||||
from title import *
|
||||
from disc import *
|
||||
|
774
archive.py
774
archive.py
@ -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
287
common.py
@ -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)
|
||||
|
||||
|
||||
|
@ -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
714
disc.py
@ -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
|
||||
|
||||
|
||||
|
97
export.py
97
export.py
@ -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')
|
||||
|
||||
|
96
formats.py
96
formats.py
@ -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):
|
||||
|
71
headers.py
71
headers.py
@ -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
|
||||
|
12
image.py
12
image.py
@ -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
187
mymenu.py
Normal 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
59
nand.py
@ -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
397
nus wadpacker.py
Normal 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
215
title.py
@ -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
338
wiimposter.py
Normal 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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user