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:
Christopher Roy Bratusek 2013-06-17 10:00:37 +02:00
parent 138af060a3
commit e573cd1f2f
6 changed files with 103 additions and 77 deletions

View File

@ -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

View File

@ -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>"

View File

@ -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

View File

@ -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")

View File

@ -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__'):

1
qwad
View File

@ -1,3 +1,4 @@
#!/bin/sh
export QWAD_X_DIR="${PWD}"
PYTHONPATH="/usr/share/Qwad/"
python /usr/share/Qwad/Qwad.pyw $@