lotsa shit but should all work internally nowwith U8, TMD, ticket on the new API;

This commit is contained in:
Xuzz 2009-07-03 01:57:24 -07:00
parent 126d9f5913
commit 66dea29df1
5 changed files with 184 additions and 229 deletions

1
Wii.py
View File

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

View File

@ -2,7 +2,7 @@ from common import *
from title import * from title import *
import zlib import zlib
class U8(): 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. """This class can unpack and pack U8 archives, which are used all over the Wii. They are often used in Banners and contents in Downloadable Titles. Please remove all headers and compression first, kthx.
The f parameter is either the source folder to pack, or the source file to unpack.""" The f parameter is either the source folder to pack, or the source file to unpack."""
@ -21,48 +21,9 @@ class U8():
self.name_offset = Struct.uint16 self.name_offset = Struct.uint16
self.data_offset = Struct.uint32 self.data_offset = Struct.uint32
self.size = Struct.uint32 self.size = Struct.uint32
def __init__(self, f): def __init__(self):
self.f = f self.files = []
def _pack(self, file, recursion, is_root = 0): #internal def _dump(self):
node = self.U8Node()
node.name_offset = len(self.strings)
if(is_root != 1):
self.strings += (file)
self.strings += ("\x00")
if(os.path.isdir(file)):
node.type = 0x0100
node.data_offset = recursion - 1
recursion += 1
files = sorted(os.listdir(file))
#if(sorted(files) == ["banner.bin", "icon.bin", "sound.bin"]):
# files = ["icon.bin", "banner.bin", "sound.bin"]
oldsz = len(self.nodes)
if(is_root != 1):
self.nodes.append(node)
os.chdir(file)
for entry in files:
if(entry != ".DS_Store" and entry[len(entry) - 4:] != "_out"):
self._pack(entry, recursion)
os.chdir("..")
self.nodes[oldsz].size = len(self.nodes) + 1
else:
f = open(file, "rb")
data = f.read()
f.close()
sz = len(data)
data += "\x00" * (align(sz, 32) - sz) #32 seems to work best for fuzzyness? I'm still really not sure
node.data_offset = len(self.data)
self.data += data
node.size = sz
node.type = 0x0000
if(is_root != 1):
self.nodes.append(node)
def pack(self, fn = ""):
"""This function will pack a folder into a U8 archive. The output file name is specified in the parameter fn. If fn is an empty string, the filename is deduced from the input folder name. Returns the output filename. """This function will pack a folder into a U8 archive. The output file name is specified in the parameter fn. If fn is an empty string, the filename is deduced from the input folder name. Returns the output filename.
This creates valid U8 archives for all purposes.""" This creates valid U8 archives for all purposes."""
@ -73,56 +34,94 @@ class U8():
header.rootnode_offset = 0x20 header.rootnode_offset = 0x20
header.zeroes = "\x00" * 16 header.zeroes = "\x00" * 16
self.nodes = [] nodes = []
self.strings = "\x00" strings = "\x00"
self.data = "" data = ''
origdir = os.getcwd()
os.chdir(self.f)
self._pack(".", 0, 1)
os.chdir(origdir)
header.header_size = (len(self.nodes) + 1) * len(rootnode) + len(self.strings) for item, value in self.files:
node = self.U8Node()
node.name_offset = len(strings)
recursion = item.count('/')
if(recursion < 0):
recursion = 0
name = item[item.rfind('/') + 1:]
strings += name + '\x00'
if(value == None):
node.type = 0x0100
node.data_offset = recursion
this_length = 0
for one, two in self.files:
subdirs = one
if(subdirs.find(item) != -1):
this_length += 1
node.size = len(nodes) + this_length + 1
else:
sz = len(value)
value += "\x00" * (align(sz, 32) - sz) #32 seems to work best for fuzzyness? I'm still really not sure
node.data_offset = len(data)
data += value
node.size = sz
node.type = 0x0000
nodes.append(node)
header.header_size = (len(nodes) + 1) * len(rootnode) + len(strings)
header.data_offset = align(header.header_size + header.rootnode_offset, 64) header.data_offset = align(header.header_size + header.rootnode_offset, 64)
rootnode.size = len(self.nodes) + 1 rootnode.size = len(nodes) + 1
rootnode.type = 0x0100 rootnode.type = 0x0100
for i in range(len(self.nodes)): for i in range(len(nodes)):
if(self.nodes[i].type == 0x0000): if(nodes[i].type == 0x0000):
self.nodes[i].data_offset += header.data_offset nodes[i].data_offset += header.data_offset
if(fn == ""): fd = ''
if(self.f[len(self.f) - 4:] == "_out"): fd += header.pack()
fn = os.path.dirname(self.f) + "/" + os.path.basename(self.f)[:-4].replace("_", ".") fd += rootnode.pack()
for nodeobj in nodes:
fd += nodeobj.pack()
fd += strings
fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
fd += data
return fd
def _dumpDir(self, dir):
if(not os.path.isdir(dir)):
os.mkdir(dir)
os.chdir(dir)
for item, data in self.files:
if(data == None):
if(not os.path.isdir(item)):
os.mkdir(item)
else: else:
fn = self.f open(item, "wb").write(data)
os.chdir("..")
fd = open(fn, "wb") def _loadDir(self, dir):
fd.write(header.pack()) try:
fd.write(rootnode.pack()) self._tmpPath += ''
for node in self.nodes: except:
fd.write(node.pack()) self._tmpPath = ''
fd.write(self.strings) os.chdir(dir)
fd.write("\x00" * (header.data_offset - header.rootnode_offset - header.header_size)) entries = os.listdir(".")
fd.write(self.data) for entry in entries:
fd.close() if(os.path.isdir(entry)):
self.files.append((self._tmpPath + entry, None))
return fn self._tmpPath += entry + '/'
self._loadDir(entry)
def unpack(self, fn = ""): elif(os.path.isfile(entry)):
"""This will unpack the U8 archive specified by the initialization parameter into either the folder specified by the parameter fn, or into a folder created with this formula: data = open(entry, "rb").read()
``filename_extension_out`` self.files.append((self._tmpPath + entry, data))
os.chdir("..")
This will recreate the directory structure, including the initial root folder in the U8 archive (such as "arc" or "meta"). Returns the output directory name.""" self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
data = open(self.f, "rb").read() def _load(self, data):
offset = 0 offset = 0
header = self.U8Header() header = self.U8Header()
header.unpack(data[offset:offset + len(header)]) header.unpack(data[offset:offset + len(header)])
offset += len(header) offset += len(header)
if(header.tag != "U\xAA8-"): assert header.tag == "U\xAA8-"
raise NameError("Bad U8 Tag")
offset = header.rootnode_offset offset = header.rootnode_offset
rootnode = self.U8Node() rootnode = self.U8Node()
@ -139,16 +138,8 @@ class U8():
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)] strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
offset += len(strings) offset += len(strings)
if(fn == ""):
fn = os.path.dirname(self.f) + "/" + os.path.basename(self.f).replace(".", "_") + "_out"
try:
origdir = os.getcwd()
os.mkdir(fn)
except:
pass
os.chdir(fn)
recursion = [rootnode.size] recursion = [rootnode.size]
recursiondir = []
counter = 0 counter = 0
for node in nodes: for node in nodes:
counter += 1 counter += 1
@ -156,78 +147,43 @@ class U8():
if(node.type == 0x0100): # folder if(node.type == 0x0100): # folder
recursion.append(node.size) recursion.append(node.size)
try: recursiondir.append(name)
os.mkdir(name) assert len(recursion) == node.data_offset + 2 #bad idea?
except: self.files.append(('/'.join(recursiondir), None))
pass
os.chdir(name)
continue
elif(node.type == 0): # file elif(node.type == 0): # file
file = open(name, "wb") self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
file.write(data[node.data_offset:node.data_offset + node.size])
offset += node.size offset += node.size
else: # unknown else: # unknown
pass #ignore pass
sz = recursion.pop() sz = recursion.pop()
if(sz == counter + 1): if(sz != counter + 1):
os.chdir("..")
else:
recursion.append(sz) recursion.append(sz)
os.chdir("..") else:
recursiondir.pop()
os.chdir(origdir)
return fn
def __str__(self): def __str__(self):
data = open(self.f, "rb").read() ret = ''
for key, value in self.files:
offset = 0 name = key[key.rfind('/') + 1:]
recursion = key.count('/')
header = self.U8Header() ret += ' ' * recursion
header.unpack(data[offset:offset + len(header)]) if(value == None):
offset += len(header) ret += '[' + name + ']'
if(header.tag != "U\xAA8-"):
raise NameError("Bad U8 Tag")
offset = header.rootnode_offset
rootnode = self.U8Node()
rootnode.unpack(data[offset:offset + len(rootnode)])
offset += len(rootnode)
nodes = []
for i in xrange(rootnode.size - 1):
node = self.U8Node()
node.unpack(data[offset:offset + len(node)])
offset += len(node)
nodes.append(node)
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
offset += len(strings)
out = "[root]\n"
recursion = [rootnode.size]
counter = 0
for node in nodes:
counter += 1
name = strings[node.name_offset:].split('\0', 1)[0]
out += " " * len(recursion)
if(node.type == 0x0100): #folder
recursion.append(node.size)
out += "[%s]\n" % name
continue
elif(node.type == 0): #file
out += "%s\n" % name
offset += node.size
else: #unknown, ignore
pass
sz = recursion.pop()
if(sz == counter + 1):
pass
else: else:
recursion.append(sz) ret += name
return out ret += '\n'
return ret
def __getitem__(self, key):
for item, val in self.files:
if(item == key):
return val
raise KeyError
def __setitem__(self, key, val):
for i in self.files:
if(self.files[i][0] == key):
self.files[i][1] = val
raise KeyError
class WAD: class WAD:
"""This class is to pack and unpack WAD files, which store a single title. You pass the input filename or input directory name to the parameter f. """This class is to pack and unpack WAD files, which store a single title. You pass the input filename or input directory name to the parameter f.
@ -240,8 +196,8 @@ class WAD:
"""Packs a WAD into the filename specified by fn, if it is not empty. If it is empty, it packs into a filename generated from the folder's name. If fakesign is True, it will fakesign the Ticket and TMD, and update them as needed. If decrypted is true, it will assume the contents are already decrypted. For now, fakesign can not be True if decrypted is False, however fakesign can be False if decrypted is True. Title ID is a long integer of the destination title id.""" """Packs a WAD into the filename specified by fn, if it is not empty. If it is empty, it packs into a filename generated from the folder's name. If fakesign is True, it will fakesign the Ticket and TMD, and update them as needed. If decrypted is true, it will assume the contents are already decrypted. For now, fakesign can not be True if decrypted is False, however fakesign can be False if decrypted is True. Title ID is a long integer of the destination title id."""
os.chdir(self.f) os.chdir(self.f)
tik = Ticket().loadFile("tik") tik = Ticket.loadFile("tik")
tmd = TMD().loadFile("tmd") tmd = TMD.loadFile("tmd")
titlekey = tik.getTitleKey() titlekey = tik.getTitleKey()
contents = tmd.getContents() contents = tmd.getContents()
@ -264,8 +220,10 @@ class WAD:
if(fakesign): if(fakesign):
tmd.setContents(contents) tmd.setContents(contents)
tmd.dump() tmd.fakesign()
tik.dump() tik.fakesign()
tmd.dumpFile("tmd")
tik.dumpFile("tik")
rawtmd = open("tmd", "rb").read() rawtmd = open("tmd", "rb").read()
rawcert = open("cert", "rb").read() rawcert = open("cert", "rb").read()
@ -341,8 +299,8 @@ class WAD:
fd.seek(64 - (tmdsize % 64), 1) fd.seek(64 - (tmdsize % 64), 1)
open('tmd', 'wb').write(rawtmd) open('tmd', 'wb').write(rawtmd)
titlekey = Ticket().loadFile("tik").getTitleKey() titlekey = Ticket.loadFile("tik").getTitleKey()
contents = TMD().loadFile("tmd").getContents() contents = TMD.loadFile("tmd").getContents()
for i in range(0, len(contents)): for i in range(0, len(contents)):
tmpsize = contents[i].size tmpsize = contents[i].size
if(tmpsize % 16 != 0): if(tmpsize % 16 != 0):
@ -380,8 +338,8 @@ class WAD:
else: else:
out += " Header %02x Type 'boot2' Certs %x Tiket %x TMD %x Data @ %x\n" % (headersize, certsize, tiksize, tmdsize, data_offset) out += " Header %02x Type 'boot2' Certs %x Tiket %x TMD %x Data @ %x\n" % (headersize, certsize, tiksize, tmdsize, data_offset)
out += str(Ticket().load(rawtik)) out += str(Ticket.load(rawtik))
out += str(TMD().load(rawtmd)) out += str(TMD.load(rawtmd))
return out return out

View File

@ -62,3 +62,31 @@ class Crypto:
# else: # else:
# return 0 # return 0
class WiiObject(object):
@classmethod
def load(cls, data):
self = cls()
self._load(data)
return self
@classmethod
def loadFile(cls, filename):
return cls.load(open(filename, "rb").read())
def dump(self):
return self._dump()
def dumpFile(self, filename):
open(filename, "wb").write(self.dump())
return filename
class WiiArchive(WiiObject):
@classmethod
def loadDir(cls, dirname):
self = cls()
self._loadDir(dirname)
return self
def dumpDir(self, dirname):
if(not os.path.isdir(dirname)):
os.mkdir(dirname)
self._dumpDir(dirname)
return dirname

36
nand.py
View File

@ -385,12 +385,14 @@ class NAND:
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) tmd = TMD.loadFile(tmdpth)
if(not os.path.isdir("export")): if(not os.path.isdir("export")):
os.mkdir("export") os.mkdir("export")
tmd.dump("export/tmd") tmd.fakesign()
tik = Ticket().loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF)) tmd.dumpFile("export/tmd")
tik.dump("export/tik") tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
tik.fakesign()
tik.dumpFile("export/tik")
contents = tmd.getContents() contents = tmd.getContents()
for i in range(tmd.tmd.numcontents): for i in range(tmd.tmd.numcontents):
path = "" path = ""
@ -645,14 +647,14 @@ class ESClass:
def Identify(self, id, version=0): def Identify(self, id, version=0):
if(not os.path.isfile(self.f + "/ticket/%08x/%08x.tik" % (id >> 32, id & 0xFFFFFFFF))): if(not os.path.isfile(self.f + "/ticket/%08x/%08x.tik" % (id >> 32, id & 0xFFFFFFFF))):
return None return None
tik = Ticket().loadFile(self.f + "/ticket/%08x/%08x.tik" % (id >> 32, id & 0xFFFFFFFF)) tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (id >> 32, id & 0xFFFFFFFF))
titleid = tik.titleid titleid = tik.titleid
path = "/title/%08x/%08x/content/title.tmd" % (titleid >> 32, titleid & 0xFFFFFFFF) path = "/title/%08x/%08x/content/title.tmd" % (titleid >> 32, titleid & 0xFFFFFFFF)
if(version): if(version):
path += ".%d" % version path += ".%d" % version
if(not os.path.isfile(self.f + path)): if(not os.path.isfile(self.f + path)):
return None return None
tmd = TMD().loadFile(self.f + path) tmd = TMD.loadFile(self.f + path)
self.title = titleid self.title = titleid
self.group = tmd.tmd.group_id self.group = tmd.tmd.group_id
return self.title return self.title
@ -670,7 +672,7 @@ class ESClass:
path += ".%d" % version path += ".%d" % version
if(not os.path.isfile(self.f + path)): if(not os.path.isfile(self.f + path)):
return None return None
return TMD().loadFile(self.f + path) return TMD.loadFile(self.f + path)
def GetTitleContentsCount(self, titleid, version=0): def GetTitleContentsCount(self, titleid, version=0):
"""Gets the number of contents the title with the specified titleid and version has.""" """Gets the number of contents the title with the specified titleid and version has."""
tmd = self.GetStoredTMD(titleid, version) tmd = self.GetStoredTMD(titleid, version)
@ -708,7 +710,7 @@ class ESClass:
return return
def AddTicket(self, tik): def AddTicket(self, tik):
"""Adds ticket to the title being added.""" """Adds ticket to the title being added."""
tik.rawdump(self.f + "/tmp/title.tik") tik.dumpFile(self.f + "/tmp/title.tik")
self.ticketadded = 1 self.ticketadded = 1
def DeleteTicket(self, tikview): def DeleteTicket(self, tikview):
"""Deletes the ticket relating to tikview """Deletes the ticket relating to tikview
@ -716,7 +718,7 @@ class ESClass:
return return
def AddTitleTMD(self, tmd): def AddTitleTMD(self, tmd):
"""Adds TMD to the title being added.""" """Adds TMD to the title being added."""
tmd.rawdump(self.f + "/tmp/title.tmd") tmd.dumpFile(self.f + "/tmp/title.tmd")
self.tmdadded = 1 self.tmdadded = 1
def AddContentStart(self, titleid, cid): def AddContentStart(self, titleid, cid):
"""Starts adding a content with content id cid to the title being added with ID titleid.""" """Starts adding a content with content id cid to the title being added with ID titleid."""
@ -724,9 +726,9 @@ class ESClass:
"Trying to start an already existing process" "Trying to start an already existing process"
return -41 return -41
if(self.tmdadded): if(self.tmdadded):
a = TMD().loadFile(self.f + "/tmp/title.tmd") a = TMD.loadFile(self.f + "/tmp/title.tmd")
else: else:
a = TMD().loadFile(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version)) a = TMD.loadFile(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version))
x = self.getContentIndexFromCID(a, cid) x = self.getContentIndexFromCID(a, cid)
if(x == None): if(x == None):
"Not a valid Content ID" "Not a valid Content ID"
@ -766,11 +768,11 @@ class ESClass:
def AddTitleFinish(self): def AddTitleFinish(self):
"""Finishes the adding of a title.""" """Finishes the adding of a title."""
if(self.ticketadded): if(self.ticketadded):
tik = Ticket().loadFile(self.f + "/tmp/title.tik") tik = Ticket.loadFile(self.f + "/tmp/title.tik")
else: else:
tik = Ticket().loadFile(self.f + "/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF)) tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF))
if(self.tmdadded): if(self.tmdadded):
tmd = TMD().loadFile(self.f + "/tmp/title.tmd") tmd = TMD.loadFile(self.f + "/tmp/title.tmd")
contents = tmd.getContents() contents = tmd.getContents()
for i in range(self.workingcidcnt): for i in range(self.workingcidcnt):
idx = self.getContentIndexFromCID(tmd, self.workingcids[i]) idx = self.getContentIndexFromCID(tmd, self.workingcids[i])
@ -805,12 +807,12 @@ class ESClass:
outfp.close() outfp.close()
if(self.tmdadded and self.use_version): if(self.tmdadded and self.use_version):
self.nand.newFile("/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version), "rwrw--", 0x0000) self.nand.newFile("/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version), "rwrw--", 0x0000)
tmd.rawdump(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version)) tmd.dumpFile(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version))
elif(self.tmdadded): elif(self.tmdadded):
self.nand.newFile("/title/%08x/%08x/content/title.tmd" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF), "rwrw--", 0x0000) self.nand.newFile("/title/%08x/%08x/content/title.tmd" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF), "rwrw--", 0x0000)
tmd.rawdump(self.f + "/title/%08x/%08x/content/title.tmd" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF)) tmd.dumpFile(self.f + "/title/%08x/%08x/content/title.tmd" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF))
if(self.ticketadded): if(self.ticketadded):
self.nand.newFile("/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF), "rwrw--", 0x0000) self.nand.newFile("/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF), "rwrw--", 0x0000)
tik.rawdump(self.f + "/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF)) tik.dumpFile(self.f + "/ticket/%08x/%08x.tik" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF))
self.AddTitleCancel() self.AddTitleCancel()
return 0 return 0

View File

@ -37,7 +37,7 @@ class TicketView:
return out return out
class Ticket: class Ticket(WiiObject):
"""Creates a ticket from the filename defined in f. This may take a longer amount of time than expected, as it also decrypts the title key. Now supports Korean tickets (but their title keys stay Korean on dump).""" """Creates a ticket from the filename defined in f. This may take a longer amount of time than expected, as it also decrypts the title key. Now supports Korean tickets (but their title keys stay Korean on dump)."""
class TicketStruct(Struct): class TicketStruct(Struct):
__endian__ = Struct.BE __endian__ = Struct.BE
@ -79,9 +79,7 @@ class Ticket:
if(self.tik.commonkey_index == 1): #korean, kekekekek! if(self.tik.commonkey_index == 1): #korean, kekekekek!
commonkey = koreankey commonkey = koreankey
self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey) self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey)
@classmethod def _load(self, data):
def load(cls, data):
self = cls()
self.tik.unpack(data[:len(self.tik)]) self.tik.unpack(data[:len(self.tik)])
commonkey = "\xEB\xE4\x2A\x22\x5E\x85\x93\xE4\x48\xD9\xC5\x45\x73\x81\xAA\xF7" commonkey = "\xEB\xE4\x2A\x22\x5E\x85\x93\xE4\x48\xD9\xC5\x45\x73\x81\xAA\xF7"
@ -92,9 +90,6 @@ class Ticket:
self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey) self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey)
return self return self
@classmethod
def loadFile(cls, filename):
return cls.load(open(filename, "rb").read())
def getTitleKey(self): def getTitleKey(self):
"""Returns a string containing the title key.""" """Returns a string containing the title key."""
return self.titlekey return self.titlekey
@ -128,7 +123,7 @@ class Ticket:
out += "\n" out += "\n"
return out return out
def dump(self, fn = ""): def fakesign(self):
"""Fakesigns (or Trucha signs) and dumps the ticket to either fn, if not empty, or overwriting the source if empty. Returns the output filename.""" """Fakesigns (or Trucha signs) and dumps the ticket to either fn, if not empty, or overwriting the source if empty. Returns the output filename."""
self.rsamod = self.rsamod = "\x00" * 256 self.rsamod = self.rsamod = "\x00" * 256
for i in range(65536): for i in range(65536):
@ -137,25 +132,13 @@ class Ticket:
break break
if(i == 65535): if(i == 65535):
raise ValueError("Failed to fakesign. Aborting...") raise ValueError("Failed to fakesign. Aborting...")
def _dump(self):
if(fn == ""):
open(self.f, "wb").write(self.tik.pack())
return self.f
else:
open(fn, "wb").write(self.tik.pack())
return fn
def rawdump(self, fn = ""):
"""Dumps the ticket to either fn, if not empty, or overwriting the source if empty. **Does not fakesign.** Returns the output filename.""" """Dumps the ticket to either fn, if not empty, or overwriting the source if empty. **Does not fakesign.** Returns the output filename."""
if(fn == ""): return self.tik.pack()
open(self.f, "wb").write(self.tik.pack())
return self.f
else:
open(fn, "wb").write(self.tik.pack())
return fn
def __len__(self): def __len__(self):
return len(self.tik) return len(self.tik)
class TMD: class TMD(WiiObject):
"""This class allows you to edit TMDs. TMD (Title Metadata) files are used in many places to hold information about titles. The parameter f to the initialization is the filename to open and create a TMD from.""" """This class allows you to edit TMDs. TMD (Title Metadata) files are used in many places to hold information about titles. The parameter f to the initialization is the filename to open and create a TMD from."""
class TMDContent(Struct): class TMDContent(Struct):
__endian__ = Struct.BE __endian__ = Struct.BE
@ -184,7 +167,7 @@ class TMD:
self.boot_index = Struct.uint16 self.boot_index = Struct.uint16
self.padding2 = Struct.uint16 self.padding2 = Struct.uint16
#contents follow this #contents follow this
def load(self, data): def _load(self, data):
self.tmd.unpack(data[:len(self.tmd)]) self.tmd.unpack(data[:len(self.tmd)])
pos = len(self.tmd) pos = len(self.tmd)
for i in range(self.tmd.numcontents): for i in range(self.tmd.numcontents):
@ -192,9 +175,6 @@ class TMD:
cont.unpack(data[pos:pos + len(cont)]) cont.unpack(data[pos:pos + len(cont)])
pos += len(cont) pos += len(cont)
self.contents.append(cont) self.contents.append(cont)
return self
def loadFile(self, filename):
return self.load(open(filename, "rb").read())
def __init__(self): def __init__(self):
self.tmd = self.TMDStruct() self.tmd = self.TMDStruct()
self.tmd.titleid = 0x0000000100000000 self.tmd.titleid = 0x0000000100000000
@ -232,7 +212,7 @@ class TMD:
for i in range(len(contents)): for i in range(len(contents)):
sz += len(contents[i]) sz += len(contents[i])
return sz return sz
def dump(self, fn = ""): def fakesign(self):
"""Dumps the TMD to the filename specified in fn, if not empty. If that is empty, it overwrites the original. This fakesigns the TMD, but does not update the hashes and the sizes, that is left as a job for you. Returns output filename.""" """Dumps the TMD to the filename specified in fn, if not empty. If that is empty, it overwrites the original. This fakesigns the TMD, but does not update the hashes and the sizes, that is left as a job for you. Returns output filename."""
for i in range(65536): for i in range(65536):
self.tmd.padding2 = i self.tmd.padding2 = i
@ -245,26 +225,14 @@ class TMD:
break break
if(i == 65535): if(i == 65535):
raise ValueError("Failed to fakesign! Aborting...") raise ValueError("Failed to fakesign! Aborting...")
def _dump(self):
if(fn == ""):
open(self.f, "wb").write(data)
return self.f
else:
open(fn, "wb").write(data)
return fn
def rawdump(self, fn = ""):
"""Same as the :dump: function, but does not fakesign the TMD. Also returns output filename.""" """Same as the :dump: function, but does not fakesign the TMD. Also returns output filename."""
data = "" data = ""
data += self.tmd.pack() data += self.tmd.pack()
for i in range(self.tmd.numcontents): for i in range(self.tmd.numcontents):
data += self.contents[i].pack() data += self.contents[i].pack()
if(fn == ""): return data
open(self.f, "wb").write(data)
return self.f
else:
open(fn, "wb").write(data)
return fn
def getTitleID(self): def getTitleID(self):
"""Returns the long integer title id.""" """Returns the long integer title id."""
return self.tmd.titleid return self.tmd.titleid
@ -320,11 +288,11 @@ class NUS:
urllib.urlretrieve(self.baseurl + "tmd" + versionstring, "tmd") urllib.urlretrieve(self.baseurl + "tmd" + versionstring, "tmd")
tmd = TMD.loadFile("tmd") tmd = TMD.loadFile("tmd")
tmd.rawdump("tmd") # strip certs tmd.dumpFile("tmd") # strip certs
urllib.urlretrieve(self.baseurl + "cetk", "tik") urllib.urlretrieve(self.baseurl + "cetk", "tik")
tik = Ticket.loadFile("tik") tik = Ticket.loadFile("tik")
tik.rawdump("tik") # strip certs tik.dumpFile("tik") # strip certs
if(decrypt): if(decrypt):
titlekey = tik.getTitleKey() titlekey = tik.getTitleKey()