"""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')
"""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')