Last active
March 12, 2025 11:43
-
-
Save idlesauce/2ded24b7b5ff296f21792a8202542aaa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import struct | |
import shutil | |
import argparse | |
PT_SCE_PROCPARAM = 0x61000001 | |
PT_SCE_MODULE_PARAM = 0x61000002 | |
SCE_PROCESS_PARAM_MAGIC = 0x4942524F | |
SCE_MODULE_PARAM_MAGIC = 0x3C13F4BF | |
SCE_PARAM_PS5_SDK_OFFSET = 0xC | |
SCE_PARAM_PS4_SDK_OFFSET = 0x8 | |
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 | |
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"] | |
def patch_file(file, ps5_sdk_version, ps4_version): | |
file.seek(PHT_COUNT_OFFSET) | |
pht_count_bytes = file.read(PHT_COUNT_SIZE) | |
segment_count = struct.unpack('<H', pht_count_bytes)[0] | |
file.seek(PHT_OFFSET_OFFSET) | |
pht_offset_bytes = file.read(PHT_OFFSET_SIZE) | |
pht_offset = struct.unpack('<Q', pht_offset_bytes)[0] | |
for i in range(segment_count): | |
file.seek(pht_offset + i * PHDR_ENTRY_SIZE) | |
file.seek(pht_offset + i * PHDR_ENTRY_SIZE + PHDR_TYPE_OFFSET) | |
segment_type_bytes = file.read(PHDR_TYPE_SIZE) | |
segment_type = struct.unpack('<I', segment_type_bytes)[0] | |
file.seek(pht_offset + i * PHDR_ENTRY_SIZE + PHDR_OFFSET_OFFSET) | |
segment_offset_bytes = file.read(PHDR_OFFSET_SIZE) | |
struct_start_offset = struct.unpack('<Q', segment_offset_bytes)[0] | |
file.seek(struct_start_offset) | |
t_param_magic = file.read(4) | |
param_magic = struct.unpack('<I', t_param_magic)[0] | |
if segment_type == PT_SCE_PROCPARAM: | |
if param_magic != SCE_PROCESS_PARAM_MAGIC: | |
struct_start_offset += 0x8 | |
file.seek(struct_start_offset) | |
t_param_magic = file.read(4) | |
param_magic = struct.unpack('<I', t_param_magic)[0] | |
if param_magic != SCE_PROCESS_PARAM_MAGIC: | |
raise Exception("Invalid magic") | |
elif segment_type == PT_SCE_MODULE_PARAM: | |
if param_magic != SCE_MODULE_PARAM_MAGIC: | |
struct_start_offset += 0x8 | |
file.seek(struct_start_offset) | |
t_param_magic = file.read(4) | |
param_magic = struct.unpack('<I', t_param_magic)[0] | |
if param_magic != SCE_MODULE_PARAM_MAGIC: | |
print(f"[?] Invalid module param magic for file '{file.name}', skipping") | |
continue | |
else: | |
continue | |
file.seek(struct_start_offset + SCE_PARAM_PS5_SDK_OFFSET) | |
original_ps5_sdk_version = struct.unpack('<I', file.read(4))[0] | |
file.seek(struct_start_offset + SCE_PARAM_PS5_SDK_OFFSET) | |
file.write(struct.pack('<I', ps5_sdk_version)) | |
print(f"Patched PS5 SDK version from 0x{original_ps5_sdk_version:08X} to 0x{ps5_sdk_version:08X} for file '{file.name}'") | |
# the game still launches without this, but might as well | |
file.seek(struct_start_offset + SCE_PARAM_PS4_SDK_OFFSET) | |
original_ps4_sdk_version = struct.unpack('<I', file.read(4))[0] | |
file.seek(struct_start_offset + SCE_PARAM_PS4_SDK_OFFSET) | |
file.write(struct.pack('<I', ps4_version)) | |
print(f"Patched PS4 SDK version from 0x{original_ps4_sdk_version:08X} to 0x{ps4_version:08X} for file '{file.name}'") | |
return True | |
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"Aborting, file '{file_path}' is a signed file, this script expects unsigned ELF files") | |
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}'") | |
patched = patch_file(file, ps5_sdk_version, ps4_version) | |
if patched: | |
print(f"Patched '{file_path}'") | |
else: | |
print(f"Failed to patch '{file_path}'") | |
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("--ps4_ver", required=True, help="PS4 version to set (e.g. 0x09040001)", type=lambda x: int(x, 0)) | |
parser.add_argument("--ps5_ver", required=True, help="PS5 SDK version to set (e.g. 0x04000031)", type=lambda x: int(x, 0)) | |
parser.add_argument("--no-backup", action="store_true", help="Do not create backup .bak files") | |
args = parser.parse_args() | |
input_path = args.input | |
create_backup = not args.no_backup | |
ps4_version = args.ps4_ver | |
ps5_sdk_version = args.ps5_ver | |
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"Invalid input '{input_path}'") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment