mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 14:55:35 -04:00
321 lines
9.0 KiB
Python
321 lines
9.0 KiB
Python
from common import *
|
|
import zlib
|
|
|
|
|
|
class U8(WiiArchive):
|
|
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): # directory
|
|
node.type = 0x0100
|
|
node.data_offset = recursion
|
|
|
|
node.size = len(nodes) + 1
|
|
for one, two in self.files:
|
|
if(one[:len(item)] == item): # find nodes in the folder
|
|
node.size += 1
|
|
else: # file
|
|
node.type = 0x0000
|
|
node.data_offset = len(data)
|
|
#print "before: " + str(len(data))
|
|
data += value + ('\x00' * (align(len(value), 32) - len(value))) # 32 seems to work best for fuzzyness? I'm still really not sure
|
|
#print "after: " + str(len(data))
|
|
node.size = len(value)
|
|
#print "sz: " + str(len(value))
|
|
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 node in nodes:
|
|
fd += node.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
|
|
|
|
for i in range(len(data)):
|
|
header = self.U8Header()
|
|
header.unpack(data[offset:offset + len(header)])
|
|
if(header.tag == "U\xAA8-"):
|
|
break
|
|
data = data[1:]
|
|
offset += len(header)
|
|
offset = header.rootnode_offset
|
|
|
|
#print header.rootnode_offset
|
|
#print header.header_size
|
|
#print header.data_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))
|
|
|
|
#print "Dir: " + name
|
|
elif(node.type == 0): # file
|
|
self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
|
|
offset += node.size
|
|
|
|
#print "File: " + name
|
|
else: # unknown type -- wtf?
|
|
pass
|
|
|
|
#print "Data Offset: " + str(node.data_offset)
|
|
#print "Size: " + str(node.size)
|
|
#print "Name Offset: " + str(node.name_offset)
|
|
#print ""
|
|
|
|
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 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)
|