#!/usr/bin/env python3

import sys, os, struct, traceback
import hashlib, hmac
import argparse, re, string

def int_with_base_type(val):
    return int(val, 0)

def try_parse_int(x, base=0):
    try:
        return int(x, base) if isinstance(x, str) else int(x)
    except:
        return None

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

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

def ilog2(x):
    if x <= 0:
        raise ValueError('math domain error')
    return len(bin(x)) - 3

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 parse_version(version):
    major, minor, patch = (version >> 8) & 0xFF, version & 0xFF, 0
    major = 10 * (major >> 4) + (major & 0xF)
    minor = 10 * (minor >> 4) + (minor & 0xF)
    return '{0:d}.{1:02d}.{2:03d}'.format(major, minor, patch)

def sha256(data):
    return hashlib.sha256(data).digest()

def hmac_sha256(key, data):
    return hmac.new(key=key, msg=data, digestmod=hashlib.sha256).digest()

class ElfError(Exception):
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return repr(self.msg)

class ElfEHdr(object):
    FMT = b'<4s5B6xB'
    EX_FMT = b'<2HI3QI6H'

    MAGIC = b'\x7fELF'
    CLASS64 = 0x2
    DATA2LSB = 0x1
    EM_X86_64 = 0x3E
    EV_CURRENT = 0x1

    ET_EXEC = 0x2
    ET_SCE_EXEC = 0xFE00
    ET_SCE_EXEC_ASLR = 0xFE10
    ET_SCE_DYNAMIC = 0xFE18
    ET_SCE_PS5_SPECIFIC = 0xFE20  # Placeholder, replace with actual PS5 type if known

    def __init__(self):
        self.magic = None
        self.machine_class = None
        self.data_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.phoff = None
        self.shoff = None
        self.flags = None
        self.ehsize = None
        self.phentsize = None
        self.phnum = None
        self.shentsize = None
        self.shnum = None
        self.shstridx = None

    def load(self, f):
        if not check_file_magic(f, ElfEHdr.MAGIC):
            raise ElfError('Invalid magic.')

        self.magic, self.machine_class, self.data_encoding, self.version, self.os_abi, self.abi_version, self.nident_size = struct.unpack(ElfEHdr.FMT, f.read(struct.calcsize(ElfEHdr.FMT)))
        if self.machine_class != ElfEHdr.CLASS64 or self.data_encoding != ElfEHdr.DATA2LSB:
            raise ElfError('Unsupported class or data encoding.')
        self.type, self.machine, self.version, self.entry, self.phoff, self.shoff, self.flags, self.ehsize, self.phentsize, self.phnum, self.shentsize, self.shnum, self.shstridx = struct.unpack(ElfEHdr.EX_FMT, f.read(struct.calcsize(ElfEHdr.EX_FMT)))
        if self.machine != ElfEHdr.EM_X86_64 or self.version != ElfEHdr.EV_CURRENT:
            raise ElfError('Unsupported machine type or version.')
        if self.phentsize != struct.calcsize(ElfPHdr.FMT) or (self.shentsize > 0 and self.shentsize != struct.calcsize(ElfSHdr.FMT)):
            raise ElfError('Unsupported header entry size.')
        if self.type not in [ElfEHdr.ET_EXEC, ElfEHdr.ET_SCE_EXEC, ElfEHdr.ET_SCE_EXEC_ASLR, ElfEHdr.ET_SCE_DYNAMIC, ElfEHdr.ET_SCE_PS5_SPECIFIC]:
            raise ElfError('Unsupported type.')

    def save(self, f):
        f.write(struct.pack(ElfEHdr.FMT, self.magic, self.machine_class, self.data_encoding, self.version, self.os_abi, self.abi_version, self.nident_size))
        f.write(struct.pack(ElfEHdr.EX_FMT, self.type, self.machine, self.version, self.entry, self.phoff, self.shoff, self.flags, self.ehsize, self.phentsize, self.phnum, self.shentsize, self.shnum, self.shstridx))

    def has_segments(self):
        return self.phentsize > 0 and self.phnum > 0

    def has_sections(self):
        return self.shentsize > 0 and self.shnum > 0

