Category PS4 Guides and Tutorials       Thread starter PSXHAX       Start date Jul 18, 2020 at 10:02 AM       72,654       233            
Proceeding the initial PS4 BCP Method Backporting Guide and Stable Version of the PS4 6.72 Jailbreak by sleirsgoevy, today @zecoxao shared PS4 Backporting Instructions to Play 6.72 Games on 5.05 Firmware via Twitter for those continuing to wait cautiously in updating their PlayStation 4 Firmware while the legendary @flatz shared on Twitter a pair of Python scripts to downgrade PS4 ELFs / SFOs to lower Firmware to aid in the process! :geek:

Download: downgrade_elf.py / downgrade_sfo.py / PS4 RiPKiT / Modding / Cheat / Backport Tool / SELFUtil by Znullptr / Noob404_EZ_PS4_Downporter_1.0.7z

downgrade_elf.py
Code:
#!/usr/bin/env python2.7
# (c) flatz

import sys, os, struct
import argparse
import shutil

from hexdump import hexdump
from pprint import pprint

def align_up(x, alignment):
    return (x + (alignment - 1)) & ~(alignment - 1)

def align_down(x, alignment):
    return x & ~(alignment - 1)

def is_intervals_overlap(p1, p2):
    return p1[0] <= p2[1] and p1[1] <= p2[0]

def check_file_magic(f, expected_magic):
    old_offset = f.tell()
    try:
        magic = f.read(len(expected_magic))
    except:
        return False
    finally:
        f.seek(old_offset)
    return magic == expected_magic

def check_sdk_version(sdk_version):
    if len(sdk_version) != 10:
        return False
    parts = sdk_version.split('.', 2)
    if len(parts) != 3:
        return False
    try:
        lengths = [2, 3, 3]
        for i, n in enumerate(parts):
            if len(n) != lengths[i]:
                return False
            n = int(n, 10)
    except:
        return False
    return True

# *** version have 001 in "patch" field
def parse_sdk_version(sdk_version):
    major, minor, patch = sdk_version >> 24, (sdk_version >> 12) & 0xFFF, sdk_version & 0xFFF
    return major, minor, patch

def stringify_sdk_version(major, minor, patch):
    return '{0:02x}.{1:03x}.{2:03x}'.format(major, minor, patch)

def unstringify_sdk_version(sdk_version):
    major, minor, patch = map(lambda x: int(x, 16), sdk_version.split('.', 2))
    return major, minor, patch

def build_sdk_version(major, minor, patch):
    sdk_version = ((major & 0xFF) << 24) | ((minor & 0xFFF) << 12) | (patch & 0xFFF)
    return sdk_version

class ElfProgramHeader(object):
    FMT = '<2I6Q'

    PT_NULL = 0x0
    PT_LOAD = 0x1
    PT_DYNAMIC = 0x2
    PT_INTERP = 0x3
    PT_TLS = 0x7
    PT_SCE_DYNLIBDATA = 0x61000000
    PT_SCE_PROCPARAM = 0x61000001
    PT_SCE_MODULE_PARAM = 0x61000002
    PT_SCE_RELRO = 0x61000010
    PT_SCE_COMMENT = 0x6FFFFF00
    PT_SCE_VERSION = 0x6FFFFF01
    PT_GNU_EH_FRAME = 0x6474E550

    PF_X = 0x1
    PF_W = 0x2
    PF_R = 0x4
    PF_RX = PF_R | PF_X
    PF_RW = PF_R | PF_W

    def __init__(self):
        self.type = None
        self.offset = None
        self.vaddr = None
        self.paddr = None
        self.file_size = None
        self.mem_size = None
        self.flags = None
        self.align = None

    def load(self, f):
        data = f.read(struct.calcsize(ElfProgramHeader.FMT))
        if len(data) != struct.calcsize(ElfProgramHeader.FMT):
            return False
        self.type, self.flags, self.offset, self.vaddr, self.paddr, self.file_size, self.mem_size, self.align = struct.unpack(ElfProgramHeader.FMT, data)
        return True

    def save(self, f):
        data = struct.pack(ElfProgramHeader.FMT, self.type, self.flags, self.offset, self.vaddr, self.paddr, self.file_size, self.mem_size, self.align)
        if len(data) != struct.calcsize(ElfProgramHeader.FMT):
            return False
        f.write(data)
        return True

