mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 14:55:35 -04:00
lotsa shit but should all work internally nowwith U8, TMD, ticket on the new API;
This commit is contained in:
parent
126d9f5913
commit
66dea29df1
1
Wii.py
1
Wii.py
@ -2,7 +2,6 @@ __all__ = []
|
||||
|
||||
from common import *
|
||||
from formats import *
|
||||
from banner import *
|
||||
from title import *
|
||||
from disc import *
|
||||
from image import *
|
||||
|
292
archive.py
292
archive.py
@ -2,7 +2,7 @@ from common import *
|
||||
from title import *
|
||||
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.
|
||||
|
||||
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.data_offset = Struct.uint32
|
||||
self.size = Struct.uint32
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
def _pack(self, file, recursion, is_root = 0): #internal
|
||||
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 = ""):
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
def _dump(self):
|
||||
"""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."""
|
||||
@ -73,56 +34,94 @@ class U8():
|
||||
header.rootnode_offset = 0x20
|
||||
header.zeroes = "\x00" * 16
|
||||
|
||||
self.nodes = []
|
||||
self.strings = "\x00"
|
||||
self.data = ""
|
||||
origdir = os.getcwd()
|
||||
os.chdir(self.f)
|
||||
self._pack(".", 0, 1)
|
||||
os.chdir(origdir)
|
||||
nodes = []
|
||||
strings = "\x00"
|
||||
data = ''
|
||||
|
||||
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)
|
||||
rootnode.size = len(self.nodes) + 1
|
||||
rootnode.size = len(nodes) + 1
|
||||
rootnode.type = 0x0100
|
||||
|
||||
for i in range(len(self.nodes)):
|
||||
if(self.nodes[i].type == 0x0000):
|
||||
self.nodes[i].data_offset += header.data_offset
|
||||
|
||||
if(fn == ""):
|
||||
if(self.f[len(self.f) - 4:] == "_out"):
|
||||
fn = os.path.dirname(self.f) + "/" + os.path.basename(self.f)[:-4].replace("_", ".")
|
||||
for i in range(len(nodes)):
|
||||
if(nodes[i].type == 0x0000):
|
||||
nodes[i].data_offset += header.data_offset
|
||||
|
||||
fd = ''
|
||||
fd += header.pack()
|
||||
fd += rootnode.pack()
|
||||
for nodeobj in nodes:
|
||||
fd += nodeobj.pack()
|
||||
fd += strings
|
||||
fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
|
||||
fd += data
|
||||
|
||||
return fd
|
||||
def _dumpDir(self, dir):
|
||||
if(not os.path.isdir(dir)):
|
||||
os.mkdir(dir)
|
||||
os.chdir(dir)
|
||||
for item, data in self.files:
|
||||
if(data == None):
|
||||
if(not os.path.isdir(item)):
|
||||
os.mkdir(item)
|
||||
else:
|
||||
fn = self.f
|
||||
|
||||
fd = open(fn, "wb")
|
||||
fd.write(header.pack())
|
||||
fd.write(rootnode.pack())
|
||||
for node in self.nodes:
|
||||
fd.write(node.pack())
|
||||
fd.write(self.strings)
|
||||
fd.write("\x00" * (header.data_offset - header.rootnode_offset - header.header_size))
|
||||
fd.write(self.data)
|
||||
fd.close()
|
||||
|
||||
return fn
|
||||
|
||||
def unpack(self, fn = ""):
|
||||
"""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:
|
||||
``filename_extension_out``
|
||||
|
||||
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."""
|
||||
data = open(self.f, "rb").read()
|
||||
|
||||
open(item, "wb").write(data)
|
||||
os.chdir("..")
|
||||
def _loadDir(self, dir):
|
||||
try:
|
||||
self._tmpPath += ''
|
||||
except:
|
||||
self._tmpPath = ''
|
||||
os.chdir(dir)
|
||||
entries = os.listdir(".")
|
||||
for entry in entries:
|
||||
if(os.path.isdir(entry)):
|
||||
self.files.append((self._tmpPath + entry, None))
|
||||
self._tmpPath += entry + '/'
|
||||
self._loadDir(entry)
|
||||
elif(os.path.isfile(entry)):
|
||||
data = open(entry, "rb").read()
|
||||
self.files.append((self._tmpPath + entry, data))
|
||||
os.chdir("..")
|
||||
self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
|
||||
def _load(self, data):
|
||||
offset = 0
|
||||
|
||||
header = self.U8Header()
|
||||
header.unpack(data[offset:offset + len(header)])
|
||||
offset += len(header)
|
||||
|
||||
if(header.tag != "U\xAA8-"):
|
||||
raise NameError("Bad U8 Tag")
|
||||
assert header.tag == "U\xAA8-"
|
||||
offset = header.rootnode_offset
|
||||
|
||||
rootnode = self.U8Node()
|
||||
@ -139,95 +138,52 @@ class U8():
|
||||
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
|
||||
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]
|
||||
recursiondir = []
|
||||
counter = 0
|
||||
for node in nodes:
|
||||
counter += 1
|
||||
name = strings[node.name_offset:].split('\0', 1)[0]
|
||||
|
||||
if(node.type == 0x0100): #folder
|
||||
if(node.type == 0x0100): # folder
|
||||
recursion.append(node.size)
|
||||
try:
|
||||
os.mkdir(name)
|
||||
except:
|
||||
pass
|
||||
os.chdir(name)
|
||||
continue
|
||||
elif(node.type == 0): #file
|
||||
file = open(name, "wb")
|
||||
file.write(data[node.data_offset:node.data_offset + node.size])
|
||||
recursiondir.append(name)
|
||||
assert len(recursion) == node.data_offset + 2 #bad idea?
|
||||
self.files.append(('/'.join(recursiondir), None))
|
||||
elif(node.type == 0): # file
|
||||
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
|
||||
offset += node.size
|
||||
else: #unknown
|
||||
pass #ignore
|
||||
|
||||
else: # unknown
|
||||
pass
|
||||
|
||||
sz = recursion.pop()
|
||||
if(sz == counter + 1):
|
||||
os.chdir("..")
|
||||
else:
|
||||
if(sz != counter + 1):
|
||||
recursion.append(sz)
|
||||
os.chdir("..")
|
||||
|
||||
os.chdir(origdir)
|
||||
return fn
|
||||
else:
|
||||
recursiondir.pop()
|
||||
def __str__(self):
|
||||
data = open(self.f, "rb").read()
|
||||
|
||||
offset = 0
|
||||
|
||||
header = self.U8Header()
|
||||
header.unpack(data[offset:offset + len(header)])
|
||||
offset += len(header)
|
||||
|
||||
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
|
||||
ret = ''
|
||||
for key, value in self.files:
|
||||
name = key[key.rfind('/') + 1:]
|
||||
recursion = key.count('/')
|
||||
ret += ' ' * recursion
|
||||
if(value == None):
|
||||
ret += '[' + name + ']'
|
||||
else:
|
||||
recursion.append(sz)
|
||||
return out
|
||||
ret += name
|
||||
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:
|
||||
"""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."""
|
||||
os.chdir(self.f)
|
||||
|
||||
tik = Ticket().loadFile("tik")
|
||||
tmd = TMD().loadFile("tmd")
|
||||
tik = Ticket.loadFile("tik")
|
||||
tmd = TMD.loadFile("tmd")
|
||||
titlekey = tik.getTitleKey()
|
||||
contents = tmd.getContents()
|
||||
|
||||
@ -264,8 +220,10 @@ class WAD:
|
||||
|
||||
if(fakesign):
|
||||
tmd.setContents(contents)
|
||||
tmd.dump()
|
||||
tik.dump()
|
||||
tmd.fakesign()
|
||||
tik.fakesign()
|
||||
tmd.dumpFile("tmd")
|
||||
tik.dumpFile("tik")
|
||||
|
||||
rawtmd = open("tmd", "rb").read()
|
||||
rawcert = open("cert", "rb").read()
|
||||
@ -341,8 +299,8 @@ class WAD:
|
||||
fd.seek(64 - (tmdsize % 64), 1)
|
||||
open('tmd', 'wb').write(rawtmd)
|
||||
|
||||
titlekey = Ticket().loadFile("tik").getTitleKey()
|
||||
contents = TMD().loadFile("tmd").getContents()
|
||||
titlekey = Ticket.loadFile("tik").getTitleKey()
|
||||
contents = TMD.loadFile("tmd").getContents()
|
||||
for i in range(0, len(contents)):
|
||||
tmpsize = contents[i].size
|
||||
if(tmpsize % 16 != 0):
|
||||
@ -380,8 +338,8 @@ class WAD:
|
||||
else:
|
||||
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(TMD().load(rawtmd))
|
||||
out += str(Ticket.load(rawtik))
|
||||
out += str(TMD.load(rawtmd))
|
||||
|
||||
return out
|
||||
|
||||
|
28
common.py
28
common.py
@ -62,3 +62,31 @@ class Crypto:
|
||||
# else:
|
||||
# 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
36
nand.py
@ -385,12 +385,14 @@ class NAND:
|
||||
tmdpth = self.f + "/title/%08x/%08x/content/title.tmd" % (title >> 32, title & 0xFFFFFFFF)
|
||||
if(version != 0):
|
||||
tmdpth += ".%d" % version
|
||||
tmd = TMD().loadFile(tmdpth)
|
||||
tmd = TMD.loadFile(tmdpth)
|
||||
if(not os.path.isdir("export")):
|
||||
os.mkdir("export")
|
||||
tmd.dump("export/tmd")
|
||||
tik = Ticket().loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
|
||||
tik.dump("export/tik")
|
||||
tmd.fakesign()
|
||||
tmd.dumpFile("export/tmd")
|
||||
tik = Ticket.loadFile(self.f + "/ticket/%08x/%08x.tik" % (title >> 32, title & 0xFFFFFFFF))
|
||||
tik.fakesign()
|
||||
tik.dumpFile("export/tik")
|
||||
contents = tmd.getContents()
|
||||
for i in range(tmd.tmd.numcontents):
|
||||
path = ""
|
||||
@ -645,14 +647,14 @@ class ESClass:
|
||||
def Identify(self, id, version=0):
|
||||
if(not os.path.isfile(self.f + "/ticket/%08x/%08x.tik" % (id >> 32, id & 0xFFFFFFFF))):
|
||||
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
|
||||
path = "/title/%08x/%08x/content/title.tmd" % (titleid >> 32, titleid & 0xFFFFFFFF)
|
||||
if(version):
|
||||
path += ".%d" % version
|
||||
if(not os.path.isfile(self.f + path)):
|
||||
return None
|
||||
tmd = TMD().loadFile(self.f + path)
|
||||
tmd = TMD.loadFile(self.f + path)
|
||||
self.title = titleid
|
||||
self.group = tmd.tmd.group_id
|
||||
return self.title
|
||||
@ -670,7 +672,7 @@ class ESClass:
|
||||
path += ".%d" % version
|
||||
if(not os.path.isfile(self.f + path)):
|
||||
return None
|
||||
return TMD().loadFile(self.f + path)
|
||||
return TMD.loadFile(self.f + path)
|
||||
def GetTitleContentsCount(self, titleid, version=0):
|
||||
"""Gets the number of contents the title with the specified titleid and version has."""
|
||||
tmd = self.GetStoredTMD(titleid, version)
|
||||
@ -708,7 +710,7 @@ class ESClass:
|
||||
return
|
||||
def AddTicket(self, tik):
|
||||
"""Adds ticket to the title being added."""
|
||||
tik.rawdump(self.f + "/tmp/title.tik")
|
||||
tik.dumpFile(self.f + "/tmp/title.tik")
|
||||
self.ticketadded = 1
|
||||
def DeleteTicket(self, tikview):
|
||||
"""Deletes the ticket relating to tikview
|
||||
@ -716,7 +718,7 @@ class ESClass:
|
||||
return
|
||||
def AddTitleTMD(self, tmd):
|
||||
"""Adds TMD to the title being added."""
|
||||
tmd.rawdump(self.f + "/tmp/title.tmd")
|
||||
tmd.dumpFile(self.f + "/tmp/title.tmd")
|
||||
self.tmdadded = 1
|
||||
def AddContentStart(self, titleid, cid):
|
||||
"""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"
|
||||
return -41
|
||||
if(self.tmdadded):
|
||||
a = TMD().loadFile(self.f + "/tmp/title.tmd")
|
||||
a = TMD.loadFile(self.f + "/tmp/title.tmd")
|
||||
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)
|
||||
if(x == None):
|
||||
"Not a valid Content ID"
|
||||
@ -766,11 +768,11 @@ class ESClass:
|
||||
def AddTitleFinish(self):
|
||||
"""Finishes the adding of a title."""
|
||||
if(self.ticketadded):
|
||||
tik = Ticket().loadFile(self.f + "/tmp/title.tik")
|
||||
tik = Ticket.loadFile(self.f + "/tmp/title.tik")
|
||||
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):
|
||||
tmd = TMD().loadFile(self.f + "/tmp/title.tmd")
|
||||
tmd = TMD.loadFile(self.f + "/tmp/title.tmd")
|
||||
contents = tmd.getContents()
|
||||
for i in range(self.workingcidcnt):
|
||||
idx = self.getContentIndexFromCID(tmd, self.workingcids[i])
|
||||
@ -805,12 +807,12 @@ class ESClass:
|
||||
outfp.close()
|
||||
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)
|
||||
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):
|
||||
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):
|
||||
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()
|
||||
return 0
|
||||
|
56
title.py
56
title.py
@ -37,7 +37,7 @@ class TicketView:
|
||||
|
||||
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)."""
|
||||
class TicketStruct(Struct):
|
||||
__endian__ = Struct.BE
|
||||
@ -79,9 +79,7 @@ class Ticket:
|
||||
if(self.tik.commonkey_index == 1): #korean, kekekekek!
|
||||
commonkey = koreankey
|
||||
self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey)
|
||||
@classmethod
|
||||
def load(cls, data):
|
||||
self = cls()
|
||||
def _load(self, data):
|
||||
self.tik.unpack(data[:len(self.tik)])
|
||||
|
||||
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)
|
||||
return self
|
||||
@classmethod
|
||||
def loadFile(cls, filename):
|
||||
return cls.load(open(filename, "rb").read())
|
||||
def getTitleKey(self):
|
||||
"""Returns a string containing the title key."""
|
||||
return self.titlekey
|
||||
@ -128,7 +123,7 @@ class Ticket:
|
||||
out += "\n"
|
||||
|
||||
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."""
|
||||
self.rsamod = self.rsamod = "\x00" * 256
|
||||
for i in range(65536):
|
||||
@ -137,25 +132,13 @@ class Ticket:
|
||||
break
|
||||
if(i == 65535):
|
||||
raise ValueError("Failed to fakesign. Aborting...")
|
||||
|
||||
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 = ""):
|
||||
def _dump(self):
|
||||
"""Dumps the ticket to either fn, if not empty, or overwriting the source if empty. **Does not fakesign.** Returns the output filename."""
|
||||
if(fn == ""):
|
||||
open(self.f, "wb").write(self.tik.pack())
|
||||
return self.f
|
||||
else:
|
||||
open(fn, "wb").write(self.tik.pack())
|
||||
return fn
|
||||
return self.tik.pack()
|
||||
def __len__(self):
|
||||
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."""
|
||||
class TMDContent(Struct):
|
||||
__endian__ = Struct.BE
|
||||
@ -184,7 +167,7 @@ class TMD:
|
||||
self.boot_index = Struct.uint16
|
||||
self.padding2 = Struct.uint16
|
||||
#contents follow this
|
||||
def load(self, data):
|
||||
def _load(self, data):
|
||||
self.tmd.unpack(data[:len(self.tmd)])
|
||||
pos = len(self.tmd)
|
||||
for i in range(self.tmd.numcontents):
|
||||
@ -192,9 +175,6 @@ class TMD:
|
||||
cont.unpack(data[pos:pos + len(cont)])
|
||||
pos += len(cont)
|
||||
self.contents.append(cont)
|
||||
return self
|
||||
def loadFile(self, filename):
|
||||
return self.load(open(filename, "rb").read())
|
||||
def __init__(self):
|
||||
self.tmd = self.TMDStruct()
|
||||
self.tmd.titleid = 0x0000000100000000
|
||||
@ -232,7 +212,7 @@ class TMD:
|
||||
for i in range(len(contents)):
|
||||
sz += len(contents[i])
|
||||
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."""
|
||||
for i in range(65536):
|
||||
self.tmd.padding2 = i
|
||||
@ -245,26 +225,14 @@ class TMD:
|
||||
break
|
||||
if(i == 65535):
|
||||
raise ValueError("Failed to fakesign! Aborting...")
|
||||
|
||||
if(fn == ""):
|
||||
open(self.f, "wb").write(data)
|
||||
return self.f
|
||||
else:
|
||||
open(fn, "wb").write(data)
|
||||
return fn
|
||||
def rawdump(self, fn = ""):
|
||||
def _dump(self):
|
||||
"""Same as the :dump: function, but does not fakesign the TMD. Also returns output filename."""
|
||||
data = ""
|
||||
data += self.tmd.pack()
|
||||
for i in range(self.tmd.numcontents):
|
||||
data += self.contents[i].pack()
|
||||
|
||||
if(fn == ""):
|
||||
open(self.f, "wb").write(data)
|
||||
return self.f
|
||||
else:
|
||||
open(fn, "wb").write(data)
|
||||
return fn
|
||||
return data
|
||||
def getTitleID(self):
|
||||
"""Returns the long integer title id."""
|
||||
return self.tmd.titleid
|
||||
@ -320,11 +288,11 @@ class NUS:
|
||||
|
||||
urllib.urlretrieve(self.baseurl + "tmd" + versionstring, "tmd")
|
||||
tmd = TMD.loadFile("tmd")
|
||||
tmd.rawdump("tmd") # strip certs
|
||||
tmd.dumpFile("tmd") # strip certs
|
||||
|
||||
urllib.urlretrieve(self.baseurl + "cetk", "tik")
|
||||
tik = Ticket.loadFile("tik")
|
||||
tik.rawdump("tik") # strip certs
|
||||
tik.dumpFile("tik") # strip certs
|
||||
if(decrypt):
|
||||
titlekey = tik.getTitleKey()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user