diff --git a/ChangeLog b/ChangeLog index 0869670..747f55e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 diff --git a/GUI/AboutQwad.py b/GUI/AboutQwad.py index 7b864fb..c398d17 100644 --- a/GUI/AboutQwad.py +++ b/GUI/AboutQwad.py @@ -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 " diff --git a/GUI/VenPri.py b/GUI/VenPri.py index 3fdd2d1..38d3d53 100644 --- a/GUI/VenPri.py +++ b/GUI/VenPri.py @@ -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 diff --git a/Qwad.pyw b/Qwad.pyw index e046fef..2436656 100644 --- a/Qwad.pyw +++ b/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") diff --git a/WiiPy/archive.py b/WiiPy/archive.py index ba9dd12..718ee20 100644 --- a/WiiPy/archive.py +++ b/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__'): diff --git a/qwad b/qwad index f833e3c..fea18d5 100755 --- a/qwad +++ b/qwad @@ -1,3 +1,4 @@ #!/bin/sh +export QWAD_X_DIR="${PWD}" PYTHONPATH="/usr/share/Qwad/" python /usr/share/Qwad/Qwad.pyw $@