Talk:Package Files

From PS4 Developer wiki
Revision as of 23:14, 19 November 2013 by Flatz (talk | contribs) (Added first revision of UnPKG tool)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

PKG file

UnPKG tool

# UnPKG rev 0x00000001, (c) flatz

import sys, os, hashlib, hmac, struct, traceback

# 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

TYPE_FLAGS_RETAIL = 1 << 31

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.key_index = (self.flags2 & 0xF000) >> 12

try:
	with open(pkg_file_path, 'rb') as pkg_file:
		magic = read_string(pkg_file, 4)
		if magic != PKG_MAGIC:
			print 'error: invalid file magic'
			sys.exit()

		type = read_uint32_be(pkg_file)
		is_retail = type & 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:
			print 'error: invalid content id'
			sys.exit()

		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)

		for i in xrange(file_table_entry_count):
			entry = file_table_entries[i]
			file_path = os.path.join(output_dir, 'entry_{0:03}.bin'.format(i))
			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 '         Unk1: 0x{0:08X}'.format(entry.unk1)
				print '         Unk2: 0x{0:08X}'.format(entry.unk2)
				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:
	print 'error: unexpected error:', sys.exc_info()[0]
	traceback.print_exc(file=sys.stdout)