class ElfPHdr(object):
    FMT = b'<2I6Q'

    PT_LOAD = 0x1
    PT_DYNAMIC = 0x2
    PT_INTERP = 0x3
    PT_TLS = 0x7
    PT_GNU_EH_FRAME = 0x6474E550
    PT_GNU_STACK = 0x6474E551
    PT_SCE_RELA = 0x60000000
    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_SCE_PS5_NEW_TYPE = 0x61000003  # Placeholder, replace with actual PS5 type if known

    PF_EXEC = 0x1
    PF_WRITE = 0x2
    PF_READ = 0x4
    PF_READ_EXEC = PF_READ | PF_EXEC
    PF_READ_WRITE = PF_READ | PF_WRITE

    def __init__(self, idx):
        self.idx = idx
        self.type = None
        self.flags = None
        self.offset = None
        self.vaddr = None
        self.paddr = None
        self.filesz = None
        self.memsz = None
        self.align = None

    def load(self, f):
        self.type, self.flags, self.offset, self.vaddr, self.paddr, self.filesz, self.memsz, self.align = struct.unpack(ElfPHdr.FMT, f.read(struct.calcsize(ElfPHdr.FMT)))

    def save(self, f):
        f.write(struct.pack(ElfPHdr.FMT, self.type, self.flags, self.offset, self.vaddr, self.paddr, self.filesz, self.memsz, self.align))

    def name(self):
        if self.type == ElfPHdr.PT_LOAD:
            if (self.flags & ElfPHdr.PF_READ_EXEC) == ElfPHdr.PF_READ_EXEC:
                return '.text'
            elif (self.flags & ElfPHdr.PF_READ_WRITE) == ElfPHdr.PF_READ_WRITE:
                return '.data'
            else:
                return '.load_{0:02}'.format(self.idx)
        else:
            return {
                ElfPHdr.PT_DYNAMIC: '.dynamic',
                ElfPHdr.PT_INTERP: '.interp',
                ElfPHdr.PT_TLS: '.tls',
                ElfPHdr.PT_GNU_EH_FRAME: '.eh_frame_hdr',
                ElfPHdr.PT_SCE_DYNLIBDATA: '.sce_dynlib_data',
                ElfPHdr.PT_SCE_PROCPARAM: '.sce_process_param',
                ElfPHdr.PT_SCE_MODULE_PARAM: '.sce_module_param',
                ElfPHdr.PT_SCE_COMMENT: '.sce_comment',
                ElfPHdr.PT_SCE_PS5_NEW_TYPE: '.sce_ps5_new',
            }.get(self.type, None)

    def class_name(self):
        if (self.flags & ElfPHdr.PF_READ_EXEC) == ElfPHdr.PF_READ_EXEC:
            return 'CODE'
        else:
            return 'DATA'

class ElfSHdr(object):
    FMT = b'<2I4Q2I2Q'

    def __init__(self, idx):
        self.idx = idx
        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.entsize = None

    def load(self, f):
        self.name, self.type, self.flags, self.addr, self.offset, self.size, self.link, self.info, self.align, self.entsize = struct.unpack(ElfSHdr.FMT, f.read(struct.calcsize(ElfSHdr.FMT)))

    def save(self, f):
        f.write(struct.pack(ElfSHdr.FMT, self.name, self.type, self.flags, self.addr, self.offset, self.size, self.link, self.info, self.align, self.entsize))

class ElfFile(object):
    def __init__(self, **kwargs):
        self.ehdr = None
        self.phdrs = None
        self.shdrs = None
        self.file_size = None
        self.digest = None
        self.segments = None
        self.sections = None
        self.ignore_shdrs = 'ignore_shdrs' in kwargs and kwargs['ignore_shdrs']

    def load(self, f):
        start_offset = f.tell()
        data = f.read()
        self.file_size = len(data)
        self.digest = sha256(data)
        f.seek(start_offset)

        self.ehdr = ElfEHdr()
        self.ehdr.load(f)

        if self.ignore_shdrs:
            self.ehdr.shnum = 0

        self.phdrs = []
        self.segments = []
        if self.ehdr.has_segments():
            for i in range(self.ehdr.phnum):
                f.seek(start_offset + self.ehdr.phoff + i * self.ehdr.phentsize)
                phdr = ElfPHdr(i)
                phdr.load(f)
                self.phdrs.append(phdr)
                if phdr.filesz > 0:
                    f.seek(start_offset + phdr.offset)
                    data = f.read(phdr.filesz)
                else:
                    data = b''
                self.segments.append(data)

        self.shdrs = []
        self.sections = []
        if self.ehdr.has_sections():
            for i in range(self.ehdr.shnum):
                f.seek(start_offset + self.ehdr.shoff + i * self.ehdr.shentsize)
                shdr = ElfSHdr(i)
                shdr.load(f)
                self.shdrs.append(shdr)
                if shdr.size > 0:
                    f.seek(start_offset + shdr.offset)
                    data = f.read(shdr.size)
                else:
                    data = b''
                self.sections.append(data)

    def save(self, f, no_sections=False):
        start_offset = f.tell()

        self.ehdr.save(f)

        if not no_sections:
            if self.ehdr.has_sections():
                for i in range(self.ehdr.shnum):
                    f.seek(start_offset + self.ehdr.shoff + i * self.ehdr.shentsize)
                    shdr = self.shdrs[i]
                    shdr.save(f)

        if self.ehdr.has_segments():
            for i in range(self.ehdr.phnum):
                f.seek(start_offset + self.ehdr.phoff + i * self.ehdr.phentsize)
                phdr = self.phdrs[i]
                phdr.save(f)

DIGEST_SIZE = 0x20
SIGNATURE_SIZE = 0x100
DEFAULT_BLOCK_SIZE = 0x1000

SELF_CONTROL_BLOCK_TYPE_NPDRM = 0x3
SELF_NPDRM_CONTROL_BLOCK_CONTENT_ID_SIZE = 0x13
SELF_NPDRM_CONTROL_BLOCK_RANDOM_PAD_SIZE = 0xD

EMPTY_DIGEST = b'\0' * DIGEST_SIZE
EMPTY_SIGNATURE = b'\0' * SIGNATURE_SIZE

class SignedElfEntry(object):
    FMT = b'<4Q'

    PROPS_ORDER_SHIFT = 0
    PROPS_ORDER_MASK = 0x1
    PROPS_ENCRYPTED_SHIFT = 1
    PROPS_ENCRYPTED_MASK = 0x1
    PROPS_SIGNED_SHIFT = 2
    PROPS_SIGNED_MASK = 0x1
    PROPS_COMPRESSED_SHIFT = 3
    PROPS_COMPRESSED_MASK = 0x1
    PROPS_WINDOW_BITS_SHIFT = 8
    PROPS_WINDOW_BITS_MASK = 0x7
    PROPS_HAS_BLOCKS_SHIFT = 11
    PROPS_HAS_BLOCKS_MASK = 0x1
    PROPS_BLOCK_SIZE_SHIFT = 12
    PROPS_BLOCK_SIZE_MASK = 0xF
    PROPS_HAS_DIGESTS_SHIFT = 16
    PROPS_HAS_DIGESTS_MASK = 0x1
    PROPS_HAS_EXTENTS_SHIFT = 17
    PROPS_HAS_EXTENTS_MASK = 0x1
    PROPS_HAS_META_SEGMENT_SHIFT = 20
    PROPS_HAS_META_SEGMENT_MASK = 0x1
    PROPS_SEGMENT_INDEX_SHIFT = 20
    PROPS_SEGMENT_INDEX_MASK = 0xFFFF
    PROPS_DEFAULT_BLOCK_SIZE = 0x1000
    PROPS_META_SEGMENT_MASK = 0xF0000

    def __init__(self, index):
        self.index = index
        self.props = None
        self.offset = None
        self.filesz = None
        self.memsz = None
        self.data = None

    def save(self, f):
        f.write(struct.pack(SignedElfEntry.FMT, self.props, self.offset, self.filesz, self.memsz))

    @property
    def order(self):
        return (self.props >> SignedElfEntry.PROPS_ORDER_SHIFT) & SignedElfEntry.PROPS_ORDER_MASK

    @order.setter
    def order(self, value):
        self.props &= ~(SignedElfEntry.PROPS_ORDER_MASK << SignedElfEntry.PROPS_ORDER_SHIFT)
        self.props |= (value & SignedElfEntry.PROPS_ORDER_MASK) << SignedElfEntry.PROPS_ORDER_SHIFT

    @property
    def encrypted(self):
        return ((self.props >> SignedElfEntry.PROPS_ENCRYPTED_SHIFT) & SignedElfEntry.PROPS_ENCRYPTED_MASK) != 0

    @encrypted.setter
    def encrypted(self, value):
        self.props &= ~(SignedElfEntry.PROPS_ENCRYPTED_MASK << SignedElfEntry.PROPS_ENCRYPTED_SHIFT)
        if value:
            self.props |= SignedElfEntry.PROPS_ENCRYPTED_MASK << SignedElfEntry.PROPS_ENCRYPTED_SHIFT

    @property
    def signed(self):
        return ((self.props >> SignedElfEntry.PROPS_SIGNED_SHIFT) & SignedElfEntry.PROPS_SIGNED_MASK) != 0

    @signed.setter
    def signed(self, value):
        self.props &= ~(SignedElfEntry.PROPS_SIGNED_MASK << SignedElfEntry.PROPS_SIGNED_SHIFT)
        if value:
            self.props |= SignedElfEntry.PROPS_SIGNED_MASK << SignedElfEntry.PROPS_SIGNED_SHIFT

    @property
    def compressed(self):
        return ((self.props >> SignedElfEntry.PROPS_COMPRESSED_SHIFT) & SignedElfEntry.PROPS_COMPRESSED_MASK) != 0

    @compressed.setter
    def compressed(self, value):
        self.props &= ~(SignedElfEntry.PROPS_COMPRESSED_MASK << SignedElfEntry.PROPS_COMPRESSED_SHIFT)
        if value:
            self.props |= SignedElfEntry.PROPS_COMPRESSED_MASK << SignedElfEntry.PROPS_COMPRESSED_SHIFT

    @property
    def has_blocks(self):
        return ((self.props >> SignedElfEntry.PROPS_HAS_BLOCKS_SHIFT) & SignedElfEntry.PROPS_HAS_BLOCKS_MASK) != 0

    @has_blocks.setter
    def has_blocks(self, value):
        self.props &= ~(SignedElfEntry.PROPS_HAS_BLOCKS_MASK << SignedElfEntry.PROPS_HAS_BLOCKS_SHIFT)
        if value:
            self.props |= SignedElfEntry.PROPS_HAS_BLOCKS_MASK << SignedElfEntry.PROPS_HAS_BLOCKS_SHIFT

    @property
    def has_digests(self):
        return ((self.props >> SignedElfEntry.PROPS_HAS_DIGESTS_SHIFT) & SignedElfEntry.PROPS_HAS_DIGESTS_MASK) != 0

    @has_digests.setter
    def has_digests(self, value):
        self.props &= ~(SignedElfEntry.PROPS_HAS_DIGESTS_MASK << SignedElfEntry.PROPS_HAS_DIGESTS_SHIFT)
        if value:
            self.props |= SignedElfEntry.PROPS_HAS_DIGESTS_MASK << SignedElfEntry.PROPS_HAS_DIGESTS_SHIFT

    @property
    def has_extents(self):
        return ((self.props >> SignedElfEntry.PROPS_HAS_EXTENTS_SHIFT) & SignedElfEntry.PROPS_HAS_EXTENTS_MASK) != 0

    @has_extents.setter
    def has_extents(self, value):
        self.props &= ~(SignedElfEntry.PROPS_HAS_EXTENTS_MASK << SignedElfEntry.PROPS_HAS_EXTENTS_SHIFT)
        if value:
            self.props |= SignedElfEntry.PROPS_HAS_EXTENTS_MASK << SignedElfEntry.PROPS_HAS_EXTENTS_SHIFT

    @property
    def has_meta_segment(self):
        return ((self.props >> SignedElfEntry.PROPS_HAS_META_SEGMENT_SHIFT) & SignedElfEntry.PROPS_HAS_META_SEGMENT_MASK) != 0

    @has_meta_segment.setter
    def has_meta_segment(self, value):
        self.props &= ~(SignedElfEntry.PROPS_HAS_META_SEGMENT_MASK << SignedElfEntry.PROPS_HAS_META_SEGMENT_SHIFT)
        if value:
            self.props |= SignedElfEntry.PROPS_HAS_META_SEGMENT_MASK << SignedElfEntry.PROPS_HAS_META_SEGMENT_SHIFT

    @property
    def wbits(self):
        return (self.props >> SignedElfEntry.PROPS_WINDOW_BITS_SHIFT) & SignedElfEntry.PROPS_WINDOW_BITS_MASK

    @wbits.setter
    def wbits(self, value):
        self.props &= ~(SignedElfEntry.PROPS_WINDOW_BITS_MASK << SignedElfEntry.PROPS_WINDOW_BITS_SHIFT)
        self.props |= (value & SignedElfEntry.PROPS_WINDOW_BITS_MASK) << SignedElfEntry.PROPS_WINDOW_BITS_SHIFT

    @property
    def block_size(self):
        if self.has_blocks:
            return 1 << (12 + (self.props >> SignedElfEntry.PROPS_BLOCK_SIZE_SHIFT) & SignedElfEntry.PROPS_BLOCK_SIZE_MASK)
        else:
            return DEFAULT_BLOCK_SIZE

    @block_size.setter
    def block_size(self, value):
        self.props &= ~(SignedElfEntry.PROPS_BLOCK_SIZE_MASK << SignedElfEntry.PROPS_BLOCK_SIZE_SHIFT)
        if self.has_blocks:
            value = ilog2(value) - 12
        else:
            value = 0
        self.props |= (value & SignedElfEntry.PROPS_BLOCK_SIZE_MASK) << SignedElfEntry.PROPS_BLOCK_SIZE_SHIFT

    @property
    def segment_index(self):
        return (self.props >> SignedElfEntry.PROPS_SEGMENT_INDEX_SHIFT) & SignedElfEntry.PROPS_SEGMENT_INDEX_MASK

    @segment_index.setter
    def segment_index(self, value):
        self.props &= ~(SignedElfEntry.PROPS_SEGMENT_INDEX_MASK << SignedElfEntry.PROPS_SEGMENT_INDEX_SHIFT)
        self.props |= (value & SignedElfEntry.PROPS_SEGMENT_INDEX_MASK) << SignedElfEntry.PROPS_SEGMENT_INDEX_SHIFT

    def is_meta_segment(self):
        return (self.props & SignedElfEntry.PROPS_META_SEGMENT_MASK) != 0

    def __repr__(self):
        return 'prs:0x{0:X} ofs:0x{1:X} fsz:0x{2:X} msz:0x{3:X}'.format(self.props, self.offset, self.filesz, self.memsz)

class SignedElfExInfo(object):
    FMT = b'<4Q32s'

    PTYPE_FAKE = 0x1
    PTYPE_NPDRM_EXEC = 0x4
    PTYPE_NPDRM_DYNLIB = 0x5
    PTYPE_SYSTEM_EXEC = 0x8
    PTYPE_SYSTEM_DYNLIB = 0x9
    PTYPE_HOST_KERNEL = 0xC
    PTYPE_SECURE_MODULE = 0xE
    PTYPE_SECURE_KERNEL = 0xF

    def __init__(self):
        self.paid = None
        self.ptype = None
        self.app_version = None
        self.fw_version = None
        self.digest = None

    def save(self, f):
        f.write(struct.pack(SignedElfExInfo.FMT, self.paid, self.ptype, self.app_version, self.fw_version, self.digest))

class SignedElfNpdrmControlBlock(object):
    FMT = b'<H14x19s13s'

    def __init__(self):
        self.type = SELF_CONTROL_BLOCK_TYPE_NPDRM
        self.content_id = None
        self.random_pad = None

    def save(self, f):
        content_id_bytes = self.content_id if isinstance(self.content_id, bytes) else self.content_id.encode('utf-8')
        random_pad_bytes = self.random_pad if isinstance(self.random_pad, bytes) else self.random_pad.encode('utf-8')
        f.write(struct.pack(self.FMT, self.type, content_id_bytes, random_pad_bytes))

class SignedElfMetaBlock(object):
    FMT = b'<80x'

    def __init__(self):
        pass

    def save(self, f):
        f.write(struct.pack(SignedElfMetaBlock.FMT))

class SignedElfMetaFooter(object):
    FMT = b'<48xI28x'

    def __init__(self):
        self.unk1 = None

    def save(self, f):
        f.write(struct.pack(SignedElfMetaFooter.FMT, self.unk1))

class SignedElfFile(object):
    COMMON_HEADER_FMT = b'<4s4B'
    EXT_HEADER_FMT = b'<I2HQ2H4x'

    MAGIC = b'\x54\x14\xF5\xEE'
    VERSION = 0x10
    MODE = 0x01
    ENDIAN = 0x01
    ATTRIBS = 0x12
    KEY_TYPE = 0x10000101
    HAS_NPDRM = False  # Disable NPDRM for PS5 unless confirmed

    FLAGS_SEGMENT_SIGNED_SHIFT = 4
    FLAGS_SEGMENT_SIGNED_MASK = 0x7

    def __init__(self, elf, **kwargs):
        self.elf = elf
        self.magic = None
        self.version = None
        self.mode = None
        self.endian = None
        self.attribs = None
        self.key_type = None
        self.header_size = None
        self.meta_size = None
        self.file_size = None
        self.num_entries = None
        self.flags = None
        self.entries = None
        self.ex_info = None
        self.npdrm_control_block = None
        self.meta_blocks = None
        self.meta_footer = None
        self.signature = None
        self.version_data = None

        self.paid = kwargs['paid'] if 'paid' in kwargs and kwargs['paid'] is not None else 0x3100000000000002
        self.ptype = kwargs['ptype'] if 'ptype' in kwargs and kwargs['ptype'] is not None else SignedElfExInfo.PTYPE_FAKE
        self.app_version = kwargs['app_version'] if 'app_version' in kwargs and kwargs['app_version'] is not None else 0
        self.fw_version = kwargs['fw_version'] if 'fw_version' in kwargs and kwargs['fw_version'] is not None else 0
        self.auth_info = kwargs['auth_info'] if 'auth_info' in kwargs and kwargs['auth_info'] is not None else None

        print("Warning: Using header fields from provided PS5 SELF. Verify auth_info and ELF types for full compatibility.")
        if self.auth_info is None:
            print("Warning: No auth_info provided. The output SELF may not be executable on PS5.")

    def _prepare(self):
        self.magic = SignedElfFile.MAGIC
        self.version = SignedElfFile.VERSION
        self.mode = SignedElfFile.MODE
        self.endian = SignedElfFile.ENDIAN
        self.attribs = SignedElfFile.ATTRIBS
        self.key_type = SignedElfFile.KEY_TYPE
        self.flags = 0x0032  # Updated from header

							  
																																

        self.entries = []
        entry_idx = 0
        for i in range(self.elf.ehdr.phnum):
            phdr = self.elf.phdrs[i]
            if phdr.type == ElfPHdr.PT_SCE_VERSION:
                self.version_data = self.elf.segments[i]
            if phdr.type not in [ElfPHdr.PT_LOAD, ElfPHdr.PT_SCE_RELRO, ElfPHdr.PT_SCE_DYNLIBDATA, ElfPHdr.PT_SCE_COMMENT, ElfPHdr.PT_SCE_PS5_NEW_TYPE]:
                continue
            # Meta entry
            meta_entry = SignedElfEntry(entry_idx)
            meta_entry.props = 0
            meta_entry.encrypted = False
            meta_entry.signed = True
            meta_entry.has_digests = True
            meta_entry.segment_index = entry_idx + 1
            meta_entry.props |= (entry_idx + 1) << 16  # Mimic header props (e.g., 0x110004, 0x310004)
            self.entries.append(meta_entry)
            # Data entry
            data_entry = SignedElfEntry(entry_idx + 1)
            data_entry.props = 0
            data_entry.encrypted = False
            data_entry.signed = True
            data_entry.has_blocks = True
            data_entry.block_size = BLOCK_SIZE
            data_entry.segment_index = i
            data_entry.props |= 0x2800  # Mimic header props (e.g., 0x00280004)
            self.entries.append(data_entry)
            entry_idx += 2
        self.num_entries = len(self.entries)

        self.ex_info = SignedElfExInfo()
        self.ex_info.paid = self.paid
        self.ex_info.ptype = self.ptype
        self.ex_info.app_version = self.app_version
        self.ex_info.fw_version = self.fw_version
        self.ex_info.digest = self.elf.digest

        if SignedElfFile.HAS_NPDRM:
            self.npdrm_control_block = SignedElfNpdrmControlBlock()
            self.npdrm_control_block.content_id = b'\0' * SELF_NPDRM_CONTROL_BLOCK_CONTENT_ID_SIZE
            self.npdrm_control_block.random_pad = b'\0' * SELF_NPDRM_CONTROL_BLOCK_RANDOM_PAD_SIZE

        self.header_size = struct.calcsize(SignedElfFile.COMMON_HEADER_FMT) + struct.calcsize(SignedElfFile.EXT_HEADER_FMT)
        self.header_size += self.num_entries * struct.calcsize(SignedElfEntry.FMT)
        self.header_size += max(self.elf.ehdr.ehsize, self.elf.ehdr.phoff + self.elf.ehdr.phentsize * self.elf.ehdr.phnum)
        self.header_size = align_up(self.header_size, 16)
        self.header_size += struct.calcsize(SignedElfExInfo.FMT)
        if SignedElfFile.HAS_NPDRM:
            self.header_size += struct.calcsize(SignedElfNpdrmControlBlock.FMT)
        self.meta_size = self.num_entries * struct.calcsize(SignedElfMetaBlock.FMT) + struct.calcsize(SignedElfMetaFooter.FMT) + SIGNATURE_SIZE

        self.meta_blocks = []
        for i in range(self.num_entries):
            meta_block = SignedElfMetaBlock()
            self.meta_blocks.append(meta_block)

        self.meta_footer = SignedElfMetaFooter()
        self.meta_footer.unk1 = 0x10000

        if self.auth_info is not None:
            self.signature = (struct.pack(b'<QQ', len(self.auth_info), self.ex_info.paid) + self.auth_info[8:]).ljust(SIGNATURE_SIZE, b'\0')
        else:
            self.signature = EMPTY_SIGNATURE

        entry_idx = 0
        offset = self.header_size + self.meta_size
        for i in range(self.elf.ehdr.phnum):
            phdr = self.elf.phdrs[i]
            if phdr.type not in [ElfPHdr.PT_LOAD, ElfPHdr.PT_SCE_RELRO, ElfPHdr.PT_SCE_DYNLIBDATA, ElfPHdr.PT_SCE_COMMENT, ElfPHdr.PT_SCE_PS5_NEW_TYPE]:
                continue
            print(f'processing segment #{i:02}...')
            meta_entry, data_entry = self.entries[entry_idx], self.entries[entry_idx + 1]

            num_blocks = align_up(phdr.filesz, BLOCK_SIZE) // BLOCK_SIZE
            meta_entry.data = EMPTY_DIGEST * num_blocks
            meta_entry.offset = offset
            meta_entry.memsz = meta_entry.filesz = len(meta_entry.data)
            offset += meta_entry.filesz
            offset = align_up(offset, 16)

            data_entry.data = self.elf.segments[i]
            data_entry.offset = offset
            data_entry.memsz = data_entry.filesz = phdr.filesz
            offset += data_entry.filesz
            offset = align_up(offset, 16)

            entry_idx += 2

        self.file_size = offset
        print(f"Prepared SELF: magic={self.magic.hex()}, entries={self.num_entries}, file_size=0x{self.file_size:X}")

    def save(self, f):
        start_offset = f.tell()
        self._prepare()

        f.write(struct.pack(SignedElfFile.COMMON_HEADER_FMT, self.magic, self.version, self.mode, self.endian, self.attribs))
        f.write(struct.pack(SignedElfFile.EXT_HEADER_FMT, self.key_type, self.header_size, self.meta_size, self.file_size, self.num_entries, self.flags))

        for entry in self.entries:
            entry.save(f)

        elf_offset = f.tell()
        elf_header_size = max(self.elf.ehdr.ehsize, self.elf.ehdr.phoff + self.elf.ehdr.phentsize * self.elf.ehdr.phnum)
        elf_header_size = align_up(elf_header_size, 16)
        self.elf.save(f, True)
        f.seek(elf_offset + elf_header_size)

        self.ex_info.save(f)

        if SignedElfFile.HAS_NPDRM:
            self.npdrm_control_block.save(f)

        for meta_block in self.meta_blocks:
            meta_block.save(f)

        self.meta_footer.save(f)
        f.write(self.signature if isinstance(self.signature, bytes) else self.signature.encode('utf-8'))

        for entry in self.entries:
            f.seek(start_offset + entry.offset)
            f.write(entry.data if isinstance(entry.data, bytes) else entry.data.encode('utf-8'))

        if self.version_data is not None:
            f.write(self.version_data if isinstance(self.version_data, bytes) else self.version_data.encode('utf-8'))

def ensure_hex_string(val, **kwargs):
    exact_size = int(kwargs['exact_size']) if 'exact_size' in kwargs else None
    min_size = int(kwargs['min_size']) if 'min_size' in kwargs else None
    max_size = int(kwargs['max_size']) if 'max_size' in kwargs else None

    val = re.sub(r'\s+', '', val)
    val_size = len(val)
    if val_size > 0:
        if val.startswith('0x') or val.startswith('0X'):
            val = val[2:]
        if len(val) % 2 != 0 or not all(x in string.hexdigits for x in val):
            return None
        val = bytes.fromhex(val)
        val_size = len(val)

    if exact_size is not None and val_size != exact_size:
        return None
    else:
        if min_size is not None and val_size < min_size:
            return None
        if max_size is not None and val_size > max_size:
            return None

    return val

def input_file_type(val):
    if not os.access(val, os.F_OK | os.R_OK) or not os.path.isfile(val):
        raise argparse.ArgumentTypeError(f'invalid input file: {val}')
    return val

def output_file_type(val):
    if os.access(val, os.F_OK) and (not os.path.isfile(val) or not os.access(val, os.F_OK | os.W_OK)):
        raise argparse.ArgumentTypeError(f'invalid output file: {val}')
    return val

def auth_info_type(val):
    new_val = ensure_hex_string(val, exact_size=0x90)  # Placeholder, replace with actual PS5 auth_info size
    if new_val is None:
        raise argparse.ArgumentTypeError(f'invalid auth info: {val}')
    return new_val

def validate_self(file_path):
    with open(file_path, 'rb') as f:
        magic = f.read(4)
        if magic != SignedElfFile.MAGIC:
            print(f"Validation failed: Incorrect magic number {magic.hex()}")
            return False
        print("Validation passed: Correct magic number")
        return True

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

parser = MyParser(description='Fake signed ELF maker for PS5')
parser.add_argument('input', type=input_file_type, help='elf/prx file path')
parser.add_argument('output', type=output_file_type, help='self/sprx file path')
parser.add_argument('--paid', type=int_with_base_type, default=0x3100000000000002, help='program authentication id')
parser.add_argument('--ptype', default=None, help='program type {fake, npdrm_exec, npdrm_dynlib, system_exec, system_dynlib, host_kernel, secure_module, secure_kernel}')
parser.add_argument('--app-version', type=int_with_base_type, default=0, help='application version')
parser.add_argument('--fw-version', type=int_with_base_type, default=0, help='firmware version')
parser.add_argument('--auth-info', type=auth_info_type, default=None, help='authentication info')
parser.add_argument('--block-size', type=int_with_base_type, default=0x4000, help='block size for segment data')

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

args = parser.parse_args()

paid = args.paid
if not (0 <= paid <= 0xFFFFFFFFFFFFFFFF):
    parser.error(f'invalid program authentication id: 0x{paid:016X}')