class ElfSectionHeader(object):
    FMT = '<2I4Q2I2Q'

    def __init__(self, fmt):
        self.name = None
        self.type = None
        self.flags = None
        self.addr = None
        self.offset = None
        self.size = None
        self.link = None
        self.info = None
        self.align = None
        self.entry_size = None

    def load(self, f):
        data = f.read(struct.calcsize(ElfProgramHeader.FMT))
        if len(data) != struct.calcsize(ElfProgramHeader.FMT):
            return False
        self.name, self.type, self.flags, self.addr, self.offset, self.size, self.link, self.info, self.align, self.entry_size = struct.unpack(ElfProgramHeader.FMT, data)
        return True

    def save(self, f):
        data = struct.pack(ElfProgramHeader.FMT, self.name, self.type, self.flags, self.addr, self.offset, self.size, self.link, self.info, self.align, self.entry_size)
        if len(data) != struct.calcsize(ElfProgramHeader.FMT):
            return False
        f.write(data)
        return True

class ElfFile(object):
    MAGIC = '\x7FELF'

    FMT = '<4s5B6xB2HI3QI6H'

    CLASS_NONE = 0
    CLASS_64 = 2

    DATA_NONE = 0
    DATA_LSB = 1

    VERSION_CURRENT = 1

    MACHINE_X86_64 = 0x3E

    TYPE_EXEC = 0x2
    TYPE_SCE_EXEC = 0xFE00
    TYPE_SCE_EXEC_ASLR = 0xFE10
    TYPE_SCE_DYNAMIC = 0xFE18

    def __init__(self):
        self.magic = None
        self.cls = None
        self.encoding = None
        self.version = None
        self.os_abi = None
        self.abi_version = None
        self.nident_size = None
        self.type = None
        self.machine = None
        self.version = None
        self.entry = None
        self.phdr_offset = None
        self.shdr_offset = None
        self.flags = None
        self.ehdr_size = None
        self.phdr_size = None
        self.phdr_count = None
        self.shdr_size = None
        self.shdr_count = None
        self.shdr_strtable_idx = None

        self.phdrs = None
        self.shdrs = None

    def check(self, f):
        old_offset = f.tell()
        try:
            result = check_file_magic(f, ElfFile.MAGIC)
        except:
            return False
        finally:
            f.seek(old_offset)
        return result

    def load(self, f):
        data = f.read(struct.calcsize(ElfFile.FMT))
        if len(data) != struct.calcsize(ElfFile.FMT):
            print('error: unable to read header')
            return False

        self.magic, self.cls, self.encoding, self.legacy_version, self.os_abi, self.abi_version, self.nident_size, self.type, self.machine, self.version, self.entry, self.phdr_offset, self.shdr_offset, self.flags, self.ehdr_size, self.phdr_size, self.phdr_count, self.shdr_size, self.shdr_count, self.shdr_strtable_idx = struct.unpack(ElfFile.FMT, data)
        if self.magic != ElfFile.MAGIC:
            print('error: invalid magic: 0x{0:08X}'.format(self.magic))
            return False
        if self.encoding != ElfFile.DATA_LSB:
            print('error: unsupported encoding: 0x{0:02X}'.format(self.encoding))
            return False
        if self.legacy_version != ElfFile.VERSION_CURRENT:
            raise Exception('Unsupported version: 0x{0:x}'.format(self.version))
        if self.cls != ElfFile.CLASS_64:
            print('error: unsupported class: 0x{0:02X}'.format(self.cls))
            return False
        if self.type not in [ElfFile.TYPE_SCE_EXEC, ElfFile.TYPE_SCE_EXEC_ASLR, ElfFile.TYPE_SCE_DYNAMIC]:
            print('error: unsupported type: 0x{0:04X}'.format(self.type))
            return False
        if self.machine != ElfFile.MACHINE_X86_64:
            print('error: unexpected machine: 0x{0:X}'.format(self.machine))
            return False
        if self.ehdr_size != struct.calcsize(ElfFile.FMT):
            print('error: invalid elf header size: 0x{0:X}'.format(self.ehdr_size))
            return False
        if self.phdr_size > 0 and self.phdr_size != struct.calcsize(ElfProgramHeader.FMT):
            print('error: invalid program header size: 0x{0:X}'.format(self.phdr_size))
            return False
        if self.shdr_size > 0 and self.shdr_size != struct.calcsize(ElfSectionHeader.FMT):
            print('error: invalid section header size: 0x{0:X}'.format(self.shdr_size))
            return False

        self.phdrs = []
        for i in xrange(self.phdr_count):
            phdr = ElfProgramHeader()
            f.seek(self.phdr_offset + i * self.phdr_size)
            if not phdr.load(f):
                print('error: unable to load program header #{0}'.format(i))
                return False
            self.phdrs.append(phdr)

        self.shdrs = []
        #if self.shdr_size > 0:
        #    for i in xrange(self.shdr_count):
        #        shdr = ElfSectionHeader()
        #        f.seek(self.shdr_offset + i * self.shdr_size)
        #        if not shdr.load(f):
        #            print('error: unable to load section header #{0}'.format(i))
        #            return False
        #        self.shdrs.append(shdr)

        return True

    def save_hdr(self, f):
        data = struct.pack(ElfFile.FMT, self.magic, self.cls, self.encoding, self.legacy_version, self.os_abi, self.abi_version, self.nident_size, self.type, self.machine, self.version, self.entry, self.phdr_offset, self.shdr_offset, self.flags, self.ehdr_size, self.phdr_size, self.phdr_count, self.shdr_size, self.shdr_count, self.shdr_strtable_idx)
        if len(data) != struct.calcsize(ElfFile.FMT):
            print('error: unable to save header')
            return False
        f.write(data)

        for i, phdr in enumerate(self.phdrs):
            f.seek(self.phdr_offset + i * self.phdr_size)
            if not phdr.save(f):
                print('error: unable to save program header #{0}'.format(i))
                return False

        for i, shdr in enumerate(self.shdrs):
            f.seek(self.shdr_offset + i * self.shdr_size)
            if not shdr.save(f):
                print('error: unable to save section header #{0}'.format(i))
                return False

        return True

    def get_phdr_by_type(self, type):
        for i, phdr in enumerate(elf.phdrs):
            if phdr.type == type:
                return phdr
        return None

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        self.print_help()
        sys.stderr.write('\nerror: {0}\n'.format(message))
        sys.exit(2)

