Skip to content

Instantly share code, notes, and snippets.

@idlesauce
Last active January 28, 2026 23:17
Show Gist options
  • Select an option

  • Save idlesauce/2ded24b7b5ff296f21792a8202542aaa to your computer and use it in GitHub Desktop.

Select an option

Save idlesauce/2ded24b7b5ff296f21792a8202542aaa to your computer and use it in GitHub Desktop.
Usage: `py ps5_elf_sdk_downgrade.py path_to_file_or_folder`, then it will prompt you to enter the desired SDK version 1-10
import os
import struct
import shutil
import argparse
# ANSI color codes
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
RESET = '\033[0m'
PT_SCE_PROCPARAM = 0x61000001
PT_SCE_MODULE_PARAM = 0x61000002
SCE_PROCESS_PARAM_MAGIC = 0x4942524F
SCE_MODULE_PARAM_MAGIC = 0x3C13F4BF
SCE_PARAM_MAGIC_OFFSET = 0x8
SCE_PARAM_MAGIC_SIZE = 0x4
SCE_PARAM_PS4_SDK_OFFSET = 0x10
SCE_PARAM_PS5_SDK_OFFSET = 0x14
SCE_PARAM_PS_VERSION_SIZE = 0x4
PHT_OFFSET_OFFSET = 0x20
PHT_OFFSET_SIZE = 0x8
PHT_COUNT_OFFSET = 0x38
PHT_COUNT_SIZE = 0x2
PHDR_ENTRY_SIZE = 0x38
PHDR_TYPE_OFFSET = 0x0
PHDR_TYPE_SIZE = 0x4
PHDR_OFFSET_OFFSET = 0x8
PHDR_OFFSET_SIZE = 0x8
PHDR_FILESIZE_OFFSET = 0x20
PHDR_FILESIZE_SIZE = 0x8
ELF_MAGIC = b'\x7FELF'
PS4_FSELF_MAGIC = b'\x4F\x15\x3D\x1D'
PS5_FSELF_MAGIC = b'\x54\x14\xF5\xEE'
executable_extensions = [".bin", ".elf", ".self", ".prx", ".sprx"]
sdk_version_pairs = {
1: (0x01000050, 0x07590001),
2: (0x02000009, 0x08050001),
3: (0x03000027, 0x08540001),
4: (0x04000031, 0x09040001),
5: (0x05000033, 0x09590001),
6: (0x06000038, 0x10090001),
7: (0x07000038, 0x10590001),
8: (0x08000041, 0x11090001),
9: (0x09000040, 0x11590001),
10: (0x10000040, 0x12090001),
}
sdk_version_pairs_min = 1
sdk_version_pairs_max = 10
format_map = { 1: '<B', 2: '<H', 4: '<I', 8: '<Q' }
def read_le_int(file, offset, size):
file.seek(offset)
data = file.read(size)
if size not in format_map:
raise ValueError(f"Unsupported size: {size}. Must be 1, 2, 4, or 8 bytes.")
return struct.unpack(format_map[size], data)[0]
def write_le_int(file, offset, size, value):
if size not in format_map:
raise ValueError(f"Unsupported size: {size}. Must be 1, 2, 4, or 8 bytes.")
data = struct.pack(format_map[size], value)
file.seek(offset)
file.write(data)
def patch_file(file, ps5_sdk_version, ps4_version):
segment_count = read_le_int(file, PHT_COUNT_OFFSET, PHT_COUNT_SIZE)
pht_offset = read_le_int(file, PHT_OFFSET_OFFSET, PHT_OFFSET_SIZE)
for i in range(segment_count):
segment_type = read_le_int(file, pht_offset + i * PHDR_ENTRY_SIZE + PHDR_TYPE_OFFSET, PHDR_TYPE_SIZE)
if segment_type != PT_SCE_PROCPARAM and segment_type != PT_SCE_MODULE_PARAM:
continue
segment_offset = read_le_int(file, pht_offset + i * PHDR_ENTRY_SIZE + PHDR_OFFSET_OFFSET, PHDR_OFFSET_SIZE)
param_struct_size = read_le_int(file, segment_offset, 4)
if param_struct_size == 0 and segment_type == PT_SCE_MODULE_PARAM:
print(f"{YELLOW}[?] Module file has no param, skipping '{file.name}'{RESET}")
return True
if param_struct_size < SCE_PARAM_PS5_SDK_OFFSET + SCE_PARAM_PS_VERSION_SIZE:
print(f"{RED}[!] Unexpected param struct size 0x{param_struct_size:X} for file '{file.name}', aborting{RESET}")
return False
magic = read_le_int(file, segment_offset + SCE_PARAM_MAGIC_OFFSET, SCE_PARAM_MAGIC_SIZE)
if (segment_type == PT_SCE_PROCPARAM and magic != SCE_PROCESS_PARAM_MAGIC) or (segment_type == PT_SCE_MODULE_PARAM and magic != SCE_MODULE_PARAM_MAGIC):
print(f"{RED}[!] Invalid param magic 0x{magic:08X} for file '{file.name}', aborting{RESET}")
return False
og_ps5_sdk_version = read_le_int(file, segment_offset + SCE_PARAM_PS5_SDK_OFFSET, 4)
og_ps4_sdk_version = read_le_int(file, segment_offset + SCE_PARAM_PS4_SDK_OFFSET, 4)
write_le_int(file, segment_offset + SCE_PARAM_PS5_SDK_OFFSET, SCE_PARAM_PS_VERSION_SIZE, ps5_sdk_version)
write_le_int(file, segment_offset + SCE_PARAM_PS4_SDK_OFFSET, SCE_PARAM_PS_VERSION_SIZE, ps4_version)
print(f"{GREEN}[+] Patched file '{file.name}': PS5 SDK version 0x{og_ps5_sdk_version:08X} -> 0x{ps5_sdk_version:08X}, PS4 version 0x{og_ps4_sdk_version:08X} -> 0x{ps4_version:08X}{RESET}")
return True
# No process or module param segment found
return False
def process_file(file_path, create_backup, ps5_sdk_version, ps4_version):
with open(file_path, 'r+b') as file:
file.seek(0)
magic = file.read(4)
if magic != ELF_MAGIC:
if magic == PS4_FSELF_MAGIC or magic == PS5_FSELF_MAGIC:
print(f"{RED}[!] Aborting, file '{file_path}' is a signed file, this script expects unsigned ELF files{RESET}")
exit(1)
return
file.seek(0)
if create_backup and not os.path.exists(file_path + ".bak"):
shutil.copyfile(file_path, file_path + ".bak")
print(f"Backup created for '{file_path}'")
if not patch_file(file, ps5_sdk_version, ps4_version):
exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Patches the SDK version PS5 ELF files")
parser.add_argument("input", help="Path to an ELF file or a folder which will be processed recursively")
parser.add_argument("--ps5_ver", help="(optional) Custom PS5 SDK version to set (e.g. 0x04000031)", type=lambda x: int(x, 0))
parser.add_argument("--ps4_ver", help="(optional) Custom PS4 version to set (e.g. 0x09040001)", type=lambda x: int(x, 0))
parser.add_argument("--no-backup", action="store_true", help="(optional) Do not create backup .bak files")
args = parser.parse_args()
ps5_sdk_version = args.ps5_ver
if ps5_sdk_version and ps5_sdk_version > 0xFFFFFFFF:
print(f"{RED}[!] Invalid PS5 SDK version '{ps5_sdk_version}', must be a 32-bit value{RESET}")
exit(1)
ps4_version = args.ps4_ver
if ps4_version and ps4_version > 0xFFFFFFFF:
print(f"{RED}[!] Invalid PS4 version '{ps4_version}', must be a 32-bit value{RESET}")
exit(1)
if not ps5_sdk_version or not ps4_version:
# Select from known pairs
print(f"Enter target PS5 SDK version ({sdk_version_pairs_min}-{sdk_version_pairs_max}):")
try:
ver_input = int(input().strip())
if ver_input not in sdk_version_pairs:
raise ValueError()
except ValueError:
print("Invalid or unsupported version")
exit(1)
selected = sdk_version_pairs[ver_input]
if not ps5_sdk_version:
ps5_sdk_version = selected[0]
if not ps4_version:
ps4_version = selected[1]
print(f"Selected PS5 SDK version 0x{ps5_sdk_version:08X} and PS4 version 0x{ps4_version:08X}")
input_path = args.input
create_backup = not args.no_backup
if os.path.isfile(input_path):
process_file(input_path, create_backup, ps5_sdk_version, ps4_version)
elif os.path.isdir(input_path):
all_files = [
os.path.join(dp, f)
for dp, dn, filenames in os.walk(input_path)
for f in filenames if os.path.splitext(f)[1] in executable_extensions
]
for file_path in all_files:
process_file(file_path, create_backup, ps5_sdk_version, ps4_version)
else:
print(f"{RED}[!] Invalid input '{input_path}'{RESET}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment