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 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 *
|
||||||
|
292
archive.py
292
archive.py
@ -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,95 +138,52 @@ 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
|
||||||
name = strings[node.name_offset:].split('\0', 1)[0]
|
name = strings[node.name_offset:].split('\0', 1)[0]
|
||||||
|
|
||||||
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
|
elif(node.type == 0): # file
|
||||||
os.chdir(name)
|
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
|
||||||
continue
|
|
||||||
elif(node.type == 0): #file
|
|
||||||
file = open(name, "wb")
|
|
||||||
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
|
||||||
|
|
||||||
|
28
common.py
28
common.py
@ -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
36
nand.py
@ -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
|
||||||
|
56
title.py
56
title.py
@ -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()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user