parser = MyParser(description='elf downgrader tool')
parser.add_argument('input', type=str, help='old file')
parser.add_argument('output', type=str, help='new file')
parser.add_argument('--verbose', action='store_true', default=False, help='show details')
parser.add_argument('--***-version', type=str, required=True, default='06.200.001', help='needed *** version')

if len(sys.argv) == 1:
    parser.print_usage()
    sys.exit(1)

args = parser.parse_args()

input_file_path = args.input
if not os.path.isfile(input_file_path):
    parser.error('invalid input file: {0}'.format(input_file_path))

output_file_path = args.output
if os.path.exists(output_file_path) and not os.path.isfile(output_file_path):
    parser.error('invalid output file: {0}'.format(output_file_path))

if args.sdk_version and not check_sdk_version(args.sdk_version):
    parser.error('bad *** version')

shutil.copyfile(input_file_path, output_file_path)

if args.verbose:
    print('processing elf file: {0}'.format(output_file_path))
with open(output_file_path, 'r+b') as f:
    elf = ElfFile()
    if not elf.check(f):
        print('error: invalid elf file format')
        sys.exit(1)
    if not elf.load(f):
        print('error: unable to load elf file')
        sys.exit(1)

    #
    # Fixing proc/module param structure.
    #

    if elf.type in [ElfFile.TYPE_SCE_EXEC, ElfFile.TYPE_SCE_EXEC_ASLR]:
        needed_type = ElfProgramHeader.PT_SCE_PROCPARAM
        param_magic = 'ORBI'
        if args.verbose:
            print('executable file detected')
    elif elf.type == ElfFile.TYPE_SCE_DYNAMIC:
        needed_type = ElfProgramHeader.PT_SCE_MODULE_PARAM
        param_magic = '\xBF\xF4\x13\x3C'
        if args.verbose:
            print('module file detected')
    else:
        print('error: unsupported elf type')
        sys.exit(1)

    major, minor, patch = unstringify_sdk_version(args.sdk_version)
    new_sdk_version = build_sdk_version(major, minor, patch)
    new_sdk_version_str = stringify_sdk_version(major, minor, patch)
    if args.verbose:
        print('wanted *** version: {0}'.format(new_sdk_version_str))

    if args.verbose:
        print('searching for {0} param segment...'.format('proc' if needed_type == ElfProgramHeader.PT_SCE_PROCPARAM else 'module'))
    phdr = elf.get_phdr_by_type(needed_type)
    if phdr is not None:
        if args.verbose:
            print('parsing param structure...')
        f.seek(phdr.offset)
        data = f.read(phdr.file_size)
        if len(data) != phdr.file_size:
            print('error: insufficient data read')
            sys.exit(1)
        param_size, = struct.unpack('<I', data[0x0:0x4])
        if param_size < 0x14:
            print('error: param structure is too small')
            sys.exit(1)
        data = data[:param_size]
        if data[0x8:0xC] != param_magic:
            print('error: unexpected param structure format')
            sys.exit(1)

        old_sdk_version, = struct.unpack('<I', data[0x10:0x14])
        major, minor, patch = parse_sdk_version(old_sdk_version)
        old_sdk_version_str = stringify_sdk_version(major, minor, patch)
        if args.verbose:
            print('*** version: {0}'.format(old_sdk_version_str))

        if old_sdk_version > new_sdk_version:
            if args.verbose:
                print('fixing param structure...')
            f.seek(phdr.offset + 0x10)
            f.write(struct.pack('<I', new_sdk_version))
    else:
        print('warning: param segment not found (elf from old ***?)')

    #
    # Removing memory holes in PHDRs.
    # Prevents error on old kernel versions: uncountigous RELRO and DATA segments
    #

    if new_sdk_version < 0x06000000: # less than 6.00 fw
        segs = []
        for i, phdr in enumerate(elf.phdrs):
            if phdr.type not in [ElfProgramHeader.PT_LOAD, ElfProgramHeader.PT_SCE_RELRO]:
                continue
            if phdr.type == ElfProgramHeader.PT_LOAD and phdr.flags == ElfProgramHeader.PF_RX:
                #print('skipping text segment...')
                continue
            #print('type:0x{0:X} vaddr:0x{1:X} paddr:0x{2:X} file_size:0x{3:X} mem_size:0x{4:X} align:0x{5:X}'.format(phdr.type, phdr.vaddr, phdr.paddr, phdr.file_size, phdr.mem_size, phdr.align))
            segs.append(phdr)

        #for i, phdr in enumerate(segs):
        #    print('vaddr:0x{0:X} mem_size:0x{1:X}'.format(phdr.vaddr, phdr.mem_size))

        segs.sort(key=lambda x: (x.vaddr, -(x.vaddr + x.mem_size)))

        i, count = 0, len(segs)
        while i < count:
            if i > 0 and (segs[i].vaddr >= segs[i - 1].vaddr and (segs[i].vaddr + segs[i].mem_size <= segs[i - 1].vaddr + segs[i - 1].mem_size)):
                #print('removing seg vaddr:0x{0:X} mem_size:0x{1:X}'.format(segs[i].vaddr, segs[i].mem_size))
                #print('  previous seg vaddr:0x{0:X} mem_size:0x{1:X}'.format(segs[i - 1].vaddr, segs[i - 1].mem_size))
                segs = segs[:i] + segs[i + 1:]
                count -= 1
                continue
            i += 1

        count = len(segs)
        has_changes = False
        for i in xrange(count):
            mem_size_aligned = align_up(segs[i].mem_size, 0x4000)
            if (i + 1) < count and (segs[i].vaddr + mem_size_aligned) < segs[i + 1].vaddr:
                segs[i].mem_size = segs[i + 1].vaddr - segs[i].vaddr
                has_changes = True

        #print('')

        #for i, phdr in enumerate(segs):
        #    #print('type:0x{0:X} vaddr:0x{1:X} paddr:0x{2:X} file_size:0x{3:X} mem_size:0x{4:X} align:0x{5:X}'.format(phdr.type, phdr.vaddr, phdr.paddr, phdr.file_size, phdr.mem_size, phdr.align))
        #    print('vaddr:0x{0:X} mem_size:0x{1:X} end_vaddr:0x{2:X}'.format(phdr.vaddr, phdr.mem_size, phdr.vaddr + phdr.mem_size))

        if has_changes:
            if args.verbose:
                print('removing memory holes...')

    #
    # Fixing version information in version segment.
    #

    if args.verbose:
        print('searching for version segment...')
    phdr = elf.get_phdr_by_type(ElfProgramHeader.PT_SCE_VERSION)
    if phdr is not None:
        if args.verbose:
            print('parsing library list...')
        f.seek(phdr.offset)
        data = f.read(phdr.file_size)
        if len(data) != phdr.file_size:
            print('error: insufficient data read')
            sys.exit(1)

        has_changes = False
        if phdr.file_size > 0:
            offset = 0
            while offset < phdr.file_size:
                length = ord(data[offset])
                offset += 1
                name = data[offset:offset + length]
                name, old_sdk_version = name.split(':', 1)
                if len(old_sdk_version) != struct.calcsize('I'):
                    print('error: unexpected library list entry format')
                    sys.exit(1)

                old_sdk_version, = struct.unpack('>I', old_sdk_version)
                major, minor, patch = parse_sdk_version(old_sdk_version)
                old_sdk_version_str = stringify_sdk_version(major, minor, patch)
                if args.verbose:
                    print('{0} (*** version: {1})'.format(name, old_sdk_version_str))

                if old_sdk_version > new_sdk_version:
                    data = data[:offset] + name + ':' + struct.pack('>I', new_sdk_version) + data[offset + length:]
                    has_changes = True

                offset += length

            if has_changes:
                if args.verbose:
                    print('fixing *** versions in library list...')
                f.seek(phdr.offset)
                f.write(data)
    else:
        if args.verbose:
            print('version segment not found')

    #
    # Fixing section headers.
    #

    if args.verbose:
        print('fixing elf header...')

    # Prevents error in orbis-bin:
    #   Section header offset (XXX) exceeds file size (YYY).
    elf.shdr_offset = 0
    elf.shdr_count = 0

    f.seek(0)
    if not elf.save_hdr(f):
        print('error: unable to save elf file')
        sys.exit(1)

if args.verbose:
    print('done')
downgrade_sfo.py
Code:
#!/usr/bin/env python2.7
# (c) flatz

import sys, os, struct
import argparse
import re

def align_up(x, alignment):
    return (x + (alignment - 1)) & ~(alignment - 1)

def align_down(x, alignment):
    return x & ~(alignment - 1)

def check_file_magic(f, expected_magic):
    old_offset = f.tell()
    try:
        magic = f.read(len(expected_magic))
    except:
        return False
    finally:
        f.seek(old_offset)
    return magic == expected_magic

def read_cstring(f):
    s = ''
    while True:
        c = f.read(1)
        if not c:
            return False
        if c == '\0':
            break
        s += c
    return s

def check_sdk_version(sdk_version):
    if len(sdk_version) != 10:
        return False
    parts = sdk_version.split('.', 2)
    if len(parts) != 3:
        return False
    try:
        lengths = [2, 3, 3]
        for i, n in enumerate(parts):
            if len(n) != lengths[i]:
                return False
            n = int(n, 10)
    except:
        return False
    return True

# *** version have 001 in "patch" field
def parse_sdk_version(sdk_version):
    major, minor, patch = sdk_version >> 24, (sdk_version >> 12) & 0xFFF, sdk_version & 0xFFF
    return major, minor, patch

def stringify_sdk_version(major, minor, patch):
    return '{0:02x}.{1:03x}.{2:03x}'.format(major, minor, patch)

def unstringify_sdk_version(sdk_version):
    major, minor, patch = map(lambda x: int(x, 16), sdk_version.split('.', 2))
    return major, minor, patch

def build_sdk_version(major, minor, patch):
    sdk_version = ((major & 0xFF) << 24) | ((minor & 0xFFF) << 12) | (patch & 0xFFF)
    return sdk_version

class SfoFile(object):
    FMT = '<4sIIII'

    MAGIC = '\x00PSF'

    FMT_STRING_SPECIAL = 0x004
    FMT_STRING         = 0x204
    FMT_UINT32         = 0x404

    class Entry(object):
        FMT = '<HHIII'

        def __init__(self):
            self.key_offset = None
            self.format = None
            self.size = None
            self.max_size = None
            self.data_offset = None

            self.key = None
            self.value = None

        def load(self, f):
            data = f.read(struct.calcsize(SfoFile.Entry.FMT))
            if len(data) != struct.calcsize(SfoFile.Entry.FMT):
                print('error: unable to read entry')
                return False

            self.key_offset, self.format, self.size, self.max_size, self.data_offset = struct.unpack(SfoFile.Entry.FMT, data)

            return True

        def save(self, f):
            data = struct.pack(SfoFile.Entry.FMT, self.key_offset, self.format, self.size, self.max_size, self.data_offset)
            if len(data) != struct.calcsize(SfoFile.Entry.FMT):
                print('error: unable to save entry')
                return False
            f.write(data)

            return True

    def __init__(self):
        self.start_offset = None
        self.magic = None
        self.version = None
        self.key_table_offset = None
        self.data_table_offset = None
        self.num_entries = None
        self.entries = None
        self.entry_map = None

    def check(self, f):
        old_offset = f.tell()
        try:
            result = check_file_magic(f, SfoFile.MAGIC)
        except:
            return False
        finally:
            f.seek(old_offset)
        return result

    def load(self, f):
        self.start_offset = f.tell()

        data = f.read(struct.calcsize(SfoFile.FMT))
        if len(data) != struct.calcsize(SfoFile.FMT):
            print('error: unable to read header')
            return False

        self.magic, self.version, self.key_table_offset, self.data_table_offset, self.num_entries = struct.unpack(SfoFile.FMT, data)
        self.entries = []
        for i in xrange(self.num_entries):
            entry = SfoFile.Entry()
            if not entry.load(f):
                return False
            assert entry.max_size >= entry.size
            self.entries.append(entry)

        self.entry_map = {}
        for i, entry in enumerate(self.entries):
            f.seek(self.start_offset + self.key_table_offset + entry.key_offset)
            entry.key = read_cstring(f)
            f.seek(self.start_offset + self.data_table_offset + entry.data_offset)
            entry.value = f.read(entry.max_size)
            entry.value = entry.value[:entry.size]
            self.entry_map[entry.key] = entry

        return True

    def fixup(self):
        self.num_entries = len(self.entries)

        self.key_table_offset = struct.calcsize(SfoFile.FMT)
        self.key_table_offset += self.num_entries * struct.calcsize(SfoFile.Entry.FMT)
        offset = 0
        for i, entry in enumerate(self.entries):
            entry.key_offset = offset
            offset += len(entry.key) + 1

        self.data_table_offset = self.key_table_offset + align_up(offset, 0x4)
        offset = 0
        for i, entry in enumerate(self.entries):
            entry.data_offset = offset
            assert len(entry.value) <= entry.max_size
            offset += entry.max_size

    def save(self, f):
        data = struct.pack(SfoFile.FMT, self.magic, self.version, self.key_table_offset, self.data_table_offset, self.num_entries)
        if len(data) != struct.calcsize(SfoFile.FMT):
            print('error: unable to save header')
            return False
        f.write(data)

        for i, entry in enumerate(self.entries):
            if not entry.save(f):
                return False

        for i, entry in enumerate(self.entries):
            f.seek(self.start_offset + self.key_table_offset + entry.key_offset)
            data = entry.key + '\0'
            f.write(data)
            f.seek(self.start_offset + self.data_table_offset + entry.data_offset)
            data = entry.value.ljust(entry.max_size, '\0')
            f.write(data)

        return True

    def get_entry(self, key):
        return self.entry_map[key] if key in self.entry_map else None

    def dump(self):
        for i, entry in enumerate(self.entries):
            assert entry.format in [SfoFile.FMT_STRING_SPECIAL, SfoFile.FMT_STRING, SfoFile.FMT_UINT32]
            if entry.format == SfoFile.FMT_STRING_SPECIAL:
                value = entry.value[:entry.size]
            elif entry.format == SfoFile.FMT_STRING:
                value = entry.value[:entry.size - 1]
            elif entry.format == SfoFile.FMT_UINT32:
                assert entry.size in [1, 2, 4, 8]
                if entry.size == struct.calcsize('B'):
                    value = '0x{0:02X}'.format(struct.unpack('<B', entry.value)[0])
                elif entry.size == struct.calcsize('H'):
                    value = '0x{0:04X}'.format(struct.unpack('<H', entry.value)[0])
                elif entry.size == struct.calcsize('I'):
                    value = '0x{0:08X}'.format(struct.unpack('<I', entry.value)[0])
                elif entry.size == struct.calcsize('Q'):
                    value = '0x{0:016X}'.format(struct.unpack('<Q', entry.value)[0])
            print('{0} = {1}'.format(entry.key, value))

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        self.print_help()
        sys.stderr.write('\nerror: {0}\n'.format(message))
        sys.exit(2)