ptype = SignedElfExInfo.PTYPE_FAKE
if args.ptype is not None:
    ptype = {
        'fake': SignedElfExInfo.PTYPE_FAKE,
        'npdrm_exec': SignedElfExInfo.PTYPE_NPDRM_EXEC,
        'npdrm_dynlib': SignedElfExInfo.PTYPE_NPDRM_DYNLIB,
        'system_exec': SignedElfExInfo.PTYPE_SYSTEM_EXEC,
        'system_dynlib': SignedElfExInfo.PTYPE_SYSTEM_DYNLIB,
        'host_kernel': SignedElfExInfo.PTYPE_HOST_KERNEL,
        'secure_module': SignedElfExInfo.PTYPE_SECURE_MODULE,
        'secure_kernel': SignedElfExInfo.PTYPE_SECURE_KERNEL,
    }.get(args.ptype.strip().lower(), None)
    if ptype is None:
        ptype = try_parse_int(args.ptype)
    if ptype is None:
        parser.error(f'invalid program type: {args.ptype}')
if not (0 <= ptype <= 0xFFFFFFFFFFFFFFFF):
    parser.error(f'invalid program type: 0x{ptype:016X}')

app_version = args.app_version
if not (0 <= app_version <= 0xFFFFFFFFFFFFFFFF):
    parser.error(f'invalid application version: 0x{app_version:016X}')

fw_version = args.fw_version
if not (0 <= fw_version <= 0xFFFFFFFFFFFFFFFF):
    parser.error(f'invalid firmware version: 0x{fw_version:016X}')

auth_info = args.auth_info
BLOCK_SIZE = args.block_size

elf_file_path = args.input
fself_file_path = args.output

print(f'loading elf file: {elf_file_path}')
try:
    with open(elf_file_path, 'rb') as f:
        elf_file = ElfFile(ignore_shdrs=True)
        elf_file.load(f)
except Exception as err:
    traceback.print_exc()
    print('')
    parser.error(f'unable to load elf file: {elf_file_path} ({err})')

print(f'saving fake signed elf file: {fself_file_path}')
try:
    with open(fself_file_path, 'wb') as f:
        self_file = SignedElfFile(elf_file, paid=paid, ptype=ptype, app_version=app_version, fw_version=fw_version, auth_info=auth_info)
        self_file.save(f)
    validate_self(fself_file_path)
except Exception as err:
    traceback.print_exc()
    print('')
    parser.error(f'unable to save fself file: {fself_file_path} ({err})')

print('done')