Talk:Package Files: Difference between revisions

From PS4 Developer wiki
Jump to navigation Jump to search
mNo edit summary
No edit summary
Line 4: Line 4:


<source lang="python">
<source lang="python">
# UnPKG rev 0x00000003, (c) flatz
# UnPKG rev 0x00000004 (public edition), (c) flatz


import sys, os, hashlib, hmac, struct, traceback
import sys, os, hashlib, hmac, struct, traceback
Line 88: Line 88:
ENTRY_TYPE_META_TABLE = 0x01
ENTRY_TYPE_META_TABLE = 0x01
ENTRY_TYPE_NAME_TABLE = 0x02
ENTRY_TYPE_NAME_TABLE = 0x02
ENTRY_TYPE_LICENSE    = 0x04
ENTRY_TYPE_FILE1      = 0x10
ENTRY_TYPE_FILE1      = 0x10
ENTRY_TYPE_FILE2      = 0x12
ENTRY_TYPE_FILE2      = 0x12
ENTRY_TABLE_MAP = {
0x0400: 'license.dat',
0x0401: 'license.info',
0x1000: 'param.sfo',
0x1001: 'playgo-chunk.dat',
0x1002: 'playgo-chunk.sha',
0x1003: 'playgo-manifest.xml',
0x1004: 'pronunciation.xml',
0x1005: 'pronunciation.sig',
0x1006: 'pic1.png',
0x1008: 'app/playgo-chunk.dat',
0x1200: 'icon0.png',
0x1220: 'pic0.png',
0x1260: 'changeinfo/changeinfo.xml',
}


class MyError(Exception):
class MyError(Exception):
Line 105: Line 122:


def read(self, f):
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, 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.key_index = (self.flags2 & 0xF000) >> 12
self.name = None
self.name = None
Line 140: Line 156:
for i in xrange(file_table_entry_count):
for i in xrange(file_table_entry_count):
entry = file_table_entries[i]
entry = file_table_entries[i]
if entry.type == ENTRY_TYPE_NAME_TABLE:
type = (entry.type >> 8) & 0xFF
if type == ENTRY_TYPE_NAME_TABLE:
pkg_file.seek(entry.offset)
pkg_file.seek(entry.offset)
data = pkg_file.read(entry.size)
data = pkg_file.read(entry.size)
Line 159: Line 176:
for i in xrange(file_table_entry_count):
for i in xrange(file_table_entry_count):
entry = file_table_entries[i]
entry = file_table_entries[i]
if entry.type == ENTRY_TYPE_FILE1 or entry.type == ENTRY_TYPE_FILE2:
type, index = (entry.type >> 8) & 0xFF, entry.type & 0xFF
if type == ENTRY_TYPE_FILE1 or type == ENTRY_TYPE_FILE2:
if entry_name_index < len(entry_names):
if entry_name_index < len(entry_names):
entry.name = entry_names[entry_name_index]
entry.name = entry_names[entry_name_index]
Line 165: Line 183:
else:
else:
raise MyError('entry name index out of bounds')
raise MyError('entry name index out of bounds')
elif entry.type == ENTRY_TYPE_META_TABLE:
elif type in ENTRY_TABLE_MAP:
entry.name = ENTRY_TABLE_MAP[entry.type]
elif type == ENTRY_TYPE_META_TABLE:
entry.name = '.meta_table'
entry.name = '.meta_table'
elif entry.type == ENTRY_TYPE_NAME_TABLE:
elif type == ENTRY_TYPE_NAME_TABLE:
entry.name = '.entry_names'
entry.name = '.entry_names'


Line 195: Line 215:
entry = file_table_entries[i]
entry = file_table_entries[i]
print '  Entry #{0:03}:'.format(i)
print '  Entry #{0:03}:'.format(i)
print '        Type: 0x{0:02X}'.format(entry.type)
print '        Type: 0x{0:08X}'.format(entry.type)
print '        Unk1: 0x{0:08X}'.format(entry.unk1)
print '        Unk1: 0x{0:08X}'.format(entry.unk1)
print '        Unk2: 0x{0:08X}'.format(entry.unk2)
if entry.name is not None:
if entry.name is not None:
print '        Name: {0}'.format(entry.name)
print '        Name: {0}'.format(entry.name)

Revision as of 00:44, 20 November 2013

PKG file

UnPKG tool

# UnPKG rev 0x00000004 (public edition), (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_LICENSE    = 0x04
ENTRY_TYPE_FILE1      = 0x10
ENTRY_TYPE_FILE2      = 0x12

ENTRY_TABLE_MAP = {
	0x0400: 'license.dat',
	0x0401: 'license.info',
	0x1000: 'param.sfo',
	0x1001: 'playgo-chunk.dat',
	0x1002: 'playgo-chunk.sha',
	0x1003: 'playgo-manifest.xml',
	0x1004: 'pronunciation.xml',
	0x1005: 'pronunciation.sig',
	0x1006: 'pic1.png',
	0x1008: 'app/playgo-chunk.dat',
	0x1200: 'icon0.png',
	0x1220: 'pic0.png',
	0x1260: 'changeinfo/changeinfo.xml',
}

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.type, self.unk1, 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
		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]
			type = (entry.type >> 8) & 0xFF
			if 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]
			type, index = (entry.type >> 8) & 0xFF, entry.type & 0xFF
			if type == ENTRY_TYPE_FILE1 or 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 type in ENTRY_TABLE_MAP:
				entry.name = ENTRY_TABLE_MAP[entry.type]
			elif type == ENTRY_TYPE_META_TABLE:
				entry.name = '.meta_table'
			elif 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:08X}'.format(entry.type)
				print '         Unk1: 0x{0:08X}'.format(entry.unk1)
				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)