parser = MyParser(description='sfo tool')
parser.add_argument('input', type=str, help='old file')
parser.add_argument('output', type=str, help='new file')
parser.add_argument('--verbose', action='store_true', default=False, help='show details')
parser.add_argument('--dump', action='store_true', default=False, help='dump entries')
parser.add_argument('--***-version', type=str, default=None, help='needed *** version')
parser.add_argument('--system-version', type=str, default=None, help='needed *** version')

if len(sys.argv) == 1:
    parser.print_usage()
    sys.exit(1)

args = parser.parse_args()

input_file_path = args.input
if not os.path.isfile(input_file_path):
    parser.error('invalid input file: {0}'.format(input_file_path))

output_file_path = args.output
if os.path.exists(output_file_path) and not os.path.isfile(output_file_path):
    parser.error('invalid output file: {0}'.format(output_file_path))

if args.sdk_version and not check_sdk_version(args.sdk_version):
    parser.error('bad *** version')
if args.system_version and not check_sdk_version(args.system_version):
    parser.error('bad system version')

if args.verbose:
    print('reading sfo file: {0}'.format(input_file_path))
with open(input_file_path, 'rb') as f:
    sfo = SfoFile()
    if not sfo.check(f):
        print('error: invalid sfo file format')
        sys.exit(1)
    if not sfo.load(f):
        print('error: unable to load sfo file')
        sys.exit(1)

if args.sdk_version is not None and 'PUBTOOLINFO' in sfo.entry_map:
    entry = sfo.entry_map['PUBTOOLINFO']
    assert entry.format == SfoFile.FMT_STRING
    sdk_ver_regexp = re.compile(r'sdk_ver=(\d{8})')
    matches = sdk_ver_regexp.search(entry.value)
    if matches is not None:
        start_pos, end_pos = matches.span(1)
        old_sdk_version = int(entry.value[start_pos:end_pos], 16)
        major, minor, patch = parse_sdk_version(old_sdk_version)
        old_sdk_version_str = stringify_sdk_version(major, minor, patch)
        if args.verbose:
            print('*** version: {0}'.format(old_sdk_version_str))
        major, minor, patch = unstringify_sdk_version(args.sdk_version)
        new_sdk_version = build_sdk_version(major, minor, patch)
        new_sdk_version_str = stringify_sdk_version(major, minor, patch)
        if old_sdk_version > new_sdk_version:
            print('fixing *** version...')
            if args.verbose:
                print('wanted *** version: {0}'.format(new_sdk_version_str))
            new_sdk_version = '{0:08X}'.format(new_sdk_version)
            assert len(new_sdk_version) == (end_pos - start_pos)
            entry.value = entry.value[:start_pos] + new_sdk_version + entry.value[end_pos:]

if args.system_version is not None and 'SYSTEM_VER' in sfo.entry_map:
    if not check_sdk_version(args.system_version):
        parser.error('error: bad *** version')
    entry = sfo.entry_map['SYSTEM_VER']
    assert entry.format == SfoFile.FMT_UINT32 and entry.size == struct.calcsize('I')
    old_system_version, = struct.unpack('<I', entry.value)
    major, minor, patch = parse_sdk_version(old_system_version)
    old_system_version_str = stringify_sdk_version(major, minor, patch)
    if args.verbose:
        print('system version: {0}'.format(old_system_version_str))
    major, minor, patch = unstringify_sdk_version(args.system_version)
    new_system_version = build_sdk_version(major, minor, patch)
    new_system_version_str = stringify_sdk_version(major, minor, patch)
    if old_system_version > new_system_version:
        print('fixing system version...')
        if args.verbose:
            print('wanted system version: {0}'.format(new_system_version_str))
        entry.value = struct.pack('<I', new_system_version)

if args.verbose:
    print('recalculating offsets...')
sfo.fixup()

if args.dump:
    print('dumping entries...')
    sfo.dump()

if args.verbose:
    print('writing sfo file: {0}'.format(output_file_path))
with open(output_file_path, 'wb') as f:
    if not sfo.save(f):
        print('error: unable to save sfo file')
        sys.exit(1)

if args.verbose:
    print('done')
And from Pastebin.com on the PS4 Backporting Instructions to Play 6.72 Games on 5.05 Firmware:

Instructions on how to backport

Step 1: The eboot


Unfself it and use readelf -a on it~
you should see this section
Code:
LOOS+0x1000010 0x00000000063c4000 0x0000000006400000 0x0000000006400000
               0x000000000083a100 0x000000000083a100  R      0x4000
Example for final fantasy vii remake demo

Align it upwards from 0x200000 bytes on 2nd Memsize, like so:
Code:
LOOS+0x1000010 0x00000000063c4000 0x0000000006400000 0x0000000006400000
               0x000000000083a100 0x0000000000a00000  R      0x4000
Then search for the *** Version in ORBI Place, for example in ff7r eboot the offset is at 0x6c00010
Code:
01 00 05 05 <- correct *** version
Then at the bottom of the eboot you can find the elf build name
Code:
PATHH���D���c:/j/workspace/B/b_BuPS4M/cw/End/Binaries/PS4/End-PS4-Shipping.self
After the PATH and name patch every single instance
Code:
05 05 00 01
Step 2: The modules

These are easy, at the bottom there's an *** version
Code:
05 05 00 01
Near end of module

