mirror of
https://gitlab.com/Nanolx/qwad.git
synced 2024-11-21 18:19:18 +01:00
bump version to 0.7
support relative paths --tmdinfo no longer fails when an IOS-tmd is check don't enforce decrypt=false on unpacking CLI
This commit is contained in:
parent
138af060a3
commit
e573cd1f2f
@ -1,3 +1,9 @@
|
||||
-- 0.7 --
|
||||
* cli-options now can properly handle relative paths
|
||||
* --tmdinfo does now longer fail when checking an IOS
|
||||
* don't enforce decrypt=false on unpacking cli
|
||||
* automatic whitespace fixes
|
||||
|
||||
-- 0.6 --
|
||||
* fixed a string in german translation
|
||||
* fixed selecting file in TMD-Viewer
|
||||
|
@ -10,7 +10,7 @@ from PyQt4.QtCore import pyqtSignature
|
||||
from Ui_AboutQwad import Ui_Dialog
|
||||
|
||||
def Version():
|
||||
return "0.5"
|
||||
return "0.7"
|
||||
def Author():
|
||||
return "Christopher Roy Bratusek <nano@tuxfamily.org>"
|
||||
|
||||
|
@ -342,6 +342,18 @@ class UnpackingCLI(Thread):
|
||||
print e
|
||||
print "Done"
|
||||
|
||||
class UnpackingCLIX(Thread):
|
||||
def __init__(self, wadpath, dirpath):
|
||||
Thread.__init__(self)
|
||||
self.wadpath = wadpath
|
||||
self.dirpath = dirpath
|
||||
def run(self):
|
||||
try:
|
||||
WAD.loadFile(self.wadpath).dumpDir(self.dirpath)
|
||||
except Exception, e:
|
||||
print e
|
||||
print "Done"
|
||||
|
||||
class Packing(Unpacking):
|
||||
def __init__(self, dirpath, wadpath, QMW, deletedir = False):
|
||||
Unpacking.__init__(self, wadpath, dirpath, QMW)
|
||||
@ -349,9 +361,9 @@ class Packing(Unpacking):
|
||||
def run(self):
|
||||
self.qobject.emit(SIGNAL("working"),PACKING)
|
||||
try:
|
||||
print self.dirpath
|
||||
print self.wadpath
|
||||
WAD.loadDir(self.dirpath).dumpFile(self.wadpath)
|
||||
print self.dirpath
|
||||
WAD.loadDir(self.dirpath).dumpFile(self.wadpath)
|
||||
if self.deletedir:
|
||||
print "Cleaning temporary files"
|
||||
self.qobject.emit(SIGNAL("working"),CLEANING)
|
||||
@ -393,6 +405,8 @@ class nusDownloading(Unpacking):
|
||||
self.version = None
|
||||
self.decrypt = decrypt
|
||||
self.titleid = titleid
|
||||
print self.titleid
|
||||
print self.version
|
||||
def run(self):
|
||||
self.qobject.emit(SIGNAL("working"),DOWNLOADING)
|
||||
try:
|
||||
@ -425,7 +439,7 @@ class nusDownloadingCLI(UnpackingCLI):
|
||||
try:
|
||||
if self.pack:
|
||||
self.dirpath = tempfile.gettempdir() + "/NUS_"+ str(time.time()).replace(".","")
|
||||
NUS(self.titleid, self.version).download(self.dirpath, decrypt = False)
|
||||
NUS(self.titleid, self.version).download(self.dirpath, decrypt = self.decrypt)
|
||||
self.packing = PackingCLI(self.dirpath, str(self.outputdir), True)
|
||||
self.packing.start()
|
||||
else:
|
||||
@ -446,7 +460,10 @@ def ShowTMD(tmdpath):
|
||||
print "Title Version : %s" % tmd.tmd.title_version
|
||||
print "Title Boot Index : %s" % tmd.tmd.boot_index
|
||||
print "Title Contents : %s" % tmd.tmd.numcontents
|
||||
print "Title IOS : %s" % TitleIDs.TitleSwapDict["%s" % ("%016x" % tmd.tmd.iosversion)]
|
||||
if ("%016x" % tmd.tmd.iosversion) == "0000000000000000":
|
||||
print "Title IOS : --"
|
||||
else:
|
||||
print "Title IOS : %s" % TitleIDs.TitleSwapDict["%s" % ("%016x" % tmd.tmd.iosversion)]
|
||||
print "Title Access Rights: %s" % tmd.tmd.access_rights
|
||||
print "Title Type : %s" % tmd.tmd.title_type
|
||||
print "Title Group ID : %s" % tmd.tmd.group_id
|
||||
|
12
Qwad.pyw
12
Qwad.pyw
@ -8,6 +8,11 @@ from PyQt4.QtCore import QTranslator, QString, QLocale
|
||||
from GUI.VenPri import MWQwad, nusDownloadingCLI, PackingCLI, UnpackingCLI, ShowTMD
|
||||
from TitleIDs import TitleDict, IOSdict, swap_dic, ChannelCLIDict, ChannelJAPDict, ChannelPALDict, ChannelUSADict, ChannelJAPVerDict, ChannelPALVerDict, ChannelUSAVerDict
|
||||
|
||||
if os.getenv("QWAD_X_DIR"):
|
||||
os.chdir(os.getenv("QWAD_X_DIR"))
|
||||
else:
|
||||
os.chdir(os.getenv("HOME"))
|
||||
|
||||
class MultipleOption(Option):
|
||||
ACTIONS = Option.ACTIONS + ("extend",)
|
||||
STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
|
||||
@ -20,10 +25,9 @@ class MultipleOption(Option):
|
||||
else:
|
||||
Option.take_action(self, action, dest, opt, value, values, parser)
|
||||
|
||||
VERSION = '0.6'
|
||||
VERSION = '0.7'
|
||||
|
||||
def opts():
|
||||
os.chdir(sys.path[0])
|
||||
description = """NUS-Downloader, WadManager and TMD-Viewer for Linux"""
|
||||
parser = OptionParser(option_class=MultipleOption,
|
||||
usage='usage: qwad [OPTIONS] ARGUMENT',
|
||||
@ -72,7 +76,7 @@ def opts():
|
||||
xarg = TitleDict[str(options.download[0])]
|
||||
nusdow = nusDownloadingCLI(int(str(xarg).lower(),16), args[0], args[1], args[2], args[3])
|
||||
else:
|
||||
nusdow = nusDownloadingCLI(int(str(options.download[0]).lower(),16), args[0], args[1], args[2], args[3])
|
||||
nusdow = nusDownloadingCLI(int(str(options.download[0]).lower(),16), args[0], args[1], args[2], args[3])
|
||||
nusdow.start()
|
||||
sys.exit(0)
|
||||
|
||||
@ -165,8 +169,6 @@ def main():
|
||||
# load Qt translations
|
||||
qttranslator = QTranslator()
|
||||
qttranslator.load(QString("qt_%1").arg(QLocale.system().name()))
|
||||
# change directory in $HOME, so that file-seletors don't start in Qwads source/data directory
|
||||
os.chdir(os.getenv("HOME"))
|
||||
# misc stuff
|
||||
Vapp = QApplication(sys.argv)
|
||||
Vapp.setOrganizationName("Nanolx")
|
||||
|
134
WiiPy/archive.py
134
WiiPy/archive.py
@ -4,7 +4,7 @@ import zlib
|
||||
|
||||
class U8(WiiArchive):
|
||||
"""This class can unpack and pack U8 archives, which are used all over the Wii. They are often used in Banners and contents in Downloadable Titles. Please remove all headers and compression first, kthx.
|
||||
|
||||
|
||||
The f parameter is either the source folder to pack, or the source file to unpack."""
|
||||
class U8Header(Struct):
|
||||
__endian__ = Struct.BE
|
||||
@ -26,32 +26,32 @@ class U8(WiiArchive):
|
||||
def _dump(self):
|
||||
header = self.U8Header()
|
||||
rootnode = self.U8Node()
|
||||
|
||||
|
||||
# constants
|
||||
header.tag = "U\xAA8-"
|
||||
header.rootnode_offset = 0x20
|
||||
header.zeroes = "\x00" * 16
|
||||
rootnode.type = 0x0100
|
||||
|
||||
|
||||
nodes = []
|
||||
strings = "\x00"
|
||||
data = ''
|
||||
|
||||
|
||||
for item, value in self.files:
|
||||
node = self.U8Node()
|
||||
|
||||
|
||||
recursion = item.count('/')
|
||||
if(recursion < 0):
|
||||
recursion = 0
|
||||
name = item[item.rfind('/') + 1:]
|
||||
|
||||
|
||||
node.name_offset = len(strings)
|
||||
strings += name + '\x00'
|
||||
|
||||
|
||||
if(value == None):
|
||||
node.type = 0x0100
|
||||
node.data_offset = recursion
|
||||
|
||||
|
||||
node.size = len(nodes)
|
||||
for one, two in self.files:
|
||||
if(one[:len(item)] == item): # find nodes in the folder
|
||||
@ -64,15 +64,15 @@ class U8(WiiArchive):
|
||||
node.size = sz
|
||||
node.type = 0x0000
|
||||
nodes.append(node)
|
||||
|
||||
|
||||
header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings)
|
||||
header.data_offset = align(header.header_size + header.rootnode_offset, 64)
|
||||
rootnode.size = len(nodes) + 1
|
||||
|
||||
|
||||
for i in range(len(nodes)):
|
||||
if(nodes[i].type == 0x0000):
|
||||
nodes[i].data_offset += header.data_offset
|
||||
|
||||
|
||||
fd = ''
|
||||
fd += header.pack()
|
||||
fd += rootnode.pack()
|
||||
@ -81,7 +81,7 @@ class U8(WiiArchive):
|
||||
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)):
|
||||
@ -115,35 +115,35 @@ class U8(WiiArchive):
|
||||
self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
|
||||
def _load(self, data):
|
||||
offset = 0
|
||||
|
||||
|
||||
header = self.U8Header()
|
||||
header.unpack(data[offset:offset + len(header)])
|
||||
offset += len(header)
|
||||
|
||||
|
||||
assert header.tag == "U\xAA8-"
|
||||
offset = header.rootnode_offset
|
||||
|
||||
|
||||
rootnode = self.U8Node()
|
||||
rootnode.unpack(data[offset:offset + len(rootnode)])
|
||||
offset += len(rootnode)
|
||||
|
||||
|
||||
nodes = []
|
||||
for i in range(rootnode.size - 1):
|
||||
node = self.U8Node()
|
||||
node.unpack(data[offset:offset + len(node)])
|
||||
offset += len(node)
|
||||
nodes.append(node)
|
||||
|
||||
|
||||
strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
|
||||
offset += len(strings)
|
||||
|
||||
|
||||
recursion = [rootnode.size]
|
||||
recursiondir = []
|
||||
counter = 0
|
||||
for node in nodes:
|
||||
counter += 1
|
||||
name = strings[node.name_offset:].split('\0', 1)[0]
|
||||
|
||||
|
||||
if(node.type == 0x0100): # folder
|
||||
recursion.append(node.size)
|
||||
recursiondir.append(name)
|
||||
@ -154,7 +154,7 @@ class U8(WiiArchive):
|
||||
offset += node.size
|
||||
else: # unknown type -- wtf?
|
||||
pass
|
||||
|
||||
|
||||
sz = recursion.pop()
|
||||
if(sz != counter + 1):
|
||||
recursion.append(sz)
|
||||
@ -190,7 +190,7 @@ class U8(WiiArchive):
|
||||
self.files[i] = (self.files[i][0], val)
|
||||
return
|
||||
self.files.append((key, val))
|
||||
|
||||
|
||||
|
||||
class WAD(WiiArchive):
|
||||
def __init__(self, boot2 = False):
|
||||
@ -206,7 +206,7 @@ class WAD(WiiArchive):
|
||||
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):
|
||||
@ -220,7 +220,7 @@ class WAD(WiiArchive):
|
||||
if(tiksize % 64 != 0):
|
||||
pos += 64 - (tiksize % 64)
|
||||
self.tik = Ticket.load(rawtik)
|
||||
|
||||
|
||||
rawtmd = data[pos:pos + tmdsize]
|
||||
pos += tmdsize
|
||||
if(self.boot2 == True):
|
||||
@ -228,7 +228,7 @@ class WAD(WiiArchive):
|
||||
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)):
|
||||
@ -244,11 +244,11 @@ class WAD(WiiArchive):
|
||||
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())
|
||||
@ -256,52 +256,52 @@ class WAD(WiiArchive):
|
||||
def _dumpDir(self, dir):
|
||||
origdir = os.getcwd()
|
||||
os.chdir(dir)
|
||||
|
||||
|
||||
contents = self.tmd.getContents()
|
||||
for i in range(len(contents)):
|
||||
open("%08x.app" % i, "wb").write(self.contents[i])
|
||||
self.tmd.dumpFile("tmd")
|
||||
self.tik.dumpFile("tik")
|
||||
open("cert", "wb").write(self.cert)
|
||||
|
||||
|
||||
os.chdir(origdir)
|
||||
def _dump(self, fakesign = True):
|
||||
titlekey = self.tik.getTitleKey()
|
||||
contents = self.tmd.getContents()
|
||||
|
||||
|
||||
apppack = ""
|
||||
for i, content in enumerate(contents):
|
||||
if(fakesign):
|
||||
content.hash = str(Crypto().createSHAHash(self.contents[content.index]))
|
||||
content.size = len(self.contents[content.index])
|
||||
|
||||
|
||||
encdata = Crypto().encryptContent(titlekey, content.index, self.contents[content.index])
|
||||
|
||||
|
||||
apppack += encdata
|
||||
if(len(encdata) % 64 != 0):
|
||||
apppack += "\x00" * (64 - (len(encdata) % 64))
|
||||
|
||||
|
||||
if(fakesign):
|
||||
self.tmd.setContents(contents)
|
||||
self.tmd.fakesign()
|
||||
self.tik.fakesign()
|
||||
|
||||
|
||||
rawtmd = self.tmd.dump()
|
||||
rawcert = self.cert
|
||||
rawtik = self.tik.dump()
|
||||
|
||||
|
||||
sz = 0
|
||||
for i in range(len(contents)):
|
||||
sz += contents[i].size
|
||||
if(sz % 64 != 0):
|
||||
sz += 64 - (contents[i].size % 64)
|
||||
|
||||
|
||||
if(self.boot2 != True):
|
||||
pack = struct.pack('>I4s6I', 32, "Is\x00\x00", len(rawcert), 0, len(rawtik), len(rawtmd), sz, 0)
|
||||
pack += "\x00" * 32
|
||||
else:
|
||||
pack = struct.pack('>IIIII12s', 32, align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40), len(rawcert), len(rawtik), len(rawtmd), "\x00" * 12)
|
||||
|
||||
|
||||
pack += rawcert
|
||||
if(len(rawcert) % 64 != 0 and self.boot2 != True):
|
||||
pack += "\x00" * (64 - (len(rawcert) % 64))
|
||||
@ -311,10 +311,10 @@ class WAD(WiiArchive):
|
||||
pack += rawtmd
|
||||
if(len(rawtmd) % 64 != 0 and self.boot2 != True):
|
||||
pack += "\x00" * (64 - (len(rawtmd) % 64))
|
||||
|
||||
|
||||
if(self.boot2 == True):
|
||||
pack += "\x00" * (align(len(rawcert) + len(rawtik) + len(rawtmd), 0x40) - (len(rawcert) + len(rawtik) + len(rawtmd)))
|
||||
|
||||
|
||||
pack += apppack
|
||||
return pack
|
||||
def __getitem__(self, idx):
|
||||
@ -325,7 +325,7 @@ class WAD(WiiArchive):
|
||||
out = ""
|
||||
out += "Wii WAD:\n"
|
||||
out += str(self.tmd)
|
||||
out += str(self.tik)
|
||||
out += str(self.tik)
|
||||
return out
|
||||
|
||||
|
||||
@ -338,7 +338,7 @@ class CCF():
|
||||
self.rootOffset = Struct.uint32
|
||||
self.filesCount = Struct.uint32
|
||||
self.zeroes8 = Struct.string(8)
|
||||
|
||||
|
||||
class CCFFile(Struct):
|
||||
__endian__ = Struct.LE
|
||||
def __format__(self):
|
||||
@ -350,86 +350,86 @@ class CCF():
|
||||
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()
|
||||
|
||||
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'
|
||||
@ -439,7 +439,7 @@ class CCF():
|
||||
decompressedBuffer = zlib.decompress(self.fd.read(fileEntry.fileSize))
|
||||
output.write(decompressedBuffer)
|
||||
output.close()
|
||||
|
||||
|
||||
currentOffset += len(fileEntry)
|
||||
|
||||
if(__name__ == '__main__'):
|
||||
|
Loading…
Reference in New Issue
Block a user