Talk:Package Files
Jump to navigation
Jump to search
PKG file
UnPKG tool
# UnPKG rev 0x00000003, (c) flatz
import sys, os, hashlib, hmac, struct, traceback
from cStringIO import StringIO
# parse arguments
if len(sys.argv) < 3:
script_file_name = os.path.split(sys.argv[0])[1]
print 'usage: {0} <pkg file> <output dir>'.format(script_file_name)
sys.exit()
pkg_file_path = sys.argv[1]
if not os.path.isfile(pkg_file_path):
print 'error: invalid file specified'
sys.exit()
output_dir = sys.argv[2]
if os.path.exists(output_dir) and not os.path.isdir(output_dir):
print 'error: invalid directory specified'
sys.exit()
elif not os.path.exists(output_dir):
os.makedirs(output_dir)
# utility
uint64_fmt, uint32_fmt, uint16_fmt, uint8_fmt = '>Q', '>I', '>H', '>B'
int64_fmt, int32_fmt, int16_fmt, int8_fmt = '>q', '>i', '>h', '>b'
def read_string(f, length):
return f.read(length)
def read_cstring(f):
s = ''
while True:
c = f.read(1)
if not c:
return False
if ord(c) == 0:
break
s += c
return s
def read_uint8_le(f):
return struct.unpack('<B', f.read(struct.calcsize('<B')))[0]
def read_uint8_be(f):
return struct.unpack('>B', f.read(struct.calcsize('>B')))[0]
def read_uint16_le(f):
return struct.unpack('<H', f.read(struct.calcsize('<H')))[0]
def read_uint16_be(f):
return struct.unpack('>H', f.read(struct.calcsize('>H')))[0]
def read_uint32_le(f):
return struct.unpack('<I', f.read(struct.calcsize('<I')))[0]
def read_uint32_be(f):
return struct.unpack('>I', f.read(struct.calcsize('>I')))[0]
def read_uint64_le(f):
return struct.unpack('<Q', f.read(struct.calcsize('<Q')))[0]
def read_uint64_be(f):
return struct.unpack('>Q', f.read(struct.calcsize('>Q')))[0]
def read_int8_le(f):
return struct.unpack('<b', f.read(struct.calcsize('<b')))[0]
def read_int8_be(f):
return struct.unpack('>b', f.read(struct.calcsize('>b')))[0]
def read_int16_le(f):
return struct.unpack('<h', f.read(struct.calcsize('<h')))[0]
def read_int16_be(f):
return struct.unpack('>h', f.read(struct.calcsize('>h')))[0]
def read_int32_le(f):
return struct.unpack('<i', f.read(struct.calcsize('<i')))[0]
def read_int32_be(f):
return struct.unpack('>i', f.read(struct.calcsize('>i')))[0]
def read_int64_le(f):
return struct.unpack('<q', f.read(struct.calcsize('<q')))[0]
def read_int64_be(f):
return struct.unpack('>q', f.read(struct.calcsize('>q')))[0]
#
PKG_MAGIC = '\x7FCNT'
CONTENT_ID_SIZE = 0x24
FILE_TYPE_FLAGS_RETAIL = 1 << 31
ENTRY_TYPE_META_TABLE = 0x01
ENTRY_TYPE_NAME_TABLE = 0x02
ENTRY_TYPE_FILE1 = 0x10
ENTRY_TYPE_FILE2 = 0x12
class MyError(Exception):
def __init__(self, message):
self.message = message
def __str__(self):
return repr(self.message)
class FileTableEntry:
entry_fmt = '>IIIIII8x'
def __init__(self):
pass
def read(self, f):
self.unk1, self.unk2, self.flags1, self.flags2, self.offset, self.size = struct.unpack(self.entry_fmt, f.read(struct.calcsize(self.entry_fmt)))
self.type = (self.unk1 >> 8) & 0xFF
self.key_index = (self.flags2 & 0xF000) >> 12
self.name = None
try:
with open(pkg_file_path, 'rb') as pkg_file:
magic = read_string(pkg_file, 4)
if magic != PKG_MAGIC:
raise MyError('invalid file magic')
type = read_uint32_be(pkg_file)
is_retail = (type & FILE_TYPE_FLAGS_RETAIL) != 0
pkg_file.seek(0x10) # FIXME: or maybe uint16 at 0x16???
file_table_entry_count = read_uint32_be(pkg_file)
pkg_file.seek(0x18)
file_table_offset = read_uint32_be(pkg_file)
pkg_file.seek(0x40)
content_id = read_cstring(pkg_file)
if len(content_id) != CONTENT_ID_SIZE:
raise MyError('invalid content id')
file_table_entries = []
pkg_file.seek(file_table_offset)
for i in xrange(file_table_entry_count):
entry = FileTableEntry()
entry.read(pkg_file)
file_table_entries.append(entry)
entry_names = None
for i in xrange(file_table_entry_count):
entry = file_table_entries[i]
if entry.type == ENTRY_TYPE_NAME_TABLE:
pkg_file.seek(entry.offset)
data = pkg_file.read(entry.size)
if data and len(data) > 0:
data = StringIO(data)
entry_names = []
c = data.read(1)
if ord(c) == 0:
while True:
name = read_cstring(data)
if not name:
break
entry_names.append(name)
else:
raise MyError('weird name table format')
break
entry_name_index = 0
for i in xrange(file_table_entry_count):
entry = file_table_entries[i]
if entry.type == ENTRY_TYPE_FILE1 or entry.type == ENTRY_TYPE_FILE2:
if entry_name_index < len(entry_names):
entry.name = entry_names[entry_name_index]
entry_name_index += 1
else:
raise MyError('entry name index out of bounds')
elif entry.type == ENTRY_TYPE_META_TABLE:
entry.name = '.meta_table'
elif entry.type == ENTRY_TYPE_NAME_TABLE:
entry.name = '.entry_names'
for i in xrange(file_table_entry_count):
entry = file_table_entries[i]
name = entry.name if entry.name is not None else 'entry_{0:03}.bin'.format(i)
file_path = os.path.join(output_dir, name)
file_dir = os.path.split(file_path)[0]
if not os.path.exists(file_dir):
os.makedirs(file_dir)
with open(file_path, 'wb') as entry_file:
pkg_file.seek(entry.offset)
data = pkg_file.read(entry.size)
entry_file.write(data)
print 'File information:'
print ' Magic: 0x{0}'.format(magic.encode('hex').upper())
print ' Type: 0x{0:08X}'.format(type), '(retail)' if is_retail else ''
print ' Content ID: {0}'.format(content_id)
print ' Num table entries: {0}'.format(file_table_entry_count)
print 'Entry table offset: 0x{0:08X}'.format(file_table_offset)
print
if file_table_entry_count > 0:
print 'Table entries:'
for i in xrange(file_table_entry_count):
entry = file_table_entries[i]
print ' Entry #{0:03}:'.format(i)
print ' Type: 0x{0:02X}'.format(entry.type)
print ' Unk1: 0x{0:08X}'.format(entry.unk1)
print ' Unk2: 0x{0:08X}'.format(entry.unk2)
if entry.name is not None:
print ' Name: {0}'.format(entry.name)
print ' Offset: 0x{0:08X}'.format(entry.offset)
print ' Size: 0x{0:08X}'.format(entry.size)
print ' Flags 1: 0x{0:08X}'.format(entry.flags1)
print ' Flags 2: 0x{0:08X}'.format(entry.flags2)
print ' Key index: {0}'.format('N/A' if entry.key_index == 0 else entry.key_index)
print
except IOError:
print 'error: i/o error during processing'
except MyError as e:
print 'error: {0}', e.message
except:
print 'error: unexpected error:', sys.exc_info()[0]
traceback.print_exc(file=sys.stdout)