Specifically for some modules, an extra step needs to be done:
Code:
Search for 18 00 00 00 00 00 00 00 BF F4 13 3C 01 00 00 00
After there is *** version, patch it
Code:
01 00 05 05
Some modules are also linked with others modules
Again at bottom
Code:
PATH0���*���C:/devel/projects/bink/build/Bink2PS4.prx
After
Code:
05 05 00 01
Every single instance

Step 3: The Param.sfo

This one is the most simple
Code:
sdk_ver=
After this put
Code:
05050001
ASCII values

Then there is a hidden one
Before the game's name put
Code:
00 00 05 05
This completes backporting

Spoiler: Related Tweets

How to Backport 6.72 Games to Run on 5.05

Finally, from zecoxao comes a Tutorial to Backport EA Titles with Flatz's Trick as follows:

Tools Required:
  • IDA Pro or Ghidra with proper PS4 loaders / tools (i'll be using IDA Pro 7.x with balika's loader for this)
  • Hexeditor of choice (I Use HxD)
  • Flatz's required functions
They are as follows:
Code:
7CxI50-xlCk
+OnbUs1CV0M
xmhnAoxN3Wk
pMxXhNozUX
Sometimes they have underscore behind them so:
Code:
_7CxI50-xlCk
_+OnbUs1CV0M
_xmhnAoxN3Wk
_pMxXhNozUX
Eboot of the game you want to fix (for example fifa 20) in ELF format, NOT FSELF!

The process:

Load your eboot with IDA and balika's loader, the process will be similar to this:

Tutorial to Backport EA Titles with Flatz's Trick 1.png

When it finishes loading go to the pink area at the bottom (the nids) under functions window:

Tutorial to Backport EA Titles with Flatz's Trick 2.png

Patch all these from:

Tutorial to Backport EA Titles with Flatz's Trick 3.png

So:
Code:
ff 25 72
ff 25 6a
ff 25 62
ff 25 5a
To:
Code:
31 C0 C3
Tutorial to Backport EA Titles with Flatz's Trick 4.png

Apply and your EA Eboot should work properly together with 5.05 backport tools.

This concludes the tutorial.

Credits:
  • flatz for the original discovery
  • Joonie for the implementation and POC in the scene
Well, since flatz's script does not work on elf generated by selfutil, this had to be done manually.. (removing mem hole ), unlike NFS heat, this does not seem to have save corruption issue.
for the mem hole, I did 600000, and here are the EA patches.
Code:
[7CxI50-xlCk] FF 25 DA A5 7B 01 >> 31 C0 C3 A5 7B 01
[OnbUs1CV0M] FF 25 EA A5 7B 01 >> 31 C0 C3 A5 7B 01
[pMxXhNozUX8] FF 25 DA A6 7B 01 >> 31 C0 C3 A6 7B 01
[xmhnAoxN3Wk] FF 25 62 A6 7B 01 >> 31 C0 C3 A6 7B 01
did you patch 25 modules + eboot? this is not an EA game and this shouldn't need those patches.
right.sprx is not needed since FPKG tool generates one every time you make a pkg.
small note: in the backporting tutorial, there were two things i left unoticed: the offset 0x28 of eboot ELF has to have 4 zeroes (start of section headers is 0) and the offset 0x3C has to have a zero (number of section headers is 0)
PS4 Backporting Instructions to Play 6.72 Games on 5.05 Firmware.jpg
 

Comments

Recent Articles
PS4 IPv6 UAF 6.70-6.72 Kernel Exploit with Patches, Maybe More Stable!
Since his PS4 Save Mounter Utility release, the PS4 6.20 ROP Execution Method, PS4 Webkit Bad_Hoist Exploit, 7.02 PS4 KEX, PS4 Webkit Exploit 6.72 Port, PS4 6.72 Jailbreak Exploit, Backporting PS4...
PS4 JSON-2-SHA1 Python Script for Game PKG Files by Hosamn
Following the JSON Format details, PS4 PKGs via SEN and the JSON Entitlement Grabber Add-on this weekend developer hosamn made available a PS4-JSON-2-SHA1 Python Script for use in checking the...
PKGDT: PS4 PKG Downgrade Tools GUI for Backporting Games by Gerfra
There's no shortage of options when seeking a Windows utility for PS4 Backporting games from 6.72 to 5.05 Firmware, with the latest being PKGDT which is a suite of PS4 PKG Downgrade Tools...
Noob404 1Click Downport Patch Maker to Downport Small PS4 PKGs
Here's the third of @noob404's recent PS4 downporting apps, following his 1Click PS4 Batch Downporter from earlier today the Noob404 1Click Downport Patch Maker is used to downport small PS4 PKGs...
Top