Category PS4 CFW and Hacks       Thread starter PSXHAX       Start date Sep 17, 2016 at 7:40 PM       43,896       40            
Not open for further replies.
A few Twitter updates today from PlayStation 4 developer RedEyeX32 who shared a teaser image of what appears to be a PS4 PKG Unpacker while Specter confirmed the payload is tested, working and he's beginning to port it to PS4 now! :)

Previously @RedEyeX32 released a PS4 Update Bypasser and PSProxy Update while there have been several PlayStation 4 Package Unpackers on the Interweb including the original GIT (Archived) from Hykem (HykemTheDemon) and flat_z with Sony PS4 PKG Tools leaks, clones from RetroA (Hisham Baig aka Hishamage), mirrors from Keyaku and the UnPKG tool source code on the Wiki (also below) with an update by XVortex available here: (5 KB)
# UnPKG rev 0x00000008 (public edition), (c) flatz
import sys, os, hashlib, hmac, struct, math, 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)
pkg_file_path = sys.argv[1]
if not os.path.isfile(pkg_file_path):
    print 'error: invalid file specified'
output_dir = sys.argv[2]
if os.path.exists(output_dir) and not os.path.isdir(output_dir):
    print 'error: invalid directory specified'
elif not os.path.exists(output_dir):
# cryptography functions
def sha256(data):
    return hashlib.sha256(data).digest()
# utility functions
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):
def read_cstring(f):
    s = ''
    while True:
        c =
        if not c:
            return False
        if ord(c) == 0:
        s += c
    return s
def read_uint8_le(f):
    return struct.unpack('<B','<B')))[0]
def read_uint8_be(f):
    return struct.unpack('>B','>B')))[0]
def read_uint16_le(f):
    return struct.unpack('<H','<H')))[0]
def read_uint16_be(f):
    return struct.unpack('>H','>H')))[0]
def read_uint32_le(f):
    return struct.unpack('<I','<I')))[0]
def read_uint32_be(f):
    return struct.unpack('>I','>I')))[0]
def read_uint64_le(f):
    return struct.unpack('<Q','<Q')))[0]
def read_uint64_be(f):
    return struct.unpack('>Q','>Q')))[0]
def read_int8_le(f):
    return struct.unpack('<b','<b')))[0]
def read_int8_be(f):
    return struct.unpack('>b','>b')))[0]
def read_int16_le(f):
    return struct.unpack('<h','<h')))[0]
def read_int16_be(f):
    return struct.unpack('>h','>h')))[0]
def read_int32_le(f):
    return struct.unpack('<i','<i')))[0]
def read_int32_be(f):
    return struct.unpack('>i','>i')))[0]
def read_int64_le(f):
    return struct.unpack('<q','<q')))[0]
def read_int64_be(f):
    return struct.unpack('>q','>q')))[0]
