"""Dump bounded pinned-image functions, including chained unwind cold blocks."""
import argparse
import struct
from pathlib import Path
import pefile
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
from capstone.x86 import X86_OP_MEM, X86_REG_RIP

parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('targets',nargs='+',type=lambda value:int(value,0))
parser.add_argument('--output',type=Path,required=True)
parser.add_argument('--image',type=Path,default=Path('artifacts/ship-model-reference/client-copy/EliteDangerous64.exe'))
parser.add_argument('--strings',action='store_true',help='Annotate readable static RIP-relative strings')
args=parser.parse_args()
image=args.image
pe=pefile.PE(str(image),fast_load=True)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXCEPTION']])
def root(row):
    for _ in range(16):
        header=pe.get_data(row[2],4)
        if not header[0]>>3&4:return row[0]
        row=struct.unpack('<III',pe.get_data(row[2]+4+((header[2]+1)&~1)*2,12))
    raise ValueError('Unwind chain depth')
disassembler=Cs(CS_ARCH_X86,CS_MODE_64)
disassembler.detail=args.strings
args.output.mkdir(parents=True,exist_ok=True)
for target in args.targets:
    lines=[f'; {image.name}: original image bytes only, no game execution',f'; root RVA {target:x}']
    count=0
    for entry in pe.DIRECTORY_ENTRY_EXCEPTION:
        row=(entry.struct.BeginAddress,entry.struct.EndAddress,entry.struct.UnwindData)
        if root(row)!=target:continue
        if row[1]-row[0]>131072:raise ValueError('Function range exceeds bound')
        lines.append(f'\n; range {row[0]:x}..{row[1]:x}')
        for instruction in disassembler.disasm(pe.get_data(row[0],row[1]-row[0]),row[0]):
            annotation=''
            if args.strings:
                for operand in instruction.operands:
                    if operand.type!=X86_OP_MEM or operand.mem.base!=X86_REG_RIP:continue
                    address=instruction.address+instruction.size+operand.mem.disp
                    try:
                        value=pe.get_data(address,128).split(b'\0',1)[0]
                        if 4<=len(value)<=120 and all(32<=byte<127 for byte in value):
                            annotation+=f' ; {address:x} {value.decode("ascii")!r}'
                    except (ValueError,pefile.PEFormatError):pass
            lines.append(f'{instruction.address:x}: {instruction.bytes.hex():<30} {instruction.mnemonic} {instruction.op_str}{annotation}')
            count+=1
    path=args.output/f'asm-full-{target:x}.txt'
    path.write_text('\n'.join(lines)+'\n',encoding='utf-8')
    print(f'{path}: {count} instructions')
