from common import * from title import * 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 def __format__(self): self.tag = Struct.string(4) self.rootnode_offset = Struct.uint32 self.header_size = Struct.uint32 self.data_offset = Struct.uint32 self.zeroes = Struct.string(16) class U8Node(Struct): __endian__ = Struct.BE def __format__(self): self.type = Struct.uint16 self.name_offset = Struct.uint16 self.data_offset = Struct.uint32 self.size = Struct.uint32 def __init__(self): self.files = [] 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 node.size += 1 node.size += 1 else: sz = len(value) node.data_offset = len(data) data += value + "\x00" * (align(sz, 32) - sz) # 32 seems to work best for fuzzyness? I'm still really not sure 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() 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) old = os.getcwd() os.chdir(dir) for item, data in self.files: if(data == None): if(not os.path.isdir(item)): os.mkdir(item) else: open(item, "wb").write(data) os.chdir(old) def _loadDir(self, dir): try: self._tmpPath += '' except: self._tmpPath = '' old = os.getcwd() 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(old) 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) assert len(recursion) == node.data_offset + 2 # haxx 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 type -- wtf? pass sz = recursion.pop() if(sz != counter + 1): recursion.append(sz) else: recursiondir.pop() def __str__(self): ret = '' for key, value in self.files: name = key[key.rfind('/') + 1:] recursion = key.count('/') ret += ' ' * recursion if(value == None): ret += '[' + name + ']' else: ret += name ret += '\n' return ret def __getitem__(self, key): for item, val in self.files: if(item == key): if(val != None): return val else: ret = [] for item2, val2 in self.files: if(item2.find(item) == 0): ret.append(item2[len(item) + 1:]) return ret[1:] raise KeyError def __setitem__(self, key, val): for i in range(len(self.files)): if(self.files[i][0] == key): self.files[i] = (self.files[i][0], val) return self.files.append((key, val)) class WAD(WiiArchive): def __init__(self, boot2 = False): self.tmd = TMD() self.tik = Ticket() self.contents = [] self.boot2 = False self.cert = "" def _load(self, data): if(self.boot2 != True): headersize, wadtype, certsize, reserved, tiksize, tmdsize, datasize, footersize, padding = struct.unpack('>I4s6I32s', data[:64]) pos = 64 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): if(certsize % 64 != 0): pos += 64 - (certsize % 64) self.cert = rawcert rawtik = data[pos:pos + tiksize] pos += tiksize if(self.boot2 != True): if(tiksize % 64 != 0): pos += 64 - (tiksize % 64) self.tik = Ticket.load(rawtik) rawtmd = data[pos:pos + tmdsize] pos += tmdsize if(self.boot2 == True): pos = data_offset 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)): tmpsize = contents[i].size if(tmpsize % 16 != 0): tmpsize += 16 - (tmpsize % 16) encdata = data[pos:pos + tmpsize] pos += tmpsize decdata = Crypto().decryptContent(titlekey, contents[i].index, encdata) self.contents.append(decdata) if(tmpsize % 64 != 0): pos += 64 - (tmpsize % 64) 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()) os.chdir(origdir) 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)) pack += rawtik if(len(rawtik) % 64 != 0 and self.boot2 != True): pack += "\x00" * (64 - (len(rawtik) % 64)) 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): return self.contents[idx] def __setitem__(self, idx, value): self.contents[idx] = value def __str__(self): out = "" out += "Wii WAD:\n" out += str(self.tmd) out += str(self.tik) return out class CCF(): class CCFHeader(Struct): __endian__ = Struct.LE def __format__(self): self.magic = Struct.string(4) self.zeroes12 = Struct.string(12) self.rootOffset = Struct.uint32 self.filesCount = Struct.uint32 self.zeroes8 = Struct.string(8) class CCFFile(Struct): __endian__ = Struct.LE def __format__(self): self.fileName = Struct.string(20) self.fileOffset = Struct.uint32 self.fileSize = Struct.uint32 self.fileSizeDecompressed = Struct.uint32 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() 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' output.write(self.fd.read(fileEntry.fileSize)) else: print 'The file is stored compressed..decompressing' decompressedBuffer = zlib.decompress(self.fd.read(fileEntry.fileSize)) output.write(decompressedBuffer) output.close() currentOffset += len(fileEntry) if(__name__ == '__main__'): wad = WAD.loadFile("testing.wad") print wad wad.dumpDir("outdir") wad.dumpFile("interesting.wad", fakesign = False) #keyword arguements work as expected when calling _dump(). awesome. wad2 = WAD.loadDir("outdir") print wad2 wad3 = WAD.loadFile("interesting.wad") print wad3 wad3.dumpDir("outdir2")