Module refinery.lib.inno.ifps
The code is based on the logic implemented in IFPSTools: https://github.com/Wack0/IFPSTools
Expand source code Browse git
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
The code is based on the logic implemented in IFPSTools:
https://github.com/Wack0/IFPSTools
"""
from __future__ import annotations
import abc
import enum
import io
import itertools
from typing import (
Callable,
Dict,
Generator,
List,
NamedTuple,
Optional,
Tuple,
Type,
TypeVar,
Union,
)
from uuid import UUID
from dataclasses import dataclass, field
from collections import OrderedDict
from functools import WRAPPER_ASSIGNMENTS, update_wrapper
from refinery.lib.structures import Struct, StructReader
from refinery.lib.inno.symbols import IFPSAPI, IFPSClasses, IFPSEvents
from refinery.lib.types import CaseInsensitiveDict
_E = TypeVar('_E', bound=Type[enum.Enum])
_C = TypeVar('_C', bound=Type)
_TAB = '\x20\x20'
def extended(_data: bytes):
if len(_data) != 10:
raise ValueError
data = int.from_bytes(_data, 'little')
sign = data >> 79
data = data ^ (sign << 79)
sign = -1.0 if sign else +1.0
exponent = data >> 64
data = data ^ (exponent << 64)
if exponent == 0:
if data == 0:
return sign * 0
exponent = -16382
elif exponent == 0b111111111111111:
if data == 0:
return sign * float('Inf')
else:
return sign * float('NaN')
else:
exponent = exponent - 16383
mantissa = data / (1 << 64)
return sign * mantissa * (2 ** exponent)
def represent(cls: _E) -> _E:
cls.__repr__ = lambda self: F'{self.__class__.__name__}.{self.name}'
cls. __str__ = lambda self: self.name
return cls
@represent
class Op(enum.IntEnum):
Assign = 0x00 # noqa
Calculate = 0x01 # noqa
Push = 0x02 # noqa
PushVar = 0x03 # noqa
Pop = 0x04 # noqa
Call = 0x05 # noqa
Jump = 0x06 # noqa
JumpTrue = 0x07 # noqa
JumpFalse = 0x08 # noqa
Ret = 0x09 # noqa
StackType = 0x0A # noqa
PushType = 0x0B # noqa
Compare = 0x0C # noqa
CallVar = 0x0D # noqa
SetPtr = 0x0E # noqa
BooleanNot = 0x0F # noqa
Neg = 0x10 # noqa
SetFlag = 0x11 # noqa
JumpFlag = 0x12 # noqa
PushEH = 0x13 # noqa
PopEH = 0x14 # noqa
IntegerNot = 0x15 # noqa
SetPtrToCopy = 0x16 # noqa
Inc = 0x17 # noqa
Dec = 0x18 # noqa
JumpPop1 = 0x19 # noqa
JumpPop2 = 0x1A # noqa
Nop = 0xFF # noqa
_INVALID = 0xDD # noqa
@classmethod
def FromInt(cls, code: int):
try:
return cls(code)
except ValueError:
return cls._INVALID
class AOp(enum.IntEnum):
Add = 0
Sub = 1
Mul = 2
Div = 3
Mod = 4
Shl = 5
Shr = 6
And = 7
BOr = 8
Xor = 9
def __str__(self):
glyph = ('+', '-', '*', '/', '%', '<<', '>>', '&', '|', '^')[self]
return F'{glyph}='
class COp(enum.IntEnum):
GE = 0
LE = 1
GT = 2
LT = 3
NE = 4
EQ = 5
IN = 6
IS = 7
def __str__(self):
return ('>=', '<=', '>', '<', '!=', '==', 'in', 'is')[self]
@represent
class TC(enum.IntEnum):
ReturnAddress = 0x00 # noqa
U08 = 0x01 # noqa
S08 = 0x02 # noqa
U16 = 0x03 # noqa
S16 = 0x04 # noqa
U32 = 0x05 # noqa
S32 = 0x06 # noqa
Single = 0x07 # noqa
Double = 0x08 # noqa
Extended = 0x09 # noqa
AnsiString = 0x0A # noqa
Record = 0x0B # noqa
Array = 0x0C # noqa
Pointer = 0x0D # noqa
PChar = 0x0E # noqa
ResourcePointer = 0x0F # noqa
Variant = 0x10 # noqa
S64 = 0x11 # noqa
Char = 0x12 # noqa
WideString = 0x13 # noqa
WideChar = 0x14 # noqa
ProcPtr = 0x15 # noqa
StaticArray = 0x16 # noqa
Set = 0x17 # noqa
Currency = 0x18 # noqa
Class = 0x19 # noqa
Interface = 0x1A # noqa
NotificationVariant = 0x1B # noqa
UnicodeString = 0x1C # noqa
Enum = 0x81 # noqa
Type = 0x82 # noqa
ExtClass = 0x83 # noqa
@property
def primitive(self) -> bool:
return self not in {
TC.Class,
TC.ProcPtr,
TC.Interface,
TC.Set,
TC.StaticArray,
TC.Array,
TC.Record,
}
@property
def container(self) -> bool:
return self in {
TC.StaticArray,
TC.Array,
TC.Record,
}
@property
def width(self):
return {
TC.Variant : 0x10,
TC.Char : 0x01,
TC.S08 : 0x01,
TC.U08 : 0x01,
TC.WideChar : 0x02,
TC.S16 : 0x02,
TC.U16 : 0x02,
TC.WideString : 0x04,
TC.UnicodeString : 0x04,
TC.Interface : 0x04,
TC.Class : 0x04,
TC.PChar : 0x04,
TC.AnsiString : 0x04,
TC.Single : 0x04,
TC.S32 : 0x04,
TC.U32 : 0x04,
TC.ProcPtr : 0x0C,
TC.Currency : 0x08,
TC.Pointer : 0x0C,
TC.Double : 0x08,
TC.S64 : 0x08,
TC.Extended : 0x0A,
TC.ReturnAddress : 0x1C,
}.get(self, 0)
@dataclass
class IFPSTypeMixin:
symbol: Optional[str] = None
attributes: Optional[List[Attribute]] = None
def __str__(self):
if self.symbol is not None:
return self.symbol
return super().__str__()
@dataclass
class IFPSTypeBase(abc.ABC):
code: TC
def simple(self, nested=False):
return True
def indexed(self):
return self.code in (
TC.StaticArray,
TC.Array,
TC.Record,
)
def display(self, indent=0):
return indent * _TAB + self.code.name
@abc.abstractmethod
def py_type(self, key: Optional[int] = None) -> Optional[type]:
...
@abc.abstractmethod
def default(self, key: Optional[int] = None):
...
@property
def primitive(self) -> bool:
return self.code.primitive
@property
def container(self) -> bool:
return self.code.container
def __str__(self):
return self.display(0)
def ifpstype(cls: _C) -> Union[_C, Type[IFPSTypeMixin]]:
cls = dataclass(cls)
mix = type(cls.__qualname__, (IFPSTypeMixin, cls), {})
assigned = set(WRAPPER_ASSIGNMENTS) - {'__annotations__'}
update_wrapper(mix, cls, assigned=assigned, updated=())
return dataclass(mix)
@ifpstype
class TPrimitive(IFPSTypeBase):
def py_type(self, *_) -> Optional[type]:
return {
TC.ReturnAddress : int,
TC.U08 : int,
TC.S08 : int,
TC.U16 : int,
TC.S16 : int,
TC.U32 : int,
TC.S32 : int,
TC.Single : float,
TC.Double : float,
TC.Extended : float,
TC.AnsiString : str,
TC.Pointer : VariableBase,
TC.PChar : str,
TC.ResourcePointer : VariableBase,
TC.Variant : object,
TC.S64 : int,
TC.Char : str,
TC.WideString : str,
TC.WideChar : str,
TC.Currency : float,
TC.UnicodeString : str,
TC.Enum : int,
TC.Type : IFPSType,
}.get(self.code)
def default(self, *_):
if self.code in (TC.Char, TC.WideChar, TC.PChar):
return '\0'
tc = self.py_type()
if issubclass(tc, (int, float, str)):
return tc()
@ifpstype
class TProcPtr(IFPSTypeBase):
void: bool
args: tuple[DeclSpecParam, ...]
def py_type(self, *_):
return None
def default(self, *_):
return None
def display(self, indent=0):
name = super().display(indent)
args = []
for k, spec in enumerate(self.args, 1):
arg = F'Arg{k}'
if not spec.const:
arg = F'*{arg}'
if spec.type is not None:
arg = F'{spec.type!s} {arg}'
args.append(arg)
args = ', '.join(args)
return F'{name}({args})'
@ifpstype
class TInterface(IFPSTypeBase):
uuid: UUID
def py_type(self, *_):
return object
def default(self, *_):
return None
def display(self, indent=0):
display = super().display(indent)
return F'{display}({self.uuid!s})'
@ifpstype
class TClass(IFPSTypeBase):
name: str
def py_type(self, *_):
return None
def default(self, *_):
return None
@ifpstype
class TSet(IFPSTypeBase):
size: int
def py_type(self, *_):
return int
def default(self, *_):
return 0
@property
def size_in_bytes(self):
q, r = divmod(self.size, 8)
return q + (r and 1 or 0)
def display(self, indent=0):
display = super().display(indent)
return F'{display}({self.size})'
@ifpstype
class TArray(IFPSTypeBase):
type: TPrimitive
def py_type(self, key: Optional[int] = None):
if key is None:
return list
return self.type.py_type()
def default(self, key: Optional[int] = None):
if key is None:
return []
return self.type.default()
def display(self, indent=0):
display = F'{_TAB * indent}{self.type!s}'
return F'array of {display}'
def simple(self, nested=False):
return self.type.simple(nested)
@ifpstype
class TStaticArray(IFPSTypeBase):
type: TPrimitive
size: int
offset: Optional[int] = None
def py_type(self, key: Optional[int] = None):
if key is None:
return list
return self.type.py_type(key)
def default(self, key: Optional[int] = None):
if key is None:
return [self.type.default() for _ in range(self.size)]
return self.type.default()
def display(self, indent=0):
display = F'{_TAB * indent}{self.type!s}'
return F'{display}[{self.size}]'
def simple(self, nested=False):
return self.type.simple(nested)
@ifpstype
class TRecord(IFPSTypeBase):
members: Tuple[TPrimitive, ...]
@property
def size(self):
return len(self.members)
def py_type(self, key: Optional[int] = None):
if key is None:
return list
return self.members[key].py_type()
def default(self, key: Optional[int] = None):
if key is None:
return [member.default() for member in self.members]
return self.members[key].default()
def simple(self, nested=False):
if nested:
return False
if len(self.members) > 10:
return False
return all(m.simple(True) for m in self.members)
def display(self, indent=0):
output = io.StringIO()
output.write(indent * _TAB)
output.write('struct {')
if self.simple():
output.write(', '.join(str(m) for m in self.members))
else:
for k, member in enumerate(self.members):
if k > 0:
output.write(',')
output.write('\n')
output.write(member.display(indent + 1))
if self.members:
output.write(F'\n{_TAB * indent}')
output.write('}')
return output.getvalue()
IFPSType = Union[
TRecord,
TStaticArray,
TArray,
TSet,
TProcPtr,
TClass,
TInterface,
TPrimitive,
]
class Value(NamedTuple):
type: IFPSType
value: Union[str, int, float, bytes, Function]
def convert(self, *_):
return self.type.py_type()
def default(self, *_):
return self.type.default()
def __repr__(self):
value = self.value
if isinstance(value, bytes):
value = value.hex()
return F'{self.type.code.name}({value!r})'
def __str__(self):
v = self.value
if isinstance(v, Function):
return F'&{v!s}'
return repr(v)
class Attribute(NamedTuple):
name: str
fields: Tuple[Value, ...]
def __repr__(self):
name = self.name
if self.fields:
name += '[{}]'.format(','.join(repr(f) for f in self.fields))
return name
@dataclass
class DeclSpecParam:
const: bool
type: Optional[TPrimitive] = None
name: Optional[str] = None
class CallType(str, enum.Enum):
Symbol = 'symbol'
Procedure = 'procedure'
Function = 'function'
def __str__(self):
return self.value
@dataclass
class DeclSpec:
void: bool
parameters: List[DeclSpecParam] = field(default_factory=list)
name: str = ''
calling_convention: Optional[str] = None
return_type: Optional[IFPSType] = None
module: Optional[str] = None
classname: Optional[str] = None
delay_load: bool = False
vtable_index: Optional[int] = None
load_with_altered_search_path: bool = False
is_property: bool = False
@property
def argc(self):
return len(self.parameters)
def represent(self, name: str, ref: bool = False, rel: bool = False):
def pparam(k: int, p: DeclSpecParam):
name = p.name or F'{VariantType.Argument!s}{k}'
if p.type is not None:
name = F'{name}: {p.type!s}'
if not p.const:
name = F'*{name}'
return name
if self.name and name in self.name:
name = self.name
spec = name
if self.vtable_index is not None:
spec = F'{self.name}[{self.vtable_index}]'
if not rel and self.classname:
spec = F'{self.classname}.{spec}'
if not rel and self.module:
spec = F'{self.module}::{spec}'
if not ref:
if self.delay_load:
spec = F'__delay_load {spec}'
if self.calling_convention:
spec = F'__{self.calling_convention} {spec}'
spec = F'{self.type} {spec}'
args = self.parameters
args = args and ', '.join(pparam(*t) for t in enumerate(args, 1)) or ''
spec = F'{spec}({args})'
if self.return_type:
spec = F'{spec}: {self.return_type!s}'
return spec
@property
def type(self):
return CallType.Procedure if self.void else CallType.Function
def __repr__(self):
return self.represent(self.name or '(*)')
@classmethod
def ParseF(cls, reader: StructReader[bytes], load_flags: bool):
def ascii():
return reader.read_c_string('latin1')
def boolean():
return bool(reader.u8())
def cc():
return {
0: 'register',
1: 'pascal',
2: 'cdecl',
3: 'stdcall',
}.get(reader.u8(), cls.calling_convention)
def read_parameters():
nonlocal void
void = not boolean()
parameters.extend(DeclSpecParam(not b) for b in reader.read())
void = True
name = None
properties = {}
parameters = []
if reader.readif(b'dll:'):
reader.readif(B'files:')
if (module := ascii()).lower().endswith('.dll'):
module = module[:-4]
properties.update(module=module)
name = ascii()
properties.update(calling_convention=cc())
if load_flags:
properties.update(delay_load=boolean(), load_with_altered_search_path=boolean())
read_parameters()
elif reader.readif(b'class:'):
if reader.remaining_bytes == 1:
spec = reader.peek(1)
void = False
parameters.append(DeclSpecParam(False))
name = {
b'+': 'CastToType',
B'-': 'SetNil'
}.get(spec)
properties.update(classname='Class', calling_convention='pascal')
else:
properties.update(classname=reader.read_terminated_array(b'|').decode('latin1'))
name = reader.read_terminated_array(b'|').decode('latin1')
if name[-1] == '@':
properties.update(is_property=True)
name = name[:-1]
properties.update(calling_convention=cc())
read_parameters()
elif reader.readif(b'intf:.'):
name = 'CoInterface'
properties.update(vtable_index=reader.u32())
properties.update(calling_convention=cc())
read_parameters()
else:
read_parameters()
return cls(void, parameters, name=name, **properties)
@classmethod
def ParseE(cls, data: bytes, ipfs: IFPSFile):
decl = data.split(B'\x20')
try:
return_type = int(decl.pop(0))
except Exception:
void = True
else:
void = return_type < 0
if not void:
return_type = ipfs.types[return_type]
else:
return_type = None
parameters = []
for param in decl:
try:
i = int(param[1:])
except Exception:
tv = None
else:
tv = ipfs.types[i]
parameters.append(
DeclSpecParam(param[:1] == B'@', tv))
return cls(void, parameters, return_type=return_type)
@dataclass
class Function:
symbol: str
decl: Optional[DeclSpec]
body: Optional[List[Instruction]] = None
attributes: Optional[List[Attribute]] = None
_bbs: Optional[Dict[int, BasicBlock]] = None
_ins: Optional[Dict[int, Instruction]] = None
@property
def name(self):
symbol = self.symbol
if (decl := self.decl) and (name := decl.name) and (symbol in name):
symbol = name
return symbol
@property
def code(self):
if code := self._ins:
return code
self._ins = code = {i.offset: i for i in self.body}
return code
def reference(self, rel: bool = False) -> str:
if self.decl is None:
return self.symbol
return self.decl.represent(self.symbol, ref=True, rel=rel)
def __repr__(self):
if self.decl is None:
return F'symbol {self.symbol}'
return self.decl.represent(self.symbol)
def __str__(self):
return self.reference()
@property
def type(self):
if self.decl is None:
return CallType.Symbol
return self.decl.type
def get_basic_blocks(self) -> Dict[int, BasicBlock]:
if (bbs := self._bbs) is not None:
return bbs
if self.body is None:
bbs = self._bbs = {}
return bbs
bbs: Dict[int, BasicBlock] = {0: (bb := BasicBlock(0))}
self._bbs = bbs
for insn in self.body:
try:
bb = bbs[insn.offset]
except KeyError:
if insn.jumptarget:
nb = bbs[insn.offset] = BasicBlock(insn.offset)
nb.sources[bb.offset] = bb
bb.targets[nb.offset] = nb
bb = nb
bb.body.append(insn)
if not insn.branches:
continue
targets = [insn.operands[0]]
sequence = insn.offset + insn.size
if not insn.jumps and insn.opcode != Op.Ret:
targets.append(sequence)
for t in targets:
if not (bt := bbs.get(t)):
bt = bbs[t] = BasicBlock(t)
bb.targets[t] = bt
bt.sources[bb.offset] = bb
for offset, bb in list(bbs.items()):
if bb.body:
continue
del bbs[offset]
for source in bb.sources.values():
source.targets.pop(offset, None)
visited: set[int] = set()
errored: set[int] = set()
def trace_stack(offset: int, stack: Optional[int]):
if offset in errored:
return
bb = bbs[offset]
if bb.stack is not None and stack != bb.stack:
stack = None
if stack is None:
errored.add(offset)
elif offset in visited:
return
else:
visited.add(offset)
bb.stack = stack
body = [] if stack is None else bb.body
for insn in body:
insn.stack = stack
stack += insn.stack_delta
for t in bb.targets:
trace_stack(t, stack)
trace_stack(0, 0)
for insn in self.body:
if (stack := insn.stack) is None:
continue
for k, op in enumerate(insn.operands):
if not isinstance(op, Operand):
continue
if not (v := op.variant) or v.type != VariantType.Local:
continue
if v.index <= stack:
continue
raise IndexError(
F'Instruction {op!s} at offset 0x{insn.offset:X} in function {self.name} has '
F'variant operand {k} whose index {v.index} exceeds the stack depth {stack}.')
return bbs
class VariableBase:
type: IFPSType
spec: Variant
def __init__(self, type: IFPSType, spec: Variant):
self.type = type
self.spec = spec
def __str__(self):
return F'{self.spec}: {self.type!s}'
@represent
class OperandType(enum.IntEnum):
Variant = 0
Value = 1
IndexedByInt = 2
IndexedByVar = 3
@represent
class EHType(enum.IntEnum):
Try = 0
Finally = 1
Catch = 2
SecondFinally = 3
@represent
class NewEH(enum.IntEnum):
Finally = 0
CatchAt = 1
SecondFinally = 2
End = 3
class VariantType(str, enum.Enum):
Global = 'GlobalVar'
Local = 'LocalVar'
Argument = 'Argument'
def __repr__(self):
return self.name
def __str__(self):
return self.value
class Variant(NamedTuple):
index: int
type: VariantType
def __repr__(self):
if self.index == 0 and self.type == VariantType.Argument:
return 'ReturnValue'
return F'{self.type!s}{self.index}'
class Operand(NamedTuple):
type: OperandType
variant: Optional[Variant] = None
value: Optional[Value] = None
index: Optional[Union[Variant, int]] = None
def __repr__(self):
return self.__tostring(repr)
def __str__(self):
return self.__tostring(str)
@property
def immediate(self):
return self.type == OperandType.Value
def __tostring(self, converter):
if self.type is OperandType.Value:
return converter(self.value)
if self.type is OperandType.Variant:
return converter(self.variant)
if self.type is OperandType.IndexedByInt:
return F'{converter(self.variant)}[0x{self.index:02X}]'
if self.type is OperandType.IndexedByVar:
return F'{converter(self.variant)}[{self.index!s}]'
raise RuntimeError(F'Unexpected OperandType {self.type!r} in {self.__class__.__name__}')
_Op_Maxlen = max(len(op.name) for op in Op)
_Op_StackD = {
Op.Push : +1,
Op.PushVar : +1,
Op.PushType : +1,
Op.Pop : -1,
Op.JumpPop1 : -1,
Op.JumpPop2 : -2,
}
@dataclass
class Instruction:
offset: int
opcode: Op
size: int = 0
stack: Optional[int] = None
operands: List[Union[str, bool, int, float, Operand, IFPSType, Function, None]] = field(default_factory=list)
operator: Optional[Union[AOp, COp]] = None
jumptarget: bool = False
def op(self, index: int):
arg = self.operands[index]
if not isinstance(arg, Operand):
raise TypeError
return arg
@property
def branches(self):
return self.opcode in (
Op.Jump,
Op.JumpFalse,
Op.JumpTrue,
Op.JumpFlag,
Op.JumpPop1,
Op.JumpPop2,
)
@property
def jumps(self):
return self.opcode in (
Op.Jump,
Op.JumpPop1,
Op.JumpPop2,
)
@property
def stack_delta(self):
return _Op_StackD.get(self.opcode, 0)
def oprep(self, labels: Optional[dict[int, str]] = None):
if self.branches:
dst = self.operands[0]
if not labels or not (label := labels.get(dst)):
label = F'0x{dst:X}'
var = [str(op) for op in self.operands[1:]]
return ', '.join((label, *var))
elif self.opcode is Op.PushEH:
ops = []
for op, name in reversed(list(zip(self.operands, NewEH))):
if op is None:
continue
ops.append(F'{name}:0x{op:X}')
return '\x20'.join(ops)
elif self.opcode is Op.PopEH:
return F'End{EHType(self.operands[0])}'
elif self.opcode is Op.SetFlag:
rep, negated = self.operands
return F'!{rep}' if negated else str(rep)
elif self.opcode is Op.Compare:
dst, a, b = self.operands
return F'{dst!s} := {a!s} {self.operator!s} {b!s}'
elif self.opcode is Op.Calculate:
dst, src = self.operands
return F'{dst!s} {self.operator!s} {src!s}'
elif self.opcode in (Op.Assign, Op.SetPtr, Op.SetPtrToCopy):
dst, src = self.operands
return F'{dst!s} := {src!s}'
else:
return ', '.join(str(op) for op in self.operands)
def pretty(self, labels: Optional[dict[int, str]] = None):
return F'{self.opcode!s:<{_Op_Maxlen}}{_TAB}{self.oprep(labels)}'.strip()
def __repr__(self):
return F'{self.opcode.name}({self.oprep()})'
def __str__(self):
return self.pretty()
@dataclass
class BasicBlock:
offset: int
stack: Optional[int] = None
body: List[Instruction] = field(default_factory=list)
sources: Dict[int, BasicBlock] = field(default_factory=dict)
targets: Dict[int, BasicBlock] = field(default_factory=dict)
@property
def stack_delta(self):
return sum(insn.stack_delta for insn in self.body)
@property
def size(self):
return sum(insn.size for insn in self.body)
class FTag(enum.IntFlag):
External = 0b0001
Exported = 0b0010
HasAttrs = 0b0100
def check(self, v):
return bool(self & v)
class IFPSFile(Struct):
MinVer = 12
MaxVer = 23
Magic = B'IFPS'
def __init__(self, reader: StructReader[memoryview], codec: str = 'latin1', unicode: bool = True):
self.codec = codec
self.unicode = unicode
self.types: List[IFPSType] = []
self.functions: List[Function] = []
self.globals: List[VariableBase] = []
self.strings: List[str] = []
self.reader = reader
if reader.remaining_bytes < 28:
raise ValueError('Less than 28 bytes in file, not enough data to parse.')
magic = reader.read(4)
if magic != self.Magic:
raise ValueError(F'Invalid magic sequence: {magic.hex()}')
self.version = reader.u32()
self.count_types = reader.u32()
self.count_functions = reader.u32()
self.count_variables = reader.u32()
self.entry = reader.u32()
self.import_size = reader.u32()
self.void = False
if self.version not in range(self.MinVer, self.MaxVer + 1):
raise NotImplementedError(
F'This IFPS file has version {self.version}, which is not in the supported range '
F'[{self.MinVer},{self.MaxVer}].')
self._known_type_names = {
TC.U08 : {'Byte', 'Boolean'},
TC.S08 : {'ShortInt'},
TC.U16 : {'Word'},
TC.S16 : {'SmallInt'},
TC.S32 : {'Integer', 'LongInt'},
TC.U32 : {'LongWord', 'Cardinal', 'HWND', 'TSetupProcessorArchitecture'},
TC.Char : {'AnsiChar'},
TC.PChar : {'PAnsiChar'},
TC.S64 : {'Int64'},
}
self._load_types()
self._name_types()
self._load_functions()
self._load_variables()
del self._known_type_names
def _name_types(self, missing_types: Optional[set[str]] = None):
tbn: dict[str, IFPSType] = CaseInsensitiveDict()
self.types_by_name = tbn
for t in self.types:
name = str(t)
code = t.code
known = self._known_type_names.get(code)
if known:
known.discard(name)
tbn[name] = t
if missing_types:
def add_type(name: str, type: IFPSType):
tbn[name] = type
self.types.append(type)
return type
def make_string(name: str = 'String'):
try:
return tbn[name]
except KeyError:
pass
for type in tbn.values():
if type.code in (
TC.AnsiString,
TC.WideString,
TC.UnicodeString,
):
break
else:
code = TC.WideString if self.unicode else TC.AnsiString
type = TPrimitive(code, symbol=name)
return add_type(name, type)
for name in tbn:
missing_types.discard(name)
if 'TGUID' in missing_types:
missing_types |= {'LongWord', 'Word', 'Byte'}
for code in TC:
name = code.name
if name not in missing_types:
continue
missing_types.discard(name)
add_type(name, TPrimitive(code, symbol=name))
for code, names in self._known_type_names.items():
for name in names:
if name not in missing_types:
continue
missing_types.discard(name)
add_type(name, TPrimitive(code, symbol=name))
for name in [
'TVarType',
'TInputQueryWizardPage',
'TInputOptionWizardPage',
'TInputDirWizardPage',
'TInputFileWizardPage',
'TOutputMsgWizardPage',
'TOutputMsgMemoWizardPage',
'TOutputProgressWizardPage',
'TOutputMarqueeProgressWizardPage',
'TDownloadWizardPage',
'ExtractionWizardPage',
'TWizardPage',
'TSetupForm',
'TComponent',
'TNewNotebookPage',
]:
if name not in missing_types:
continue
add_type(name, TClass(TC.Class, name, symbol=name))
if (name := 'String') in missing_types:
make_string(name)
if (name := 'AnyString') in missing_types:
make_string(name)
if (name := 'TArrayOfString') in missing_types:
add_type(name, TArray(TC.Array, make_string()))
if (name := 'IUnknown') in missing_types:
add_type(name, TInterface(TC.Interface, UUID('{00000000-0000-0000-C000-000000000046}')))
if (name := 'TGUID') in missing_types:
add_type(name, TRecord(TC.Record, (
tbn['LongWord'],
tbn['Word'],
tbn['Word'],
TStaticArray(tbn['Byte'], 8)
), symbol=name))
def _load_types(self):
def _normalize(n: str):
return IFPSClasses.Types.get(n.casefold(), n)
reader = self.reader
types = self.types
for k in range(self.count_types):
typecode = reader.u8()
exported = bool(typecode & 0x80)
typecode = typecode & 0x7F
try:
code = TC(typecode)
except ValueError as V:
raise ValueError(F'Unknown type code value 0x{typecode:02X}.') from V
if code in (TC.Class, TC.ExtClass):
t = TClass(code, _normalize(reader.read_length_prefixed_ascii()))
elif code is TC.ProcPtr:
spec = reader.read_length_prefixed()
void = bool(spec[0])
args = tuple(DeclSpecParam(not b) for b in spec[1:])
t = TProcPtr(code, void, args)
elif code is TC.Interface:
guid = UUID(bytes=bytes(reader.read(0x10)))
t = TInterface(code, guid)
elif code is TC.Set:
t = TSet(code, reader.u32())
elif code is TC.StaticArray:
type = types[reader.u32()]
size = reader.u32()
offset = None if self.version <= 22 else reader.u32()
t = TStaticArray(code, type, size, offset)
elif code is TC.Array:
t = TArray(code, types[reader.u32()])
elif code is TC.Record:
length = reader.u32()
members = tuple(types[reader.u32()] for _ in range(length))
t = TRecord(code, members, symbol=F'RECORD{k}')
else:
t = TPrimitive(code, symbol=code.name)
if exported:
t.symbol = _normalize(reader.read_length_prefixed_ascii())
if self.version <= 21:
t.name = _normalize(reader.read_length_prefixed_ascii())
types.append(t)
if self.version >= 21:
t.attributes = list(self._read_attributes())
def _read_value(self, reader: Optional[StructReader] = None) -> Value:
if reader is None:
reader = self.reader
type = self.types[reader.u32()]
size = type.code.width
processor: Optional[Callable[[], Union[int, float, str, bytes]]] = {
TC.U08 : reader.u8,
TC.S08 : reader.i8,
TC.U16 : reader.u16,
TC.S16 : reader.i16,
TC.U32 : reader.u32,
TC.S32 : reader.i32,
TC.S64 : reader.i64,
TC.Single : reader.f32,
TC.Double : reader.f64,
TC.Extended : lambda: extended(reader.read(10)),
TC.AnsiString : lambda: reader.read_length_prefixed(encoding=self.codec),
TC.PChar : lambda: reader.read_length_prefixed(encoding=self.codec),
TC.WideString : reader.read_length_prefixed_utf16,
TC.UnicodeString : reader.read_length_prefixed_utf16,
TC.Char : lambda: chr(reader.u8()),
TC.WideChar : lambda: chr(reader.u16()),
TC.ProcPtr : lambda: self.functions[reader.u32() - 1],
TC.Set : lambda: int.from_bytes(reader.read(type.size_in_bytes), 'little'),
TC.Currency : lambda: reader.u64() / 10_000,
}.get(type.code, None)
if processor is not None:
data = processor()
elif size > 0:
data = bytes(reader.read(size))
else:
raise ValueError(F'Unable to read attribute of type {type!s}.')
if isinstance(data, str) and data not in self.strings:
self.strings.append(data)
return Value(type, data)
def _read_attributes(self) -> Generator[Attribute, None, None]:
reader = self.reader
count = reader.u32()
for _ in range(count):
name = reader.read_length_prefixed_ascii()
fields = tuple(self._read_value() for _ in range(reader.u32()))
yield Attribute(name, fields)
def _load_functions(self):
def _signature(name: str, decl: Optional[DeclSpec]):
signature = IFPSAPI.get(name, IFPSEvents.get(name)) if name else None
if decl and decl.classname and (ic := IFPSClasses.Classes.get(decl.classname)):
signature = ic.members.get(decl.name, signature)
decl.classname = ic.name
return signature
reader = self.reader
rewind = reader.tell()
width = len(F'{self.count_functions:X}')
missing_types = set()
load_flags = (self.version >= 23)
reparsed = False
all_void = True
all_long = True
has_dll_imports = False
while True:
for k in range(self.count_functions):
decl = None
body = None
name = F'F{k:0{width}X}'
tags = reader.u8()
attributes = None
exported = FTag.Exported.check(tags)
if FTag.External.check(tags):
name = reader.read_length_prefixed_ascii(8)
if exported:
read = StructReader(bytes(reader.read_length_prefixed()))
decl = DeclSpec.ParseF(read, load_flags)
if not reparsed and decl.module is not None:
has_dll_imports = True
# inno: 0d13564460b4cca289ac60221e86ca5719d7217a8eb76671b4b2a8407c2af6b4
# ifps: 6c211c02652317903b23c827cbc311a258fcd6197eec6a3d2f91986bd8accb0e
# This script reports version 22 and therefore, load_flags starts as False.
# However, it should be true; the reasons are unclear. The code below is
# an attempt to identify incorrect load_flags values heuristically. When
# there are no __delay_load functions present, reading them with load_flags
# set to False will result in only procedures (void=True) with at least
# 2 arguments.
if not decl.void:
all_void = False
if len(decl.parameters) < 2:
all_long = False
else:
offset = reader.u32()
length = reader.u32()
if exported:
name = reader.read_length_prefixed_ascii()
decl = DeclSpec.ParseE(bytes(reader.read_length_prefixed()), self)
self.void = decl.void
else:
self.void = False
with reader.detour(offset):
body = list(self._parse_bytecode(reader.read(length)))
if FTag.HasAttrs.check(tags):
attributes = list(self._read_attributes())
fn = Function(name, decl, body, exported, attributes)
self.functions.append(fn)
if has_dll_imports and all_long and all_void and not reparsed:
load_flags = True
reparsed = True
reader.seekset(rewind)
self.functions.clear()
else:
break
for function in self.functions:
name = function.symbol
decl = function.decl
if (signature := _signature(name, decl)) and decl and signature.argc == decl.argc:
for old, new in itertools.zip_longest(decl.parameters, signature.parameters):
if not new:
break
if old and (t := old.type):
t.symbol = new.type
continue
if t := self.types_by_name.get(new.type):
t.symbol = new.type
else:
missing_types.add(new.type)
if sr := signature.return_type:
if (dr := decl.return_type) or (dr := self.types_by_name.get(sr)):
dr.symbol = sr
else:
missing_types.add(sr)
self.type_name_conflicts = self._name_types(missing_types)
for function in self.functions:
decl = function.decl
if signature := _signature(function.name, decl):
decl = decl or DeclSpec(True)
decl.void = signature.void
parameters = decl.parameters
if signature.argc != len(parameters):
decl.parameters = parameters = [DeclSpecParam(True) for _ in range(signature.argc)]
for old, new in zip(parameters, signature.parameters):
if old.type is None:
old.type = self.types_by_name.get(new.type)
old.name = new.name or old.name
old.const = new.const
function.symbol = decl.name = signature.name
if (rt := signature.return_type) and (decl.return_type is None):
decl.return_type = self.types_by_name.get(rt, decl.return_type)
function.decl = decl
for function in self.functions:
if function.body is None:
continue
for instruction in function.body:
if instruction.opcode is Op.Call:
t: Function = self.functions[instruction.operands[0]]
instruction.operands[0] = t
def _load_variables(self):
reader = self.reader
for index in range(self.count_variables):
code = reader.u32()
spec = Variant(index, VariantType.Global)
if reader.u8() & 1:
spec = reader.read_length_prefixed_ascii()
self.globals.append(VariableBase(self.types[code], spec))
def _read_variant(self, index: int) -> Variant:
if index < 0x40000000:
return Variant(index, VariantType.Global)
index -= 0x60000000
if index >= 0:
return Variant(index, VariantType.Local)
index = -index if self.void else ~index
return Variant(index, VariantType.Argument)
def _read_operand(self, reader: StructReader) -> Operand:
ot = OperandType(reader.u8())
kw = {}
if ot is OperandType.Variant:
kw.update(variant=self._read_variant(reader.u32()))
if ot is OperandType.Value:
kw.update(value=self._read_value(reader))
if ot >= OperandType.IndexedByInt:
kw.update(variant=self._read_variant(reader.u32()))
index = reader.u32()
if ot is OperandType.IndexedByVar:
index = self._read_variant(index)
kw.update(index=index)
return Operand(ot, **kw)
def _parse_bytecode(self, data: memoryview) -> Generator[Instruction, None, None]:
disassembly: Dict[int, Instruction] = OrderedDict()
reader = StructReader(data)
argcount = {
Op.Assign: 2,
Op.CallVar: 1,
Op.Dec: 1,
Op.Inc: 1,
Op.BooleanNot: 1,
Op.Neg: 1,
Op.IntegerNot: 1,
Op.SetPtrToCopy: 2,
Op.SetPtr: 2,
}
while not reader.eof:
def arg(k=1):
for _ in range(k):
args.append(self._read_operand(reader))
addr = reader.tell()
cval = reader.u8()
code = Op.FromInt(cval)
insn = Instruction(addr, code)
args = insn.operands
disassembly[insn.offset] = insn
aryness = argcount.get(code)
if aryness is not None:
arg(aryness)
elif code in (Op.Ret, Op.Nop, Op.Pop):
pass
elif code is Op.Calculate:
insn.operator = AOp(reader.u8())
arg(2)
elif code in (Op.Push, Op.PushVar):
arg()
elif code in (Op.Jump, Op.JumpFlag):
target = reader.i32()
args.append(reader.tell() + target)
elif code is Op.Call:
args.append(reader.u32())
elif code in (Op.JumpTrue, Op.JumpFalse):
target = reader.i32()
val = self._read_operand(reader)
args.append(reader.tell() + target)
args.append(val)
elif code is Op.JumpPop1:
target = reader.i32()
args.append(reader.tell() + target)
elif code is Op.JumpPop2:
target = reader.i32()
args.append(reader.tell() + target)
elif code is Op.StackType:
args.append(self._read_variant(reader.u32()))
args.append(reader.u32())
elif code is Op.PushType:
args.append(self.types[reader.u32()])
elif code is Op.Compare:
insn.operator = COp(reader.u8())
arg(3)
elif code is Op.SetFlag:
arg()
args.append(bool(reader.u8()))
elif code is Op.PushEH:
args.extend(reader.i32() for _ in range(4))
for k, a in enumerate(args):
args[k] = a + reader.tell() if a >= 0 else None
elif code is Op.PopEH:
args.append(reader.u8())
elif code is Op._INVALID:
raise ValueError(F'Unsupported opcode: 0x{cval:02X}')
else:
raise ValueError(F'Unhandled opcode: {code.name}')
insn.size = reader.tell() - addr
for k, instruction in enumerate(disassembly.values()):
if not instruction.branches:
continue
target = instruction.operands[0]
try:
disassembly[target].jumptarget = True
except KeyError as K:
raise RuntimeError(
F'The jump target of instruction {k} at 0x{instruction.offset:X} is invalid; '
F'the invalid instruction is a {instruction.opcode.name} to 0x{target:X}.'
) from K
yield from disassembly.values()
def __str__(self):
return self.disassembly()
def disassembly(self) -> str:
def sortkey(f: Function):
d = (d.module or '', d.classname or '', d.void) if (d := f.decl) else ('', '', True)
return (*d, f.name)
for function in self.functions:
function.get_basic_blocks()
classes: dict[str, dict[str, Function]] = {}
external: list[Function] = []
internal: list[Function] = []
for t in self.types:
if isinstance(t, TClass):
classes[t.name] = {}
for function in self.functions:
if (decl := function.decl) and (name := decl.classname):
try:
members = classes[name]
except KeyError:
members = classes[name] = {}
members[decl.name] = function
continue
dl = internal if function.body else external
dl.append(function)
external.sort(key=sortkey)
output = io.StringIO()
_omax = max((
max(insn.offset for insn in fn.body)
for fn in self.functions if fn.body
), default=0)
_smax = max((
max(insn.stack for insn in fn.body if insn.stack is not None)
for fn in self.functions if fn.body
), default=0)
_omax = max(len(self.types), len(self.globals), _omax)
_omax = len(F'{_omax:X}')
_smax = len(F'{_smax:d}')
if classes:
for name, members in classes.items():
if not members:
output.write(F'external class {name};\n')
output.write('\n')
for name, members in classes.items():
if not members:
continue
output.write(F'external class {name}')
if members:
for spec in members.values():
output.write(F'\n{_TAB}{spec.decl.represent(spec.symbol, rel=True)}')
output.write('\nend')
output.write(';\n\n')
if self.types:
for type in self.types:
if type.code != TC.Record and type.symbol in (type.code.name, None):
continue
if isinstance(type, TClass):
continue
output.write(F'typedef {type.symbol} = {type.display()}\n')
output.write('\n')
if self.globals:
for variable in self.globals:
output.write(F'global {variable!s}\n')
output.write('\n')
if external:
for function in external:
output.write(F'external {function!r}\n')
output.write('\n')
if internal:
for function in internal:
output.write(F'{function!r}\nbegin\n')
labels = [insn.offset for insn in function.body if insn.jumptarget]
labelw = max(len(str(len(labels))), 2)
labeld = {v: F'JumpDestination{k:0{labelw}d}' for k, v in enumerate(labels, 1)}
labelc = 0
for instruction in function.body:
stack = instruction.stack
stack = '?' * _smax if stack is None else F'{stack:>{_smax}d}'
if instruction.jumptarget:
output.write(F'{labeld[labels[labelc]]}:\n')
labelc += 1
output.write(F'{_TAB}0x{instruction.offset:0{_omax}X}{_TAB}{stack}{_TAB}{instruction.pretty(labeld)}\n')
output.write('end;\n\n')
return output.getvalue().strip()
Functions
def extended(_data)
-
Expand source code Browse git
def extended(_data: bytes): if len(_data) != 10: raise ValueError data = int.from_bytes(_data, 'little') sign = data >> 79 data = data ^ (sign << 79) sign = -1.0 if sign else +1.0 exponent = data >> 64 data = data ^ (exponent << 64) if exponent == 0: if data == 0: return sign * 0 exponent = -16382 elif exponent == 0b111111111111111: if data == 0: return sign * float('Inf') else: return sign * float('NaN') else: exponent = exponent - 16383 mantissa = data / (1 << 64) return sign * mantissa * (2 ** exponent)
def represent(cls)
-
Expand source code Browse git
def represent(cls: _E) -> _E: cls.__repr__ = lambda self: F'{self.__class__.__name__}.{self.name}' cls. __str__ = lambda self: self.name return cls
def ifpstype(cls)
-
Expand source code Browse git
def ifpstype(cls: _C) -> Union[_C, Type[IFPSTypeMixin]]: cls = dataclass(cls) mix = type(cls.__qualname__, (IFPSTypeMixin, cls), {}) assigned = set(WRAPPER_ASSIGNMENTS) - {'__annotations__'} update_wrapper(mix, cls, assigned=assigned, updated=()) return dataclass(mix)
Classes
class Op (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class Op(enum.IntEnum): Assign = 0x00 # noqa Calculate = 0x01 # noqa Push = 0x02 # noqa PushVar = 0x03 # noqa Pop = 0x04 # noqa Call = 0x05 # noqa Jump = 0x06 # noqa JumpTrue = 0x07 # noqa JumpFalse = 0x08 # noqa Ret = 0x09 # noqa StackType = 0x0A # noqa PushType = 0x0B # noqa Compare = 0x0C # noqa CallVar = 0x0D # noqa SetPtr = 0x0E # noqa BooleanNot = 0x0F # noqa Neg = 0x10 # noqa SetFlag = 0x11 # noqa JumpFlag = 0x12 # noqa PushEH = 0x13 # noqa PopEH = 0x14 # noqa IntegerNot = 0x15 # noqa SetPtrToCopy = 0x16 # noqa Inc = 0x17 # noqa Dec = 0x18 # noqa JumpPop1 = 0x19 # noqa JumpPop2 = 0x1A # noqa Nop = 0xFF # noqa _INVALID = 0xDD # noqa @classmethod def FromInt(cls, code: int): try: return cls(code) except ValueError: return cls._INVALID
Ancestors
- enum.IntEnum
- builtins.int
- enum.Enum
Class variables
var Assign
var Calculate
var Push
var PushVar
var Pop
var Call
var Jump
var JumpTrue
var JumpFalse
var Ret
var StackType
var PushType
var Compare
var CallVar
var SetPtr
var BooleanNot
var Neg
var SetFlag
var JumpFlag
var PushEH
var PopEH
var IntegerNot
var SetPtrToCopy
var Inc
var Dec
var JumpPop1
var JumpPop2
var Nop
Static methods
def FromInt(code)
-
Expand source code Browse git
@classmethod def FromInt(cls, code: int): try: return cls(code) except ValueError: return cls._INVALID
class AOp (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class AOp(enum.IntEnum): Add = 0 Sub = 1 Mul = 2 Div = 3 Mod = 4 Shl = 5 Shr = 6 And = 7 BOr = 8 Xor = 9 def __str__(self): glyph = ('+', '-', '*', '/', '%', '<<', '>>', '&', '|', '^')[self] return F'{glyph}='
Ancestors
- enum.IntEnum
- builtins.int
- enum.Enum
Class variables
var Add
var Sub
var Mul
var Div
var Mod
var Shl
var Shr
var And
var BOr
var Xor
class COp (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class COp(enum.IntEnum): GE = 0 LE = 1 GT = 2 LT = 3 NE = 4 EQ = 5 IN = 6 IS = 7 def __str__(self): return ('>=', '<=', '>', '<', '!=', '==', 'in', 'is')[self]
Ancestors
- enum.IntEnum
- builtins.int
- enum.Enum
Class variables
var GE
var LE
var GT
var LT
var NE
var EQ
var IN
var IS
class TC (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class TC(enum.IntEnum): ReturnAddress = 0x00 # noqa U08 = 0x01 # noqa S08 = 0x02 # noqa U16 = 0x03 # noqa S16 = 0x04 # noqa U32 = 0x05 # noqa S32 = 0x06 # noqa Single = 0x07 # noqa Double = 0x08 # noqa Extended = 0x09 # noqa AnsiString = 0x0A # noqa Record = 0x0B # noqa Array = 0x0C # noqa Pointer = 0x0D # noqa PChar = 0x0E # noqa ResourcePointer = 0x0F # noqa Variant = 0x10 # noqa S64 = 0x11 # noqa Char = 0x12 # noqa WideString = 0x13 # noqa WideChar = 0x14 # noqa ProcPtr = 0x15 # noqa StaticArray = 0x16 # noqa Set = 0x17 # noqa Currency = 0x18 # noqa Class = 0x19 # noqa Interface = 0x1A # noqa NotificationVariant = 0x1B # noqa UnicodeString = 0x1C # noqa Enum = 0x81 # noqa Type = 0x82 # noqa ExtClass = 0x83 # noqa @property def primitive(self) -> bool: return self not in { TC.Class, TC.ProcPtr, TC.Interface, TC.Set, TC.StaticArray, TC.Array, TC.Record, } @property def container(self) -> bool: return self in { TC.StaticArray, TC.Array, TC.Record, } @property def width(self): return { TC.Variant : 0x10, TC.Char : 0x01, TC.S08 : 0x01, TC.U08 : 0x01, TC.WideChar : 0x02, TC.S16 : 0x02, TC.U16 : 0x02, TC.WideString : 0x04, TC.UnicodeString : 0x04, TC.Interface : 0x04, TC.Class : 0x04, TC.PChar : 0x04, TC.AnsiString : 0x04, TC.Single : 0x04, TC.S32 : 0x04, TC.U32 : 0x04, TC.ProcPtr : 0x0C, TC.Currency : 0x08, TC.Pointer : 0x0C, TC.Double : 0x08, TC.S64 : 0x08, TC.Extended : 0x0A, TC.ReturnAddress : 0x1C, }.get(self, 0)
Ancestors
- enum.IntEnum
- builtins.int
- enum.Enum
Class variables
var ReturnAddress
var U08
var S08
var U16
var S16
var U32
var S32
var Single
var Double
var Extended
var AnsiString
var Record
var Array
var Pointer
var PChar
var ResourcePointer
var Variant
var S64
var Char
var WideString
var WideChar
var ProcPtr
var StaticArray
var Set
var Currency
var Class
var Interface
var NotificationVariant
var UnicodeString
var Enum
var Type
var ExtClass
Instance variables
var primitive
-
Expand source code Browse git
@property def primitive(self) -> bool: return self not in { TC.Class, TC.ProcPtr, TC.Interface, TC.Set, TC.StaticArray, TC.Array, TC.Record, }
var container
-
Expand source code Browse git
@property def container(self) -> bool: return self in { TC.StaticArray, TC.Array, TC.Record, }
var width
-
Expand source code Browse git
@property def width(self): return { TC.Variant : 0x10, TC.Char : 0x01, TC.S08 : 0x01, TC.U08 : 0x01, TC.WideChar : 0x02, TC.S16 : 0x02, TC.U16 : 0x02, TC.WideString : 0x04, TC.UnicodeString : 0x04, TC.Interface : 0x04, TC.Class : 0x04, TC.PChar : 0x04, TC.AnsiString : 0x04, TC.Single : 0x04, TC.S32 : 0x04, TC.U32 : 0x04, TC.ProcPtr : 0x0C, TC.Currency : 0x08, TC.Pointer : 0x0C, TC.Double : 0x08, TC.S64 : 0x08, TC.Extended : 0x0A, TC.ReturnAddress : 0x1C, }.get(self, 0)
class IFPSTypeMixin (symbol=None, attributes=None)
-
IFPSTypeMixin(symbol: 'Optional[str]' = None, attributes: 'Optional[List[Attribute]]' = None)
Expand source code Browse git
class IFPSTypeMixin: symbol: Optional[str] = None attributes: Optional[List[Attribute]] = None def __str__(self): if self.symbol is not None: return self.symbol return super().__str__()
Subclasses
Class variables
var symbol
var attributes
class IFPSTypeBase (code)
-
IFPSTypeBase(code: 'TC')
Expand source code Browse git
class IFPSTypeBase(abc.ABC): code: TC def simple(self, nested=False): return True def indexed(self): return self.code in ( TC.StaticArray, TC.Array, TC.Record, ) def display(self, indent=0): return indent * _TAB + self.code.name @abc.abstractmethod def py_type(self, key: Optional[int] = None) -> Optional[type]: ... @abc.abstractmethod def default(self, key: Optional[int] = None): ... @property def primitive(self) -> bool: return self.code.primitive @property def container(self) -> bool: return self.code.container def __str__(self): return self.display(0)
Ancestors
- abc.ABC
Subclasses
Class variables
var code
Instance variables
var primitive
-
Expand source code Browse git
@property def primitive(self) -> bool: return self.code.primitive
var container
-
Expand source code Browse git
@property def container(self) -> bool: return self.code.container
Methods
def simple(self, nested=False)
-
Expand source code Browse git
def simple(self, nested=False): return True
def indexed(self)
-
Expand source code Browse git
def indexed(self): return self.code in ( TC.StaticArray, TC.Array, TC.Record, )
def display(self, indent=0)
-
Expand source code Browse git
def display(self, indent=0): return indent * _TAB + self.code.name
def py_type(self, key=None)
-
Expand source code Browse git
@abc.abstractmethod def py_type(self, key: Optional[int] = None) -> Optional[type]: ...
def default(self, key=None)
-
Expand source code Browse git
@abc.abstractmethod def default(self, key: Optional[int] = None): ...
class TPrimitive (code)
-
TPrimitive(code: 'TC')
Expand source code Browse git
class TPrimitive(IFPSTypeBase): def py_type(self, *_) -> Optional[type]: return { TC.ReturnAddress : int, TC.U08 : int, TC.S08 : int, TC.U16 : int, TC.S16 : int, TC.U32 : int, TC.S32 : int, TC.Single : float, TC.Double : float, TC.Extended : float, TC.AnsiString : str, TC.Pointer : VariableBase, TC.PChar : str, TC.ResourcePointer : VariableBase, TC.Variant : object, TC.S64 : int, TC.Char : str, TC.WideString : str, TC.WideChar : str, TC.Currency : float, TC.UnicodeString : str, TC.Enum : int, TC.Type : IFPSType, }.get(self.code) def default(self, *_): if self.code in (TC.Char, TC.WideChar, TC.PChar): return '\0' tc = self.py_type() if issubclass(tc, (int, float, str)): return tc()
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var code
Methods
def py_type(self, *_)
-
Expand source code Browse git
def py_type(self, *_) -> Optional[type]: return { TC.ReturnAddress : int, TC.U08 : int, TC.S08 : int, TC.U16 : int, TC.S16 : int, TC.U32 : int, TC.S32 : int, TC.Single : float, TC.Double : float, TC.Extended : float, TC.AnsiString : str, TC.Pointer : VariableBase, TC.PChar : str, TC.ResourcePointer : VariableBase, TC.Variant : object, TC.S64 : int, TC.Char : str, TC.WideString : str, TC.WideChar : str, TC.Currency : float, TC.UnicodeString : str, TC.Enum : int, TC.Type : IFPSType, }.get(self.code)
def default(self, *_)
-
Expand source code Browse git
def default(self, *_): if self.code in (TC.Char, TC.WideChar, TC.PChar): return '\0' tc = self.py_type() if issubclass(tc, (int, float, str)): return tc()
class TProcPtr (code, void, args)
-
TProcPtr(code: 'TC', void: 'bool', args: 'tuple[DeclSpecParam, …]')
Expand source code Browse git
class TProcPtr(IFPSTypeBase): void: bool args: tuple[DeclSpecParam, ...] def py_type(self, *_): return None def default(self, *_): return None def display(self, indent=0): name = super().display(indent) args = [] for k, spec in enumerate(self.args, 1): arg = F'Arg{k}' if not spec.const: arg = F'*{arg}' if spec.type is not None: arg = F'{spec.type!s} {arg}' args.append(arg) args = ', '.join(args) return F'{name}({args})'
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var void
var args
Methods
def py_type(self, *_)
-
Expand source code Browse git
def py_type(self, *_): return None
def default(self, *_)
-
Expand source code Browse git
def default(self, *_): return None
def display(self, indent=0)
-
Expand source code Browse git
def display(self, indent=0): name = super().display(indent) args = [] for k, spec in enumerate(self.args, 1): arg = F'Arg{k}' if not spec.const: arg = F'*{arg}' if spec.type is not None: arg = F'{spec.type!s} {arg}' args.append(arg) args = ', '.join(args) return F'{name}({args})'
class TInterface (code, uuid)
-
TInterface(code: 'TC', uuid: 'UUID')
Expand source code Browse git
class TInterface(IFPSTypeBase): uuid: UUID def py_type(self, *_): return object def default(self, *_): return None def display(self, indent=0): display = super().display(indent) return F'{display}({self.uuid!s})'
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var uuid
Methods
def py_type(self, *_)
-
Expand source code Browse git
def py_type(self, *_): return object
def default(self, *_)
-
Expand source code Browse git
def default(self, *_): return None
def display(self, indent=0)
-
Expand source code Browse git
def display(self, indent=0): display = super().display(indent) return F'{display}({self.uuid!s})'
class TClass (code, name)
-
TClass(code: 'TC', name: 'str')
Expand source code Browse git
class TClass(IFPSTypeBase): name: str def py_type(self, *_): return None def default(self, *_): return None
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var name
Methods
def py_type(self, *_)
-
Expand source code Browse git
def py_type(self, *_): return None
def default(self, *_)
-
Expand source code Browse git
def default(self, *_): return None
class TSet (code, size)
-
TSet(code: 'TC', size: 'int')
Expand source code Browse git
class TSet(IFPSTypeBase): size: int def py_type(self, *_): return int def default(self, *_): return 0 @property def size_in_bytes(self): q, r = divmod(self.size, 8) return q + (r and 1 or 0) def display(self, indent=0): display = super().display(indent) return F'{display}({self.size})'
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var size
Instance variables
var size_in_bytes
-
Expand source code Browse git
@property def size_in_bytes(self): q, r = divmod(self.size, 8) return q + (r and 1 or 0)
Methods
def py_type(self, *_)
-
Expand source code Browse git
def py_type(self, *_): return int
def default(self, *_)
-
Expand source code Browse git
def default(self, *_): return 0
def display(self, indent=0)
-
Expand source code Browse git
def display(self, indent=0): display = super().display(indent) return F'{display}({self.size})'
class TArray (code, type)
-
TArray(code: 'TC', type: 'TPrimitive')
Expand source code Browse git
class TArray(IFPSTypeBase): type: TPrimitive def py_type(self, key: Optional[int] = None): if key is None: return list return self.type.py_type() def default(self, key: Optional[int] = None): if key is None: return [] return self.type.default() def display(self, indent=0): display = F'{_TAB * indent}{self.type!s}' return F'array of {display}' def simple(self, nested=False): return self.type.simple(nested)
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var type
Methods
def py_type(self, key=None)
-
Expand source code Browse git
def py_type(self, key: Optional[int] = None): if key is None: return list return self.type.py_type()
def default(self, key=None)
-
Expand source code Browse git
def default(self, key: Optional[int] = None): if key is None: return [] return self.type.default()
def display(self, indent=0)
-
Expand source code Browse git
def display(self, indent=0): display = F'{_TAB * indent}{self.type!s}' return F'array of {display}'
def simple(self, nested=False)
-
Expand source code Browse git
def simple(self, nested=False): return self.type.simple(nested)
class TStaticArray (code, type, size, offset=None)
-
TStaticArray(code: 'TC', type: 'TPrimitive', size: 'int', offset: 'Optional[int]' = None)
Expand source code Browse git
class TStaticArray(IFPSTypeBase): type: TPrimitive size: int offset: Optional[int] = None def py_type(self, key: Optional[int] = None): if key is None: return list return self.type.py_type(key) def default(self, key: Optional[int] = None): if key is None: return [self.type.default() for _ in range(self.size)] return self.type.default() def display(self, indent=0): display = F'{_TAB * indent}{self.type!s}' return F'{display}[{self.size}]' def simple(self, nested=False): return self.type.simple(nested)
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var type
var size
var offset
Methods
def py_type(self, key=None)
-
Expand source code Browse git
def py_type(self, key: Optional[int] = None): if key is None: return list return self.type.py_type(key)
def default(self, key=None)
-
Expand source code Browse git
def default(self, key: Optional[int] = None): if key is None: return [self.type.default() for _ in range(self.size)] return self.type.default()
def display(self, indent=0)
-
Expand source code Browse git
def display(self, indent=0): display = F'{_TAB * indent}{self.type!s}' return F'{display}[{self.size}]'
def simple(self, nested=False)
-
Expand source code Browse git
def simple(self, nested=False): return self.type.simple(nested)
class TRecord (code, members)
-
TRecord(code: 'TC', members: 'Tuple[TPrimitive, …]')
Expand source code Browse git
class TRecord(IFPSTypeBase): members: Tuple[TPrimitive, ...] @property def size(self): return len(self.members) def py_type(self, key: Optional[int] = None): if key is None: return list return self.members[key].py_type() def default(self, key: Optional[int] = None): if key is None: return [member.default() for member in self.members] return self.members[key].default() def simple(self, nested=False): if nested: return False if len(self.members) > 10: return False return all(m.simple(True) for m in self.members) def display(self, indent=0): output = io.StringIO() output.write(indent * _TAB) output.write('struct {') if self.simple(): output.write(', '.join(str(m) for m in self.members)) else: for k, member in enumerate(self.members): if k > 0: output.write(',') output.write('\n') output.write(member.display(indent + 1)) if self.members: output.write(F'\n{_TAB * indent}') output.write('}') return output.getvalue()
Ancestors
- IFPSTypeBase
- abc.ABC
Subclasses
Class variables
var members
Instance variables
var size
-
Expand source code Browse git
@property def size(self): return len(self.members)
Methods
def py_type(self, key=None)
-
Expand source code Browse git
def py_type(self, key: Optional[int] = None): if key is None: return list return self.members[key].py_type()
def default(self, key=None)
-
Expand source code Browse git
def default(self, key: Optional[int] = None): if key is None: return [member.default() for member in self.members] return self.members[key].default()
def simple(self, nested=False)
-
Expand source code Browse git
def simple(self, nested=False): if nested: return False if len(self.members) > 10: return False return all(m.simple(True) for m in self.members)
def display(self, indent=0)
-
Expand source code Browse git
def display(self, indent=0): output = io.StringIO() output.write(indent * _TAB) output.write('struct {') if self.simple(): output.write(', '.join(str(m) for m in self.members)) else: for k, member in enumerate(self.members): if k > 0: output.write(',') output.write('\n') output.write(member.display(indent + 1)) if self.members: output.write(F'\n{_TAB * indent}') output.write('}') return output.getvalue()
class Value (type, value)
-
Value(type, value)
Expand source code Browse git
class Value(NamedTuple): type: IFPSType value: Union[str, int, float, bytes, Function] def convert(self, *_): return self.type.py_type() def default(self, *_): return self.type.default() def __repr__(self): value = self.value if isinstance(value, bytes): value = value.hex() return F'{self.type.code.name}({value!r})' def __str__(self): v = self.value if isinstance(v, Function): return F'&{v!s}' return repr(v)
Ancestors
- builtins.tuple
Instance variables
var type
-
Alias for field number 0
var value
-
Alias for field number 1
Methods
def convert(self, *_)
-
Expand source code Browse git
def convert(self, *_): return self.type.py_type()
def default(self, *_)
-
Expand source code Browse git
def default(self, *_): return self.type.default()
class Attribute (name, fields)
-
Attribute(name, fields)
Expand source code Browse git
class Attribute(NamedTuple): name: str fields: Tuple[Value, ...] def __repr__(self): name = self.name if self.fields: name += '[{}]'.format(','.join(repr(f) for f in self.fields)) return name
Ancestors
- builtins.tuple
Instance variables
var name
-
Alias for field number 0
var fields
-
Alias for field number 1
class DeclSpecParam (const, type=None, name=None)
-
DeclSpecParam(const: 'bool', type: 'Optional[TPrimitive]' = None, name: 'Optional[str]' = None)
Expand source code Browse git
class DeclSpecParam: const: bool type: Optional[TPrimitive] = None name: Optional[str] = None
Class variables
var const
var type
var name
class CallType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class CallType(str, enum.Enum): Symbol = 'symbol' Procedure = 'procedure' Function = 'function' def __str__(self): return self.value
Ancestors
- builtins.str
- enum.Enum
Class variables
var Symbol
var Procedure
var Function
class DeclSpec (void, parameters=<factory>, name='', calling_convention=None, return_type=None, module=None, classname=None, delay_load=False, vtable_index=None, load_with_altered_search_path=False, is_property=False)
-
DeclSpec(void: 'bool', parameters: 'List[DeclSpecParam]' =
, name: 'str' = '', calling_convention: 'Optional[str]' = None, return_type: 'Optional[IFPSType]' = None, module: 'Optional[str]' = None, classname: 'Optional[str]' = None, delay_load: 'bool' = False, vtable_index: 'Optional[int]' = None, load_with_altered_search_path: 'bool' = False, is_property: 'bool' = False) Expand source code Browse git
class DeclSpec: void: bool parameters: List[DeclSpecParam] = field(default_factory=list) name: str = '' calling_convention: Optional[str] = None return_type: Optional[IFPSType] = None module: Optional[str] = None classname: Optional[str] = None delay_load: bool = False vtable_index: Optional[int] = None load_with_altered_search_path: bool = False is_property: bool = False @property def argc(self): return len(self.parameters) def represent(self, name: str, ref: bool = False, rel: bool = False): def pparam(k: int, p: DeclSpecParam): name = p.name or F'{VariantType.Argument!s}{k}' if p.type is not None: name = F'{name}: {p.type!s}' if not p.const: name = F'*{name}' return name if self.name and name in self.name: name = self.name spec = name if self.vtable_index is not None: spec = F'{self.name}[{self.vtable_index}]' if not rel and self.classname: spec = F'{self.classname}.{spec}' if not rel and self.module: spec = F'{self.module}::{spec}' if not ref: if self.delay_load: spec = F'__delay_load {spec}' if self.calling_convention: spec = F'__{self.calling_convention} {spec}' spec = F'{self.type} {spec}' args = self.parameters args = args and ', '.join(pparam(*t) for t in enumerate(args, 1)) or '' spec = F'{spec}({args})' if self.return_type: spec = F'{spec}: {self.return_type!s}' return spec @property def type(self): return CallType.Procedure if self.void else CallType.Function def __repr__(self): return self.represent(self.name or '(*)') @classmethod def ParseF(cls, reader: StructReader[bytes], load_flags: bool): def ascii(): return reader.read_c_string('latin1') def boolean(): return bool(reader.u8()) def cc(): return { 0: 'register', 1: 'pascal', 2: 'cdecl', 3: 'stdcall', }.get(reader.u8(), cls.calling_convention) def read_parameters(): nonlocal void void = not boolean() parameters.extend(DeclSpecParam(not b) for b in reader.read()) void = True name = None properties = {} parameters = [] if reader.readif(b'dll:'): reader.readif(B'files:') if (module := ascii()).lower().endswith('.dll'): module = module[:-4] properties.update(module=module) name = ascii() properties.update(calling_convention=cc()) if load_flags: properties.update(delay_load=boolean(), load_with_altered_search_path=boolean()) read_parameters() elif reader.readif(b'class:'): if reader.remaining_bytes == 1: spec = reader.peek(1) void = False parameters.append(DeclSpecParam(False)) name = { b'+': 'CastToType', B'-': 'SetNil' }.get(spec) properties.update(classname='Class', calling_convention='pascal') else: properties.update(classname=reader.read_terminated_array(b'|').decode('latin1')) name = reader.read_terminated_array(b'|').decode('latin1') if name[-1] == '@': properties.update(is_property=True) name = name[:-1] properties.update(calling_convention=cc()) read_parameters() elif reader.readif(b'intf:.'): name = 'CoInterface' properties.update(vtable_index=reader.u32()) properties.update(calling_convention=cc()) read_parameters() else: read_parameters() return cls(void, parameters, name=name, **properties) @classmethod def ParseE(cls, data: bytes, ipfs: IFPSFile): decl = data.split(B'\x20') try: return_type = int(decl.pop(0)) except Exception: void = True else: void = return_type < 0 if not void: return_type = ipfs.types[return_type] else: return_type = None parameters = [] for param in decl: try: i = int(param[1:]) except Exception: tv = None else: tv = ipfs.types[i] parameters.append( DeclSpecParam(param[:1] == B'@', tv)) return cls(void, parameters, return_type=return_type)
Class variables
var void
var parameters
var name
var calling_convention
var return_type
var module
var classname
var delay_load
var vtable_index
var load_with_altered_search_path
var is_property
Static methods
def ParseF(reader, load_flags)
-
Expand source code Browse git
@classmethod def ParseF(cls, reader: StructReader[bytes], load_flags: bool): def ascii(): return reader.read_c_string('latin1') def boolean(): return bool(reader.u8()) def cc(): return { 0: 'register', 1: 'pascal', 2: 'cdecl', 3: 'stdcall', }.get(reader.u8(), cls.calling_convention) def read_parameters(): nonlocal void void = not boolean() parameters.extend(DeclSpecParam(not b) for b in reader.read()) void = True name = None properties = {} parameters = [] if reader.readif(b'dll:'): reader.readif(B'files:') if (module := ascii()).lower().endswith('.dll'): module = module[:-4] properties.update(module=module) name = ascii() properties.update(calling_convention=cc()) if load_flags: properties.update(delay_load=boolean(), load_with_altered_search_path=boolean()) read_parameters() elif reader.readif(b'class:'): if reader.remaining_bytes == 1: spec = reader.peek(1) void = False parameters.append(DeclSpecParam(False)) name = { b'+': 'CastToType', B'-': 'SetNil' }.get(spec) properties.update(classname='Class', calling_convention='pascal') else: properties.update(classname=reader.read_terminated_array(b'|').decode('latin1')) name = reader.read_terminated_array(b'|').decode('latin1') if name[-1] == '@': properties.update(is_property=True) name = name[:-1] properties.update(calling_convention=cc()) read_parameters() elif reader.readif(b'intf:.'): name = 'CoInterface' properties.update(vtable_index=reader.u32()) properties.update(calling_convention=cc()) read_parameters() else: read_parameters() return cls(void, parameters, name=name, **properties)
def ParseE(data, ipfs)
-
Expand source code Browse git
@classmethod def ParseE(cls, data: bytes, ipfs: IFPSFile): decl = data.split(B'\x20') try: return_type = int(decl.pop(0)) except Exception: void = True else: void = return_type < 0 if not void: return_type = ipfs.types[return_type] else: return_type = None parameters = [] for param in decl: try: i = int(param[1:]) except Exception: tv = None else: tv = ipfs.types[i] parameters.append( DeclSpecParam(param[:1] == B'@', tv)) return cls(void, parameters, return_type=return_type)
Instance variables
var argc
-
Expand source code Browse git
@property def argc(self): return len(self.parameters)
var type
-
Expand source code Browse git
@property def type(self): return CallType.Procedure if self.void else CallType.Function
Methods
def represent(self, name, ref=False, rel=False)
-
Expand source code Browse git
def represent(self, name: str, ref: bool = False, rel: bool = False): def pparam(k: int, p: DeclSpecParam): name = p.name or F'{VariantType.Argument!s}{k}' if p.type is not None: name = F'{name}: {p.type!s}' if not p.const: name = F'*{name}' return name if self.name and name in self.name: name = self.name spec = name if self.vtable_index is not None: spec = F'{self.name}[{self.vtable_index}]' if not rel and self.classname: spec = F'{self.classname}.{spec}' if not rel and self.module: spec = F'{self.module}::{spec}' if not ref: if self.delay_load: spec = F'__delay_load {spec}' if self.calling_convention: spec = F'__{self.calling_convention} {spec}' spec = F'{self.type} {spec}' args = self.parameters args = args and ', '.join(pparam(*t) for t in enumerate(args, 1)) or '' spec = F'{spec}({args})' if self.return_type: spec = F'{spec}: {self.return_type!s}' return spec
class Function (symbol, decl, body=None, attributes=None)
-
Function(symbol: 'str', decl: 'Optional[DeclSpec]', body: 'Optional[List[Instruction]]' = None, attributes: 'Optional[List[Attribute]]' = None, _bbs: 'Optional[Dict[int, BasicBlock]]' = None, _ins: 'Optional[Dict[int, Instruction]]' = None)
Expand source code Browse git
class Function: symbol: str decl: Optional[DeclSpec] body: Optional[List[Instruction]] = None attributes: Optional[List[Attribute]] = None _bbs: Optional[Dict[int, BasicBlock]] = None _ins: Optional[Dict[int, Instruction]] = None @property def name(self): symbol = self.symbol if (decl := self.decl) and (name := decl.name) and (symbol in name): symbol = name return symbol @property def code(self): if code := self._ins: return code self._ins = code = {i.offset: i for i in self.body} return code def reference(self, rel: bool = False) -> str: if self.decl is None: return self.symbol return self.decl.represent(self.symbol, ref=True, rel=rel) def __repr__(self): if self.decl is None: return F'symbol {self.symbol}' return self.decl.represent(self.symbol) def __str__(self): return self.reference() @property def type(self): if self.decl is None: return CallType.Symbol return self.decl.type def get_basic_blocks(self) -> Dict[int, BasicBlock]: if (bbs := self._bbs) is not None: return bbs if self.body is None: bbs = self._bbs = {} return bbs bbs: Dict[int, BasicBlock] = {0: (bb := BasicBlock(0))} self._bbs = bbs for insn in self.body: try: bb = bbs[insn.offset] except KeyError: if insn.jumptarget: nb = bbs[insn.offset] = BasicBlock(insn.offset) nb.sources[bb.offset] = bb bb.targets[nb.offset] = nb bb = nb bb.body.append(insn) if not insn.branches: continue targets = [insn.operands[0]] sequence = insn.offset + insn.size if not insn.jumps and insn.opcode != Op.Ret: targets.append(sequence) for t in targets: if not (bt := bbs.get(t)): bt = bbs[t] = BasicBlock(t) bb.targets[t] = bt bt.sources[bb.offset] = bb for offset, bb in list(bbs.items()): if bb.body: continue del bbs[offset] for source in bb.sources.values(): source.targets.pop(offset, None) visited: set[int] = set() errored: set[int] = set() def trace_stack(offset: int, stack: Optional[int]): if offset in errored: return bb = bbs[offset] if bb.stack is not None and stack != bb.stack: stack = None if stack is None: errored.add(offset) elif offset in visited: return else: visited.add(offset) bb.stack = stack body = [] if stack is None else bb.body for insn in body: insn.stack = stack stack += insn.stack_delta for t in bb.targets: trace_stack(t, stack) trace_stack(0, 0) for insn in self.body: if (stack := insn.stack) is None: continue for k, op in enumerate(insn.operands): if not isinstance(op, Operand): continue if not (v := op.variant) or v.type != VariantType.Local: continue if v.index <= stack: continue raise IndexError( F'Instruction {op!s} at offset 0x{insn.offset:X} in function {self.name} has ' F'variant operand {k} whose index {v.index} exceeds the stack depth {stack}.') return bbs
Class variables
var symbol
var decl
var body
var attributes
Instance variables
var name
-
Expand source code Browse git
@property def name(self): symbol = self.symbol if (decl := self.decl) and (name := decl.name) and (symbol in name): symbol = name return symbol
var code
-
Expand source code Browse git
@property def code(self): if code := self._ins: return code self._ins = code = {i.offset: i for i in self.body} return code
var type
-
Expand source code Browse git
@property def type(self): if self.decl is None: return CallType.Symbol return self.decl.type
Methods
def reference(self, rel=False)
-
Expand source code Browse git
def reference(self, rel: bool = False) -> str: if self.decl is None: return self.symbol return self.decl.represent(self.symbol, ref=True, rel=rel)
def get_basic_blocks(self)
-
Expand source code Browse git
def get_basic_blocks(self) -> Dict[int, BasicBlock]: if (bbs := self._bbs) is not None: return bbs if self.body is None: bbs = self._bbs = {} return bbs bbs: Dict[int, BasicBlock] = {0: (bb := BasicBlock(0))} self._bbs = bbs for insn in self.body: try: bb = bbs[insn.offset] except KeyError: if insn.jumptarget: nb = bbs[insn.offset] = BasicBlock(insn.offset) nb.sources[bb.offset] = bb bb.targets[nb.offset] = nb bb = nb bb.body.append(insn) if not insn.branches: continue targets = [insn.operands[0]] sequence = insn.offset + insn.size if not insn.jumps and insn.opcode != Op.Ret: targets.append(sequence) for t in targets: if not (bt := bbs.get(t)): bt = bbs[t] = BasicBlock(t) bb.targets[t] = bt bt.sources[bb.offset] = bb for offset, bb in list(bbs.items()): if bb.body: continue del bbs[offset] for source in bb.sources.values(): source.targets.pop(offset, None) visited: set[int] = set() errored: set[int] = set() def trace_stack(offset: int, stack: Optional[int]): if offset in errored: return bb = bbs[offset] if bb.stack is not None and stack != bb.stack: stack = None if stack is None: errored.add(offset) elif offset in visited: return else: visited.add(offset) bb.stack = stack body = [] if stack is None else bb.body for insn in body: insn.stack = stack stack += insn.stack_delta for t in bb.targets: trace_stack(t, stack) trace_stack(0, 0) for insn in self.body: if (stack := insn.stack) is None: continue for k, op in enumerate(insn.operands): if not isinstance(op, Operand): continue if not (v := op.variant) or v.type != VariantType.Local: continue if v.index <= stack: continue raise IndexError( F'Instruction {op!s} at offset 0x{insn.offset:X} in function {self.name} has ' F'variant operand {k} whose index {v.index} exceeds the stack depth {stack}.') return bbs
class VariableBase (type, spec)
-
Expand source code Browse git
class VariableBase: type: IFPSType spec: Variant def __init__(self, type: IFPSType, spec: Variant): self.type = type self.spec = spec def __str__(self): return F'{self.spec}: {self.type!s}'
Subclasses
Class variables
var type
var spec
class OperandType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class OperandType(enum.IntEnum): Variant = 0 Value = 1 IndexedByInt = 2 IndexedByVar = 3
Ancestors
- enum.IntEnum
- builtins.int
- enum.Enum
Class variables
var Variant
var Value
var IndexedByInt
var IndexedByVar
class EHType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class EHType(enum.IntEnum): Try = 0 Finally = 1 Catch = 2 SecondFinally = 3
Ancestors
- enum.IntEnum
- builtins.int
- enum.Enum
Class variables
var Try
var Finally
var Catch
var SecondFinally
class NewEH (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class NewEH(enum.IntEnum): Finally = 0 CatchAt = 1 SecondFinally = 2 End = 3
Ancestors
- enum.IntEnum
- builtins.int
- enum.Enum
Class variables
var Finally
var CatchAt
var SecondFinally
var End
class VariantType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class VariantType(str, enum.Enum): Global = 'GlobalVar' Local = 'LocalVar' Argument = 'Argument' def __repr__(self): return self.name def __str__(self): return self.value
Ancestors
- builtins.str
- enum.Enum
Class variables
var Global
var Local
var Argument
class Variant (index, type)
-
Variant(index, type)
Expand source code Browse git
class Variant(NamedTuple): index: int type: VariantType def __repr__(self): if self.index == 0 and self.type == VariantType.Argument: return 'ReturnValue' return F'{self.type!s}{self.index}'
Ancestors
- builtins.tuple
Instance variables
var index
-
Alias for field number 0
var type
-
Alias for field number 1
class Operand (type, variant=None, value=None, index=None)
-
Operand(type, variant, value, index)
Expand source code Browse git
class Operand(NamedTuple): type: OperandType variant: Optional[Variant] = None value: Optional[Value] = None index: Optional[Union[Variant, int]] = None def __repr__(self): return self.__tostring(repr) def __str__(self): return self.__tostring(str) @property def immediate(self): return self.type == OperandType.Value def __tostring(self, converter): if self.type is OperandType.Value: return converter(self.value) if self.type is OperandType.Variant: return converter(self.variant) if self.type is OperandType.IndexedByInt: return F'{converter(self.variant)}[0x{self.index:02X}]' if self.type is OperandType.IndexedByVar: return F'{converter(self.variant)}[{self.index!s}]' raise RuntimeError(F'Unexpected OperandType {self.type!r} in {self.__class__.__name__}')
Ancestors
- builtins.tuple
Instance variables
var type
-
Alias for field number 0
var variant
-
Alias for field number 1
var value
-
Alias for field number 2
var index
-
Alias for field number 3
var immediate
-
Expand source code Browse git
@property def immediate(self): return self.type == OperandType.Value
class Instruction (offset, opcode, size=0, stack=None, operands=<factory>, operator=None, jumptarget=False)
-
Instruction(offset: 'int', opcode: 'Op', size: 'int' = 0, stack: 'Optional[int]' = None, operands: 'List[Union[str, bool, int, float, Operand, IFPSType, Function, None]]' =
, operator: 'Optional[Union[AOp, COp]]' = None, jumptarget: 'bool' = False) Expand source code Browse git
class Instruction: offset: int opcode: Op size: int = 0 stack: Optional[int] = None operands: List[Union[str, bool, int, float, Operand, IFPSType, Function, None]] = field(default_factory=list) operator: Optional[Union[AOp, COp]] = None jumptarget: bool = False def op(self, index: int): arg = self.operands[index] if not isinstance(arg, Operand): raise TypeError return arg @property def branches(self): return self.opcode in ( Op.Jump, Op.JumpFalse, Op.JumpTrue, Op.JumpFlag, Op.JumpPop1, Op.JumpPop2, ) @property def jumps(self): return self.opcode in ( Op.Jump, Op.JumpPop1, Op.JumpPop2, ) @property def stack_delta(self): return _Op_StackD.get(self.opcode, 0) def oprep(self, labels: Optional[dict[int, str]] = None): if self.branches: dst = self.operands[0] if not labels or not (label := labels.get(dst)): label = F'0x{dst:X}' var = [str(op) for op in self.operands[1:]] return ', '.join((label, *var)) elif self.opcode is Op.PushEH: ops = [] for op, name in reversed(list(zip(self.operands, NewEH))): if op is None: continue ops.append(F'{name}:0x{op:X}') return '\x20'.join(ops) elif self.opcode is Op.PopEH: return F'End{EHType(self.operands[0])}' elif self.opcode is Op.SetFlag: rep, negated = self.operands return F'!{rep}' if negated else str(rep) elif self.opcode is Op.Compare: dst, a, b = self.operands return F'{dst!s} := {a!s} {self.operator!s} {b!s}' elif self.opcode is Op.Calculate: dst, src = self.operands return F'{dst!s} {self.operator!s} {src!s}' elif self.opcode in (Op.Assign, Op.SetPtr, Op.SetPtrToCopy): dst, src = self.operands return F'{dst!s} := {src!s}' else: return ', '.join(str(op) for op in self.operands) def pretty(self, labels: Optional[dict[int, str]] = None): return F'{self.opcode!s:<{_Op_Maxlen}}{_TAB}{self.oprep(labels)}'.strip() def __repr__(self): return F'{self.opcode.name}({self.oprep()})' def __str__(self): return self.pretty()
Class variables
var offset
var opcode
var operands
var size
var stack
var operator
var jumptarget
Instance variables
var branches
-
Expand source code Browse git
@property def branches(self): return self.opcode in ( Op.Jump, Op.JumpFalse, Op.JumpTrue, Op.JumpFlag, Op.JumpPop1, Op.JumpPop2, )
var jumps
-
Expand source code Browse git
@property def jumps(self): return self.opcode in ( Op.Jump, Op.JumpPop1, Op.JumpPop2, )
var stack_delta
-
Expand source code Browse git
@property def stack_delta(self): return _Op_StackD.get(self.opcode, 0)
Methods
def op(self, index)
-
Expand source code Browse git
def op(self, index: int): arg = self.operands[index] if not isinstance(arg, Operand): raise TypeError return arg
def oprep(self, labels=None)
-
Expand source code Browse git
def oprep(self, labels: Optional[dict[int, str]] = None): if self.branches: dst = self.operands[0] if not labels or not (label := labels.get(dst)): label = F'0x{dst:X}' var = [str(op) for op in self.operands[1:]] return ', '.join((label, *var)) elif self.opcode is Op.PushEH: ops = [] for op, name in reversed(list(zip(self.operands, NewEH))): if op is None: continue ops.append(F'{name}:0x{op:X}') return '\x20'.join(ops) elif self.opcode is Op.PopEH: return F'End{EHType(self.operands[0])}' elif self.opcode is Op.SetFlag: rep, negated = self.operands return F'!{rep}' if negated else str(rep) elif self.opcode is Op.Compare: dst, a, b = self.operands return F'{dst!s} := {a!s} {self.operator!s} {b!s}' elif self.opcode is Op.Calculate: dst, src = self.operands return F'{dst!s} {self.operator!s} {src!s}' elif self.opcode in (Op.Assign, Op.SetPtr, Op.SetPtrToCopy): dst, src = self.operands return F'{dst!s} := {src!s}' else: return ', '.join(str(op) for op in self.operands)
def pretty(self, labels=None)
-
Expand source code Browse git
def pretty(self, labels: Optional[dict[int, str]] = None): return F'{self.opcode!s:<{_Op_Maxlen}}{_TAB}{self.oprep(labels)}'.strip()
class BasicBlock (offset, stack=None, body=<factory>, sources=<factory>, targets=<factory>)
-
BasicBlock(offset: 'int', stack: 'Optional[int]' = None, body: 'List[Instruction]' =
, sources: 'Dict[int, BasicBlock]' = , targets: 'Dict[int, BasicBlock]' = ) Expand source code Browse git
class BasicBlock: offset: int stack: Optional[int] = None body: List[Instruction] = field(default_factory=list) sources: Dict[int, BasicBlock] = field(default_factory=dict) targets: Dict[int, BasicBlock] = field(default_factory=dict) @property def stack_delta(self): return sum(insn.stack_delta for insn in self.body) @property def size(self): return sum(insn.size for insn in self.body)
Class variables
var offset
var body
var sources
var targets
var stack
Instance variables
var stack_delta
-
Expand source code Browse git
@property def stack_delta(self): return sum(insn.stack_delta for insn in self.body)
var size
-
Expand source code Browse git
@property def size(self): return sum(insn.size for insn in self.body)
class FTag (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class FTag(enum.IntFlag): External = 0b0001 Exported = 0b0010 HasAttrs = 0b0100 def check(self, v): return bool(self & v)
Ancestors
- enum.IntFlag
- builtins.int
- enum.Flag
- enum.Enum
Class variables
var External
var Exported
var HasAttrs
Methods
def check(self, v)
-
Expand source code Browse git
def check(self, v): return bool(self & v)
class IFPSFile (reader, codec='latin1', unicode=True)
-
A class to parse structured data. A
Struct
class can be instantiated as follows:foo = Struct(data, bar=29)
The initialization routine of the structure will be called with a single argument
reader
. If the objectdata
is already aStructReader
, then it will be passed asreader
. Otherwise, the argument will be wrapped in aStructReader
. Additional arguments to the struct are passed through.Expand source code Browse git
class IFPSFile(Struct): MinVer = 12 MaxVer = 23 Magic = B'IFPS' def __init__(self, reader: StructReader[memoryview], codec: str = 'latin1', unicode: bool = True): self.codec = codec self.unicode = unicode self.types: List[IFPSType] = [] self.functions: List[Function] = [] self.globals: List[VariableBase] = [] self.strings: List[str] = [] self.reader = reader if reader.remaining_bytes < 28: raise ValueError('Less than 28 bytes in file, not enough data to parse.') magic = reader.read(4) if magic != self.Magic: raise ValueError(F'Invalid magic sequence: {magic.hex()}') self.version = reader.u32() self.count_types = reader.u32() self.count_functions = reader.u32() self.count_variables = reader.u32() self.entry = reader.u32() self.import_size = reader.u32() self.void = False if self.version not in range(self.MinVer, self.MaxVer + 1): raise NotImplementedError( F'This IFPS file has version {self.version}, which is not in the supported range ' F'[{self.MinVer},{self.MaxVer}].') self._known_type_names = { TC.U08 : {'Byte', 'Boolean'}, TC.S08 : {'ShortInt'}, TC.U16 : {'Word'}, TC.S16 : {'SmallInt'}, TC.S32 : {'Integer', 'LongInt'}, TC.U32 : {'LongWord', 'Cardinal', 'HWND', 'TSetupProcessorArchitecture'}, TC.Char : {'AnsiChar'}, TC.PChar : {'PAnsiChar'}, TC.S64 : {'Int64'}, } self._load_types() self._name_types() self._load_functions() self._load_variables() del self._known_type_names def _name_types(self, missing_types: Optional[set[str]] = None): tbn: dict[str, IFPSType] = CaseInsensitiveDict() self.types_by_name = tbn for t in self.types: name = str(t) code = t.code known = self._known_type_names.get(code) if known: known.discard(name) tbn[name] = t if missing_types: def add_type(name: str, type: IFPSType): tbn[name] = type self.types.append(type) return type def make_string(name: str = 'String'): try: return tbn[name] except KeyError: pass for type in tbn.values(): if type.code in ( TC.AnsiString, TC.WideString, TC.UnicodeString, ): break else: code = TC.WideString if self.unicode else TC.AnsiString type = TPrimitive(code, symbol=name) return add_type(name, type) for name in tbn: missing_types.discard(name) if 'TGUID' in missing_types: missing_types |= {'LongWord', 'Word', 'Byte'} for code in TC: name = code.name if name not in missing_types: continue missing_types.discard(name) add_type(name, TPrimitive(code, symbol=name)) for code, names in self._known_type_names.items(): for name in names: if name not in missing_types: continue missing_types.discard(name) add_type(name, TPrimitive(code, symbol=name)) for name in [ 'TVarType', 'TInputQueryWizardPage', 'TInputOptionWizardPage', 'TInputDirWizardPage', 'TInputFileWizardPage', 'TOutputMsgWizardPage', 'TOutputMsgMemoWizardPage', 'TOutputProgressWizardPage', 'TOutputMarqueeProgressWizardPage', 'TDownloadWizardPage', 'ExtractionWizardPage', 'TWizardPage', 'TSetupForm', 'TComponent', 'TNewNotebookPage', ]: if name not in missing_types: continue add_type(name, TClass(TC.Class, name, symbol=name)) if (name := 'String') in missing_types: make_string(name) if (name := 'AnyString') in missing_types: make_string(name) if (name := 'TArrayOfString') in missing_types: add_type(name, TArray(TC.Array, make_string())) if (name := 'IUnknown') in missing_types: add_type(name, TInterface(TC.Interface, UUID('{00000000-0000-0000-C000-000000000046}'))) if (name := 'TGUID') in missing_types: add_type(name, TRecord(TC.Record, ( tbn['LongWord'], tbn['Word'], tbn['Word'], TStaticArray(tbn['Byte'], 8) ), symbol=name)) def _load_types(self): def _normalize(n: str): return IFPSClasses.Types.get(n.casefold(), n) reader = self.reader types = self.types for k in range(self.count_types): typecode = reader.u8() exported = bool(typecode & 0x80) typecode = typecode & 0x7F try: code = TC(typecode) except ValueError as V: raise ValueError(F'Unknown type code value 0x{typecode:02X}.') from V if code in (TC.Class, TC.ExtClass): t = TClass(code, _normalize(reader.read_length_prefixed_ascii())) elif code is TC.ProcPtr: spec = reader.read_length_prefixed() void = bool(spec[0]) args = tuple(DeclSpecParam(not b) for b in spec[1:]) t = TProcPtr(code, void, args) elif code is TC.Interface: guid = UUID(bytes=bytes(reader.read(0x10))) t = TInterface(code, guid) elif code is TC.Set: t = TSet(code, reader.u32()) elif code is TC.StaticArray: type = types[reader.u32()] size = reader.u32() offset = None if self.version <= 22 else reader.u32() t = TStaticArray(code, type, size, offset) elif code is TC.Array: t = TArray(code, types[reader.u32()]) elif code is TC.Record: length = reader.u32() members = tuple(types[reader.u32()] for _ in range(length)) t = TRecord(code, members, symbol=F'RECORD{k}') else: t = TPrimitive(code, symbol=code.name) if exported: t.symbol = _normalize(reader.read_length_prefixed_ascii()) if self.version <= 21: t.name = _normalize(reader.read_length_prefixed_ascii()) types.append(t) if self.version >= 21: t.attributes = list(self._read_attributes()) def _read_value(self, reader: Optional[StructReader] = None) -> Value: if reader is None: reader = self.reader type = self.types[reader.u32()] size = type.code.width processor: Optional[Callable[[], Union[int, float, str, bytes]]] = { TC.U08 : reader.u8, TC.S08 : reader.i8, TC.U16 : reader.u16, TC.S16 : reader.i16, TC.U32 : reader.u32, TC.S32 : reader.i32, TC.S64 : reader.i64, TC.Single : reader.f32, TC.Double : reader.f64, TC.Extended : lambda: extended(reader.read(10)), TC.AnsiString : lambda: reader.read_length_prefixed(encoding=self.codec), TC.PChar : lambda: reader.read_length_prefixed(encoding=self.codec), TC.WideString : reader.read_length_prefixed_utf16, TC.UnicodeString : reader.read_length_prefixed_utf16, TC.Char : lambda: chr(reader.u8()), TC.WideChar : lambda: chr(reader.u16()), TC.ProcPtr : lambda: self.functions[reader.u32() - 1], TC.Set : lambda: int.from_bytes(reader.read(type.size_in_bytes), 'little'), TC.Currency : lambda: reader.u64() / 10_000, }.get(type.code, None) if processor is not None: data = processor() elif size > 0: data = bytes(reader.read(size)) else: raise ValueError(F'Unable to read attribute of type {type!s}.') if isinstance(data, str) and data not in self.strings: self.strings.append(data) return Value(type, data) def _read_attributes(self) -> Generator[Attribute, None, None]: reader = self.reader count = reader.u32() for _ in range(count): name = reader.read_length_prefixed_ascii() fields = tuple(self._read_value() for _ in range(reader.u32())) yield Attribute(name, fields) def _load_functions(self): def _signature(name: str, decl: Optional[DeclSpec]): signature = IFPSAPI.get(name, IFPSEvents.get(name)) if name else None if decl and decl.classname and (ic := IFPSClasses.Classes.get(decl.classname)): signature = ic.members.get(decl.name, signature) decl.classname = ic.name return signature reader = self.reader rewind = reader.tell() width = len(F'{self.count_functions:X}') missing_types = set() load_flags = (self.version >= 23) reparsed = False all_void = True all_long = True has_dll_imports = False while True: for k in range(self.count_functions): decl = None body = None name = F'F{k:0{width}X}' tags = reader.u8() attributes = None exported = FTag.Exported.check(tags) if FTag.External.check(tags): name = reader.read_length_prefixed_ascii(8) if exported: read = StructReader(bytes(reader.read_length_prefixed())) decl = DeclSpec.ParseF(read, load_flags) if not reparsed and decl.module is not None: has_dll_imports = True # inno: 0d13564460b4cca289ac60221e86ca5719d7217a8eb76671b4b2a8407c2af6b4 # ifps: 6c211c02652317903b23c827cbc311a258fcd6197eec6a3d2f91986bd8accb0e # This script reports version 22 and therefore, load_flags starts as False. # However, it should be true; the reasons are unclear. The code below is # an attempt to identify incorrect load_flags values heuristically. When # there are no __delay_load functions present, reading them with load_flags # set to False will result in only procedures (void=True) with at least # 2 arguments. if not decl.void: all_void = False if len(decl.parameters) < 2: all_long = False else: offset = reader.u32() length = reader.u32() if exported: name = reader.read_length_prefixed_ascii() decl = DeclSpec.ParseE(bytes(reader.read_length_prefixed()), self) self.void = decl.void else: self.void = False with reader.detour(offset): body = list(self._parse_bytecode(reader.read(length))) if FTag.HasAttrs.check(tags): attributes = list(self._read_attributes()) fn = Function(name, decl, body, exported, attributes) self.functions.append(fn) if has_dll_imports and all_long and all_void and not reparsed: load_flags = True reparsed = True reader.seekset(rewind) self.functions.clear() else: break for function in self.functions: name = function.symbol decl = function.decl if (signature := _signature(name, decl)) and decl and signature.argc == decl.argc: for old, new in itertools.zip_longest(decl.parameters, signature.parameters): if not new: break if old and (t := old.type): t.symbol = new.type continue if t := self.types_by_name.get(new.type): t.symbol = new.type else: missing_types.add(new.type) if sr := signature.return_type: if (dr := decl.return_type) or (dr := self.types_by_name.get(sr)): dr.symbol = sr else: missing_types.add(sr) self.type_name_conflicts = self._name_types(missing_types) for function in self.functions: decl = function.decl if signature := _signature(function.name, decl): decl = decl or DeclSpec(True) decl.void = signature.void parameters = decl.parameters if signature.argc != len(parameters): decl.parameters = parameters = [DeclSpecParam(True) for _ in range(signature.argc)] for old, new in zip(parameters, signature.parameters): if old.type is None: old.type = self.types_by_name.get(new.type) old.name = new.name or old.name old.const = new.const function.symbol = decl.name = signature.name if (rt := signature.return_type) and (decl.return_type is None): decl.return_type = self.types_by_name.get(rt, decl.return_type) function.decl = decl for function in self.functions: if function.body is None: continue for instruction in function.body: if instruction.opcode is Op.Call: t: Function = self.functions[instruction.operands[0]] instruction.operands[0] = t def _load_variables(self): reader = self.reader for index in range(self.count_variables): code = reader.u32() spec = Variant(index, VariantType.Global) if reader.u8() & 1: spec = reader.read_length_prefixed_ascii() self.globals.append(VariableBase(self.types[code], spec)) def _read_variant(self, index: int) -> Variant: if index < 0x40000000: return Variant(index, VariantType.Global) index -= 0x60000000 if index >= 0: return Variant(index, VariantType.Local) index = -index if self.void else ~index return Variant(index, VariantType.Argument) def _read_operand(self, reader: StructReader) -> Operand: ot = OperandType(reader.u8()) kw = {} if ot is OperandType.Variant: kw.update(variant=self._read_variant(reader.u32())) if ot is OperandType.Value: kw.update(value=self._read_value(reader)) if ot >= OperandType.IndexedByInt: kw.update(variant=self._read_variant(reader.u32())) index = reader.u32() if ot is OperandType.IndexedByVar: index = self._read_variant(index) kw.update(index=index) return Operand(ot, **kw) def _parse_bytecode(self, data: memoryview) -> Generator[Instruction, None, None]: disassembly: Dict[int, Instruction] = OrderedDict() reader = StructReader(data) argcount = { Op.Assign: 2, Op.CallVar: 1, Op.Dec: 1, Op.Inc: 1, Op.BooleanNot: 1, Op.Neg: 1, Op.IntegerNot: 1, Op.SetPtrToCopy: 2, Op.SetPtr: 2, } while not reader.eof: def arg(k=1): for _ in range(k): args.append(self._read_operand(reader)) addr = reader.tell() cval = reader.u8() code = Op.FromInt(cval) insn = Instruction(addr, code) args = insn.operands disassembly[insn.offset] = insn aryness = argcount.get(code) if aryness is not None: arg(aryness) elif code in (Op.Ret, Op.Nop, Op.Pop): pass elif code is Op.Calculate: insn.operator = AOp(reader.u8()) arg(2) elif code in (Op.Push, Op.PushVar): arg() elif code in (Op.Jump, Op.JumpFlag): target = reader.i32() args.append(reader.tell() + target) elif code is Op.Call: args.append(reader.u32()) elif code in (Op.JumpTrue, Op.JumpFalse): target = reader.i32() val = self._read_operand(reader) args.append(reader.tell() + target) args.append(val) elif code is Op.JumpPop1: target = reader.i32() args.append(reader.tell() + target) elif code is Op.JumpPop2: target = reader.i32() args.append(reader.tell() + target) elif code is Op.StackType: args.append(self._read_variant(reader.u32())) args.append(reader.u32()) elif code is Op.PushType: args.append(self.types[reader.u32()]) elif code is Op.Compare: insn.operator = COp(reader.u8()) arg(3) elif code is Op.SetFlag: arg() args.append(bool(reader.u8())) elif code is Op.PushEH: args.extend(reader.i32() for _ in range(4)) for k, a in enumerate(args): args[k] = a + reader.tell() if a >= 0 else None elif code is Op.PopEH: args.append(reader.u8()) elif code is Op._INVALID: raise ValueError(F'Unsupported opcode: 0x{cval:02X}') else: raise ValueError(F'Unhandled opcode: {code.name}') insn.size = reader.tell() - addr for k, instruction in enumerate(disassembly.values()): if not instruction.branches: continue target = instruction.operands[0] try: disassembly[target].jumptarget = True except KeyError as K: raise RuntimeError( F'The jump target of instruction {k} at 0x{instruction.offset:X} is invalid; ' F'the invalid instruction is a {instruction.opcode.name} to 0x{target:X}.' ) from K yield from disassembly.values() def __str__(self): return self.disassembly() def disassembly(self) -> str: def sortkey(f: Function): d = (d.module or '', d.classname or '', d.void) if (d := f.decl) else ('', '', True) return (*d, f.name) for function in self.functions: function.get_basic_blocks() classes: dict[str, dict[str, Function]] = {} external: list[Function] = [] internal: list[Function] = [] for t in self.types: if isinstance(t, TClass): classes[t.name] = {} for function in self.functions: if (decl := function.decl) and (name := decl.classname): try: members = classes[name] except KeyError: members = classes[name] = {} members[decl.name] = function continue dl = internal if function.body else external dl.append(function) external.sort(key=sortkey) output = io.StringIO() _omax = max(( max(insn.offset for insn in fn.body) for fn in self.functions if fn.body ), default=0) _smax = max(( max(insn.stack for insn in fn.body if insn.stack is not None) for fn in self.functions if fn.body ), default=0) _omax = max(len(self.types), len(self.globals), _omax) _omax = len(F'{_omax:X}') _smax = len(F'{_smax:d}') if classes: for name, members in classes.items(): if not members: output.write(F'external class {name};\n') output.write('\n') for name, members in classes.items(): if not members: continue output.write(F'external class {name}') if members: for spec in members.values(): output.write(F'\n{_TAB}{spec.decl.represent(spec.symbol, rel=True)}') output.write('\nend') output.write(';\n\n') if self.types: for type in self.types: if type.code != TC.Record and type.symbol in (type.code.name, None): continue if isinstance(type, TClass): continue output.write(F'typedef {type.symbol} = {type.display()}\n') output.write('\n') if self.globals: for variable in self.globals: output.write(F'global {variable!s}\n') output.write('\n') if external: for function in external: output.write(F'external {function!r}\n') output.write('\n') if internal: for function in internal: output.write(F'{function!r}\nbegin\n') labels = [insn.offset for insn in function.body if insn.jumptarget] labelw = max(len(str(len(labels))), 2) labeld = {v: F'JumpDestination{k:0{labelw}d}' for k, v in enumerate(labels, 1)} labelc = 0 for instruction in function.body: stack = instruction.stack stack = '?' * _smax if stack is None else F'{stack:>{_smax}d}' if instruction.jumptarget: output.write(F'{labeld[labels[labelc]]}:\n') labelc += 1 output.write(F'{_TAB}0x{instruction.offset:0{_omax}X}{_TAB}{stack}{_TAB}{instruction.pretty(labeld)}\n') output.write('end;\n\n') return output.getvalue().strip()
Ancestors
Class variables
var MinVer
var MaxVer
var Magic
Methods
def disassembly(self)
-
Expand source code Browse git
def disassembly(self) -> str: def sortkey(f: Function): d = (d.module or '', d.classname or '', d.void) if (d := f.decl) else ('', '', True) return (*d, f.name) for function in self.functions: function.get_basic_blocks() classes: dict[str, dict[str, Function]] = {} external: list[Function] = [] internal: list[Function] = [] for t in self.types: if isinstance(t, TClass): classes[t.name] = {} for function in self.functions: if (decl := function.decl) and (name := decl.classname): try: members = classes[name] except KeyError: members = classes[name] = {} members[decl.name] = function continue dl = internal if function.body else external dl.append(function) external.sort(key=sortkey) output = io.StringIO() _omax = max(( max(insn.offset for insn in fn.body) for fn in self.functions if fn.body ), default=0) _smax = max(( max(insn.stack for insn in fn.body if insn.stack is not None) for fn in self.functions if fn.body ), default=0) _omax = max(len(self.types), len(self.globals), _omax) _omax = len(F'{_omax:X}') _smax = len(F'{_smax:d}') if classes: for name, members in classes.items(): if not members: output.write(F'external class {name};\n') output.write('\n') for name, members in classes.items(): if not members: continue output.write(F'external class {name}') if members: for spec in members.values(): output.write(F'\n{_TAB}{spec.decl.represent(spec.symbol, rel=True)}') output.write('\nend') output.write(';\n\n') if self.types: for type in self.types: if type.code != TC.Record and type.symbol in (type.code.name, None): continue if isinstance(type, TClass): continue output.write(F'typedef {type.symbol} = {type.display()}\n') output.write('\n') if self.globals: for variable in self.globals: output.write(F'global {variable!s}\n') output.write('\n') if external: for function in external: output.write(F'external {function!r}\n') output.write('\n') if internal: for function in internal: output.write(F'{function!r}\nbegin\n') labels = [insn.offset for insn in function.body if insn.jumptarget] labelw = max(len(str(len(labels))), 2) labeld = {v: F'JumpDestination{k:0{labelw}d}' for k, v in enumerate(labels, 1)} labelc = 0 for instruction in function.body: stack = instruction.stack stack = '?' * _smax if stack is None else F'{stack:>{_smax}d}' if instruction.jumptarget: output.write(F'{labeld[labels[labelc]]}:\n') labelc += 1 output.write(F'{_TAB}0x{instruction.offset:0{_omax}X}{_TAB}{stack}{_TAB}{instruction.pretty(labeld)}\n') output.write('end;\n\n') return output.getvalue().strip()