mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 14:55:35 -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
|
import struct, sys
|
||||||
|
|
||||||
class StructType(tuple):
|
class StructType(tuple):
|
||||||
def __getitem__(self, value):
|
def __getitem__(self, value):
|
||||||
return [self] * value
|
return [self] * value
|
||||||
def __call__(self, value, endian='<'):
|
def __call__(self, value, endian='<'):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return struct.unpack(endian + tuple.__getitem__(self, 0), value[:tuple.__getitem__(self, 1)])[0]
|
return struct.unpack(endian + tuple.__getitem__(self, 0), value[:tuple.__getitem__(self, 1)])[0]
|
||||||
else:
|
else:
|
||||||
return struct.pack(endian + tuple.__getitem__(self, 0), value)
|
return struct.pack(endian + tuple.__getitem__(self, 0), value)
|
||||||
|
|
||||||
class StructException(Exception):
|
class StructException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Struct(object):
|
class Struct(object):
|
||||||
__slots__ = ('__attrs__', '__baked__', '__defs__', '__endian__', '__next__', '__sizes__', '__values__')
|
__slots__ = ('__attrs__', '__baked__', '__defs__', '__endian__', '__next__', '__sizes__', '__values__')
|
||||||
int8 = StructType(('b', 1))
|
int8 = StructType(('b', 1))
|
||||||
uint8 = StructType(('B', 1))
|
uint8 = StructType(('B', 1))
|
||||||
|
|
||||||
int16 = StructType(('h', 2))
|
int16 = StructType(('h', 2))
|
||||||
uint16 = StructType(('H', 2))
|
uint16 = StructType(('H', 2))
|
||||||
|
|
||||||
int32 = StructType(('l', 4))
|
int32 = StructType(('l', 4))
|
||||||
uint32 = StructType(('L', 4))
|
uint32 = StructType(('L', 4))
|
||||||
|
|
||||||
int64 = StructType(('q', 8))
|
int64 = StructType(('q', 8))
|
||||||
uint64 = StructType(('Q', 8))
|
uint64 = StructType(('Q', 8))
|
||||||
|
|
||||||
float = StructType(('f', 4))
|
float = StructType(('f', 4))
|
||||||
|
|
||||||
@classmethod
|
def string(cls, len, offset=0, encoding=None, stripNulls=False, value=''):
|
||||||
def string(cls, len, offset=0, encoding=None, stripNulls=False, value=''):
|
return StructType(('string', (len, offset, encoding, stripNulls, value)))
|
||||||
return StructType(('string', (len, offset, encoding, stripNulls, value)))
|
string = classmethod(string)
|
||||||
|
|
||||||
LE = '<'
|
LE = '<'
|
||||||
BE = '>'
|
BE = '>'
|
||||||
__endian__ = '<'
|
__endian__ = '<'
|
||||||
|
|
||||||
def __init__(self, func=None, unpack=None, **kwargs):
|
def __init__(self, func=None, unpack=None, **kwargs):
|
||||||
self.__defs__ = []
|
self.__defs__ = []
|
||||||
self.__sizes__ = []
|
self.__sizes__ = []
|
||||||
self.__attrs__ = []
|
self.__attrs__ = []
|
||||||
self.__values__ = {}
|
self.__values__ = {}
|
||||||
self.__next__ = True
|
self.__next__ = True
|
||||||
self.__baked__ = False
|
self.__baked__ = False
|
||||||
|
|
||||||
if func == None:
|
if func == None:
|
||||||
self.__format__()
|
self.__format__()
|
||||||
else:
|
else:
|
||||||
sys.settrace(self.__trace__)
|
sys.settrace(self.__trace__)
|
||||||
func()
|
func()
|
||||||
for name in func.func_code.co_varnames:
|
for name in func.func_code.co_varnames:
|
||||||
value = self.__frame__.f_locals[name]
|
value = self.__frame__.f_locals[name]
|
||||||
self.__setattr__(name, value)
|
self.__setattr__(name, value)
|
||||||
|
|
||||||
self.__baked__ = True
|
self.__baked__ = True
|
||||||
|
|
||||||
if unpack != None:
|
if unpack != None:
|
||||||
if isinstance(unpack, tuple):
|
if isinstance(unpack, tuple):
|
||||||
self.unpack(*unpack)
|
self.unpack(*unpack)
|
||||||
else:
|
else:
|
||||||
self.unpack(unpack)
|
self.unpack(unpack)
|
||||||
|
|
||||||
if len(kwargs):
|
if len(kwargs):
|
||||||
for name in kwargs:
|
for name in kwargs:
|
||||||
self.__values__[name] = kwargs[name]
|
self.__values__[name] = kwargs[name]
|
||||||
|
|
||||||
def __trace__(self, frame, event, arg):
|
def __trace__(self, frame, event, arg):
|
||||||
self.__frame__ = frame
|
self.__frame__ = frame
|
||||||
sys.settrace(None)
|
sys.settrace(None)
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if name in self.__slots__:
|
if name in self.__slots__:
|
||||||
return object.__setattr__(self, name, value)
|
return object.__setattr__(self, name, value)
|
||||||
|
|
||||||
if self.__baked__ == False:
|
if self.__baked__ == False:
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
value = [value]
|
value = [value]
|
||||||
attrname = name
|
attrname = name
|
||||||
else:
|
else:
|
||||||
attrname = '*' + name
|
attrname = '*' + name
|
||||||
|
|
||||||
self.__values__[name] = None
|
self.__values__[name] = None
|
||||||
|
|
||||||
for sub in value:
|
for sub in value:
|
||||||
if isinstance(sub, Struct):
|
if isinstance(sub, Struct):
|
||||||
sub = sub.__class__
|
sub = sub.__class__
|
||||||
try:
|
try:
|
||||||
if issubclass(sub, Struct):
|
if issubclass(sub, Struct):
|
||||||
sub = ('struct', sub)
|
sub = ('struct', sub)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
type_, size = tuple(sub)
|
type_, size = tuple(sub)
|
||||||
if type_ == 'string':
|
if type_ == 'string':
|
||||||
self.__defs__.append(Struct.string)
|
self.__defs__.append(Struct.string)
|
||||||
self.__sizes__.append(size)
|
self.__sizes__.append(size)
|
||||||
self.__attrs__.append(attrname)
|
self.__attrs__.append(attrname)
|
||||||
self.__next__ = True
|
self.__next__ = True
|
||||||
|
|
||||||
if attrname[0] != '*':
|
if attrname[0] != '*':
|
||||||
self.__values__[name] = size[3]
|
self.__values__[name] = size[3]
|
||||||
elif self.__values__[name] == None:
|
elif self.__values__[name] == None:
|
||||||
self.__values__[name] = [size[3] for val in value]
|
self.__values__[name] = [size[3] for val in value]
|
||||||
elif type_ == 'struct':
|
elif type_ == 'struct':
|
||||||
self.__defs__.append(Struct)
|
self.__defs__.append(Struct)
|
||||||
self.__sizes__.append(size)
|
self.__sizes__.append(size)
|
||||||
self.__attrs__.append(attrname)
|
self.__attrs__.append(attrname)
|
||||||
self.__next__ = True
|
self.__next__ = True
|
||||||
|
|
||||||
if attrname[0] != '*':
|
if attrname[0] != '*':
|
||||||
self.__values__[name] = size()
|
self.__values__[name] = size()
|
||||||
elif self.__values__[name] == None:
|
elif self.__values__[name] == None:
|
||||||
self.__values__[name] = [size() for val in value]
|
self.__values__[name] = [size() for val in value]
|
||||||
else:
|
else:
|
||||||
if self.__next__:
|
if self.__next__:
|
||||||
self.__defs__.append('')
|
self.__defs__.append('')
|
||||||
self.__sizes__.append(0)
|
self.__sizes__.append(0)
|
||||||
self.__attrs__.append([])
|
self.__attrs__.append([])
|
||||||
self.__next__ = False
|
self.__next__ = False
|
||||||
|
|
||||||
self.__defs__[-1] += type_
|
self.__defs__[-1] += type_
|
||||||
self.__sizes__[-1] += size
|
self.__sizes__[-1] += size
|
||||||
self.__attrs__[-1].append(attrname)
|
self.__attrs__[-1].append(attrname)
|
||||||
|
|
||||||
if attrname[0] != '*':
|
if attrname[0] != '*':
|
||||||
self.__values__[name] = 0
|
self.__values__[name] = 0
|
||||||
elif self.__values__[name] == None:
|
elif self.__values__[name] == None:
|
||||||
self.__values__[name] = [0 for val in value]
|
self.__values__[name] = [0 for val in value]
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.__values__[name] = value
|
self.__values__[name] = value
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if self.__baked__ == False:
|
if self.__baked__ == False:
|
||||||
return name
|
return name
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return self.__values__[name]
|
return self.__values__[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
ret = 0
|
ret = 0
|
||||||
arraypos, arrayname = None, None
|
arraypos, arrayname = None, None
|
||||||
|
|
||||||
for i in range(len(self.__defs__)):
|
for i in range(len(self.__defs__)):
|
||||||
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
|
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
|
||||||
|
|
||||||
if sdef == Struct.string:
|
if sdef == Struct.string:
|
||||||
size, offset, encoding, stripNulls, value = size
|
size, offset, encoding, stripNulls, value = size
|
||||||
if isinstance(size, str):
|
if isinstance(size, str):
|
||||||
size = self.__values__[size] + offset
|
size = self.__values__[size] + offset
|
||||||
elif sdef == Struct:
|
elif sdef == Struct:
|
||||||
if attrs[0] == '*':
|
if attrs[0] == '*':
|
||||||
if arrayname != attrs:
|
if arrayname != attrs:
|
||||||
arrayname = attrs
|
arrayname = attrs
|
||||||
arraypos = 0
|
arraypos = 0
|
||||||
size = len(self.__values__[attrs[1:]][arraypos])
|
size = len(self.__values__[attrs[1:]][arraypos])
|
||||||
size = len(self.__values__[attrs])
|
size = len(self.__values__[attrs])
|
||||||
|
|
||||||
ret += size
|
ret += size
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def unpack(self, data, pos=0):
|
def unpack(self, data, pos=0):
|
||||||
for name in self.__values__:
|
for name in self.__values__:
|
||||||
if not isinstance(self.__values__[name], Struct):
|
if not isinstance(self.__values__[name], Struct):
|
||||||
self.__values__[name] = None
|
self.__values__[name] = None
|
||||||
elif self.__values__[name].__class__ == list and len(self.__values__[name]) != 0:
|
elif self.__values__[name].__class__ == list and len(self.__values__[name]) != 0:
|
||||||
if not isinstance(self.__values__[name][0], Struct):
|
if not isinstance(self.__values__[name][0], Struct):
|
||||||
self.__values__[name] = None
|
self.__values__[name] = None
|
||||||
|
|
||||||
arraypos, arrayname = None, None
|
arraypos, arrayname = None, None
|
||||||
|
|
||||||
for i in range(len(self.__defs__)):
|
for i in range(len(self.__defs__)):
|
||||||
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
|
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
|
||||||
|
|
||||||
if sdef == Struct.string:
|
if sdef == Struct.string:
|
||||||
size, offset, encoding, stripNulls, value = size
|
size, offset, encoding, stripNulls, value = size
|
||||||
if isinstance(size, str):
|
if isinstance(size, str):
|
||||||
size = self.__values__[size] + offset
|
size = self.__values__[size] + offset
|
||||||
|
|
||||||
temp = data[pos:pos+size]
|
temp = data[pos:pos+size]
|
||||||
if len(temp) != size:
|
if len(temp) != size:
|
||||||
raise StructException('Expected %i byte string, got %i' % (size, len(temp)))
|
raise StructException('Expected %i byte string, got %i' % (size, len(temp)))
|
||||||
|
|
||||||
if encoding != None:
|
if encoding != None:
|
||||||
temp = temp.decode(encoding)
|
temp = temp.decode(encoding)
|
||||||
|
|
||||||
if stripNulls:
|
if stripNulls:
|
||||||
temp = temp.rstrip('\0')
|
temp = temp.rstrip('\0')
|
||||||
|
|
||||||
if attrs[0] == '*':
|
if attrs[0] == '*':
|
||||||
name = attrs[1:]
|
name = attrs[1:]
|
||||||
if self.__values__[name] == None:
|
if self.__values__[name] == None:
|
||||||
self.__values__[name] = []
|
self.__values__[name] = []
|
||||||
self.__values__[name].append(temp)
|
self.__values__[name].append(temp)
|
||||||
else:
|
else:
|
||||||
self.__values__[attrs] = temp
|
self.__values__[attrs] = temp
|
||||||
pos += size
|
pos += size
|
||||||
elif sdef == Struct:
|
elif sdef == Struct:
|
||||||
if attrs[0] == '*':
|
if attrs[0] == '*':
|
||||||
if arrayname != attrs:
|
if arrayname != attrs:
|
||||||
arrayname = attrs
|
arrayname = attrs
|
||||||
arraypos = 0
|
arraypos = 0
|
||||||
name = attrs[1:]
|
name = attrs[1:]
|
||||||
self.__values__[attrs][arraypos].unpack(data, pos)
|
self.__values__[attrs][arraypos].unpack(data, pos)
|
||||||
pos += len(self.__values__[attrs][arraypos])
|
pos += len(self.__values__[attrs][arraypos])
|
||||||
arraypos += 1
|
arraypos += 1
|
||||||
else:
|
else:
|
||||||
self.__values__[attrs].unpack(data, pos)
|
self.__values__[attrs].unpack(data, pos)
|
||||||
pos += len(self.__values__[attrs])
|
pos += len(self.__values__[attrs])
|
||||||
else:
|
else:
|
||||||
values = struct.unpack(self.__endian__+sdef, data[pos:pos+size])
|
values = struct.unpack(self.__endian__+sdef, data[pos:pos+size])
|
||||||
pos += size
|
pos += size
|
||||||
j = 0
|
j = 0
|
||||||
for name in attrs:
|
for name in attrs:
|
||||||
if name[0] == '*':
|
if name[0] == '*':
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
if self.__values__[name] == None:
|
if self.__values__[name] == None:
|
||||||
self.__values__[name] = []
|
self.__values__[name] = []
|
||||||
self.__values__[name].append(values[j])
|
self.__values__[name].append(values[j])
|
||||||
else:
|
else:
|
||||||
self.__values__[name] = values[j]
|
self.__values__[name] = values[j]
|
||||||
j += 1
|
j += 1
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def pack(self):
|
def pack(self):
|
||||||
arraypos, arrayname = None, None
|
arraypos, arrayname = None, None
|
||||||
|
|
||||||
ret = ''
|
ret = ''
|
||||||
for i in range(len(self.__defs__)):
|
for i in range(len(self.__defs__)):
|
||||||
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
|
sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
|
||||||
|
|
||||||
if sdef == Struct.string:
|
if sdef == Struct.string:
|
||||||
size, offset, encoding, stripNulls, value = size
|
size, offset, encoding, stripNulls, value = size
|
||||||
if isinstance(size, str):
|
if isinstance(size, str):
|
||||||
size = self.__values__[size]+offset
|
size = self.__values__[size]+offset
|
||||||
|
|
||||||
if attrs[0] == '*':
|
if attrs[0] == '*':
|
||||||
if arrayname != attrs:
|
if arrayname != attrs:
|
||||||
arraypos = 0
|
arraypos = 0
|
||||||
arrayname = attrs
|
arrayname = attrs
|
||||||
temp = self.__values__[attrs[1:]][arraypos]
|
temp = self.__values__[attrs[1:]][arraypos]
|
||||||
arraypos += 1
|
arraypos += 1
|
||||||
else:
|
else:
|
||||||
temp = self.__values__[attrs]
|
temp = self.__values__[attrs]
|
||||||
|
|
||||||
if encoding != None:
|
if encoding != None:
|
||||||
temp = temp.encode(encoding)
|
temp = temp.encode(encoding)
|
||||||
|
|
||||||
temp = temp[:size]
|
temp = temp[:size]
|
||||||
ret += temp + ('\0' * (size - len(temp)))
|
ret += temp + ('\0' * (size - len(temp)))
|
||||||
elif sdef == Struct:
|
elif sdef == Struct:
|
||||||
if attrs[0] == '*':
|
if attrs[0] == '*':
|
||||||
if arrayname != attrs:
|
if arrayname != attrs:
|
||||||
arraypos = 0
|
arraypos = 0
|
||||||
arrayname = attrs
|
arrayname = attrs
|
||||||
ret += self.__values__[attrs[1:]][arraypos].pack()
|
ret += self.__values__[attrs[1:]][arraypos].pack()
|
||||||
arraypos += 1
|
arraypos += 1
|
||||||
else:
|
else:
|
||||||
ret += self.__values__[attrs].pack()
|
ret += self.__values__[attrs].pack()
|
||||||
else:
|
else:
|
||||||
values = []
|
values = []
|
||||||
for name in attrs:
|
for name in attrs:
|
||||||
if name[0] == '*':
|
if name[0] == '*':
|
||||||
if arrayname != name:
|
if arrayname != name:
|
||||||
arraypos = 0
|
arraypos = 0
|
||||||
arrayname = name
|
arrayname = name
|
||||||
values.append(self.__values__[name[1:]][arraypos])
|
values.append(self.__values__[name[1:]][arraypos])
|
||||||
arraypos += 1
|
arraypos += 1
|
||||||
else:
|
else:
|
||||||
values.append(self.__values__[name])
|
values.append(self.__values__[name])
|
||||||
|
|
||||||
ret += struct.pack(self.__endian__+sdef, *values)
|
ret += struct.pack(self.__endian__+sdef, *values)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def __getitem__(self, value):
|
def __getitem__(self, value):
|
||||||
return [('struct', self.__class__)] * value
|
return [('struct', self.__class__)] * value
|
||||||
|
1
Wii.py
1
Wii.py
@ -1,6 +1,7 @@
|
|||||||
__all__ = []
|
__all__ = []
|
||||||
|
|
||||||
from common import *
|
from common import *
|
||||||
|
|
||||||
from formats import *
|
from formats import *
|
||||||
from title import *
|
from title import *
|
||||||
from disc import *
|
from disc import *
|
||||||
|
774
archive.py
774
archive.py
@ -1,454 +1,320 @@
|
|||||||
from common import *
|
from common import *
|
||||||
from title import *
|
import zlib
|
||||||
import zlib
|
|
||||||
|
|
||||||
class U8(WiiArchive):
|
class U8(WiiArchive):
|
||||||
"""This class can unpack and pack U8 archives, which are used all over the Wii. They are often used in Banners and contents in Downloadable Titles. Please remove all headers and compression first, kthx.
|
class U8Header(Struct):
|
||||||
|
__endian__ = Struct.BE
|
||||||
The f parameter is either the source folder to pack, or the source file to unpack."""
|
def __format__(self):
|
||||||
class U8Header(Struct):
|
self.tag = Struct.string(4)
|
||||||
__endian__ = Struct.BE
|
self.rootnode_offset = Struct.uint32
|
||||||
def __format__(self):
|
self.header_size = Struct.uint32
|
||||||
self.tag = Struct.string(4)
|
self.data_offset = Struct.uint32
|
||||||
self.rootnode_offset = Struct.uint32
|
self.zeroes = Struct.string(16)
|
||||||
self.header_size = Struct.uint32
|
class U8Node(Struct):
|
||||||
self.data_offset = Struct.uint32
|
__endian__ = Struct.BE
|
||||||
self.zeroes = Struct.string(16)
|
def __format__(self):
|
||||||
class U8Node(Struct):
|
self.type = Struct.uint16
|
||||||
__endian__ = Struct.BE
|
self.name_offset = Struct.uint16
|
||||||
def __format__(self):
|
self.data_offset = Struct.uint32
|
||||||
self.type = Struct.uint16
|
self.size = Struct.uint32
|
||||||
self.name_offset = Struct.uint16
|
def __init__(self):
|
||||||
self.data_offset = Struct.uint32
|
self.files = []
|
||||||
self.size = Struct.uint32
|
def _dump(self):
|
||||||
def __init__(self):
|
header = self.U8Header()
|
||||||
self.files = []
|
rootnode = self.U8Node()
|
||||||
def _dump(self):
|
|
||||||
header = self.U8Header()
|
# constants
|
||||||
rootnode = self.U8Node()
|
header.tag = "U\xAA8-"
|
||||||
|
header.rootnode_offset = 0x20
|
||||||
# constants
|
header.zeroes = "\x00" * 16
|
||||||
header.tag = "U\xAA8-"
|
rootnode.type = 0x0100
|
||||||
header.rootnode_offset = 0x20
|
|
||||||
header.zeroes = "\x00" * 16
|
nodes = []
|
||||||
rootnode.type = 0x0100
|
strings = '\x00'
|
||||||
|
data = ''
|
||||||
nodes = []
|
|
||||||
strings = "\x00"
|
for item, value in self.files:
|
||||||
data = ''
|
node = self.U8Node()
|
||||||
|
|
||||||
for item, value in self.files:
|
recursion = item.count('/')
|
||||||
node = self.U8Node()
|
if(recursion < 0):
|
||||||
|
recursion = 0
|
||||||
recursion = item.count('/')
|
name = item[item.rfind('/') + 1:]
|
||||||
if(recursion < 0):
|
|
||||||
recursion = 0
|
node.name_offset = len(strings)
|
||||||
name = item[item.rfind('/') + 1:]
|
strings += name + '\x00'
|
||||||
|
|
||||||
node.name_offset = len(strings)
|
if(value == None): # directory
|
||||||
strings += name + '\x00'
|
node.type = 0x0100
|
||||||
|
node.data_offset = recursion
|
||||||
if(value == None):
|
|
||||||
node.type = 0x0100
|
node.size = len(nodes) + 1
|
||||||
node.data_offset = recursion
|
for one, two in self.files:
|
||||||
|
if(one[:len(item)] == item): # find nodes in the folder
|
||||||
node.size = len(nodes)
|
node.size += 1
|
||||||
for one, two in self.files:
|
else: # file
|
||||||
if(one[:len(item)] == item): # find nodes in the folder
|
node.type = 0x0000
|
||||||
node.size += 1
|
node.data_offset = len(data)
|
||||||
node.size += 1
|
#print "before: " + str(len(data))
|
||||||
else:
|
data += value + ('\x00' * (align(len(value), 32) - len(value))) # 32 seems to work best for fuzzyness? I'm still really not sure
|
||||||
sz = len(value)
|
#print "after: " + str(len(data))
|
||||||
node.data_offset = len(data)
|
node.size = len(value)
|
||||||
data += value + "\x00" * (align(sz, 32) - sz) # 32 seems to work best for fuzzyness? I'm still really not sure
|
#print "sz: " + str(len(value))
|
||||||
node.size = sz
|
nodes.append(node)
|
||||||
node.type = 0x0000
|
|
||||||
nodes.append(node)
|
header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings)
|
||||||
|
header.data_offset = align(header.header_size + header.rootnode_offset, 64)
|
||||||
header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings)
|
rootnode.size = len(nodes) + 1
|
||||||
header.data_offset = align(header.header_size + header.rootnode_offset, 64)
|
|
||||||
rootnode.size = len(nodes) + 1
|
for i in range(len(nodes)):
|
||||||
|
if(nodes[i].type == 0x0000):
|
||||||
for i in range(len(nodes)):
|
nodes[i].data_offset += header.data_offset
|
||||||
if(nodes[i].type == 0x0000):
|
|
||||||
nodes[i].data_offset += header.data_offset
|
fd = ''
|
||||||
|
fd += header.pack()
|
||||||
fd = ''
|
fd += rootnode.pack()
|
||||||
fd += header.pack()
|
for node in nodes:
|
||||||
fd += rootnode.pack()
|
fd += node.pack()
|
||||||
for nodeobj in nodes:
|
fd += strings
|
||||||
fd += nodeobj.pack()
|
fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
|
||||||
fd += strings
|
fd += data
|
||||||
fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
|
|
||||||
fd += data
|
return fd
|
||||||
|
def _dumpDir(self, dir):
|
||||||
return fd
|
if(not os.path.isdir(dir)):
|
||||||
def _dumpDir(self, dir):
|
os.mkdir(dir)
|
||||||
if(not os.path.isdir(dir)):
|
old = os.getcwd()
|
||||||
os.mkdir(dir)
|
os.chdir(dir)
|
||||||
old = os.getcwd()
|
for item, data in self.files:
|
||||||
os.chdir(dir)
|
if(data == None):
|
||||||
for item, data in self.files:
|
if(not os.path.isdir(item)):
|
||||||
if(data == None):
|
os.mkdir(item)
|
||||||
if(not os.path.isdir(item)):
|
else:
|
||||||
os.mkdir(item)
|
open(item, "wb").write(data)
|
||||||
else:
|
os.chdir(old)
|
||||||
open(item, "wb").write(data)
|
def _loadDir(self, dir):
|
||||||
os.chdir(old)
|
try:
|
||||||
def _loadDir(self, dir):
|
self._tmpPath += ''
|
||||||
try:
|
except:
|
||||||
self._tmpPath += ''
|
self._tmpPath = ''
|
||||||
except:
|
old = os.getcwd()
|
||||||
self._tmpPath = ''
|
os.chdir(dir)
|
||||||
old = os.getcwd()
|
entries = os.listdir(".")
|
||||||
os.chdir(dir)
|
for entry in entries:
|
||||||
entries = os.listdir(".")
|
if(os.path.isdir(entry)):
|
||||||
for entry in entries:
|
self.files.append((self._tmpPath + entry, None))
|
||||||
if(os.path.isdir(entry)):
|
self._tmpPath += entry + '/'
|
||||||
self.files.append((self._tmpPath + entry, None))
|
self._loadDir(entry)
|
||||||
self._tmpPath += entry + '/'
|
elif(os.path.isfile(entry)):
|
||||||
self._loadDir(entry)
|
data = open(entry, "rb").read()
|
||||||
elif(os.path.isfile(entry)):
|
self.files.append((self._tmpPath + entry, data))
|
||||||
data = open(entry, "rb").read()
|
os.chdir(old)
|
||||||
self.files.append((self._tmpPath + entry, data))
|
self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
|
||||||
os.chdir(old)
|
def _load(self, data):
|
||||||
self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
|
offset = 0
|
||||||
def _load(self, data):
|
|
||||||
offset = 0
|
for i in range(len(data)):
|
||||||
|
header = self.U8Header()
|
||||||
header = self.U8Header()
|
header.unpack(data[offset:offset + len(header)])
|
||||||
header.unpack(data[offset:offset + len(header)])
|
if(header.tag == "U\xAA8-"):
|
||||||
offset += len(header)
|
break
|
||||||
|
data = data[1:]
|
||||||
assert header.tag == "U\xAA8-"
|
offset += len(header)
|
||||||
offset = header.rootnode_offset
|
offset = header.rootnode_offset
|
||||||
|
|
||||||
rootnode = self.U8Node()
|
#print header.rootnode_offset
|
||||||
rootnode.unpack(data[offset:offset + len(rootnode)])
|
#print header.header_size
|
||||||
offset += len(rootnode)
|
#print header.data_offset
|
||||||
|
|
||||||
nodes = []
|
rootnode = self.U8Node()
|
||||||
for i in range(rootnode.size - 1):
|
rootnode.unpack(data[offset:offset + len(rootnode)])
|
||||||
node = self.U8Node()
|
offset += len(rootnode)
|
||||||
node.unpack(data[offset:offset + len(node)])
|
|
||||||
offset += len(node)
|
nodes = []
|
||||||
nodes.append(node)
|
for i in range(rootnode.size - 1):
|
||||||
|
node = self.U8Node()
|
||||||
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
|
node.unpack(data[offset:offset + len(node)])
|
||||||
offset += len(strings)
|
offset += len(node)
|
||||||
|
nodes.append(node)
|
||||||
recursion = [rootnode.size]
|
|
||||||
recursiondir = []
|
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
|
||||||
counter = 0
|
offset += len(strings)
|
||||||
for node in nodes:
|
|
||||||
counter += 1
|
recursion = [rootnode.size]
|
||||||
name = strings[node.name_offset:].split('\0', 1)[0]
|
recursiondir = []
|
||||||
|
counter = 0
|
||||||
if(node.type == 0x0100): # folder
|
for node in nodes:
|
||||||
recursion.append(node.size)
|
counter += 1
|
||||||
recursiondir.append(name)
|
name = strings[node.name_offset:].split('\0', 1)[0]
|
||||||
assert len(recursion) == node.data_offset + 2 # haxx
|
|
||||||
self.files.append(('/'.join(recursiondir), None))
|
if(node.type == 0x0100): # folder
|
||||||
elif(node.type == 0): # file
|
recursion.append(node.size)
|
||||||
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
|
recursiondir.append(name)
|
||||||
offset += node.size
|
assert len(recursion) == node.data_offset + 2 # haxx
|
||||||
else: # unknown type -- wtf?
|
self.files.append(('/'.join(recursiondir), None))
|
||||||
pass
|
|
||||||
|
#print "Dir: " + name
|
||||||
sz = recursion.pop()
|
elif(node.type == 0): # file
|
||||||
if(sz != counter + 1):
|
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
|
||||||
recursion.append(sz)
|
offset += node.size
|
||||||
else:
|
|
||||||
recursiondir.pop()
|
#print "File: " + name
|
||||||
def __str__(self):
|
else: # unknown type -- wtf?
|
||||||
ret = ''
|
pass
|
||||||
for key, value in self.files:
|
|
||||||
name = key[key.rfind('/') + 1:]
|
#print "Data Offset: " + str(node.data_offset)
|
||||||
recursion = key.count('/')
|
#print "Size: " + str(node.size)
|
||||||
ret += ' ' * recursion
|
#print "Name Offset: " + str(node.name_offset)
|
||||||
if(value == None):
|
#print ""
|
||||||
ret += '[' + name + ']'
|
|
||||||
else:
|
sz = recursion.pop()
|
||||||
ret += name
|
if(sz != counter + 1):
|
||||||
ret += '\n'
|
recursion.append(sz)
|
||||||
return ret
|
else:
|
||||||
def __getitem__(self, key):
|
recursiondir.pop()
|
||||||
for item, val in self.files:
|
def __str__(self):
|
||||||
if(item == key):
|
ret = ''
|
||||||
if(val != None):
|
for key, value in self.files:
|
||||||
return val
|
name = key[key.rfind('/') + 1:]
|
||||||
else:
|
recursion = key.count('/')
|
||||||
ret = []
|
ret += ' ' * recursion
|
||||||
for item2, val2 in self.files:
|
if(value == None):
|
||||||
if(item2.find(item) == 0):
|
ret += '[' + name + ']'
|
||||||
ret.append(item2[len(item) + 1:])
|
else:
|
||||||
return ret[1:]
|
ret += name
|
||||||
raise KeyError
|
ret += '\n'
|
||||||
def __setitem__(self, key, val):
|
return ret
|
||||||
for i in range(len(self.files)):
|
def __getitem__(self, key):
|
||||||
if(self.files[i][0] == key):
|
for item, val in self.files:
|
||||||
self.files[i] = (self.files[i][0], val)
|
if(item == key):
|
||||||
return
|
if(val != None):
|
||||||
self.files.append((key, val))
|
return val
|
||||||
|
else:
|
||||||
|
ret = []
|
||||||
class WAD(WiiArchive):
|
for item2, val2 in self.files:
|
||||||
def __init__(self, boot2 = False):
|
if(item2.find(item) == 0):
|
||||||
self.tmd = TMD()
|
ret.append(item2[len(item) + 1:])
|
||||||
self.tik = Ticket()
|
return ret[1:]
|
||||||
self.contents = []
|
raise KeyError
|
||||||
self.boot2 = False
|
def __setitem__(self, key, val):
|
||||||
self.cert = ""
|
for i in range(len(self.files)):
|
||||||
def _load(self, data):
|
if(self.files[i][0] == key):
|
||||||
if(self.boot2 != True):
|
self.files[i] = (self.files[i][0], val)
|
||||||
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding = struct.unpack('>I4s6I32s', data[:64])
|
return
|
||||||
pos = 64
|
self.files.append((key, val))
|
||||||
else:
|
|
||||||
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', data[:32])
|
|
||||||
pos = 32
|
|
||||||
|
class CCF:
|
||||||
rawcert = data[pos:pos + certsize]
|
class CCFHeader(Struct):
|
||||||
pos += certsize
|
__endian__ = Struct.LE
|
||||||
if(self.boot2 != True):
|
def __format__(self):
|
||||||
if(certsize % 64 != 0):
|
self.magic = Struct.string(4)
|
||||||
pos += 64 - (certsize % 64)
|
self.zeroes12 = Struct.string(12)
|
||||||
self.cert = rawcert
|
self.rootOffset = Struct.uint32
|
||||||
|
self.filesCount = Struct.uint32
|
||||||
rawtik = data[pos:pos + tiksize]
|
self.zeroes8 = Struct.string(8)
|
||||||
pos += tiksize
|
|
||||||
if(self.boot2 != True):
|
class CCFFile(Struct):
|
||||||
if(tiksize % 64 != 0):
|
__endian__ = Struct.LE
|
||||||
pos += 64 - (tiksize % 64)
|
def __format__(self):
|
||||||
self.tik = Ticket.load(rawtik)
|
self.fileName = Struct.string(20)
|
||||||
|
self.fileOffset = Struct.uint32
|
||||||
rawtmd = data[pos:pos + tmdsize]
|
self.fileSize = Struct.uint32
|
||||||
pos += tmdsize
|
self.fileSizeDecompressed = Struct.uint32
|
||||||
if(self.boot2 == True):
|
|
||||||
pos = data_offset
|
def __init__(self, fileName):
|
||||||
else:
|
self.fileName = fileName
|
||||||
pos += 64 - (tmdsize % 64)
|
self.fd = open(fileName, 'r+b')
|
||||||
self.tmd = TMD.load(rawtmd)
|
|
||||||
|
def compress(self, folder):
|
||||||
titlekey = self.tik.getTitleKey()
|
fileList = []
|
||||||
contents = self.tmd.getContents()
|
|
||||||
for i in range(0, len(contents)):
|
fileHdr = self.CCFHeader()
|
||||||
tmpsize = contents[i].size
|
|
||||||
if(tmpsize % 16 != 0):
|
files = os.listdir(folder)
|
||||||
tmpsize += 16 - (tmpsize % 16)
|
|
||||||
encdata = data[pos:pos + tmpsize]
|
fileHdr.magic = "\x43\x43\x46\x00"
|
||||||
pos += tmpsize
|
fileHdr.zeroes12 = '\x00' * 12
|
||||||
decdata = Crypto().decryptContent(titlekey, contents[i].index, encdata)
|
fileHdr.rootOffset = 0x20
|
||||||
self.contents.append(decdata)
|
fileHdr.zeroes8 = '\x00' * 8
|
||||||
if(tmpsize % 64 != 0):
|
|
||||||
pos += 64 - (tmpsize % 64)
|
currentOffset = len(fileHdr)
|
||||||
def _loadDir(self, dir):
|
packedFiles = 0
|
||||||
origdir = os.getcwd()
|
previousFileEndOffset = 0
|
||||||
os.chdir(dir)
|
|
||||||
|
for file in files:
|
||||||
self.tmd = TMD.loadFile("tmd")
|
if os.path.isdir(folder + '/' + file) or file == '.DS_Store':
|
||||||
self.tik = Ticket.loadFile("tik")
|
continue
|
||||||
self.cert = open("cert", "rb").read()
|
else:
|
||||||
|
fileList.append(file)
|
||||||
contents = self.tmd.getContents()
|
|
||||||
for i in range(len(contents)):
|
fileHdr.filesCount = len(fileList)
|
||||||
self.contents.append(open("%08x.app" % i, "rb").read())
|
self.fd.write(fileHdr.pack())
|
||||||
os.chdir(origdir)
|
self.fd.write('\x00' * (fileHdr.filesCount * len(self.CCFFile())))
|
||||||
def _dumpDir(self, dir):
|
|
||||||
origdir = os.getcwd()
|
for fileNumber in range(len(fileList)):
|
||||||
os.chdir(dir)
|
|
||||||
|
fileEntry = self.CCFFile()
|
||||||
contents = self.tmd.getContents()
|
|
||||||
for i in range(len(contents)):
|
compressedBuffer = zlib.compress(open(folder + '/' + fileList[fileNumber]).read())
|
||||||
open("%08x.app" % i, "wb").write(self.contents[i])
|
|
||||||
self.tmd.dumpFile("tmd")
|
fileEntry.fileName = fileList[fileNumber]
|
||||||
self.tik.dumpFile("tik")
|
fileEntry.fileSize = len(compressedBuffer)
|
||||||
open("cert", "wb").write(self.cert)
|
fileEntry.fileSizeDecompressed = os.stat(folder + '/' + fileList[fileNumber]).st_size
|
||||||
|
fileEntry.fileOffset = align(self.fd.tell(), 32) / 32
|
||||||
os.chdir(origdir)
|
|
||||||
def _dump(self, fakesign = True):
|
print 'File {0} ({1}Kb) placed at offset 0x{2:X}'.format(fileEntry.fileName, fileEntry.fileSize / 1024, fileEntry.fileOffset * 32)
|
||||||
titlekey = self.tik.getTitleKey()
|
|
||||||
contents = self.tmd.getContents()
|
self.fd.seek(len(fileHdr) + fileNumber * len(self.CCFFile()))
|
||||||
|
self.fd.write(fileEntry.pack())
|
||||||
apppack = ""
|
self.fd.seek(fileEntry.fileOffset * 32)
|
||||||
for i, content in enumerate(contents):
|
self.fd.write(compressedBuffer)
|
||||||
if(fakesign):
|
|
||||||
content.hash = str(Crypto().createSHAHash(self.contents[content.index]))
|
self.fd.close()
|
||||||
content.size = len(self.contents[content.index])
|
|
||||||
|
def decompress(self):
|
||||||
encdata = Crypto().encryptContent(titlekey, content.index, self.contents[content.index])
|
fileHdr = self.CCFHeader()
|
||||||
|
hdrData = self.fd.read(len(fileHdr))
|
||||||
apppack += encdata
|
fileHdr.unpack(hdrData)
|
||||||
if(len(encdata) % 64 != 0):
|
|
||||||
apppack += "\x00" * (64 - (len(encdata) % 64))
|
print 'Found {0} file/s and root node at 0x{1:X}'.format(fileHdr.filesCount, fileHdr.rootOffset)
|
||||||
|
|
||||||
if(fakesign):
|
if fileHdr.magic != "\x43\x43\x46\x00":
|
||||||
self.tmd.setContents(contents)
|
raise ValueError("Wrong magic, 0x{0}".format(fileHdr.magic))
|
||||||
self.tmd.fakesign()
|
|
||||||
self.tik.fakesign()
|
try:
|
||||||
|
os.mkdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
|
||||||
rawtmd = self.tmd.dump()
|
except:
|
||||||
rawcert = self.cert
|
pass
|
||||||
rawtik = self.tik.dump()
|
|
||||||
|
os.chdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
|
||||||
sz = 0
|
|
||||||
for i in range(len(contents)):
|
currentOffset = len(fileHdr)
|
||||||
sz += contents[i].size
|
|
||||||
if(sz % 64 != 0):
|
for x in range(fileHdr.filesCount):
|
||||||
sz += 64 - (contents[i].size % 64)
|
self.fd.seek(currentOffset)
|
||||||
|
|
||||||
if(self.boot2 != True):
|
fileEntry = self.CCFFile()
|
||||||
pack = struct.pack('>I4s6I', 32, "Is\x00\x00", len(rawcert), 0, len(rawtik), len(rawtmd), sz, 0)
|
fileData = self.fd.read(len(fileEntry))
|
||||||
pack += "\x00" * 32
|
fileEntry.unpack(fileData)
|
||||||
else:
|
|
||||||
pack = struct.pack('>IIIII12s', 32, align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40), len(rawcert), len(rawtik), len(rawtmd), "\x00" * 12)
|
fileEntry.fileOffset = fileEntry.fileOffset * 32
|
||||||
|
|
||||||
pack += rawcert
|
print 'File {0} at offset 0x{1:X}'.format(fileEntry.fileName, fileEntry.fileOffset)
|
||||||
if(len(rawcert) % 64 != 0 and self.boot2 != True):
|
print 'File size {0}Kb ({1}Kb decompressed)'.format(fileEntry.fileSize / 1024, fileEntry.fileSizeDecompressed / 1024)
|
||||||
pack += "\x00" * (64 - (len(rawcert) % 64))
|
|
||||||
pack += rawtik
|
output = open(fileEntry.fileName.rstrip('\0'), 'w+b')
|
||||||
if(len(rawtik) % 64 != 0 and self.boot2 != True):
|
|
||||||
pack += "\x00" * (64 - (len(rawtik) % 64))
|
self.fd.seek(fileEntry.fileOffset)
|
||||||
pack += rawtmd
|
if fileEntry.fileSize == fileEntry.fileSizeDecompressed:
|
||||||
if(len(rawtmd) % 64 != 0 and self.boot2 != True):
|
print 'The file is stored uncompressed'
|
||||||
pack += "\x00" * (64 - (len(rawtmd) % 64))
|
output.write(self.fd.read(fileEntry.fileSize))
|
||||||
|
else:
|
||||||
if(self.boot2 == True):
|
print 'The file is stored compressed..decompressing'
|
||||||
pack += "\x00" * (align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40) - (len(rawcert) + len(rawtik) + len(rawtmd)))
|
decompressedBuffer = zlib.decompress(self.fd.read(fileEntry.fileSize))
|
||||||
|
output.write(decompressedBuffer)
|
||||||
pack += apppack
|
output.close()
|
||||||
return pack
|
|
||||||
def __getitem__(self, idx):
|
currentOffset += len(fileEntry)
|
||||||
return self.contents[idx]
|
|
||||||
def __setitem__(self, idx, value):
|
|
||||||
self.contents[idx] = value
|
|
||||||
def __str__(self):
|
|
||||||
out = ""
|
|
||||||
out += "Wii WAD:\n"
|
|
||||||
out += str(self.tmd)
|
|
||||||
out += str(self.tik)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
class CCF():
|
|
||||||
class CCFHeader(Struct):
|
|
||||||
__endian__ = Struct.LE
|
|
||||||
def __format__(self):
|
|
||||||
self.magic = Struct.string(4)
|
|
||||||
self.zeroes12 = Struct.string(12)
|
|
||||||
self.rootOffset = Struct.uint32
|
|
||||||
self.filesCount = Struct.uint32
|
|
||||||
self.zeroes8 = Struct.string(8)
|
|
||||||
|
|
||||||
class CCFFile(Struct):
|
|
||||||
__endian__ = Struct.LE
|
|
||||||
def __format__(self):
|
|
||||||
self.fileName = Struct.string(20)
|
|
||||||
self.fileOffset = Struct.uint32
|
|
||||||
self.fileSize = Struct.uint32
|
|
||||||
self.fileSizeDecompressed = Struct.uint32
|
|
||||||
|
|
||||||
def __init__(self, fileName):
|
|
||||||
self.fileName = fileName
|
|
||||||
self.fd = open(fileName, 'r+b')
|
|
||||||
|
|
||||||
def compress(self, folder):
|
|
||||||
fileList = []
|
|
||||||
|
|
||||||
fileHdr = self.CCFHeader()
|
|
||||||
|
|
||||||
files = os.listdir(folder)
|
|
||||||
|
|
||||||
fileHdr.magic = "\x43\x43\x46\x00"
|
|
||||||
fileHdr.zeroes12 = '\x00' * 12
|
|
||||||
fileHdr.rootOffset = 0x20
|
|
||||||
fileHdr.zeroes8 = '\x00' * 8
|
|
||||||
|
|
||||||
currentOffset = len(fileHdr)
|
|
||||||
packedFiles = 0
|
|
||||||
previousFileEndOffset = 0
|
|
||||||
|
|
||||||
for file in files:
|
|
||||||
if os.path.isdir(folder + '/' + file) or file == '.DS_Store':
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
fileList.append(file)
|
|
||||||
|
|
||||||
fileHdr.filesCount = len(fileList)
|
|
||||||
self.fd.write(fileHdr.pack())
|
|
||||||
self.fd.write('\x00' * (fileHdr.filesCount * len(self.CCFFile())))
|
|
||||||
|
|
||||||
for fileNumber in range(len(fileList)):
|
|
||||||
|
|
||||||
fileEntry = self.CCFFile()
|
|
||||||
|
|
||||||
compressedBuffer = zlib.compress(open(folder + '/' + fileList[fileNumber]).read())
|
|
||||||
|
|
||||||
fileEntry.fileName = fileList[fileNumber]
|
|
||||||
fileEntry.fileSize = len(compressedBuffer)
|
|
||||||
fileEntry.fileSizeDecompressed = os.stat(folder + '/' + fileList[fileNumber]).st_size
|
|
||||||
fileEntry.fileOffset = align(self.fd.tell(), 32) / 32
|
|
||||||
|
|
||||||
print 'File {0} ({1}Kb) placed at offset 0x{2:X}'.format(fileEntry.fileName, fileEntry.fileSize / 1024, fileEntry.fileOffset * 32)
|
|
||||||
|
|
||||||
self.fd.seek(len(fileHdr) + fileNumber * len(self.CCFFile()))
|
|
||||||
self.fd.write(fileEntry.pack())
|
|
||||||
self.fd.seek(fileEntry.fileOffset * 32)
|
|
||||||
self.fd.write(compressedBuffer)
|
|
||||||
|
|
||||||
self.fd.close()
|
|
||||||
|
|
||||||
def decompress(self):
|
|
||||||
fileHdr = self.CCFHeader()
|
|
||||||
hdrData = self.fd.read(len(fileHdr))
|
|
||||||
fileHdr.unpack(hdrData)
|
|
||||||
|
|
||||||
print 'Found {0} file/s and root node at 0x{1:X}'.format(fileHdr.filesCount, fileHdr.rootOffset)
|
|
||||||
|
|
||||||
if fileHdr.magic != "\x43\x43\x46\x00":
|
|
||||||
raise ValueError("Wrong magic, 0x{0}".format(fileHdr.magic))
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.mkdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
os.chdir(os.path.dirname(self.fileName) + '/' + self.fd.name.replace(".", "_") + "_out")
|
|
||||||
|
|
||||||
currentOffset = len(fileHdr)
|
|
||||||
|
|
||||||
for x in range(fileHdr.filesCount):
|
|
||||||
self.fd.seek(currentOffset)
|
|
||||||
|
|
||||||
fileEntry = self.CCFFile()
|
|
||||||
fileData = self.fd.read(len(fileEntry))
|
|
||||||
fileEntry.unpack(fileData)
|
|
||||||
|
|
||||||
fileEntry.fileOffset = fileEntry.fileOffset * 32
|
|
||||||
|
|
||||||
print 'File {0} at offset 0x{1:X}'.format(fileEntry.fileName, fileEntry.fileOffset)
|
|
||||||
print 'File size {0}Kb ({1}Kb decompressed)'.format(fileEntry.fileSize / 1024, fileEntry.fileSizeDecompressed / 1024)
|
|
||||||
|
|
||||||
output = open(fileEntry.fileName.rstrip('\0'), 'w+b')
|
|
||||||
|
|
||||||
self.fd.seek(fileEntry.fileOffset)
|
|
||||||
if fileEntry.fileSize == fileEntry.fileSizeDecompressed:
|
|
||||||
print 'The file is stored uncompressed'
|
|
||||||
output.write(self.fd.read(fileEntry.fileSize))
|
|
||||||
else:
|
|
||||||
print 'The file is stored compressed..decompressing'
|
|
||||||
decompressedBuffer = zlib.decompress(self.fd.read(fileEntry.fileSize))
|
|
||||||
output.write(decompressedBuffer)
|
|
||||||
output.close()
|
|
||||||
|
|
||||||
currentOffset += len(fileEntry)
|
|
||||||
|
|
||||||
if(__name__ == '__main__'):
|
|
||||||
wad = WAD.loadFile("testing.wad")
|
|
||||||
print wad
|
|
||||||
wad.dumpDir("outdir")
|
|
||||||
wad.dumpFile("interesting.wad", fakesign = False) #keyword arguements work as expected when calling _dump(). awesome.
|
|
||||||
wad2 = WAD.loadDir("outdir")
|
|
||||||
print wad2
|
|
||||||
wad3 = WAD.loadFile("interesting.wad")
|
|
||||||
print wad3
|
|
||||||
wad3.dumpDir("outdir2")
|
|
||||||
|
287
common.py
287
common.py
@ -1,137 +1,150 @@
|
|||||||
import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array, time, sys, tempfile, wave
|
import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array, time, sys, tempfile, wave
|
||||||
from cStringIO import StringIO
|
from binascii import *
|
||||||
|
from cStringIO import StringIO
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from PIL import Image
|
from Crypto.Cipher import AES
|
||||||
|
from PIL import Image
|
||||||
from Struct import Struct
|
|
||||||
|
from Struct import Struct
|
||||||
def align(x, boundary):
|
|
||||||
if(x % boundary):
|
def align(x, boundary):
|
||||||
x += (x + boundary) - (x % boundary)
|
while x % boundary != 0:
|
||||||
return x
|
x += 1
|
||||||
|
return x
|
||||||
def clamp(var, min, max):
|
|
||||||
if var < min: var = min
|
def clamp(var, min, max):
|
||||||
if var > max: var = max
|
if var < min: var = min
|
||||||
return var
|
if var > max: var = max
|
||||||
|
return var
|
||||||
def abs(var):
|
|
||||||
if var < 0:
|
def abs(var):
|
||||||
var = var + (2 * var)
|
if var < 0:
|
||||||
return var
|
var = var + (2 * var)
|
||||||
|
return var
|
||||||
def hexdump(s, sep=" "): # just dumps hex values
|
|
||||||
return sep.join(map(lambda x: "%02x" % ord(x), s))
|
def hexdump(s, sep=" "): # just dumps hex values
|
||||||
|
return sep.join(map(lambda x: "%02x" % ord(x), s))
|
||||||
def hexdump2(src, length = 16): # dumps to a "hex editor" style output
|
|
||||||
result = []
|
def hexdump2(src, length = 16): # dumps to a "hex editor" style output
|
||||||
for i in xrange(0, len(src), length):
|
result = []
|
||||||
s = src[i:i + length]
|
for i in xrange(0, len(src), length):
|
||||||
if(len(s) % 4 == 0):
|
s = src[i:i + length]
|
||||||
mod = 0
|
if(len(s) % 4 == 0):
|
||||||
else:
|
mod = 0
|
||||||
mod = 1
|
else:
|
||||||
hexa = ''
|
mod = 1
|
||||||
for j in range((len(s) / 4) + mod):
|
hexa = ''
|
||||||
hexa += ' '.join(["%02X" % ord(x) for x in s[j * 4:j * 4 + 4]])
|
for j in range((len(s) / 4) + mod):
|
||||||
if(j != ((len(s) / 4) + mod) - 1):
|
hexa += ' '.join(["%02X" % ord(x) for x in s[j * 4:j * 4 + 4]])
|
||||||
hexa += ' '
|
if(j != ((len(s) / 4) + mod) - 1):
|
||||||
printable = s.translate(''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]))
|
hexa += ' '
|
||||||
result.append("0x%04X %-*s %s\n" % (i, (length * 3) + 2, hexa, printable))
|
printable = s.translate(''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]))
|
||||||
return ''.join(result)
|
result.append("0x%04X %-*s %s\n" % (i, (length * 3) + 2, hexa, printable))
|
||||||
|
return ''.join(result)
|
||||||
class Crypto(object):
|
|
||||||
"""This is a Cryptographic/hash class used to abstract away things (to make changes easier)"""
|
class Crypto(object):
|
||||||
align = 64
|
"""This is a Cryptographic/hash class used to abstract away things (to make changes easier)"""
|
||||||
@classmethod
|
align = 64
|
||||||
def decryptData(self, key, iv, data, align = True):
|
|
||||||
"""Decrypts some data (aligns to 64 bytes, if needed)."""
|
def decryptData(self, key, iv, data, align = True):
|
||||||
if((len(data) % self.align) != 0 and align):
|
"""Decrypts some data (aligns to 64 bytes, if needed)."""
|
||||||
return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
|
if((len(data) % self.align) != 0 and align):
|
||||||
else:
|
return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
|
||||||
return AES.new(key, AES.MODE_CBC, iv).decrypt(data)
|
else:
|
||||||
@classmethod
|
return AES.new(key, AES.MODE_CBC, iv).decrypt(data)
|
||||||
def encryptData(self, key, iv, data, align = True):
|
decryptData = classmethod(decryptData)
|
||||||
"""Encrypts some data (aligns to 64 bytes, if needed)."""
|
|
||||||
if((len(data) % self.align) != 0 and align):
|
def encryptData(self, key, iv, data, align = True):
|
||||||
return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
|
"""Encrypts some data (aligns to 64 bytes, if needed)."""
|
||||||
else:
|
if((len(data) % self.align) != 0 and align):
|
||||||
return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
|
return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align))))
|
||||||
@classmethod
|
else:
|
||||||
def decryptContent(self, titlekey, idx, data):
|
return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
|
||||||
"""Decrypts a Content."""
|
encryptData = classmethod(encryptData)
|
||||||
iv = struct.pack(">H", idx) + "\x00" * 14
|
|
||||||
return self.decryptData(titlekey, iv, data)
|
def decryptContent(self, titlekey, idx, data):
|
||||||
@classmethod
|
"""Decrypts a Content."""
|
||||||
def decryptTitleKey(self, commonkey, tid, enckey):
|
iv = struct.pack(">H", idx) + "\x00" * 14
|
||||||
"""Decrypts a Content."""
|
return self.decryptData(titlekey, iv, data)
|
||||||
iv = struct.pack(">Q", tid) + "\x00" * 8
|
decryptContent = classmethod(decryptContent)
|
||||||
return self.decryptData(commonkey, iv, enckey, False)
|
|
||||||
@classmethod
|
def decryptTitleKey(self, commonkey, tid, enckey):
|
||||||
def encryptContent(self, titlekey, idx, data):
|
"""Decrypts a Content."""
|
||||||
"""Encrypts a Content."""
|
iv = struct.pack(">Q", tid) + "\x00" * 8
|
||||||
iv = struct.pack(">H", idx) + "\x00" * 14
|
return self.decryptData(commonkey, iv, enckey, False)
|
||||||
return self.encryptData(titlekey, iv, data)
|
decryptTitleKey = classmethod(decryptTitleKey)
|
||||||
@classmethod
|
|
||||||
def createSHAHash(self, data): #tested WORKING (without padding)
|
def encryptContent(self, titlekey, idx, data):
|
||||||
return hashlib.sha1(data).digest()
|
"""Encrypts a Content."""
|
||||||
@classmethod
|
iv = struct.pack(">H", idx) + "\x00" * 14
|
||||||
def createSHAHashHex(self, data):
|
return self.encryptData(titlekey, iv, data)
|
||||||
return hashlib.sha1(data).hexdigest()
|
encryptContent = classmethod(encryptContent)
|
||||||
@classmethod
|
|
||||||
def createMD5HashHex(self, data):
|
def createSHAHash(self, data): #tested WORKING (without padding)
|
||||||
return hashlib.md5(data).hexdigest()
|
return hashlib.sha1(data).digest()
|
||||||
@classmethod
|
createSHAHash = classmethod(createSHAHash)
|
||||||
def createMD5Hash(self, data):
|
|
||||||
return hashlib.md5(data).digest()
|
def createSHAHashHex(self, data):
|
||||||
@classmethod
|
return hashlib.sha1(data).hexdigest()
|
||||||
def validateSHAHash(self, data, hash):
|
createSHAHashHex = classmethod(createSHAHashHex)
|
||||||
contentHash = hashlib.sha1(data).digest()
|
|
||||||
return 1
|
def createMD5HashHex(self, data):
|
||||||
if (contentHash == hash):
|
return hashlib.md5(data).hexdigest()
|
||||||
return 1
|
createMD5HashHex = classmethod(createMD5HashHex)
|
||||||
else:
|
|
||||||
#raise ValueError('Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash)) + 'Expected : %s : len %i' % (hexdump(hash), len(hash)))
|
def createMD5Hash(self, data):
|
||||||
return 0
|
return hashlib.md5(data).digest()
|
||||||
|
createMD5Hash = classmethod(createMD5Hash)
|
||||||
class WiiObject(object):
|
|
||||||
@classmethod
|
def validateSHAHash(self, data, hash):
|
||||||
def load(cls, data, *args, **kwargs):
|
contentHash = hashlib.sha1(data).digest()
|
||||||
self = cls()
|
return 1
|
||||||
self._load(data, *args, **kwargs)
|
if (contentHash == hash):
|
||||||
return self
|
return 1
|
||||||
@classmethod
|
else:
|
||||||
def loadFile(cls, filename, *args, **kwargs):
|
#raise ValueError('Content hash : %s : len %i' % (hexdump(contentHash), len(contentHash)) + 'Expected : %s : len %i' % (hexdump(hash), len(hash)))
|
||||||
return cls.load(open(filename, "rb").read(), *args, **kwargs)
|
return 0
|
||||||
|
validateSHAHash = classmethod(validateSHAHash)
|
||||||
def dump(self, *args, **kwargs):
|
|
||||||
return self._dump(*args, **kwargs)
|
class WiiObject(object):
|
||||||
def dumpFile(self, filename, *args, **kwargs):
|
def load(cls, data, *args, **kwargs):
|
||||||
open(filename, "wb").write(self.dump(*args, **kwargs))
|
self = cls()
|
||||||
return filename
|
self._load(data, *args, **kwargs)
|
||||||
|
return self
|
||||||
class WiiArchive(WiiObject):
|
load = classmethod(load)
|
||||||
@classmethod
|
|
||||||
def loadDir(cls, dirname):
|
def loadFile(cls, filename, *args, **kwargs):
|
||||||
self = cls()
|
return cls.load(open(filename, "rb").read(), *args, **kwargs)
|
||||||
self._loadDir(dirname)
|
loadFile = classmethod(loadFile)
|
||||||
return self
|
|
||||||
|
def dump(self, *args, **kwargs):
|
||||||
def dumpDir(self, dirname):
|
return self._dump(*args, **kwargs)
|
||||||
if(not os.path.isdir(dirname)):
|
def dumpFile(self, filename, *args, **kwargs):
|
||||||
os.mkdir(dirname)
|
open(filename, "wb").write(self.dump(*args, **kwargs))
|
||||||
self._dumpDir(dirname)
|
return filename
|
||||||
return dirname
|
|
||||||
|
class WiiArchive(WiiObject):
|
||||||
class WiiHeader(object):
|
def loadDir(cls, dirname):
|
||||||
def __init__(self, data):
|
self = cls()
|
||||||
self.data = data
|
self._loadDir(dirname)
|
||||||
def addFile(self, filename):
|
return self
|
||||||
open(filename, "wb").write(self.add())
|
loadDir = classmethod(loadDir)
|
||||||
def removeFile(self, filename):
|
|
||||||
open(filename, "wb").write(self.remove())
|
def dumpDir(self, dirname):
|
||||||
@classmethod
|
if(not os.path.isdir(dirname)):
|
||||||
def loadFile(cls, filename, *args, **kwargs):
|
os.mkdir(dirname)
|
||||||
return cls(open(filename, "rb").read(), *args, **kwargs)
|
self._dumpDir(dirname)
|
||||||
|
return dirname
|
||||||
|
|
||||||
|
class WiiHeader(object):
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
def addFile(self, filename):
|
||||||
|
open(filename, "wb").write(self.add())
|
||||||
|
def removeFile(self, filename):
|
||||||
|
open(filename, "wb").write(self.remove())
|
||||||
|
def loadFile(cls, filename, *args, **kwargs):
|
||||||
|
return cls(open(filename, "rb").read(), *args, **kwargs)
|
||||||
|
loadFile = classmethod(loadFile)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,31 @@
|
|||||||
from common import *
|
from common import *
|
||||||
|
|
||||||
|
# Copyright (c) 2008 Hector Martin <marcan@marcansoft.com>
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * The name of the author may not be used to endorse or promote products derived
|
||||||
|
# from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||||
|
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
class LZ77(WiiHeader):
|
class LZ77(WiiHeader):
|
||||||
class WiiLZ77: # class by marcan, used under scope of BSD license
|
class WiiLZ77: # class by marcan, used under scope of BSD license
|
||||||
TYPE_LZ77 = 1
|
TYPE_LZ77 = 1
|
||||||
@ -46,7 +72,7 @@ class LZ77(WiiHeader):
|
|||||||
hdr = self.data[:4]
|
hdr = self.data[:4]
|
||||||
if hdr != "LZ77":
|
if hdr != "LZ77":
|
||||||
return self.data
|
return self.data
|
||||||
file = StringIO.StringIO(self.data)
|
file = StringIO(self.data)
|
||||||
file.seek(4)
|
file.seek(4)
|
||||||
unc = self.WiiLZ77(file, file.tell())
|
unc = self.WiiLZ77(file, file.tell())
|
||||||
data = unc.uncompress()
|
data = unc.uncompress()
|
||||||
|
714
disc.py
714
disc.py
@ -1,357 +1,357 @@
|
|||||||
from common import *
|
from common import *
|
||||||
from title import *
|
from title import *
|
||||||
|
|
||||||
class WOD: #WiiOpticalDisc
|
class WOD: #WiiOpticalDisc
|
||||||
class discHeader(Struct):
|
class discHeader(Struct):
|
||||||
__endian__ = Struct.BE
|
__endian__ = Struct.BE
|
||||||
def __format__(self):
|
def __format__(self):
|
||||||
self.discId = Struct.string(1)
|
self.discId = Struct.string(1)
|
||||||
self.gameCode = Struct.string(2)
|
self.gameCode = Struct.string(2)
|
||||||
self.region = Struct.string(1)
|
self.region = Struct.string(1)
|
||||||
self.makerCode = Struct.uint8[2]
|
self.makerCode = Struct.uint8[2]
|
||||||
self.h = Struct.uint8
|
self.h = Struct.uint8
|
||||||
self.version = Struct.uint8
|
self.version = Struct.uint8
|
||||||
self.audioStreaming = Struct.uint8
|
self.audioStreaming = Struct.uint8
|
||||||
self.streamingBufSize = Struct.uint8
|
self.streamingBufSize = Struct.uint8
|
||||||
self.unused = Struct.uint8[14]
|
self.unused = Struct.uint8[14]
|
||||||
self.magic = Struct.uint32
|
self.magic = Struct.uint32
|
||||||
self.title = Struct.string(64)
|
self.title = Struct.string(64)
|
||||||
self.hashVerify = Struct.uint8
|
self.hashVerify = Struct.uint8
|
||||||
self.h3verify = Struct.uint8
|
self.h3verify = Struct.uint8
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ret = ''
|
ret = ''
|
||||||
ret += '%s [%s%s%s]\n' % (self.title, self.discId, self.gameCode, self.region)
|
ret += '%s [%s%s%s]\n' % (self.title, self.discId, self.gameCode, self.region)
|
||||||
if self.region == 'P':
|
if self.region == 'P':
|
||||||
ret += 'Region : PAL\n'
|
ret += 'Region : PAL\n'
|
||||||
elif self.region == 'E':
|
elif self.region == 'E':
|
||||||
ret += 'Region : NTSC\n'
|
ret += 'Region : NTSC\n'
|
||||||
elif self.region == 'J':
|
elif self.region == 'J':
|
||||||
ret += 'Region : JPN\n'
|
ret += 'Region : JPN\n'
|
||||||
ret += 'Version 0x%x Maker %i%i Audio streaming %x\n' % (self.version, self.makerCode[0], self.makerCode[1], self.audioStreaming)
|
ret += 'Version 0x%x Maker %i%i Audio streaming %x\n' % (self.version, self.makerCode[0], self.makerCode[1], self.audioStreaming)
|
||||||
ret += 'Hash verify flag 0x%x H3 verify flag : 0x%x\n' % (self.hashVerify, self.h3verify)
|
ret += 'Hash verify flag 0x%x H3 verify flag : 0x%x\n' % (self.hashVerify, self.h3verify)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
# Many many thanks to Wiipower
|
# Many many thanks to Wiipower
|
||||||
class Apploader(Struct):
|
class Apploader(Struct):
|
||||||
__endian__ = Struct.BE
|
__endian__ = Struct.BE
|
||||||
def __format__(self):
|
def __format__(self):
|
||||||
self.buildDate = Struct.string(16)
|
self.buildDate = Struct.string(16)
|
||||||
self.entryPoint = Struct.uint32
|
self.entryPoint = Struct.uint32
|
||||||
self.size = Struct.uint32
|
self.size = Struct.uint32
|
||||||
self.trailingSize = Struct.uint32
|
self.trailingSize = Struct.uint32
|
||||||
self.padding = Struct.uint8[4]
|
self.padding = Struct.uint8[4]
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ret = ''
|
ret = ''
|
||||||
ret += 'Apploader built on %s\n' % self.buildDate
|
ret += 'Apploader built on %s\n' % self.buildDate
|
||||||
ret += 'Entry point 0x%x\n' % self.entryPoint
|
ret += 'Entry point 0x%x\n' % self.entryPoint
|
||||||
ret += 'Size %i (%i of them are trailing)\n' % (self.size, self.trailingSize)
|
ret += 'Size %i (%i of them are trailing)\n' % (self.size, self.trailingSize)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ret = ''
|
ret = ''
|
||||||
ret += '%s\n' % self.discHdr
|
ret += '%s\n' % self.discHdr
|
||||||
ret += 'Found %i partitions (table at 0x%x)\n' % (self.partitionCount, self.partsTableOffset)
|
ret += 'Found %i partitions (table at 0x%x)\n' % (self.partitionCount, self.partsTableOffset)
|
||||||
ret += 'Found %i channels (table at 0x%x)\n' % (self.channelsCount, self.chansTableOffset)
|
ret += 'Found %i channels (table at 0x%x)\n' % (self.channelsCount, self.chansTableOffset)
|
||||||
ret += '\n'
|
ret += '\n'
|
||||||
ret += 'Partition %i opened (type 0x%x) at 0x%x\n' % (self.partitionOpen, self.partitionType, self.partitionOffset)
|
ret += 'Partition %i opened (type 0x%x) at 0x%x\n' % (self.partitionOpen, self.partitionType, self.partitionOffset)
|
||||||
ret += 'Partition name : %s' % self.partitionHdr
|
ret += 'Partition name : %s' % self.partitionHdr
|
||||||
ret += 'Partition key : %s\n' % hexdump(self.partitionKey)
|
ret += 'Partition key : %s\n' % hexdump(self.partitionKey)
|
||||||
ret += 'Partition IOS : IOS%i\n' % self.partitionIos
|
ret += 'Partition IOS : IOS%i\n' % self.partitionIos
|
||||||
ret += 'Partition tmd : 0x%x (%x)\n' % (self.tmdOffset, self.tmdSize)
|
ret += 'Partition tmd : 0x%x (%x)\n' % (self.tmdOffset, self.tmdSize)
|
||||||
ret += 'Partition main.dol : 0x%x (%x)\n' % (self.dolOffset, self.dolSize)
|
ret += 'Partition main.dol : 0x%x (%x)\n' % (self.dolOffset, self.dolSize)
|
||||||
ret += 'Partition FST : 0x%x (%x)\n' % (self.fstSize, self.fstOffset)
|
ret += 'Partition FST : 0x%x (%x)\n' % (self.fstSize, self.fstOffset)
|
||||||
ret += '%s\n' % (self.appLdr)
|
ret += '%s\n' % (self.appLdr)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def __init__(self, f):
|
def __init__(self, f):
|
||||||
self.f = f
|
self.f = f
|
||||||
self.fp = open(f, 'rb')
|
self.fp = open(f, 'rb')
|
||||||
|
|
||||||
self.discHdr = self.discHeader().unpack(self.fp.read(0x400))
|
self.discHdr = self.discHeader().unpack(self.fp.read(0x400))
|
||||||
if self.discHdr.magic != 0x5D1C9EA3:
|
if self.discHdr.magic != 0x5D1C9EA3:
|
||||||
raise Exception('Wrong disc magic')
|
raise Exception('Wrong disc magic')
|
||||||
|
|
||||||
self.fp.seek(0x40000)
|
self.fp.seek(0x40000)
|
||||||
|
|
||||||
self.partitionCount = 1 + struct.unpack(">I", self.fp.read(4))[0]
|
self.partitionCount = 1 + struct.unpack(">I", self.fp.read(4))[0]
|
||||||
self.partsTableOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
|
self.partsTableOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
|
||||||
|
|
||||||
self.channelsCount = struct.unpack(">I", self.fp.read(4))[0]
|
self.channelsCount = struct.unpack(">I", self.fp.read(4))[0]
|
||||||
self.chansTableOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
|
self.chansTableOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
|
||||||
|
|
||||||
self.markedBlocks = []
|
self.markedBlocks = []
|
||||||
|
|
||||||
self.partitionOpen = -1
|
self.partitionOpen = -1
|
||||||
self.partitionOffset = -1
|
self.partitionOffset = -1
|
||||||
self.partitionType = -1
|
self.partitionType = -1
|
||||||
|
|
||||||
def markContent(self, offset, size):
|
def markContent(self, offset, size):
|
||||||
blockStart = offset / 0x7C00
|
blockStart = offset / 0x7C00
|
||||||
blockLen = (align(size, 0x7C00)) / 0x7C00
|
blockLen = (align(size, 0x7C00)) / 0x7C00
|
||||||
|
|
||||||
for x in range(blockStart, blockStart + blockLen):
|
for x in range(blockStart, blockStart + blockLen):
|
||||||
try:
|
try:
|
||||||
self.markedBlocks.index(blockStart + x)
|
self.markedBlocks.index(blockStart + x)
|
||||||
except:
|
except:
|
||||||
self.markedBlocks.append(blockStart + x)
|
self.markedBlocks.append(blockStart + x)
|
||||||
|
|
||||||
def decryptBlock(self, block):
|
def decryptBlock(self, block):
|
||||||
if len(block) != 0x8000:
|
if len(block) != 0x8000:
|
||||||
raise Exception('Block size too big/small')
|
raise Exception('Block size too big/small')
|
||||||
|
|
||||||
blockIV = block[0x3d0:0x3e0]
|
blockIV = block[0x3d0:0x3e0]
|
||||||
#print 'IV %s (len %i)\n' % (hexdump(blockIV), len(blockIV))
|
#print 'IV %s (len %i)\n' % (hexdump(blockIV), len(blockIV))
|
||||||
blockData = block[0x0400:0x8000]
|
blockData = block[0x0400:0x8000]
|
||||||
|
|
||||||
return Crypto().decryptData(self.partitionKey, blockIV, blockData, True)
|
return Crypto().decryptData(self.partitionKey, blockIV, blockData, True)
|
||||||
|
|
||||||
def readBlock(self, blockNumber):
|
def readBlock(self, blockNumber):
|
||||||
self.fp.seek(self.partitionOffset + 0x20000 + (0x8000 * blockNumber))
|
self.fp.seek(self.partitionOffset + 0x20000 + (0x8000 * blockNumber))
|
||||||
return self.decryptBlock(self.fp.read(0x8000))
|
return self.decryptBlock(self.fp.read(0x8000))
|
||||||
|
|
||||||
def readPartition(self, offset, size):
|
def readPartition(self, offset, size):
|
||||||
|
|
||||||
readStart = offset / 0x7C00
|
readStart = offset / 0x7C00
|
||||||
readLen = (align(size, 0x7C00)) / 0x7C00
|
readLen = (align(size, 0x7C00)) / 0x7C00
|
||||||
blob = ''
|
blob = ''
|
||||||
|
|
||||||
#print 'Read at 0x%x (Start on %i block, ends at %i block) for %i bytes' % (offset, readStart, readStart + readLen, size)
|
#print 'Read at 0x%x (Start on %i block, ends at %i block) for %i bytes' % (offset, readStart, readStart + readLen, size)
|
||||||
|
|
||||||
self.fp.seek(self.partitionOffset + 0x20000 + (0x8000 * readStart))
|
self.fp.seek(self.partitionOffset + 0x20000 + (0x8000 * readStart))
|
||||||
|
|
||||||
for x in range(readLen + 1):
|
for x in range(readLen + 1):
|
||||||
blob += self.decryptBlock(self.fp.read(0x8000))
|
blob += self.decryptBlock(self.fp.read(0x8000))
|
||||||
|
|
||||||
self.markContent(offset, size)
|
self.markContent(offset, size)
|
||||||
|
|
||||||
#print 'Read from 0x%x to 0x%x' % (offset, offset + size)
|
#print 'Read from 0x%x to 0x%x' % (offset, offset + size)
|
||||||
offset -= readStart * 0x7C00
|
offset -= readStart * 0x7C00
|
||||||
return blob[offset:offset + size]
|
return blob[offset:offset + size]
|
||||||
|
|
||||||
def readUnencrypted(self, offset, size):
|
def readUnencrypted(self, offset, size):
|
||||||
if offset + size > 0x20000:
|
if offset + size > 0x20000:
|
||||||
raise Exception('This read is on encrypted data')
|
raise Exception('This read is on encrypted data')
|
||||||
|
|
||||||
# FIXMII : Needs testing, extracting the tmd cause to have 10 null bytes in the end instead of 10 useful bytes at start :|
|
# FIXMII : Needs testing, extracting the tmd cause to have 10 null bytes in the end instead of 10 useful bytes at start :|
|
||||||
self.fp.seek(self.partitionOffset + 0x2A4 + offset)
|
self.fp.seek(self.partitionOffset + 0x2A4 + offset)
|
||||||
return self.fp.read(size)
|
return self.fp.read(size)
|
||||||
class fstObject(object):
|
class fstObject(object):
|
||||||
#TODO: add ability to extract file by path
|
#TODO: add ability to extract file by path
|
||||||
def __init__(self, name, iso=None):
|
def __init__(self, name, iso=None):
|
||||||
''' do init stuff here '''
|
''' do init stuff here '''
|
||||||
self.parent = None
|
self.parent = None
|
||||||
self.type = 1 #directory: 1, file:0
|
self.type = 1 #directory: 1, file:0
|
||||||
self.name = name
|
self.name = name
|
||||||
self.nameOff = 0
|
self.nameOff = 0
|
||||||
self.fileOffset = 0
|
self.fileOffset = 0
|
||||||
self.size = 0
|
self.size = 0
|
||||||
self.children = []
|
self.children = []
|
||||||
self.iso = iso
|
self.iso = iso
|
||||||
def addChild(self, child):
|
def addChild(self, child):
|
||||||
if self.type == 0:
|
if self.type == 0:
|
||||||
raise Exception('I am not a directory.')
|
raise Exception('I am not a directory.')
|
||||||
child.parent = self
|
child.parent = self
|
||||||
self.children.append(child)
|
self.children.append(child)
|
||||||
def getISO(self):
|
def getISO(self):
|
||||||
if(self.parent == None):
|
if(self.parent == None):
|
||||||
return self.iso
|
return self.iso
|
||||||
return self.parent.getISO()
|
return self.parent.getISO()
|
||||||
def getList(self, pad=0):
|
def getList(self, pad=0):
|
||||||
if self.type == 0:
|
if self.type == 0:
|
||||||
return ("\t" * pad) + self.getPath() + "\n"
|
return ("\t" * pad) + self.getPath() + "\n"
|
||||||
str = "%s[%s]\n" % ("\t" * (pad), self.getPath())
|
str = "%s[%s]\n" % ("\t" * (pad), self.getPath())
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
str += child.getList(pad+1)
|
str += child.getList(pad+1)
|
||||||
return str
|
return str
|
||||||
def count(self):
|
def count(self):
|
||||||
if self.type == 0:
|
if self.type == 0:
|
||||||
return 1
|
return 1
|
||||||
i = 0
|
i = 0
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
i += child.count()
|
i += child.count()
|
||||||
return i
|
return i
|
||||||
def getPath(self):
|
def getPath(self):
|
||||||
if(self.parent == None):
|
if(self.parent == None):
|
||||||
return "/"
|
return "/"
|
||||||
if(self.type == 1):
|
if(self.type == 1):
|
||||||
return self.parent.getPath() + self.name + "/"
|
return self.parent.getPath() + self.name + "/"
|
||||||
return self.parent.getPath() + self.name
|
return self.parent.getPath() + self.name
|
||||||
def write(self, cwd):
|
def write(self, cwd):
|
||||||
if(self.type==0):
|
if(self.type==0):
|
||||||
print cwd + self.getPath()
|
print cwd + self.getPath()
|
||||||
#print self.nameOff
|
#print self.nameOff
|
||||||
open(cwd + self.getPath(), 'w+b').write(self.getISO().readPartition(self.fileOffset, self.size))
|
open(cwd + self.getPath(), 'w+b').write(self.getISO().readPartition(self.fileOffset, self.size))
|
||||||
if(self.type==1):
|
if(self.type==1):
|
||||||
if(self.parent != None):
|
if(self.parent != None):
|
||||||
try:
|
try:
|
||||||
os.makedirs(cwd + self.getPath())
|
os.makedirs(cwd + self.getPath())
|
||||||
except:
|
except:
|
||||||
j = None
|
j = None
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
child.write(cwd)
|
child.write(cwd)
|
||||||
def parseFst(self, fst, names, i, fstDir):
|
def parseFst(self, fst, names, i, fstDir):
|
||||||
size = struct.unpack(">I", fst[(12*i + 8):(12*i + 8) + 4])[0]
|
size = struct.unpack(">I", fst[(12*i + 8):(12*i + 8) + 4])[0]
|
||||||
nameOff = struct.unpack(">I", fst[(12*i):(12*i) + 4])[0] & 0x00ffffff
|
nameOff = struct.unpack(">I", fst[(12*i):(12*i) + 4])[0] & 0x00ffffff
|
||||||
fileName = names[nameOff:]
|
fileName = names[nameOff:]
|
||||||
fileName = fileName[:fileName.find('\0')]
|
fileName = fileName[:fileName.find('\0')]
|
||||||
|
|
||||||
if i == 0:
|
if i == 0:
|
||||||
j = 1
|
j = 1
|
||||||
while(j<size):
|
while(j<size):
|
||||||
j = self.parseFst(fst, names, j, fstDir)
|
j = self.parseFst(fst, names, j, fstDir)
|
||||||
return size
|
return size
|
||||||
if fst[12 * i] == '\x01':
|
if fst[12 * i] == '\x01':
|
||||||
newDir = self.fstObject(fileName)
|
newDir = self.fstObject(fileName)
|
||||||
j = i+1
|
j = i+1
|
||||||
while(j<size):
|
while(j<size):
|
||||||
j = self.parseFst(fst, names, j, newDir)
|
j = self.parseFst(fst, names, j, newDir)
|
||||||
fstDir.addChild(newDir)
|
fstDir.addChild(newDir)
|
||||||
return size
|
return size
|
||||||
else:
|
else:
|
||||||
fileOffset = 4 * struct.unpack(">I", fst[(12*i + 4):(12*i + 4) + 4])[0]
|
fileOffset = 4 * struct.unpack(">I", fst[(12*i + 4):(12*i + 4) + 4])[0]
|
||||||
newFile = self.fstObject(fileName)
|
newFile = self.fstObject(fileName)
|
||||||
newFile.type = 0
|
newFile.type = 0
|
||||||
newFile.fileOffset = fileOffset
|
newFile.fileOffset = fileOffset
|
||||||
newFile.size = size
|
newFile.size = size
|
||||||
newFile.nameOff = nameOff
|
newFile.nameOff = nameOff
|
||||||
fstDir.addChild(newFile)
|
fstDir.addChild(newFile)
|
||||||
self.markContent(fileOffset, size)
|
self.markContent(fileOffset, size)
|
||||||
return i+1
|
return i+1
|
||||||
|
|
||||||
def openPartition(self, index):
|
def openPartition(self, index):
|
||||||
if index+1 > self.partitionCount:
|
if index+1 > self.partitionCount:
|
||||||
raise ValueError('Partition index too big')
|
raise ValueError('Partition index too big')
|
||||||
|
|
||||||
self.partitionOpen = index
|
self.partitionOpen = index
|
||||||
|
|
||||||
self.partitionOffset = self.partsTableOffset + (8 * self.partitionOpen)
|
self.partitionOffset = self.partsTableOffset + (8 * self.partitionOpen)
|
||||||
|
|
||||||
self.fp.seek(self.partsTableOffset + (8 * self.partitionOpen))
|
self.fp.seek(self.partsTableOffset + (8 * self.partitionOpen))
|
||||||
|
|
||||||
self.partitionOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
|
self.partitionOffset = struct.unpack(">I", self.fp.read(4))[0] << 2
|
||||||
self.partitionType = struct.unpack(">I", self.fp.read(4))[0]
|
self.partitionType = struct.unpack(">I", self.fp.read(4))[0]
|
||||||
|
|
||||||
self.fp.seek(self.partitionOffset)
|
self.fp.seek(self.partitionOffset)
|
||||||
|
|
||||||
self.tikData = self.fp.read(0x2A4)
|
self.tikData = self.fp.read(0x2A4)
|
||||||
self.partitionKey = Ticket(self.tikData).getTitleKey()
|
self.partitionKey = Ticket(self.tikData).getTitleKey()
|
||||||
|
|
||||||
self.tmdSize = struct.unpack(">I", self.fp.read(4))[0]
|
self.tmdSize = struct.unpack(">I", self.fp.read(4))[0]
|
||||||
self.tmdOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
self.tmdOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
||||||
|
|
||||||
self.certsSize = struct.unpack(">I", self.fp.read(4))[0]
|
self.certsSize = struct.unpack(">I", self.fp.read(4))[0]
|
||||||
self.certsOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
self.certsOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
||||||
|
|
||||||
self.H3TableOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
self.H3TableOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
||||||
|
|
||||||
self.dataOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
self.dataOffset = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
||||||
self.dataSize = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
self.dataSize = struct.unpack(">I", self.fp.read(4))[0] >> 2
|
||||||
|
|
||||||
self.fstOffset = 4 * struct.unpack(">I", self.readPartition (0x424, 4))[0]
|
self.fstOffset = 4 * struct.unpack(">I", self.readPartition (0x424, 4))[0]
|
||||||
self.fstSize = 4 * struct.unpack(">I", self.readPartition (0x428, 4))[0]
|
self.fstSize = 4 * struct.unpack(">I", self.readPartition (0x428, 4))[0]
|
||||||
|
|
||||||
self.dolOffset = 4 * struct.unpack(">I", self.readPartition (0x420, 4))[0]
|
self.dolOffset = 4 * struct.unpack(">I", self.readPartition (0x420, 4))[0]
|
||||||
self.dolSize = self.fstOffset - self.dolOffset
|
self.dolSize = self.fstOffset - self.dolOffset
|
||||||
|
|
||||||
self.appLdr = self.Apploader().unpack(self.readPartition (0x2440, 32))
|
self.appLdr = self.Apploader().unpack(self.readPartition (0x2440, 32))
|
||||||
self.partitionHdr = self.discHeader().unpack(self.readPartition (0x0, 0x400))
|
self.partitionHdr = self.discHeader().unpack(self.readPartition (0x0, 0x400))
|
||||||
|
|
||||||
self.partitionIos = TMD(self.getPartitionTmd()).getIOSVersion() & 0x0fffffff
|
self.partitionIos = TMD(self.getPartitionTmd()).getIOSVersion() & 0x0fffffff
|
||||||
|
|
||||||
def getFst(self):
|
def getFst(self):
|
||||||
fstBuf = self.readPartition(self.fstOffset, self.fstSize)
|
fstBuf = self.readPartition(self.fstOffset, self.fstSize)
|
||||||
return fstBuf
|
return fstBuf
|
||||||
|
|
||||||
def getIsoBootmode(self):
|
def getIsoBootmode(self):
|
||||||
if self.discHdr.discId == 'R' or self.discHdr.discId == '_':
|
if self.discHdr.discId == 'R' or self.discHdr.discId == '_':
|
||||||
return 2
|
return 2
|
||||||
elif self.discHdr.discId == '0':
|
elif self.discHdr.discId == '0':
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def getOpenedPartition(self):
|
def getOpenedPartition(self):
|
||||||
return self.partitionOpen
|
return self.partitionOpen
|
||||||
|
|
||||||
def getOpenedPartitionOffset(self):
|
def getOpenedPartitionOffset(self):
|
||||||
return self.partitionOffset
|
return self.partitionOffset
|
||||||
|
|
||||||
def getOpenedPartitionType(self):
|
def getOpenedPartitionType(self):
|
||||||
return self.partitionType
|
return self.partitionType
|
||||||
|
|
||||||
def getPartitionsCount(self):
|
def getPartitionsCount(self):
|
||||||
return self.partitionCount
|
return self.partitionCount
|
||||||
|
|
||||||
def getChannelsCount(self):
|
def getChannelsCount(self):
|
||||||
return self.channelsCount
|
return self.channelsCount
|
||||||
|
|
||||||
def getPartitionCerts(self):
|
def getPartitionCerts(self):
|
||||||
return self.readUnencrypted(self.certsOffset, self.certsSize)
|
return self.readUnencrypted(self.certsOffset, self.certsSize)
|
||||||
|
|
||||||
def getPartitionH3Table(self):
|
def getPartitionH3Table(self):
|
||||||
return self.readUnencrypted(self.H3TableOffset, 0x18000)
|
return self.readUnencrypted(self.H3TableOffset, 0x18000)
|
||||||
|
|
||||||
def getPartitionTmd(self):
|
def getPartitionTmd(self):
|
||||||
return self.readUnencrypted(self.tmdOffset, self.tmdSize)
|
return self.readUnencrypted(self.tmdOffset, self.tmdSize)
|
||||||
|
|
||||||
def getPartitionTik(self):
|
def getPartitionTik(self):
|
||||||
self.fp.seek(self.partitionOffset)
|
self.fp.seek(self.partitionOffset)
|
||||||
return self.fp.read(0x2A4)
|
return self.fp.read(0x2A4)
|
||||||
|
|
||||||
def getPartitionApploader(self):
|
def getPartitionApploader(self):
|
||||||
return self.readPartition (0x2440, self.appLdr.size + self.appLdr.trailingSize + 32)
|
return self.readPartition (0x2440, self.appLdr.size + self.appLdr.trailingSize + 32)
|
||||||
|
|
||||||
def getPartitionMainDol(self):
|
def getPartitionMainDol(self):
|
||||||
return self.readPartition (self.dolOffset, self.dolSize)
|
return self.readPartition (self.dolOffset, self.dolSize)
|
||||||
|
|
||||||
def dumpPartition(self, fn):
|
def dumpPartition(self, fn):
|
||||||
rawPartition = open(fn, 'w+b')
|
rawPartition = open(fn, 'w+b')
|
||||||
|
|
||||||
print 'Partition useful data %i Mb' % (align(len(self.markedBlocks) * 0x7C00, 1024) / 1024 / 1024)
|
print 'Partition useful data %i Mb' % (align(len(self.markedBlocks) * 0x7C00, 1024) / 1024 / 1024)
|
||||||
|
|
||||||
self.fp.seek(self.partitionOffset)
|
self.fp.seek(self.partitionOffset)
|
||||||
rawPartition.write(self.fp.read(0x2A4)) # Write teh TIK
|
rawPartition.write(self.fp.read(0x2A4)) # Write teh TIK
|
||||||
rawPartition.write(self.readUnencrypted(0, 0x20000 - 0x2A4)) # Write the TMD and other stuff
|
rawPartition.write(self.readUnencrypted(0, 0x20000 - 0x2A4)) # Write the TMD and other stuff
|
||||||
|
|
||||||
for x in range(len(self.markedBlocks)):
|
for x in range(len(self.markedBlocks)):
|
||||||
rawPartition.write(self.readBlock(self.markedBlocks[x])) # Write each decrypted block
|
rawPartition.write(self.readBlock(self.markedBlocks[x])) # Write each decrypted block
|
||||||
|
|
||||||
class updateInf():
|
class updateInf:
|
||||||
def __init__(self, f):
|
def __init__(self, f):
|
||||||
self.buffer = open(f, 'r+b').read()
|
self.buffer = open(f, 'r+b').read()
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
out = ''
|
out = ''
|
||||||
|
|
||||||
self.buildDate = self.buffer[:0x10]
|
self.buildDate = self.buffer[:0x10]
|
||||||
self.fileCount = struct.unpack('>L', self.buffer[0x10:0x14])[0]
|
self.fileCount = struct.unpack('>L', self.buffer[0x10:0x14])[0]
|
||||||
|
|
||||||
out += 'This update partition was built on %s and has %i files\n\n' % (self.buildDate, self.fileCount)
|
out += 'This update partition was built on %s and has %i files\n\n' % (self.buildDate, self.fileCount)
|
||||||
|
|
||||||
for x in range(self.fileCount):
|
for x in range(self.fileCount):
|
||||||
updateEntry = self.buffer[0x20 + x * 0x200:0x20 + (x + 1) * 0x200]
|
updateEntry = self.buffer[0x20 + x * 0x200:0x20 + (x + 1) * 0x200]
|
||||||
titleType = struct.unpack('>L', updateEntry[:0x4])[0]
|
titleType = struct.unpack('>L', updateEntry[:0x4])[0]
|
||||||
titleAttr = struct.unpack('>L', updateEntry[0x4:0x8])[0]
|
titleAttr = struct.unpack('>L', updateEntry[0x4:0x8])[0]
|
||||||
titleUnk1 = struct.unpack('>L', updateEntry[0x8:0xC])[0]
|
titleUnk1 = struct.unpack('>L', updateEntry[0x8:0xC])[0]
|
||||||
titleType2 = struct.unpack('>L', updateEntry[0xC:0x10])[0]
|
titleType2 = struct.unpack('>L', updateEntry[0xC:0x10])[0]
|
||||||
titleFile = updateEntry[0x10:0x50]
|
titleFile = updateEntry[0x10:0x50]
|
||||||
titleFile = titleFile[:titleFile.find('\x00')]
|
titleFile = titleFile[:titleFile.find('\x00')]
|
||||||
titleID = struct.unpack('>Q', updateEntry[0x50:0x58])[0]
|
titleID = struct.unpack('>Q', updateEntry[0x50:0x58])[0]
|
||||||
titleMajor = struct.unpack('>B', updateEntry[0x58:0x59])[0]
|
titleMajor = struct.unpack('>B', updateEntry[0x58:0x59])[0]
|
||||||
titleMinor = struct.unpack('>B', updateEntry[0x59:0x5A])[0]
|
titleMinor = struct.unpack('>B', updateEntry[0x59:0x5A])[0]
|
||||||
titleName = updateEntry[0x60:0xA0]
|
titleName = updateEntry[0x60:0xA0]
|
||||||
titleName = titleName[:titleName.find('\x00')]
|
titleName = titleName[:titleName.find('\x00')]
|
||||||
titleInfo = updateEntry[0xA0:0xE0]
|
titleInfo = updateEntry[0xA0:0xE0]
|
||||||
titleInfo = titleInfo[:titleInfo.find('\x00')]
|
titleInfo = titleInfo[:titleInfo.find('\x00')]
|
||||||
out += 'Update type : 0x%x\n' % titleType
|
out += 'Update type : 0x%x\n' % titleType
|
||||||
out += 'Update flag : %i (0 means critical, 1 means need reboot)\n' % titleAttr
|
out += 'Update flag : %i (0 means critical, 1 means need reboot)\n' % titleAttr
|
||||||
out += 'Update file : %s\n' % titleFile
|
out += 'Update file : %s\n' % titleFile
|
||||||
out += 'Update ID : %lu\n' % titleID
|
out += 'Update ID : %lu\n' % titleID
|
||||||
out += 'Update version : %i.%i\n' % (titleMajor, titleMinor)
|
out += 'Update version : %i.%i\n' % (titleMajor, titleMinor)
|
||||||
out += 'Update name : %s\n' % titleName
|
out += 'Update name : %s\n' % titleName
|
||||||
out += 'Update info : %s\n' % titleInfo
|
out += 'Update info : %s\n' % titleInfo
|
||||||
out += '\n'
|
out += '\n'
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
97
export.py
97
export.py
@ -251,3 +251,100 @@ class Savegame():
|
|||||||
|
|
||||||
def getFilesCount(self):
|
def getFilesCount(self):
|
||||||
return self.bkHdr.filesCount
|
return self.bkHdr.filesCount
|
||||||
|
|
||||||
|
|
||||||
|
class locDat:
|
||||||
|
class locHeader(Struct):
|
||||||
|
def __format__(self):
|
||||||
|
self.magic = Struct.string(4)
|
||||||
|
self.md5 = Struct.string(16)
|
||||||
|
|
||||||
|
def __init__(self, f):
|
||||||
|
self.sdKey = '\xab\x01\xb9\xd8\xe1\x62\x2b\x08\xaf\xba\xd8\x4d\xbf\xc2\xa5\x5d'
|
||||||
|
self.sdIv = '\x21\x67\x12\xe6\xaa\x1f\x68\x9f\x95\xc5\xa2\x23\x24\xdc\x6a\x98'
|
||||||
|
|
||||||
|
self.titles = []
|
||||||
|
self.usedBlocks = 0
|
||||||
|
self.freeBlocks = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.fp = open(f, 'r+')
|
||||||
|
except:
|
||||||
|
raise Exception('File not found')
|
||||||
|
|
||||||
|
plainBuffer = Crypto().decryptData(self.sdKey, self.sdIv, self.fp.read(), False)
|
||||||
|
|
||||||
|
self.hdr = self.locHeader().unpack(plainBuffer[:0x14])
|
||||||
|
|
||||||
|
for x in range(240):
|
||||||
|
self.titles.append(plainBuffer[0x14 + x * 4:0x14 + (x + 1) * 4])
|
||||||
|
if self.titles[x] == '\x00\x00\x00\x00':
|
||||||
|
self.freeBlocks += 1
|
||||||
|
|
||||||
|
self.usedBlocks = 240 - self.freeBlocks
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
out = ''
|
||||||
|
out += 'Used %i blocks out of 240\n\n' % self.usedBlocks
|
||||||
|
for x in range(240):
|
||||||
|
if self.titles[x] == '\x00\x00\x00\x00':
|
||||||
|
out += 'Block %i on page %i is empty\n' % (x, x / 12)
|
||||||
|
else:
|
||||||
|
out += 'Block %i on page %i hold title %s\n' % (x, x / 12, self.titles[x])
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def getFreeBlocks(self):
|
||||||
|
return self.freeBlocks
|
||||||
|
|
||||||
|
def getUsedBlocks(self):
|
||||||
|
return self.usedBlocks
|
||||||
|
|
||||||
|
def isBlockFree(self, x, y, page):
|
||||||
|
if self.titles[((x + (y * 4) + (page * 12)))] == '\x00\x00\x00\x00':
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def isTitleInList(self, title):
|
||||||
|
try:
|
||||||
|
return self.titles.index(title.upper())
|
||||||
|
except:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def getPageTitles(self, page):
|
||||||
|
if page > 19:
|
||||||
|
raise Exception('Out of bounds')
|
||||||
|
|
||||||
|
return self.titles[12 * page:12 * (page + 1)]
|
||||||
|
|
||||||
|
def getTitle(self, x, y, page):
|
||||||
|
if x > 3 or y > 2 or page > 19:
|
||||||
|
raise Exception('Out of bounds')
|
||||||
|
|
||||||
|
return self.titles[((x + (y * 4) + (page * 12)))]
|
||||||
|
|
||||||
|
def setTitle(self, x, y, page, element):
|
||||||
|
if x > 3 or y > 2 or page > 19 or len(element) > 4:
|
||||||
|
raise Exception('Out of bounds')
|
||||||
|
|
||||||
|
self.titles[((x + (y * 4) + (page * 12)))] = element.upper()
|
||||||
|
|
||||||
|
titles = ''
|
||||||
|
|
||||||
|
titles += self.hdr.magic
|
||||||
|
titles += self.hdr.md5
|
||||||
|
|
||||||
|
for x in range(240):
|
||||||
|
titles += self.titles[x]
|
||||||
|
|
||||||
|
titles += '\x00' * 12
|
||||||
|
|
||||||
|
titles = titles[:0x4] + Crypto().createMD5Hash(titles) + titles[0x14:]
|
||||||
|
|
||||||
|
self.fp.seek(0)
|
||||||
|
self.fp.write(Crypto().encryptData(self.sdKey, self.sdIv, titles))
|
||||||
|
|
||||||
|
def delTitle(self, x, y, page):
|
||||||
|
self.setTitle(x, y, page, '\x00\x00\x00\x00')
|
||||||
|
|
||||||
|
96
formats.py
96
formats.py
@ -1,104 +1,8 @@
|
|||||||
from binascii import *
|
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from common import *
|
from common import *
|
||||||
from title import *
|
from title import *
|
||||||
|
|
||||||
class locDat:
|
|
||||||
class locHeader(Struct):
|
|
||||||
def __format__(self):
|
|
||||||
self.magic = Struct.string(4)
|
|
||||||
self.md5 = Struct.string(16)
|
|
||||||
|
|
||||||
def __init__(self, f):
|
|
||||||
self.sdKey = '\xab\x01\xb9\xd8\xe1\x62\x2b\x08\xaf\xba\xd8\x4d\xbf\xc2\xa5\x5d'
|
|
||||||
self.sdIv = '\x21\x67\x12\xe6\xaa\x1f\x68\x9f\x95\xc5\xa2\x23\x24\xdc\x6a\x98'
|
|
||||||
|
|
||||||
self.titles = []
|
|
||||||
self.usedBlocks = 0
|
|
||||||
self.freeBlocks = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.fp = open(f, 'r+')
|
|
||||||
except:
|
|
||||||
raise Exception('File not found')
|
|
||||||
|
|
||||||
plainBuffer = Crypto().decryptData(self.sdKey, self.sdIv, self.fp.read(), False)
|
|
||||||
|
|
||||||
self.hdr = self.locHeader().unpack(plainBuffer[:0x14])
|
|
||||||
|
|
||||||
for x in range(240):
|
|
||||||
self.titles.append(plainBuffer[0x14 + x * 4:0x14 + (x + 1) * 4])
|
|
||||||
if self.titles[x] == '\x00\x00\x00\x00':
|
|
||||||
self.freeBlocks += 1
|
|
||||||
|
|
||||||
self.usedBlocks = 240 - self.freeBlocks
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
out = ''
|
|
||||||
out += 'Used %i blocks out of 240\n\n' % self.usedBlocks
|
|
||||||
for x in range(240):
|
|
||||||
if self.titles[x] == '\x00\x00\x00\x00':
|
|
||||||
out += 'Block %i on page %i is empty\n' % (x, x / 12)
|
|
||||||
else:
|
|
||||||
out += 'Block %i on page %i hold title %s\n' % (x, x / 12, self.titles[x])
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
def getFreeBlocks(self):
|
|
||||||
return self.freeBlocks
|
|
||||||
|
|
||||||
def getUsedBlocks(self):
|
|
||||||
return self.usedBlocks
|
|
||||||
|
|
||||||
def isBlockFree(self, x, y, page):
|
|
||||||
if self.titles[((x + (y * 4) + (page * 12)))] == '\x00\x00\x00\x00':
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def isTitleInList(self, title):
|
|
||||||
try:
|
|
||||||
return self.titles.index(title.upper())
|
|
||||||
except:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def getPageTitles(self, page):
|
|
||||||
if page > 19:
|
|
||||||
raise Exception('Out of bounds')
|
|
||||||
|
|
||||||
return self.titles[12 * page:12 * (page + 1)]
|
|
||||||
|
|
||||||
def getTitle(self, x, y, page):
|
|
||||||
if x > 3 or y > 2 or page > 19:
|
|
||||||
raise Exception('Out of bounds')
|
|
||||||
|
|
||||||
return self.titles[((x + (y * 4) + (page * 12)))]
|
|
||||||
|
|
||||||
def setTitle(self, x, y, page, element):
|
|
||||||
if x > 3 or y > 2 or page > 19 or len(element) > 4:
|
|
||||||
raise Exception('Out of bounds')
|
|
||||||
|
|
||||||
self.titles[((x + (y * 4) + (page * 12)))] = element.upper()
|
|
||||||
|
|
||||||
titles = ''
|
|
||||||
|
|
||||||
titles += self.hdr.magic
|
|
||||||
titles += self.hdr.md5
|
|
||||||
|
|
||||||
for x in range(240):
|
|
||||||
titles += self.titles[x]
|
|
||||||
|
|
||||||
titles += '\x00' * 12
|
|
||||||
|
|
||||||
titles = titles[:0x4] + Crypto().createMD5Hash(titles) + titles[0x14:]
|
|
||||||
|
|
||||||
self.fp.seek(0)
|
|
||||||
self.fp.write(Crypto().encryptData(self.sdKey, self.sdIv, titles))
|
|
||||||
|
|
||||||
def delTitle(self, x, y, page):
|
|
||||||
self.setTitle(x, y, page, '\x00\x00\x00\x00')
|
|
||||||
|
|
||||||
class CONF:
|
class CONF:
|
||||||
"""This class deals with setting.txt which holds some important information like region and serial number """
|
"""This class deals with setting.txt which holds some important information like region and serial number """
|
||||||
def __init__(self, f):
|
def __init__(self, f):
|
||||||
|
71
headers.py
71
headers.py
@ -1,44 +1,29 @@
|
|||||||
from common import *
|
from common import *
|
||||||
|
|
||||||
class IMD5(WiiHeader):
|
class IMD5(WiiHeader):
|
||||||
"""This class can add and remove IMD5 headers to files. The parameter f is the file to use for the addition or removal of the header. IMD5 headers are found in banner.bin, icon.bin, and sound.bin."""
|
|
||||||
class IMD5Header(Struct):
|
class IMD5Header(Struct):
|
||||||
__endian__ = Struct.BE
|
__endian__ = Struct.BE
|
||||||
def __format__(self):
|
def __format__(self):
|
||||||
self.tag = Struct.string(4)
|
self.tag = Struct.string(4)
|
||||||
self.size = Struct.uint32
|
self.size = Struct.uint32
|
||||||
self.zeroes = Struct.uint8[8]
|
self.zeroes = Struct.string(8)
|
||||||
self.crypto = Struct.string(16)
|
self.crypto = Struct.string(16)
|
||||||
def add(self):
|
def add(self):
|
||||||
data = self.data
|
|
||||||
|
|
||||||
imd5 = self.IMD5Header()
|
imd5 = self.IMD5Header()
|
||||||
imd5.tag = "IMD5"
|
imd5.tag = "IMD5"
|
||||||
imd5.size = len(data)
|
imd5.size = len(self.data)
|
||||||
for i in range(8):
|
imd5.zeroes = '\x00' * 8
|
||||||
imd5.zeroes[i] = 0x00
|
imd5.crypto = str(Crypto.createMD5Hash(self.data))
|
||||||
imd5.crypto = str(Crypto().createMD5Hash(data))
|
self.data = imd5.pack() + self.data
|
||||||
data = imd5.pack() + data
|
return self.data
|
||||||
|
|
||||||
return data
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""This will remove an IMD5 header from the file specified in f, if one exists. If there is no IMD5 header, it will output the file as it is. It will output in the parameter fn if available, otherwise it will overwrite the source. Returns the output filename."""
|
|
||||||
data = self.data
|
|
||||||
imd5 = self.IMD5Header()
|
imd5 = self.IMD5Header()
|
||||||
if(data[:4] != "IMD5"):
|
if(self.data[:4] != "IMD5"):
|
||||||
if(fn != ""):
|
return self.data
|
||||||
open(fn, "wb").write(data)
|
self.data = self.data[len(imd5):]
|
||||||
return fn
|
return self.data
|
||||||
else:
|
|
||||||
return self.f
|
|
||||||
data = data[len(imd5):]
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
class IMET(WiiHeader):
|
class IMET(WiiHeader):
|
||||||
"""IMET headers are found in Opening.bnr and 0000000.app files. They contain the channel titles and more metadata about channels. They are in two different formats with different amounts of padding before the start of the IMET header. This class suports both.
|
|
||||||
|
|
||||||
The parameter f is used to specify the input file name."""
|
|
||||||
class IMETHeader(Struct):
|
class IMETHeader(Struct):
|
||||||
__endian__ = Struct.BE
|
__endian__ = Struct.BE
|
||||||
def __format__(self):
|
def __format__(self):
|
||||||
@ -50,14 +35,11 @@ class IMET(WiiHeader):
|
|||||||
self.names = Struct.string(84, encoding = "utf-16-be", stripNulls = True)[7]
|
self.names = Struct.string(84, encoding = "utf-16-be", stripNulls = True)[7]
|
||||||
self.zeroes2 = Struct.uint8[840]
|
self.zeroes2 = Struct.uint8[840]
|
||||||
self.hash = Struct.string(16)
|
self.hash = Struct.string(16)
|
||||||
def add(self, iconsz, bannersz, soundsz, name = "", langs = [], fn = ""):
|
def add(self, iconsz, bannersz, soundsz, name = '', langs = []):
|
||||||
"""This function adds an IMET header to the file specified with f in the initializer. The file will be output to fn if it is not empty, otherwise it will overwrite the input file. You must specify the size of banner.bin in bannersz, and respectivly for iconsz and soundsz. langs is an optional arguement that is a list of different langauge channel titles. name is the english name that is copied everywhere in langs that there is an empty string. Returns the output filename."""
|
|
||||||
data = self.data
|
|
||||||
imet = self.IMETHeader()
|
imet = self.IMETHeader()
|
||||||
|
for i in range(len(imet.zeroes)):
|
||||||
for i in imet.zeroes:
|
|
||||||
imet.zeroes[i] = 0x00
|
imet.zeroes[i] = 0x00
|
||||||
imet.tag = "IMET"
|
imet.tag = 'IMET'
|
||||||
imet.unk = 0x0000060000000003
|
imet.unk = 0x0000060000000003
|
||||||
imet.sizes[0] = iconsz
|
imet.sizes[0] = iconsz
|
||||||
imet.sizes[1] = bannersz
|
imet.sizes[1] = bannersz
|
||||||
@ -68,45 +50,40 @@ class IMET(WiiHeader):
|
|||||||
imet.names[i] = langs[i]
|
imet.names[i] = langs[i]
|
||||||
else:
|
else:
|
||||||
imet.names[i] = name
|
imet.names[i] = name
|
||||||
for i in imet.zeroes2:
|
for i in range(len(imet.zeroes2)):
|
||||||
imet.zeroes2[i] = 0x00
|
imet.zeroes2[i] = 0x00
|
||||||
imet.hash = "\x00" * 16
|
imet.hash = '\x00' * 16
|
||||||
|
|
||||||
tmp = imet.pack()
|
tmp = imet.pack()
|
||||||
imet.hash = Crypto().createMD5Hash(tmp[0x40:0x640]) #0x00 or 0x40?
|
imet.hash = Crypto.createMD5Hash(tmp[0x40:0x640])
|
||||||
|
self.data = imet.pack() + self.data
|
||||||
data = imet.pack() + data
|
print "testing %x %x %x %x %x" % (len(imet), 128, 840, 0x1A << 1, 84)
|
||||||
|
return self.data
|
||||||
return data
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
data = self.data
|
data = self.data
|
||||||
if(data[0x80:0x84] == "IMET"):
|
if(data[0x80:0x84] == 'IMET'):
|
||||||
data = data[0x640:]
|
data = data[0x640:]
|
||||||
elif(data[0x40:0x44] == "IMET"):
|
elif(data[0x40:0x44] == 'IMET'):
|
||||||
data = data[0x640:]
|
data = data[0x640:]
|
||||||
return data
|
return data
|
||||||
def getTitle(self):
|
def getTitle(self):
|
||||||
imet = self.IMETHeader()
|
imet = self.IMETHeader()
|
||||||
data = self.data
|
data = self.data
|
||||||
|
if(data[0x40:0x44] == 'IMET'):
|
||||||
if(data[0x40:0x44] == "IMET"):
|
|
||||||
pass
|
pass
|
||||||
elif(data[0x80:0x84] == "IMET"):
|
elif(data[0x80:0x84] == 'IMET'):
|
||||||
data = data[0x40:]
|
data = data[0x40:]
|
||||||
else:
|
else:
|
||||||
raise ValueError("No IMET header found!")
|
raise ValueError("No IMET header found!")
|
||||||
|
|
||||||
imet.unpack(data[:len(imet)])
|
imet.unpack(data[:len(imet)])
|
||||||
name = imet.names[1]
|
name = imet.names[1]
|
||||||
topop = []
|
topop = []
|
||||||
for i in range(len(name)):
|
for i in range(len(name)):
|
||||||
if(name[i] == "\x00"):
|
if(name[i] == '\x00'):
|
||||||
topop.append(i)
|
topop.append(i)
|
||||||
name = list(name)
|
name = list(name)
|
||||||
popped = 0 #don't ask me why I did this
|
popped = 0 #don't ask me why I did this
|
||||||
for pop in topop:
|
for pop in topop:
|
||||||
name.pop(pop - popped)
|
name.pop(pop - popped)
|
||||||
popped += 1
|
popped += 1
|
||||||
|
|
||||||
name = ''.join(name)
|
name = ''.join(name)
|
||||||
return name
|
return name
|
||||||
|
12
image.py
12
image.py
@ -1,6 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
from common import *
|
from common import *
|
||||||
import wx
|
|
||||||
|
|
||||||
def flatten(myTuple):
|
def flatten(myTuple):
|
||||||
if (len(myTuple) == 4):
|
if (len(myTuple) == 4):
|
||||||
@ -31,7 +29,7 @@ def avg(w0, w1, c0, c1):
|
|||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
class TPL():
|
class TPL:
|
||||||
"""This is the class to generate TPL texutres from PNG images, and to convert TPL textures to PNG images. The parameter file specifies the filename of the source, either a PNG image or a TPL image.
|
"""This is the class to generate TPL texutres from PNG images, and to convert TPL textures to PNG images. The parameter file specifies the filename of the source, either a PNG image or a TPL image.
|
||||||
|
|
||||||
Currently supported are the following formats to convert from TPL (all formats): RGBA8, RGB565, RGB5A3, I4, IA4, I8, IA8, CI4, CI8, CMP, CI14X2.
|
Currently supported are the following formats to convert from TPL (all formats): RGBA8, RGB565, RGB5A3, I4, IA4, I8, IA8, CI4, CI8, CMP, CI14X2.
|
||||||
@ -483,7 +481,10 @@ class TPL():
|
|||||||
return outfile
|
return outfile
|
||||||
def getSizes(self):
|
def getSizes(self):
|
||||||
"""This returns a tuple containing the width and height of the TPL image filename in the class initializer. Will only return the size of single textured TPL images."""
|
"""This returns a tuple containing the width and height of the TPL image filename in the class initializer. Will only return the size of single textured TPL images."""
|
||||||
data = open(self.file, "rb").read()
|
if(self.file):
|
||||||
|
data = open(self.file, "rb").read()
|
||||||
|
else:
|
||||||
|
data = self.data
|
||||||
|
|
||||||
header = self.TPLHeader()
|
header = self.TPLHeader()
|
||||||
textures = []
|
textures = []
|
||||||
@ -506,9 +507,6 @@ class TPL():
|
|||||||
h = tex.height
|
h = tex.height
|
||||||
return (w, h)
|
return (w, h)
|
||||||
def toScreen(self): #single texture only
|
def toScreen(self): #single texture only
|
||||||
"""This will draw a simple window with the TPL image displayed on it. It uses WxPython for the window creation and management. The window has a minimum width and height of 300 x 200. Does not return a value.
|
|
||||||
|
|
||||||
Again, only a single texture is supported."""
|
|
||||||
import wx
|
import wx
|
||||||
class imp(wx.Dialog):
|
class imp(wx.Dialog):
|
||||||
def __init__(self, title, im):
|
def __init__(self, title, im):
|
||||||
|
187
mymenu.py
Normal file
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 = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin", self)
|
||||||
a.deleteTitle(tid)
|
a.deleteTitle(tid)
|
||||||
|
|
||||||
def importTitle(self, prefix, tmd, tik, add_to_menu = True, is_decrypted = False, result_decrypted = False):
|
def importTitle(self, title, add_to_menu = True, result_decrypted = False, use_version = True):
|
||||||
"""When passed a prefix (the directory to obtain the .app files from, sorted by content id), a TMD instance, and a Ticket instance, this will add that title to the NAND base folder specified in the constructor. If add_to_menu is True, the title (if neccessary) will be added to the menu. The default is True. Unless is_decrypted is set, the contents are assumed to be encrypted. If result_decrypted is True, then the contents will not end up decrypted."""
|
"""When passed a prefix (the directory to obtain the .app files from, sorted by content id), a TMD instance, and a Ticket instance, this will add that title to the NAND base folder specified in the constructor. If add_to_menu is True, the title (if neccessary) will be added to the menu. The default is True. Unless is_decrypted is set, the contents are assumed to be encrypted. If result_decrypted is True, then the contents will not end up decrypted."""
|
||||||
self.ES.AddTitleStart(tmd, None, None, is_decrypted, result_decrypted, use_version = True)
|
self.ES.AddTitleStart(title.tmd, None, None, True, result_decrypted, use_version = use_version)
|
||||||
self.ES.AddTitleTMD(tmd)
|
self.ES.AddTitleTMD(title.tmd)
|
||||||
self.ES.AddTicket(tik)
|
self.ES.AddTicket(title.tik)
|
||||||
contents = tmd.getContents()
|
contents = title.tmd.getContents()
|
||||||
for i in range(tmd.tmd.numcontents):
|
for i, content in enumerate(contents):
|
||||||
self.ES.AddContentStart(tmd.tmd.titleid, contents[i].cid)
|
self.ES.AddContentStart(title.tmd.titleid, content.cid)
|
||||||
fp = open(prefix + "/%08x.app" % contents[i].cid, "rb")
|
data = title[content.index]
|
||||||
data = fp.read()
|
self.ES.AddContentData(content.cid, data)
|
||||||
fp.close()
|
self.ES.AddContentFinish(content.cid)
|
||||||
self.ES.AddContentData(contents[i].cid, data)
|
|
||||||
self.ES.AddContentFinish(contents[i].cid)
|
|
||||||
self.ES.AddTitleFinish()
|
self.ES.AddTitleFinish()
|
||||||
if(add_to_menu == True):
|
if(add_to_menu == True):
|
||||||
if(((tmd.tmd.titleid >> 32) != 0x00010008) and ((tmd.tmd.titleid >> 32) != 0x00000001)):
|
if(((tmd.tmd.titleid >> 32) != 0x00010008) and ((tmd.tmd.titleid >> 32) != 0x00000001)):
|
||||||
self.addTitleToMenu(tmd.tmd.titleid)
|
self.addTitleToMenu(tmd.tmd.titleid)
|
||||||
|
|
||||||
def createWADFromTitle(self, title, cert, output, version=0):
|
def getTitle(self, title_id, version = None, fakesign = True):
|
||||||
|
title = Title()
|
||||||
tmdpth = self.f + "/title/%08x/%08x/content/title.tmd" % (title >> 32, title & 0xFFFFFFFF)
|
tmdpth = self.f + "/title/%08x/%08x/content/title.tmd" % (title >> 32, title & 0xFFFFFFFF)
|
||||||
if(version != 0):
|
if(version != 0):
|
||||||
tmdpth += ".%d" % version
|
tmdpth += ".%d" % version
|
||||||
tmd = TMD.loadFile(tmdpth)
|
title.tmd = TMD.loadFile(tmdpth)
|
||||||
if(not os.path.isdir("export")):
|
if(fakesign):
|
||||||
os.mkdir("export")
|
title.tmd.fakesign()
|
||||||
tmd.fakesign()
|
title.tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
|
||||||
tmd.dumpFile("export/tmd")
|
if(fakesign):
|
||||||
tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
|
title.tik.fakesign()
|
||||||
tik.fakesign()
|
contents = title.tmd.getContents()
|
||||||
tik.dumpFile("export/tik")
|
for i in range(len(contents)):
|
||||||
contents = tmd.getContents()
|
|
||||||
for i in range(tmd.tmd.numcontents):
|
|
||||||
path = ""
|
path = ""
|
||||||
if(contents[i].type == 0x0001):
|
if(contents[i].type == 0x0001):
|
||||||
path = self.f + "/title/%08x/%08x/content/%08x.app" % (title >> 32, title & 0xFFFFFFFF, contents[i].cid)
|
path = self.f + "/title/%08x/%08x/content/%08x.app" % (title >> 32, title & 0xFFFFFFFF, contents[i].cid)
|
||||||
@ -403,23 +400,11 @@ class NAND:
|
|||||||
fp = open(path, "rb")
|
fp = open(path, "rb")
|
||||||
data = fp.read()
|
data = fp.read()
|
||||||
fp.close()
|
fp.close()
|
||||||
fp = open("export/%08x.app" % contents[i].index, "wb")
|
title.contents.append(data)
|
||||||
fp.write(data)
|
|
||||||
fp.close()
|
|
||||||
fp = open(cert, "rb")
|
fp = open(cert, "rb")
|
||||||
data = fp.read()
|
data = fp.read()
|
||||||
fp.close()
|
fp.close()
|
||||||
fp = open("export/cert", "wb")
|
title.cert = data
|
||||||
fp.write(data)
|
|
||||||
fp.close()
|
|
||||||
WAD("export").pack(output)
|
|
||||||
for i in range(tmd.tmd.numcontents):
|
|
||||||
os.remove("export/%08x.app" % contents[i].index)
|
|
||||||
os.remove("export/tmd")
|
|
||||||
os.remove("export/tik")
|
|
||||||
os.remove("export/cert")
|
|
||||||
os.rmdir("export")
|
|
||||||
|
|
||||||
|
|
||||||
class ISFSClass:
|
class ISFSClass:
|
||||||
"""This class contains an interface to the NAND that simulates the permissions system and all other aspects of the ISFS.
|
"""This class contains an interface to the NAND that simulates the permissions system and all other aspects of the ISFS.
|
||||||
|
397
nus wadpacker.py
Normal file
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."""
|
"""Sets the boot index of the TMD to the value of index."""
|
||||||
self.tmd.boot_index = index
|
self.tmd.boot_index = index
|
||||||
|
|
||||||
|
class Title(WiiArchive):
|
||||||
|
def __init__(self, boot2 = False):
|
||||||
|
self.tmd = TMD()
|
||||||
|
self.tik = Ticket()
|
||||||
|
self.contents = []
|
||||||
|
self.boot2 = False
|
||||||
|
self.cert = ""
|
||||||
|
def _load(self, data):
|
||||||
|
if(self.boot2 != True):
|
||||||
|
headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding = struct.unpack('>I4s6I32s', data[:64])
|
||||||
|
pos = 64
|
||||||
|
else:
|
||||||
|
headersize, data_offset, certsize, tiksize, tmdsize, padding = struct.unpack('>IIIII12s', data[:32])
|
||||||
|
pos = 32
|
||||||
|
|
||||||
|
rawcert = data[pos:pos + certsize]
|
||||||
|
pos += certsize
|
||||||
|
if(self.boot2 != True):
|
||||||
|
if(certsize % 64 != 0):
|
||||||
|
pos += 64 - (certsize % 64)
|
||||||
|
self.cert = rawcert
|
||||||
|
|
||||||
|
rawtik = data[pos:pos + tiksize]
|
||||||
|
pos += tiksize
|
||||||
|
if(self.boot2 != True):
|
||||||
|
if(tiksize % 64 != 0):
|
||||||
|
pos += 64 - (tiksize % 64)
|
||||||
|
self.tik = Ticket.load(rawtik)
|
||||||
|
|
||||||
|
rawtmd = data[pos:pos + tmdsize]
|
||||||
|
pos += tmdsize
|
||||||
|
if(self.boot2 == True):
|
||||||
|
pos = data_offset
|
||||||
|
else:
|
||||||
|
pos += 64 - (tmdsize % 64)
|
||||||
|
self.tmd = TMD.load(rawtmd)
|
||||||
|
|
||||||
|
titlekey = self.tik.getTitleKey()
|
||||||
|
contents = self.tmd.getContents()
|
||||||
|
for i in range(0, len(contents)):
|
||||||
|
tmpsize = contents[i].size
|
||||||
|
if(tmpsize % 16 != 0):
|
||||||
|
tmpsize += 16 - (tmpsize % 16)
|
||||||
|
encdata = data[pos:pos + tmpsize]
|
||||||
|
pos += tmpsize
|
||||||
|
decdata = Crypto().decryptContent(titlekey, contents[i].index, encdata)
|
||||||
|
self.contents.append(decdata)
|
||||||
|
if(tmpsize % 64 != 0):
|
||||||
|
pos += 64 - (tmpsize % 64)
|
||||||
|
def _loadDir(self, dir):
|
||||||
|
origdir = os.getcwd()
|
||||||
|
os.chdir(dir)
|
||||||
|
|
||||||
|
self.tmd = TMD.loadFile("tmd")
|
||||||
|
self.tik = Ticket.loadFile("tik")
|
||||||
|
self.cert = open("cert", "rb").read()
|
||||||
|
|
||||||
|
contents = self.tmd.getContents()
|
||||||
|
for i in range(len(contents)):
|
||||||
|
self.contents.append(open("%08x.app" % i, "rb").read())
|
||||||
|
os.chdir(origdir)
|
||||||
|
def _dumpDir(self, dir, useidx = True, decrypt = True):
|
||||||
|
origdir = os.getcwd()
|
||||||
|
os.chdir(dir)
|
||||||
|
|
||||||
|
contents = self.tmd.getContents()
|
||||||
|
titlekey = self.tik.getTitleKey()
|
||||||
|
for i, content in enumerate(contents):
|
||||||
|
if(useidx == True):
|
||||||
|
output = content.index
|
||||||
|
else:
|
||||||
|
output = content.cid
|
||||||
|
if(decrypt == True):
|
||||||
|
open("%08x.app" % output, "wb").write(self.contents[i])
|
||||||
|
else:
|
||||||
|
open("%08x.app" % output, "wb").write(Crypto.encryptContent(titlekey, content.index, self.contents[content.index]))
|
||||||
|
self.tmd.dumpFile("tmd")
|
||||||
|
self.tik.dumpFile("tik")
|
||||||
|
open("cert", "wb").write(self.cert)
|
||||||
|
|
||||||
|
os.chdir(origdir)
|
||||||
|
def _dump(self, fakesign = True):
|
||||||
|
titlekey = self.tik.getTitleKey()
|
||||||
|
contents = self.tmd.getContents()
|
||||||
|
|
||||||
|
apppack = ""
|
||||||
|
for i, content in enumerate(contents):
|
||||||
|
if(fakesign):
|
||||||
|
content.hash = str(Crypto.createSHAHash(self.contents[content.index]))
|
||||||
|
content.size = len(self.contents[content.index])
|
||||||
|
|
||||||
|
encdata = Crypto.encryptContent(titlekey, content.index, self.contents[content.index])
|
||||||
|
|
||||||
|
apppack += encdata
|
||||||
|
if(len(encdata) % 64 != 0):
|
||||||
|
apppack += "\x00" * (64 - (len(encdata) % 64))
|
||||||
|
|
||||||
|
if(fakesign):
|
||||||
|
self.tmd.setContents(contents)
|
||||||
|
self.tmd.fakesign()
|
||||||
|
self.tik.fakesign()
|
||||||
|
|
||||||
|
rawtmd = self.tmd.dump()
|
||||||
|
rawcert = self.cert
|
||||||
|
rawtik = self.tik.dump()
|
||||||
|
|
||||||
|
sz = 0
|
||||||
|
for i in range(len(contents)):
|
||||||
|
sz += contents[i].size
|
||||||
|
if(sz % 64 != 0):
|
||||||
|
sz += 64 - (contents[i].size % 64)
|
||||||
|
|
||||||
|
if(self.boot2 != True):
|
||||||
|
pack = struct.pack('>I4s6I', 32, "Is\x00\x00", len(rawcert), 0, len(rawtik), len(rawtmd), sz, 0)
|
||||||
|
pack += "\x00" * 32
|
||||||
|
else:
|
||||||
|
pack = struct.pack('>IIIII12s', 32, align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40), len(rawcert), len(rawtik), len(rawtmd), "\x00" * 12)
|
||||||
|
|
||||||
|
pack += rawcert
|
||||||
|
if(len(rawcert) % 64 != 0 and self.boot2 != True):
|
||||||
|
pack += "\x00" * (64 - (len(rawcert) % 64))
|
||||||
|
pack += rawtik
|
||||||
|
if(len(rawtik) % 64 != 0 and self.boot2 != True):
|
||||||
|
pack += "\x00" * (64 - (len(rawtik) % 64))
|
||||||
|
pack += rawtmd
|
||||||
|
if(len(rawtmd) % 64 != 0 and self.boot2 != True):
|
||||||
|
pack += "\x00" * (64 - (len(rawtmd) % 64))
|
||||||
|
|
||||||
|
if(self.boot2 == True):
|
||||||
|
pack += "\x00" * (align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40) - (len(rawcert) + len(rawtik) + len(rawtmd)))
|
||||||
|
|
||||||
|
pack += apppack
|
||||||
|
return pack
|
||||||
|
def fakesign(self):
|
||||||
|
self.tik.fakesign()
|
||||||
|
self.tmd.fakesign()
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
return self.contents[idx]
|
||||||
|
def __setitem__(self, idx, value):
|
||||||
|
self.contents[idx] = value
|
||||||
|
def __str__(self):
|
||||||
|
out = ""
|
||||||
|
out += "Wii WAD:\n"
|
||||||
|
out += str(self.tmd)
|
||||||
|
out += str(self.tik)
|
||||||
|
return out
|
||||||
|
|
||||||
|
WAD = Title
|
||||||
|
|
||||||
class NUS:
|
class NUS:
|
||||||
"""This class can download titles from NUS, or Nintendo Update Server. The titleid parameter is the long integer version of the title to download. The version parameter is optional and specifies the version to download. If version is not given, it is assumed to be the latest version on NUS."""
|
"""This class can download titles from NUS, or Nintendo Update Server. The titleid parameter is the long integer version of the title to download. The version parameter is optional and specifies the version to download. If version is not given, it is assumed to be the latest version on NUS."""
|
||||||
def __init__(self, titleid, version = None):
|
url = "http://nus.cdn.shop.wii.com/ccs/download/"
|
||||||
self.titleid = titleid
|
def download(self, titleid, version = None):
|
||||||
self.baseurl = "http://nus.cdn.shop.wii.com/ccs/download/%08x%08x/" % (titleid >> 32, titleid & 0xFFFFFFFF)
|
|
||||||
self.version = version
|
|
||||||
def download(self, fn = "", decrypt = True, useidx = True):
|
|
||||||
"""This will download a title from NUS into a directory either specified by fn (if it is not empty) or a directory created by the title id in hex form. If decrypt is true, it will decrypt the contents, otherwise it will not. A certs file is always created to enable easy WAD Packing. The parameter useidx specifies wheither to use the index or the content id for the file naming (default is index)."""
|
|
||||||
if(fn == ""):
|
|
||||||
fn = "%08x%08x" % (self.titleid >> 32, self.titleid & 0xFFFFFFFF)
|
|
||||||
try:
|
|
||||||
os.mkdir(fn)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
os.chdir(fn)
|
|
||||||
|
|
||||||
certs = ""
|
certs = ""
|
||||||
rawtmd = urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/tmd.289").read()
|
tmd = TMD.load(urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/tmd.289").read())
|
||||||
rawtik = urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/cetk").read()
|
tik = Ticket.load(urllib.urlopen("http://nus.cdn.shop.wii.com/ccs/download/0000000100000002/cetk").read())
|
||||||
|
|
||||||
certs += rawtik[0x2A4:0x2A4 + 0x300] #XS
|
certs += tik.dump()[0x2A4:0x2A4 + 0x300] #XS
|
||||||
certs += rawtik[0x2A4 + 0x300:] #CA (tik)
|
certs += tik.dump()[0x2A4 + 0x300:] #CA (tik)
|
||||||
certs += rawtmd[0x328:0x328 + 0x300] #CP
|
certs += tmd.dump()[0x328:0x328 + 0x300] #CP
|
||||||
|
|
||||||
if(Crypto().createMD5HashHex(certs) != "7ff50e2733f7a6be1677b6f6c9b625dd"):
|
if(Crypto.createMD5HashHex(certs) != "7ff50e2733f7a6be1677b6f6c9b625dd"):
|
||||||
raise ValueError("Failed to create certs! MD5 mistatch.")
|
raise ValueError("Failed to create certs! MD5 mistatch.")
|
||||||
|
|
||||||
open("cert", "wb").write(certs)
|
|
||||||
|
|
||||||
if(self.version == None):
|
if(self.version == None):
|
||||||
versionstring = ""
|
versionstring = ""
|
||||||
else:
|
else:
|
||||||
versionstring = ".%u" % self.version
|
versionstring = ".%u" % self.version
|
||||||
|
|
||||||
|
titleurl = self.url + "%08x%08x/" % (titleid >> 32, titleid & 0xFFFFFFFF)
|
||||||
|
|
||||||
urllib.urlretrieve(self.baseurl + "tmd" + versionstring, "tmd")
|
tmd = TMD.load(urllib.urlopen(titleurl + "tmd" + versionstring).read())
|
||||||
tmd = TMD.loadFile("tmd")
|
tik = Ticket.load(urllib.urlopen(titleurl + "cetk").read())
|
||||||
tmd.dumpFile("tmd") # strip certs
|
|
||||||
|
|
||||||
urllib.urlretrieve(self.baseurl + "cetk", "tik")
|
title = Title()
|
||||||
tik = Ticket.loadFile("tik")
|
title.tmd = tmd
|
||||||
tik.dumpFile("tik") # strip certs
|
title.tik = tik
|
||||||
if(decrypt):
|
title.cert = certs
|
||||||
titlekey = tik.getTitleKey()
|
|
||||||
|
|
||||||
contents = tmd.getContents()
|
contents = tmd.getContents()
|
||||||
for content in contents:
|
for content in contents:
|
||||||
output = content.cid
|
encdata = urllib.urlopen(titleurl + ("%08x" % content.cid)).read(content.size)
|
||||||
if(useidx):
|
decdata = Crypto.decryptContent(titlekey, content.index, encdata)
|
||||||
output = content.index
|
if(Crypto.validateSHAHash(decdata, content.hash) == 0):
|
||||||
|
raise ValueError("Decryption failed! SHA1 mismatch.")
|
||||||
urllib.urlretrieve(self.baseurl + ("%08x" % content.cid), "%08x.app" % output)
|
title.contents.append(decdata)
|
||||||
|
download = classmethod(download)
|
||||||
if(decrypt):
|
|
||||||
data = open("%08x.app" % output, "rb").read(content.size)
|
|
||||||
tmpdata = Crypto().decryptContent(titlekey, content.index, data)
|
|
||||||
if(Crypto().validateSHAHash(tmpdata, content.hash) == 0):
|
|
||||||
raise ValueError("Decryption failed! SHA1 mismatch.")
|
|
||||||
open("%08x.app" % output, "wb").write(tmpdata)
|
|
||||||
|
|
||||||
os.chdir("..")
|
|
||||||
|
338
wiimposter.py
Normal file
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