# main code
SHA256_HASH_SIZE = 0x20
ENTRY_TYPE_0x800        = 0x0010
ENTRY_TYPE_0x200        = 0x0020
ENTRY_TYPE_0x180        = 0x0080
    ENTRY_TYPE_DIGEST_TABLE: '.digests',
    ENTRY_TYPE_0x800: '.entry_0x800',
    ENTRY_TYPE_0x200: '.entry_0x200',
    ENTRY_TYPE_0x180: '.entry_0x180',
    ENTRY_TYPE_NAME_TABLE: '.names',
    0x0400: 'license.dat',
    0x0401: '',
    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',
    0x1240: 'snd0.at9',
    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):
    def read(self, f):
        self.type, self.unk1, self.flags1, self.flags2, self.offset, self.size = struct.unpack(self.entry_fmt,
        self.key_index = (self.flags2 & 0xF000) >> 12 = None
    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
 # FIXME: or maybe uint16 at 0x16???
        num_table_entries = read_uint32_be(pkg_file)

        num_system_entries = read_uint16_be(pkg_file)

        file_table_offset = read_uint32_be(pkg_file)

        main_entries_data_size = read_uint32_be(pkg_file)

        body_offset = read_uint32_be(pkg_file)
        body_size = read_uint32_be(pkg_file)

        content_offset = read_uint32_be(pkg_file)
        content_size = read_uint32_be(pkg_file)

        content_id = read_cstring(pkg_file)
        if len(content_id) != CONTENT_ID_SIZE:
            raise MyError('invalid content id')

        main_entries1_digest =
        main_entries2_digest =
        digest_table_digest =
        body_digest =

        content_digest =
        content_one_block_digest =
        table_entries = []
        table_entries_map = {}
        for i in xrange(num_table_entries):
            entry = FileTableEntry()
            table_entries_map[entry.type] = len(table_entries)
        entry_names = None
        entry_digests = None
        for i in xrange(num_table_entries):
            entry = table_entries[i]
            if entry.type == ENTRY_TYPE_NAME_TABLE:
                data =
                if data and len(data) > 0:
                    data = StringIO(data)
                    entry_names = []
                    c =
                    if ord(c) == 0:
                        while True:
                            name = read_cstring(data)
                            if not name:
                        raise MyError('weird name table format')
        entry_name_index = 0
        for i in xrange(num_table_entries):
            entry = 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_names[entry_name_index]
                    entry_name_index += 1
                    raise MyError('entry name index out of bounds')
            elif entry.type in ENTRY_TABLE_MAP:
       = ENTRY_TABLE_MAP[entry.type]
            if entry.type == ENTRY_TYPE_DIGEST_TABLE and entry_digests is None:
                entry_digests =
        data = ''
            entry = table_entries[table_entries_map[entry_type]]
            data +=
        computed_main_entries1_digest = sha256(data)
        data = ''
        for entry_type in [ENTRY_TYPE_0x800, ENTRY_TYPE_0x200, ENTRY_TYPE_0x180, ENTRY_TYPE_META_TABLE]:
            entry = table_entries[table_entries_map[entry_type]]
            size = entry.size if entry_type != ENTRY_TYPE_META_TABLE else num_system_entries * META_ENTRY_SIZE
            data +=
        computed_main_entries2_digest = sha256(data)
        entry = table_entries[table_entries_map[ENTRY_TYPE_DIGEST_TABLE]]
        data =
        computed_digest_table_digest = sha256(data)

        body =
        computed_body_digest = sha256(body)
        computed_entry_digests = '\x00' * SHA256_HASH_SIZE
        for i in xrange(num_table_entries):
            entry = table_entries[i]
            if entry.type == ENTRY_TYPE_DIGEST_TABLE:
            data =
            computed_entry_digests += sha256(data)
        for i in xrange(num_table_entries):
            entry = table_entries[i]
            name = if 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):
            with open(file_path, 'wb') as entry_file:
                data =
        block_size = 0x10000
        num_blocks = 1 + int((content_size - 1) / block_size) if content_size > 0 else 0

        data =
        computed_content_one_block_digest = sha256(data)
        hash_context = hashlib.sha256()
        bytes_left = content_size
        for i in xrange(num_blocks):
            current_size = block_size if bytes_left > block_size else bytes_left
            data =
            bytes_left -= block_size
        computed_content_digest = hash_context.digest()
        is_digests_valid = computed_main_entries1_digest == main_entries1_digest
        is_digests_valid = is_digests_valid and computed_main_entries2_digest == main_entries2_digest
        is_digests_valid = is_digests_valid and computed_digest_table_digest == digest_table_digest
        is_digests_valid = is_digests_valid and computed_body_digest == body_digest
        is_digests_valid = is_digests_valid and computed_entry_digests == entry_digests
        is_digests_valid = is_digests_valid and computed_content_digest == content_digest
        is_digests_valid = is_digests_valid and computed_content_one_block_digest == content_one_block_digest
            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(num_table_entries)
        print 'Entry table offset: 0x{0:08X}'.format(file_table_offset)
        print '     Digest status: {0}'.format('OK' if is_digests_valid else 'FAIL')
        if num_table_entries > 0:
            print 'Table entries:'
            for i in xrange(num_table_entries):
                entry = 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 is not None:
                    print '         Name: {0}'.format(
                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)
except IOError:
    print 'error: i/o error during processing'
except MyError as e:
    print 'error: {0}', e.message
    print 'error: unexpected error:', sys.exc_info()[0]
Note: Use '00000000000000000000000000000000' for the PKG Gen password in case others need to ever unpack it, then everyone will know the PKG password.
Here are some more PlayStation 4 PKG (Package) file guides / tutorials for those interested:
Finally, below is a How To Unpack PKG Files Tutorial from 0x199 as follows:

With the method of running PKG files without the need of a license soon coming from flat_z, I'm going to show you today how you can unpack PKG files and view their contents without the need of a exploited PS4.

  • Python installed on your PC (install it here)

First off, let me mention you will need the full PKG file. So if your game has multiple PKG files, you will need to merge it first to make it 1 full PKG file. Here's a tutorial on how you can do that.

This method does not unpack everything from the PKG file, because the PFS block is encrypted. We will first need the key for it.

Let's do this
  1. Download the script created by flat_z here:
  2. Put this script in a folder on your desktop and call the folder "unpkg" without the quotations.
  3. In the unpkg folder, make a new folder called "output" without the quotations.
  4. Download a PKG file (PS4 PKG Finder) you want to unpack.
  5. Open command prompt (CMD) and CD to the folder. Example: cd C:/Users/John/Desktop/unpkg
  6. Type PKG_FILENAME.pkg C:/Users/John/Desktop/unpkg/output
  7. Hit enter and go to the output folder to see the extracted PKG file contents.
Props to
  • flat_z for developing this script
  • ZeraTron for reminding me about this and explaining how to run it
  • PSDevwiki for hosting the script
Cheers to @B7U3 C50SS and @Ensight, @VultraAID and @mcmrc1 in the PSXHAX Shoutbox for the heads up! :D
PS4 PKG Unpacker.jpg
:idea: Reminder: Those without a Verified Badge yet on Discord to access the private areas we recommend Joining Us! Why? The waiting process takes a week for new Members, and there's a lot we're unable to share on public forums including the latest PS4 PKG Games. 🏴‍☠️


Not open for further replies.


Senior Member
Hectic 1 step closer

ive been using psx downloadhelper 1.8 to actually download and play psn games through my ps4 but its sooooooo slow. using the program itself im able to go online and play online still on 3.50. i managed to dl a killstrain which is over 2gb in 3 minutes but now trying to download singstar at 258mb with 10m remaining and still going :(

edit- didnt realise i was dl'ing :rolleyes:

Correct me if im wrong but once this works we will be to dl share full games off psn


Senior Member
There are a few questions that are puzzling my mind. I have been following the ps3 scene for 6-7 years! And right now I know that it is slowly dying.

However the release of this psn pkg unpacker... What does it mean ? Why would spencer port it to the ps4, so the ps4 can decrypt your pkgs ?

Also if we can fix license on ps4 this would mean backups :eek:
Not open for further replies.
Recent Articles
Minecraft Dungeons Battles Onto New PS4 Game Releases Next Week
Inspired by classic dungeon crawlers, get ready to begin a new quest in the action adventure game Minecraft Dungeons, set in the unmistakable blocky Minecraft universe next week on PlayStation 4...
SpecterDev Shares Low-Level Details on Porting MUSL to PS4
Since the OpenOrbis PlayStation 4 Toolchain release and related guides on his YouTube Channel, PS4 scene developer @SpecterDev of (Patreon) shared via Twitter a Blog Post detailing...
Sony's Days of Play 2020 Sale Offers Deals on Games and More
With Memorial Day weekend and the unofficial start of summer upon us things are heating up outside as well as in Sony's Days of Play 2020 sale with deals on PlayStation games, accessories, PS Now...
Super Console Wars 1.0 PS4 Homebrew Game PKG by Lapy & Acekone1!
A few weeks back we saw PS4-Xplorer File Manager Theme and Avatar Maker followed by a PS4-Xplorer 1.19 update, and today PlayStation 4 homebrew developer @Lapy returns via Twitter with designer...