Module refinery.lib.inno.emulator
An emulator for Inno Setup executables. The implementation is unlikely to be 100% correct as it was engineered by making various malicious scripts execute reasonably well, not by implementing an exact copy of the (only) reference implementation. This is grew and grew as I wrote it and seems mildly insane in hindsight.
Expand source code Browse git
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
An emulator for Inno Setup executables. The implementation is unlikely to be 100% correct as it
was engineered by making various malicious scripts execute reasonably well, not by implementing
an exact copy of [the (only) reference implementation][PS]. This is grew and grew as I wrote it
and seems mildly insane in hindsight.
[PS]: https://github.com/remobjects/pascalscript
"""
from __future__ import annotations
from typing import (
get_origin,
Any,
Callable,
ClassVar,
Dict,
Set,
Generic,
List,
Tuple,
NamedTuple,
Optional,
Sequence,
TypeVar,
Union,
)
from dataclasses import dataclass, field
from enum import auto, Enum, IntFlag
from functools import partial
from pathlib import Path
from string import Formatter
from time import process_time
from urllib.parse import unquote
from datetime import datetime, timedelta
from refinery.lib.tools import cached_property
from refinery.lib.types import CaseInsensitiveDict
from refinery.lib.inno.archive import InnoArchive, Flags
from refinery.lib.types import AST, INF, NoMask
from refinery.lib.patterns import formats
from refinery.lib.inno.ifps import (
AOp,
COp,
EHType,
Function,
IFPSFile,
IFPSType,
Op,
Operand,
OperandType,
TArray,
TC,
TRecord,
TStaticArray,
Value,
VariableBase,
Variant,
VariantType,
)
import fnmatch
import hashlib
import inspect
import io
import math
import operator
import random
import re
import struct
import time
_T = TypeVar('_T')
class OleObject:
def __init__(self, name):
self.name = name
def __repr__(self):
return F'OleObject({self.name!r})'
def __str__(self):
return self.name
class Variable(VariableBase, Generic[_T]):
type: IFPSType
spec: Optional[Variant]
data: Optional[Union[List[Variable], _T]]
path: Tuple[int, ...]
@property
def container(self):
return self.type.container
@property
def pointer(self):
return self.type.code == TC.Pointer
def __len__(self):
return len(self.data)
def __bool__(self):
return True
def __getitem__(self, key: int):
var = self.deref()
if var.container:
return var.at(key).get()
else:
return var.data[key]
def __setitem__(self, key: int, v: _T):
var = self.deref()
if var.container:
var.at(key).set(v)
else:
var.data[key] = var._wrap(v)
def at(self, k: int):
return self.deref().data[k]
def deref(var):
while True:
val = var.data
if not isinstance(val, Variable):
return var
var = val
def __init__(
self,
type: IFPSType,
spec: Optional[Variant] = None,
path: Tuple[int, ...] = (),
data: Optional[Union[_T, List]] = None
):
super().__init__(type, spec)
self.path = path
self._int_size = _size = {
TC.U08: +1,
TC.U16: +1,
TC.U32: +1,
TC.S08: -1,
TC.S16: -1,
TC.S32: -1,
TC.S64: -1,
}.get((code := type.code), 0) * code.width
if _size:
bits = abs(_size) * 8
umax = (1 << bits)
self._int_bits = bits
self._int_mask = umax - 1
if _size < 0:
self._int_good = range(-(umax >> 1), (umax >> 1))
else:
self._int_good = range(umax)
else:
self._int_mask = NoMask
self._int_bits = INF
self._int_good = AST
if data is None:
def default(type: IFPSType, *sub_path):
if isinstance(type, TRecord):
return [Variable(t, spec, (*path, *sub_path, k)) for k, t in enumerate(type.members)]
if isinstance(type, TStaticArray):
t = type.type
return [Variable(t, spec, (*path, *sub_path, k)) for k in range(type.size)]
if isinstance(type, TArray):
return []
if sub_path:
return Variable(type, spec, (*path, *sub_path))
else:
return type.default()
self.data = default(type)
else:
self.set(data)
def _wrap(self, value: Union[Value, _T], key: Optional[int] = None) -> _T:
if (t := self.type.py_type(key)) and not isinstance(value, t):
if issubclass(t, int):
if isinstance(value, str) and len(value) == 1:
return ord(value[0])
if isinstance(value, float):
return int(value)
elif isinstance(value, int):
if issubclass(t, str):
return chr(value)
if issubclass(t, float):
return float(value)
raise TypeError(F'Assigning value {value!r} to variable of type {self.type}.')
if s := self._int_size and value not in self._int_good:
mask = self._int_mask
value &= mask
if s < 0 and (value >> (self._int_bits - 1)):
value = -(-value & mask)
return value
def resize(self, n: int):
t = self.type
m = n - len(self.data)
if t.code != TC.Array:
if t.code not in (TC.StaticArray, TC.Record):
raise TypeError
if n == t.size:
return
raise ValueError(F'Attempt to resize {t} of size {t.size} to {n}.')
if m <= 0:
del self.data[n:]
return
for k in range(m):
self.data.append(Variable(t.type, self.spec, (*self.path, k)))
def setptr(self, var: Variable, copy: bool = False):
if not self.pointer:
raise TypeError
if not isinstance(var, Variable):
raise TypeError
if copy:
var = Variable(var.type, data=var.get())
self.data = var
def set(
self,
value: Union[_T, Sequence, Variable],
):
if isinstance(value, Variable):
if value.container:
dst = self.deref()
if not dst.container:
raise TypeError(F'Attempting to assign container type {value.type} to non-container {self.type}.')
dst.resize(len(value))
for k, v in enumerate(value.data):
dst.data[k].set(v)
return
if value.pointer:
if self.pointer:
self.data = value.data
else:
self.set(value.deref())
return
value = value.get()
if isinstance(value, (Enum, Value)):
value = value.value
if self.pointer:
return self.deref().set(value)
elif self.container:
if not isinstance(value, (list, tuple)):
raise TypeError
self.resize(len(value))
for k, v in enumerate(value):
self.data[k].set(v)
else:
self.data = self._wrap(value)
def get(self) -> _T:
if self.pointer:
return self.deref().get()
if self.container:
data: List[Variable] = self.data
return [v.get() for v in data]
return self.data
@property
def name(self):
if self.spec is None:
return 'Unbound'
name = F'{self.spec!s}'
for k in self.path:
name = F'{name}[{k}]'
return name
def __repr__(self):
rep = self.name
if (val := self.data) is None:
return rep
if self.type.code is TC.Set:
val = F'{val:b}'
elif self.pointer:
val: Variable
return F'{rep} -> {val.name}'
elif isinstance(val, (str, int, float, list)):
val = repr(self.get())
else:
return rep
return F'{rep} = {val}'
class NeedSymbol(NotImplementedError):
pass
class OpCodeNotImplemented(NotImplementedError):
pass
class EmulatorException(RuntimeError):
pass
class AbortEmulation(Exception):
pass
class IFPSException(RuntimeError):
def __init__(self, msg: str, parent: Optional[BaseException] = None):
super().__init__(msg)
self.parent = parent
class EmulatorTimeout(TimeoutError):
pass
class EmulatorExecutionLimit(TimeoutError):
pass
class EmulatorMaxStack(MemoryError):
pass
class EmulatorMaxCalls(MemoryError):
pass
@dataclass
class ExceptionHandler:
finally_one: Optional[int]
catch_error: Optional[int]
finally_two: Optional[int]
handler_end: int
current: EHType = EHType.Try
class IFPSEmulatedFunction(NamedTuple):
call: Callable
spec: List[bool]
static: bool
void: bool = False
@property
def argc(self):
return len(self.spec)
@dataclass
class IFPSEmulatorConfig:
x64: bool = True
admin: bool = True
windows_os_version: Tuple[int, int, int] = (10, 0, 10240)
windows_sp_version: Tuple[int, int] = (2, 0)
throw_abort: bool = False
trace_calls: bool = False
log_passwords: bool = True
wizard_silent: bool = True
max_opcodes: int = 0
max_seconds: int = 60
start_time: datetime = field(default_factory=datetime.now)
milliseconds_per_instruction: float = 0.001
sleep_scale: float = 0.0
max_data_stack: int = 1_000_000
max_call_stack: int = 4096
environment: Dict[str, str] = field(default_factory=dict)
user_name: str = 'Frank'
host_name: str = 'Frank-PC'
inno_name: str = 'ThisInstall'
language: str = 'en'
executable: str = 'C:\\Install.exe'
install_to: str = 'I:\\'
lcid: int = 0x0409
@property
def cwd(self):
return Path(self.executable).parent
class TSetupStep(int, Enum):
ssPreInstall = 0
ssInstall = auto()
ssPostInstall = auto()
ssDone = auto()
class TSplitType(int, Enum):
stAll = 0
stExcludeEmpty = auto()
stExcludeLastEmpty = auto()
class TUninstallStep(int, Enum):
usAppMutexCheck = 0
usUninstall = auto()
usPostUninstall = auto()
usDone = auto()
class TSetupProcessorArchitecture(int, Enum):
paUnknown = 0
paX86 = auto()
paX64 = auto()
paArm32 = auto()
paArm64 = auto()
class PageID(int, Enum):
wpWelcome = 1
wpLicense = auto()
wpPassword = auto()
wpInfoBefore = auto()
wpUserInfo = auto()
wpSelectDir = auto()
wpSelectComponents = auto()
wpSelectProgramGroup = auto()
wpSelectTasks = auto()
wpReady = auto()
wpPreparing = auto()
wpInstalling = auto()
wpInfoAfter = auto()
wpFinished = auto()
class IFPSCall(NamedTuple):
name: str
args: tuple
class FPUControl(IntFlag):
InvalidOperation = 0b0_00_0_00_00_00_000001 # noqa
DenormalizedOperand = 0b0_00_0_00_00_00_000010 # noqa
ZeroDivide = 0b0_00_0_00_00_00_000100 # noqa
Overflow = 0b0_00_0_00_00_00_001000 # noqa
Underflow = 0b0_00_0_00_00_00_010000 # noqa
PrecisionError = 0b0_00_0_00_00_00_100000 # noqa
Reserved1 = 0b0_00_0_00_00_01_000000 # noqa
Reserved2 = 0b0_00_0_00_00_10_000000 # noqa
ExtendPrecision = 0b0_00_0_00_01_00_000000 # noqa
DoublePrecision = 0b0_00_0_00_10_00_000000 # noqa
MaxPrecision = 0b0_00_0_00_11_00_000000 # noqa
RoundDown = 0b0_00_0_01_00_00_000000 # noqa
RoundUp = 0b0_00_0_10_00_00_000000 # noqa
RoundTowardZero = 0b0_00_0_11_00_00_000000 # noqa
AffineInfinity = 0b0_00_1_00_00_00_000000 # noqa
Reserved3 = 0b0_01_0_00_00_00_000000 # noqa
Reserved4 = 0b0_10_0_00_00_00_000000 # noqa
ReservedBits = 0b0_11_0_00_00_11_000000 # noqa
class IFPSEmulator:
def __init__(
self,
archive: Union[InnoArchive, IFPSFile],
options: Optional[IFPSEmulatorConfig] = None,
**more
):
if isinstance(archive, InnoArchive):
self.inno = archive
self.ifps = ifps = archive.ifps
else:
self.inno = None
self.ifps = ifps = archive
self.config = options or IFPSEmulatorConfig(**more)
self.globals = [Variable(v.type, v.spec) for v in ifps.globals]
self.stack: List[Variable] = []
self.trace: List[IFPSCall] = []
self.passwords: Set[str] = set()
self.jumpflag = False
self.fpucw = FPUControl(0)
self.clock = 0
self.seconds_slept = 0.0
self.mutexes: Set[str] = set()
self.symbols: Dict[str, Function] = CaseInsensitiveDict()
for pfn in ifps.functions:
self.symbols[pfn.name] = pfn
def __repr__(self):
return self.__class__.__name__
def unimplemented(self, function: Function):
raise NeedSymbol(function.name)
def emulate_function(self, function: Function, *args):
self.stack.clear()
decl = function.decl
if decl is None:
raise NotImplementedError(F'Do not know how to call {function!s}.')
if (n := len(decl.parameters)) != (m := len(args)):
raise ValueError(
F'Function {function!s} expects {n} arguments, only {m} were given.')
for index, (argument, parameter) in enumerate(zip(args, decl.parameters), 1):
variable = Variable(parameter.type, Variant(index, VariantType.Local))
variable.set(argument)
self.stack.append(variable)
self.stack.reverse()
if not decl.void:
result = Variable(decl.return_type, Variant(0, VariantType.Argument))
self.stack.append(result)
self.call(function)
self.stack.clear()
if not decl.void:
return result.get()
def call(self, function: Function):
def operator_div(a, b):
return a // b if isinstance(a, int) and isinstance(b, int) else a / b
def operator_in(a, b):
return a in b
def getvar(op: Union[Variant, Operand]) -> Variable:
if not isinstance(op, Operand):
v = op
k = None
elif op.type is OperandType.Value:
raise TypeError('Attempting to retrieve variable for an immediate operand.')
else:
v = op.variant
k = op.index
if op.type is OperandType.IndexedByVar:
k = getvar(k).get()
t, i = v.type, v.index
if t is VariantType.Argument:
if function.decl.void:
i -= 1
var = self.stack[sp - i]
elif t is VariantType.Global:
var = self.globals[i]
elif t is VariantType.Local:
var = self.stack[sp + i]
else:
raise TypeError
if k is not None:
var = var.at(k)
return var
def getval(op: Operand):
if op.immediate:
return op.value.value
return getvar(op).get()
def setval(op: Operand, new):
if op.immediate:
raise RuntimeError('attempt to assign to an immediate')
getvar(op).set(new)
class CallState(NamedTuple):
fn: Function
ip: int
sp: int
eh: List[ExceptionHandler]
callstack: List[CallState] = []
exec_start = process_time()
ip: int = 0
sp: int = len(self.stack) - 1
pending_exception = None
exceptions = []
while True:
if 0 < self.config.max_data_stack < len(callstack):
raise EmulatorMaxCalls
if function.body is None:
decl = function.decl
name = function.name
tcls = decl and (decl.classname or decl.module)
tcls = tcls or ''
registry: Dict[str, IFPSEmulatedFunction] = self.external_symbols.get(tcls, {})
handler = registry.get(name)
if handler:
void = handler.void
argc = handler.argc
elif decl:
void = decl.void
argc = decl.argc
else:
void = True
argc = 0
try:
rpos = 0 if void else 1
args = [self.stack[~k] for k in range(rpos, argc + rpos)]
except IndexError:
raise EmulatorException(
F'Cannot call {function!s}; {argc} arguments + {rpos} return values expected,'
F' but stack size is only {len(self.stack)}.')
if self.config.trace_calls:
self.trace.append(IFPSCall(str(function), tuple(a.get() for a in args)))
if handler is None:
self.unimplemented(function)
else:
if decl and (decl.void != handler.void or decl.argc != handler.argc):
raise RuntimeError(F'Handler for {function!s} does not match the declaration.')
for k, (var, byref) in enumerate(zip(args, handler.spec)):
if not byref:
args[k] = var.get()
if not handler.static:
args.insert(0, self)
try:
return_value = handler.call(*args)
except BaseException as b:
pending_exception = IFPSException(F'Error calling {function.name}: {b!s}', b)
else:
if not handler.void:
self.stack[-1].set(return_value)
if not callstack:
if pending_exception is None:
return
raise pending_exception
function, ip, sp, exceptions = callstack.pop()
continue
while insn := function.code.get(ip, None):
if 0 < self.config.max_seconds < process_time() - exec_start:
raise EmulatorTimeout
if 0 < self.config.max_opcodes < self.clock:
raise EmulatorExecutionLimit
if 0 < self.config.max_data_stack < len(self.stack):
raise EmulatorMaxStack
try:
if pe := pending_exception:
pending_exception = None
raise pe
opc = insn.opcode
ip += insn.size
self.clock += 1
if opc == Op.Nop:
continue
elif opc == Op.Assign:
dst = getvar(insn.op(0))
src = insn.op(1)
if src.immediate:
dst.set(src.value)
else:
dst.set(getvar(src))
elif opc == Op.Calculate:
calculate = {
AOp.Add: operator.add,
AOp.Sub: operator.sub,
AOp.Mul: operator.mul,
AOp.Div: operator_div,
AOp.Mod: operator.mod,
AOp.Shl: operator.lshift,
AOp.Shr: operator.rshift,
AOp.And: operator.and_,
AOp.BOr: operator.or_,
AOp.Xor: operator.xor,
}[insn.operator]
src = insn.op(1)
dst = insn.op(0)
sv = getval(src)
dv = getval(dst)
fpu = isinstance(sv, float) or isinstance(dv, float)
try:
result = calculate(dv, sv)
if fpu and not isinstance(result, float):
raise FloatingPointError
except FloatingPointError as FPE:
if not self.fpucw & FPUControl.InvalidOperation:
result = float('nan')
else:
raise IFPSException('invalid operation', FPE) from FPE
except OverflowError as OFE:
if fpu and self.fpucw & FPUControl.Overflow:
result = float('nan')
else:
raise IFPSException('arithmetic overflow', OFE) from OFE
except ZeroDivisionError as ZDE:
if fpu and self.fpucw & FPUControl.ZeroDivide:
result = float('nan')
else:
raise IFPSException('division by zero', ZDE) from ZDE
setval(dst, result)
elif opc == Op.Push:
# TODO: I do not actually know how this works
self.stack.append(getval(insn.op(0)))
elif opc == Op.PushVar:
self.stack.append(getvar(insn.op(0)))
elif opc == Op.Pop:
self.temp = self.stack.pop()
elif opc == Op.Call:
callstack.append(CallState(function, ip, sp, exceptions))
function = insn.operands[0]
ip = 0
sp = len(self.stack) - 1
exceptions = []
break
elif opc == Op.Jump:
ip = insn.operands[0]
elif opc == Op.JumpTrue:
if getval(insn.op(1)):
ip = insn.operands[0]
elif opc == Op.JumpFalse:
if not getval(insn.op(1)):
ip = insn.operands[0]
elif opc == Op.Ret:
del self.stack[sp + 1:]
if not callstack:
return
function, ip, sp, exceptions = callstack.pop()
break
elif opc == Op.StackType:
raise OpCodeNotImplemented(str(opc))
elif opc == Op.PushType:
self.stack.append(Variable(
insn.operands[0],
Variant(len(self.stack) - sp, VariantType.Local)
))
elif opc == Op.Compare:
compare = {
COp.GE: operator.ge,
COp.LE: operator.le,
COp.GT: operator.gt,
COp.LT: operator.lt,
COp.NE: operator.ne,
COp.EQ: operator.eq,
COp.IN: operator_in,
COp.IS: operator.is_,
}[insn.operator]
d = getvar(insn.op(0))
a = getval(insn.op(1))
b = getval(insn.op(2))
d.set(compare(a, b))
elif opc == Op.CallVar:
pfn = getval(insn.op(0))
if isinstance(pfn, int):
pfn = self.ifps.functions[pfn]
if isinstance(pfn, Function):
self.call(pfn)
elif opc in (Op.SetPtr, Op.SetPtrToCopy):
copy = False
if opc == Op.SetPtrToCopy:
copy = True
dst = getvar(insn.op(0))
src = getvar(insn.op(1))
dst.setptr(src, copy=copy)
elif opc == Op.BooleanNot:
setval(a := insn.op(0), not getval(a))
elif opc == Op.IntegerNot:
setval(a := insn.op(0), ~getval(a))
elif opc == Op.Neg:
setval(a := insn.op(0), -getval(a))
elif opc == Op.SetFlag:
condition, negated = insn.operands
self.jumpflag = getval(condition) ^ negated
elif opc == Op.JumpFlag:
if self.jumpflag:
ip = insn.operands[0]
elif opc == Op.PushEH:
exceptions.append(ExceptionHandler(*insn.operands))
elif opc == Op.PopEH:
tp = None
et = EHType(insn.operands[0])
eh = exceptions[-1]
if eh.current != et:
raise RuntimeError(F'Expected {eh.current} block to end, but {et} was ended instead.')
while tp is None:
if et is None:
raise RuntimeError
tp, et = {
EHType.Catch : (eh.finally_one, EHType.Finally),
EHType.Try : (eh.finally_one, EHType.Finally),
EHType.Finally : (eh.finally_two, EHType.SecondFinally),
EHType.SecondFinally : (eh.handler_end, None),
}[et]
eh.current = et
ip = tp
if et is None:
exceptions.pop()
elif opc == Op.Inc:
setval(a := insn.op(0), getval(a) + 1)
elif opc == Op.Dec:
setval(a := insn.op(0), getval(a) - 1)
elif opc == Op.JumpPop1:
self.stack.pop()
ip = insn.operands[0]
elif opc == Op.JumpPop2:
self.stack.pop()
self.stack.pop()
ip = insn.operands[0]
else:
raise RuntimeError(F'Function contains invalid opcode at 0x{ip:X}.')
except IFPSException as EE:
try:
eh = exceptions[-1]
except IndexError:
raise EE
et = EHType.Try
tp = None
while tp is None:
if et is None:
raise RuntimeError
tp, et = {
EHType.Try : (eh.catch_error, EHType.Catch),
EHType.Catch : (eh.finally_one, EHType.Finally),
EHType.Finally : (eh.finally_two, EHType.SecondFinally),
EHType.SecondFinally : (eh.handler_end, None),
}[et]
if et is None:
raise EE
eh.current = et
ip = tp
except AbortEmulation:
raise
except EmulatorException:
raise
# except Exception as RE:
# raise EmulatorException(
# F'In {function.symbol} at 0x{insn.offset:X} (cycle {cycle}), '
# F'emulation of {insn!r} failed: {RE!s}')
if ip is None:
raise RuntimeError(F'Instruction pointer moved out of bounds to 0x{ip:X}.')
external_symbols: ClassVar[
Dict[str, # class name for methods or empty string for functions
Dict[str, IFPSEmulatedFunction]] # method or function name to emulation info
] = CaseInsensitiveDict()
def external(*args, static=True, __reg: dict = external_symbols, **kwargs):
def decorator(pfn):
signature = inspect.signature(pfn)
name: str = kwargs.get('name', pfn.__name__)
csep: str = '.'
if csep not in name:
csep = '__'
classname, _, name = name.rpartition(csep)
if (registry := __reg.get(classname)) is None:
registry = __reg[classname] = CaseInsensitiveDict()
void = kwargs.get('void', signature.return_annotation == signature.empty)
parameters: List[bool] = []
specs = iter(signature.parameters.values())
if not static:
next(specs)
for spec in specs:
try:
hint = eval(spec.annotation)
except Exception as E:
raise RuntimeError(F'Invalid signature: {signature}') from E
if not isinstance(hint, type):
hint = get_origin(hint)
var = isinstance(hint, type) and issubclass(hint, Variable)
parameters.append(var)
registry[name] = e = IFPSEmulatedFunction(pfn, parameters, static, void)
aliases = kwargs.get('alias', [])
if isinstance(aliases, str):
aliases = [aliases]
for name in aliases:
registry[name] = e
if static:
pfn = staticmethod(pfn)
return pfn
return decorator(args[0]) if args else decorator
@external(static=False)
def TPasswordEdit__Text(self, value: str) -> str:
if value:
self.passwords.add(value)
return value
@external
def kernel32__GetTickCount() -> int:
return time.monotonic_ns() // 1_000_000
@external
def user32__GetSystemMetrics(index: int) -> int:
if index == 80:
return 1
if index == 43:
return 2
return 0
@external
def IsX86Compatible() -> bool:
return True
@external(alias=[
'sArm64',
'IsArm32Compatible',
'Debugging',
'IsUninstaller',
])
def Terminated() -> bool:
return False
@external(static=False)
def IsAdmin(self) -> bool:
return self.config.admin
@external(static=False, alias='Sleep')
def kernel32__Sleep(self, ms: int):
seconds = ms / 1000.0
self.seconds_slept += seconds
time.sleep(seconds * self.config.sleep_scale)
@external
def Random(top: int) -> int:
return random.randrange(0, top)
@external(alias='StrGet')
def WStrGet(string: Variable[str], index: int) -> str:
if index <= 0:
raise ValueError
return string[index - 1:index]
@external(alias='StrSet')
def WStrSet(char: str, index: int, dst: Variable[str]):
old = dst.get()
index -= 1
dst.set(old[:index] + char + old[index:])
@external(static=False)
def GetEnv(self, name: str) -> str:
return self.config.environment.get(name, F'%{name}%')
@external
def Beep():
pass
@external(static=False)
def Abort(self):
if self.config.throw_abort:
raise AbortEmulation
@external
def DirExists(path: str) -> bool:
return True
@external
def ForceDirectories(path: str) -> bool:
return True
@external(alias='LoadStringFromLockedFile')
def LoadStringFromFile(path: str, out: Variable[str]) -> bool:
return True
@external(alias='LoadStringsFromLockedFile')
def LoadStringsFromFile(path: str, out: Variable[str]) -> bool:
return True
@cached_property
def constant_map(self) -> dict[str, str]:
tmp = random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ', k=5)
cfg = self.config
map = {
'app' : cfg.install_to,
'win' : R'C:\Windows',
'sys' : R'C:\Windows\System',
'sysnative' : R'C:\Windows\System32',
'src' : str(Path(cfg.executable).parent),
'sd' : R'C:',
'commonpf' : R'C:\Program Files',
'commoncf' : R'C:\Program Files\Common Files',
'tmp' : RF'C:\Windows\Temp\IS-{tmp}',
'commonfonts' : R'C:\Windows\Fonts',
'dao' : R'C:\Program Files\Common Files\Microsoft Shared\DAO',
'dotnet11' : R'C:\Windows\Microsoft.NET\Framework\v1.1.4322',
'dotnet20' : R'C:\Windows\Microsoft.NET\Framework\v3.0',
'dotnet2032' : R'C:\Windows\Microsoft.NET\Framework\v3.0',
'dotnet40' : R'C:\Windows\Microsoft.NET\Framework\v4.0.30319',
'dotnet4032' : R'C:\Windows\Microsoft.NET\Framework\v4.0.30319',
'group' : RF'C:\Users\{cfg.user_name}\Start Menu\Programs\{cfg.inno_name}',
'localappdata' : RF'C:\Users\{cfg.user_name}\AppData\Local',
'userappdata' : RF'C:\Users\{cfg.user_name}\AppData\Roaming',
'userdesktop' : RF'C:\Users\{cfg.user_name}\Desktop',
'userdocs' : RF'C:\Users\{cfg.user_name}\Documents',
'userfavourites' : RF'C:\Users\{cfg.user_name}\Favourites',
'usersavedgames' : RF'C:\Users\{cfg.user_name}\Saved Games',
'usersendto' : RF'C:\Users\{cfg.user_name}\SendTo',
'userstartmenu' : RF'C:\Users\{cfg.user_name}\Start Menu',
'userprograms' : RF'C:\Users\{cfg.user_name}\Start Menu\Programs',
'userstartup' : RF'C:\Users\{cfg.user_name}\Start Menu\Programs\Startup',
'usertemplates' : RF'C:\Users\{cfg.user_name}\Templates',
'commonappdata' : R'C:\ProgramData',
'commondesktop' : R'C:\ProgramData\Microsoft\Windows\Desktop',
'commondocs' : R'C:\ProgramData\Microsoft\Windows\Documents',
'commonstartmenu' : R'C:\ProgramData\Microsoft\Windows\Start Menu',
'commonprograms' : R'C:\ProgramData\Microsoft\Windows\Start Menu\Programs',
'commonstartup' : R'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup',
'commontemplates' : R'C:\ProgramData\Microsoft\Windows\Templates',
'cmd' : R'C:\Windows\System32\cmd.exe',
'computername' : cfg.host_name,
'groupname' : cfg.inno_name,
'hwnd' : '0',
'wizardhwnd' : '0',
'language' : cfg.language,
'srcexe' : cfg.executable,
'sysuserinfoname' : '{sysuserinfoname}',
'sysuserinfoorg' : '{sysuserinfoorg}',
'userinfoname' : '{userinfoname}',
'userinfoorg' : '{userinfoorg}',
'userinfoserial' : '{userinfoserial}',
'username' : cfg.user_name,
'log' : '',
}
if (inno := self.inno) is None or (inno.setup_info.Header.Flags & Flags.Uninstallable):
map['uninstallexe'] = RF'{cfg.install_to}\unins000.exe'
if cfg.x64:
map['syswow64'] = R'C:\Windows\SysWOW64'
map['commonpf32'] = R'C:\Program Files (x86)'
map['commoncf32'] = R'C:\Program Files (x86)\Common Files'
map['commonpf64'] = R'C:\Program Files'
map['commoncf64'] = R'C:\Program Files\Common Files'
map['dotnet2064'] = R'C:\Windows\Microsoft.NET\Framework64\v3.0'
map['dotnet4064'] = R'C:\Windows\Microsoft.NET\Framework64\v4.0.30319'
else:
map['syswow64'] = R'C:\Windows\System32'
map['commonpf32'] = R'C:\Program Files'
map['commoncf32'] = R'C:\Program Files\Common Files'
if cfg.windows_os_version[0] >= 10:
map['userfonts'] = RF'{map["localappdata"]}\Microsoft\Windows\Fonts'
if cfg.windows_os_version[0] >= 7:
map['usercf'] = RF'{map["localappdata"]}\Programs\Common'
map['userpf'] = RF'{map["localappdata"]}\Programs'
for auto_var, admin_var, user_var in [
('autoappdata', 'commonappdata', 'userappdata', ), # noqa
('autocf', 'commoncf', 'usercf', ), # noqa
('autocf32', 'commoncf32', 'usercf', ), # noqa
('autocf64', 'commoncf64', 'usercf', ), # noqa
('autodesktop', 'commondesktop', 'userdesktop', ), # noqa
('autodocs', 'commondocs', 'userdocs', ), # noqa
('autofonts', 'commonfonts', 'userfonts', ), # noqa
('autopf', 'commonpf', 'userpf', ), # noqa
('autopf32', 'commonpf32', 'userpf', ), # noqa
('autopf64', 'commonpf64', 'userpf', ), # noqa
('autoprograms', 'commonprograms', 'userprograms', ), # noqa
('autostartmenu', 'commonstartmenu', 'userstartmenu', ), # noqa
('autostartup', 'commonstartup', 'userstartup', ), # noqa
('autotemplates', 'commontemplates', 'usertemplates', ), # noqa
]:
try:
map[auto_var] = map[admin_var] if cfg.admin else map[user_var]
except KeyError:
continue
for legacy, new in [
('cf', 'commoncf', ), # noqa
('cf32', 'commoncf32', ), # noqa
('cf64', 'commoncf64', ), # noqa
('fonts', 'commonfonts', ), # noqa
('pf', 'commonpf', ), # noqa
('pf32', 'commonpf32', ), # noqa
('pf64', 'commonpf64', ), # noqa
('sendto', 'usersendto', ), # noqa
]:
try:
map[legacy] = map[new]
except KeyError:
continue
return map
@external(static=False)
def ExpandConstant(self, string: str) -> str:
return self.expand_constant(string)
@external(static=False)
def ExpandConstantEx(self, string: str, custom_var: str, custom_val: str) -> str:
return self.expand_constant(string, custom_var, custom_val)
def expand_constant(
self,
string: str,
custom_var: Optional[str] = None,
custom_val: Optional[str] = None,
unescape: bool = False
):
config = self.config
expand = partial(self.expand_constant, unescape=True)
with io.StringIO() as result:
constants = self.constant_map
formatter = Formatter()
backslash = False
for prefix, spec, modifier, conversion in formatter.parse(string):
if backslash and prefix[:1] == '\\':
prefix = prefix[1:]
if unescape:
prefix = unquote(prefix)
result.write(prefix)
if spec is None:
continue
elif spec == '\\':
if modifier or conversion:
raise IFPSException('Invalid format string.', ValueError(string))
value = spec
elif spec == custom_var:
value = custom_val
elif spec.startswith('%'):
name, p, default = spec[1:].partition('|')
name = expand(name)
default = expand(default)
try:
value = config.environment[name]
except KeyError:
value = default if p else F'%{name}%'
elif spec == 'drive':
value = self.ExtractFileDrive(expand(modifier))
elif spec == 'ini':
# {ini:Filename,Section,Key|DefaultValue}
_, _, default = modifier.partition('|')
value = expand(default)
elif spec == 'cm':
# {cm:LaunchProgram,Inno Setup}
# The example above translates to "Launch Inno Setup" if English is the active language.
name, _, args = modifier.partition(',')
value = self.CustomMessage(expand(name))
elif spec == 'reg':
# {reg:HKXX\SubkeyName,ValueName|DefaultValue}
_, _, default = modifier.partition('|')
value = expand(default)
elif spec == 'param':
# {param:ParamName|DefaultValue}
_, _, default = modifier.partition('|')
value = expand(default)
else:
try:
value = constants[spec]
except KeyError as KE:
raise IFPSException(F'invalid format field {spec}', KE) from KE
backslash = value.endswith('\\')
result.write(value)
return result.getvalue()
@external
def DeleteFile(path: str) -> bool:
return True
@external
def FileExists(file_name: str) -> bool:
return False
@external
def Log(log: str):
...
@external
def Inc(p: Variable[Variable[int]]):
p.set(p.get() + 1)
@external
def Dec(p: Variable[Variable[int]]):
p.set(p.get() - 1)
@external
def FindFirst(file_name: str, frec: Variable) -> bool:
return False
@external
def Trunc(x: float) -> float:
return math.trunc(x)
@external
def GetSpaceOnDisk(
path: str,
in_megabytes: bool,
avail: Variable[int],
space: Variable[int],
) -> bool:
_a = 3_000_000
_t = 5_000_000
if not in_megabytes:
_a *= 1000
_t *= 1000
avail.set(_a)
space.set(_t)
return True
@external
def GetSpaceOnDisk64(
path: str,
avail: Variable[int],
space: Variable[int],
) -> bool:
avail.set(3_000_000_000)
space.set(5_000_000_000)
return True
@external
def Exec(
exe: str,
cmd: str,
cwd: str,
show: int,
wait: int,
out: Variable[int],
) -> bool:
out.set(0)
return True
@external
def GetCmdTail() -> str:
return ''
@external
def ParamCount() -> int:
return 0
@external
def ParamStr(index: int) -> str:
return ''
@external
def ActiveLanguage() -> str:
return 'en'
@external(static=False)
def CustomMessage(self, msg_name: str) -> str:
by_language = {}
for msg in self.inno.setup_info.Messages:
if msg.EncodedName == msg_name:
lng = msg.Language.Name
if lng == self.config.language:
return msg.Value
by_language[lng] = msg.Value
try:
return by_language[0]
except KeyError:
pass
try:
return next(iter(by_language.values()))
except StopIteration:
raise IFPSException(F'Custom message with name {msg_name} not found.')
@external
def FmtMessage(fmt: str, args: List[str]) -> str:
fmt = fmt.replace('{', '{{')
fmt = fmt.replace('}', '}}')
fmt = '%'.join(re.sub('%(\\d+)', '{\\1}', p) for p in fmt.split('%%'))
return fmt.format(*args)
@external
def Format(fmt: str, args: List[Union[str, int, float]]) -> str:
try:
formatted = fmt % tuple(args)
except Exception:
raise IFPSException('invalid format')
else:
return formatted
@external(static=False)
def SetupMessage(self, id: int) -> str:
try:
return self.inno.setup_info.Messages[id].Value
except (AttributeError, IndexError):
return ''
@external(static=False, alias=['Is64BitInstallMode', 'IsX64Compatible', 'IsX64OS'])
def IsWin64(self) -> bool:
return self.config.x64
@external(static=False)
def IsX86OS(self) -> bool:
return not self.config.x64
@external
def RaiseException(msg: str):
raise IFPSException(msg)
@external(static=False)
def ProcessorArchitecture(self) -> int:
if self.config.x64:
return TSetupProcessorArchitecture.paX64.value
else:
return TSetupProcessorArchitecture.paX86.value
@external(static=False)
def GetUserNameString(self) -> str:
return self.config.user_name
@external(static=False)
def GetComputerNameString(self) -> str:
return self.config.host_name
@external(static=False)
def GetUILanguage(self) -> str:
return self.config.lcid
@external
def GetArrayLength(array: Variable) -> int:
array = array.deref()
return len(array)
@external
def SetArrayLength(array: Variable, n: int):
a = array.deref()
a.resize(n)
@external(static=False)
def WizardForm(self) -> object:
return self
@external
def Unassigned() -> None:
return None
@external
def Null() -> None:
return None
@external(static=False)
def Set8087CW(self, cw: int):
self.fpucw = FPUControl(cw)
@external(static=False)
def Get8087CW(self) -> int:
return self.fpucw.value
@external(static=False)
def GetDateTimeString(
self,
fmt: str,
date_separator: str,
time_separator: str,
) -> str:
now = self.config.start_time
now = now + timedelta(
milliseconds=(self.config.milliseconds_per_instruction * self.clock))
now = now + timedelta(seconds=self.seconds_slept)
date_separator = date_separator.lstrip('\0')
time_separator = time_separator.lstrip('\0')
def dt(m: re.Match[str]):
spec = m[1]
ampm = m[2]
if ampm:
am, _, pm = ampm.partition('/')
spec = spec.upper()
suffix = now.strftime('%p').lower()
suffix = {'am': am, 'pm': pm}[suffix]
else:
suffix = ''
if spec == 'dddddd' or spec == 'ddddd':
return now.date.isoformat()
if spec == 't':
return now.time().isoformat('minutes')
if spec == 'tt':
return now.time().isoformat('seconds')
if spec == 'd':
return str(now.day)
if spec == 'm':
return str(now.month)
if spec == 'h':
return str(now.hour)
if spec == 'n':
return str(now.minute)
if spec == 's':
return str(now.second)
if spec == 'H':
return now.strftime('%I').lstrip('0') + suffix
if spec == '/':
return date_separator or spec
if spec == ':':
return time_separator or spec
return now.strftime({
'dddd' : '%A',
'ddd' : '%a',
'dd' : '%d',
'mmmm' : '%B',
'mmm' : '%b',
'mm' : '%m',
'yyyy' : '%Y',
'yy' : '%y',
'hh' : '%H',
'HH' : '%I' + suffix,
'nn' : '%M',
'ss' : '%S',
}.get(spec, m[0]))
split = re.split(F'({formats.string!s})', fmt)
for k in range(0, len(split), 2):
split[k] = re.sub('([dmyhnst]+)((?:[aA][mM]?/[pP][mM]?)?)', dt, split[k])
for k in range(1, len(split), 2):
split[k] = split[k][1:-1]
return ''.join(split)
@external
def Chr(b: int) -> str:
return chr(b)
@external
def Ord(c: str) -> int:
return ord(c)
@external
def Copy(string: str, index: int, count: int) -> str:
index -= 1
return string[index:index + count]
@external
def Length(string: str) -> int:
return len(string)
@external(alias='AnsiLowercase')
def Lowercase(string: str) -> str:
return string.lower()
@external(alias='AnsiUppercase')
def Uppercase(string: str) -> str:
return string.upper()
@external
def StringOfChar(c: str, count: int) -> str:
return c * count
@external
def Delete(string: Variable[str], index: int, count: int):
index -= 1
old = string.get()
string.set(old[:index] + old[index + count:])
@external
def Insert(string: str, dest: Variable[str], index: int):
index -= 1
old = dest.get()
dest.set(old[:index] + string + old[index:])
@external(static=False)
def StringChange(self, string: Variable[str], old: str, new: str) -> int:
return self.StringChangeEx(string, old, new, False)
@external
def StringChangeEx(string: Variable[str], old: str, new: str, _: bool) -> int:
haystack = string.get()
count = haystack.count(old)
string.set(haystack.replace(old, new))
return count
@external
def Pos(string: str, sub: str) -> int:
return string.find(sub) + 1
@external
def AddQuotes(string: str) -> str:
if string and (string[0] != '"' or string[~0] != '"') and ' ' in string:
string = F'"{string}"'
return string
@external
def RemoveQuotes(string: str) -> str:
if string and string[0] == '"' and string[~0] == '"':
string = string[1:-1]
return string
@external(static=False)
def CompareText(self, a: str, b: str) -> int:
return self.CompareStr(a.casefold(), b.casefold())
@external
def CompareStr(a: str, b: str) -> int:
if a > b:
return +1
if a < b:
return -1
return 0
@external
def SameText(a: str, b: str) -> bool:
return a.casefold() == b.casefold()
@external
def SameStr(a: str, b: str) -> bool:
return a == b
@external
def IsWildcard(pattern: str) -> bool:
return '*' in pattern or '?' in pattern
@external
def WildcardMatch(text: str, pattern: str) -> bool:
return fnmatch.fnmatch(text, pattern)
@external
def Trim(string: str) -> str:
return string.strip()
@external
def TrimLeft(string: str) -> str:
return string.lstrip()
@external
def TrimRight(string: str) -> str:
return string.rstrip()
@external
def StringJoin(sep: str, values: List[str]) -> str:
return sep.join(values)
@external
def StringSplitEx(string: str, separators: List[str], quote: str, how: TSplitType) -> List[str]:
if not quote:
parts = [string]
else:
quote = re.escape(quote)
parts = re.split(F'({quote}.*?{quote})', string)
sep = '|'.join(re.escape(s) for s in separators)
out = []
if how == TSplitType.stExcludeEmpty:
sep = F'(?:{sep})+'
for k in range(0, len(parts)):
if k & 1 == 1:
out.append(parts[k])
continue
out.extend(re.split(sep, string))
if how == TSplitType.stExcludeLastEmpty:
for k in reversed(range(len(out))):
if not out[k]:
out.pop(k)
break
return out
@external(static=False)
def StringSplit(self, string: str, separators: List[str], how: TSplitType) -> List[str]:
return self.StringSplitEx(string, separators, None, how)
@external(alias='StrToInt64')
def StrToInt(s: str) -> int:
return int(s)
@external(alias='StrToInt64Def')
def StrToIntDef(s: str, d: int) -> int:
try:
return int(s)
except Exception:
return d
@external
def StrToFloat(s: str) -> float:
return float(s)
@external(alias='FloatToStr')
def IntToStr(i: int) -> str:
return str(i)
@external
def StrToVersion(s: str, v: Variable[int]) -> bool:
try:
packed = bytes(map(int, s.split('.')))
except Exception:
return False
if len(packed) != 4:
return False
v.set(int.from_bytes(packed, 'little'))
return True
@external
def CharLength(string: str, index: int) -> int:
return 1
@external
def AddBackslash(string: str) -> str:
if string and string[~0] != '\\':
string = F'{string}\\'
return string
@external
def AddPeriod(string: str) -> str:
if string and string[~0] != '.':
string = F'{string}.'
return string
@external(static=False)
def RemoveBackslashUnlessRoot(self, string: str) -> str:
path = Path(string)
if len(path.parts) == 1:
return str(path)
return self.RemoveBackslash(string)
@external
def RemoveBackslash(string: str) -> str:
return string.rstrip('\\/')
@external
def ChangeFileExt(name: str, ext: str) -> str:
if not ext.startswith('.'):
ext = F'.{ext}'
return str(Path(name).with_suffix(ext))
@external
def ExtractFileExt(name: str) -> str:
return Path(name).suffix
@external(alias='ExtractFilePath')
def ExtractFileDir(name: str) -> str:
dirname = str(Path(name).parent)
return '' if dirname == '.' else dirname
@external
def ExtractFileName(name: str) -> str:
if name:
name = Path(name).parts[-1]
return name
@external
def ExtractFileDrive(name: str) -> str:
if name:
parts = Path(name).parts
if len(parts) >= 2 and parts[0] == '\\' and parts[1] == '?':
parts = parts[2:]
if parts[0] == '\\':
if len(parts) >= 3:
return '\\'.join(parts[:3])
else:
root = parts[0]
if len(root) == 2 and root[1] == ':':
return root
return ''
@external
def ExtractRelativePath(base: str, dst: str) -> str:
return str(Path(dst).relative_to(base))
@external(static=False, alias='ExpandUNCFileName')
def ExpandFileName(self, name: str) -> str:
if self.ExtractFileDrive(name):
return name
return str(self.config.cwd / name)
@external
def SetLength(string: Variable[str], size: int):
old = string.get()
old = old.ljust(size, '\0')
string.set(old[:size])
@external(alias='OemToCharBuff')
def CharToOemBuff(string: str) -> str:
# TODO
return string
@external
def Utf8Encode(string: str) -> str:
return string.encode('utf8').decode('latin1')
@external
def Utf8Decode(string: str) -> str:
return string.encode('latin1').decode('utf8')
@external
def GetMD5OfString(string: str) -> str:
return hashlib.md5(string.encode('latin1')).hexdigest()
@external
def GetMD5OfUnicodeString(string: str) -> str:
return hashlib.md5(string.encode('utf8')).hexdigest()
@external
def GetSHA1OfString(string: str) -> str:
return hashlib.sha1(string.encode('latin1')).hexdigest()
@external
def GetSHA1OfUnicodeString(string: str) -> str:
return hashlib.sha1(string.encode('utf8')).hexdigest()
@external
def GetSHA256OfString(string: str) -> str:
return hashlib.sha256(string.encode('latin1')).hexdigest()
@external
def GetSHA256OfUnicodeString(string: str) -> str:
return hashlib.sha256(string.encode('utf8')).hexdigest()
@external
def SysErrorMessage(code: int) -> str:
return F'[description for error {code:08X}]'
@external
def MinimizePathName(path: str, font: object, max_len: int) -> str:
return path
@external(static=False)
def CheckForMutexes(self, mutexes: str) -> bool:
return any(m in self.mutexes for m in mutexes.split(','))
@external(static=False)
def CreateMutex(self, name: str):
self.mutexes.add(name)
@external(static=False)
def GetWinDir(self) -> str:
return self.expand_constant('{win}')
@external(static=False)
def GetSystemDir(self) -> str:
return self.expand_constant('{sys}')
@external(static=False)
def GetWindowsVersion(self) -> int:
version = int.from_bytes(
struct.pack('>BBH', *self.config.windows_os_version), 'big')
return version
@external(static=False)
def GetWindowsVersionEx(self, tv: Variable[Union[int, bool]]):
tv[0], tv[1], tv[2] = self.config.windows_os_version # noqa
tv[3], tv[4] = self.config.windows_sp_version # noqa
tv[5], tv[6], tv[7] = True, 0, 0
@external(static=False)
def GetWindowsVersionString(self) -> str:
return '{0}.{1:02d}.{2:04d}'.format(*self.config.windows_os_version)
@external
def CreateOleObject(name: str) -> OleObject:
return OleObject(name)
@external
def GetActiveOleObject(name: str) -> OleObject:
return OleObject(name)
@external
def IDispatchInvoke(ole: OleObject, prop_set: bool, name: str, value: Any) -> int:
return 0
@external
def FindWindowByClassName(name: str) -> int:
return 0
@external(static=False)
def WizardSilent(self) -> bool:
return self.config.wizard_silent
@external(static=False)
def SizeOf(self, var: Variable) -> int:
if var.pointer:
return (self.config.x64 + 1) * 4
if var.container:
return sum(self.SizeOf(x) for x in var.data)
return var.type.code.width
del external
class InnoSetupEmulator(IFPSEmulator):
def emulate_installation(self, password=''):
class SetupDispatcher:
InitializeSetup: Callable
InitializeWizard: Callable
CurStepChanged: Callable
ShouldSkipPage: Callable
CurPageChanged: Callable
PrepareToInstall: Callable
CheckPassword: Callable
NextButtonClick: Callable
DeinitializeSetup: Callable
def __getattr__(_, name):
return (lambda *a: self.emulate_function(pfn, *a)) if (
pfn := self.symbols.get(name)
) else (lambda *_: False)
Setup = SetupDispatcher()
Setup.InitializeSetup()
Setup.InitializeWizard()
Setup.CurStepChanged(TSetupStep.ssPreInstall)
for page in PageID:
if not Setup.ShouldSkipPage(page):
Setup.CurPageChanged(page)
if page == PageID.wpPreparing:
Setup.PrepareToInstall(False)
if page == PageID.wpPassword:
Setup.CheckPassword(password)
Setup.NextButtonClick(page)
if page == PageID.wpPreparing:
Setup.CurStepChanged(TSetupStep.ssInstall)
if page == PageID.wpInfoAfter:
Setup.CurStepChanged(TSetupStep.ssPostInstall)
Setup.CurStepChanged(TSetupStep.ssDone)
Setup.DeinitializeSetup()
def unimplemented(self, function: Function):
decl = function.decl
if decl is None:
return
if not decl.void:
rc = 1
rv = self.stack[-1]
if not rv.container:
rt = rv.type.py_type()
if isinstance(rt, type) and issubclass(rt, int):
rv.set(1)
else:
rc = 0
for k in range(rc, rc + len(decl.parameters)):
ptr: Variable[Variable] = self.stack[-k]
if not ptr.pointer:
continue
var = ptr.deref()
if var.container:
continue
vt = var.type.py_type()
if isinstance(vt, type) and issubclass(vt, int):
var.set(1)
Classes
class OleObject (name)
-
Expand source code Browse git
class OleObject: def __init__(self, name): self.name = name def __repr__(self): return F'OleObject({self.name!r})' def __str__(self): return self.name
class Variable (type, spec=None, path=(), data=None)
-
Abstract base class for generic types.
A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::
class Mapping(Generic[KT, VT]): def getitem(self, key: KT) -> VT: … # Etc.
This class can then be used as follows::
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default
Expand source code Browse git
class Variable(VariableBase, Generic[_T]): type: IFPSType spec: Optional[Variant] data: Optional[Union[List[Variable], _T]] path: Tuple[int, ...] @property def container(self): return self.type.container @property def pointer(self): return self.type.code == TC.Pointer def __len__(self): return len(self.data) def __bool__(self): return True def __getitem__(self, key: int): var = self.deref() if var.container: return var.at(key).get() else: return var.data[key] def __setitem__(self, key: int, v: _T): var = self.deref() if var.container: var.at(key).set(v) else: var.data[key] = var._wrap(v) def at(self, k: int): return self.deref().data[k] def deref(var): while True: val = var.data if not isinstance(val, Variable): return var var = val def __init__( self, type: IFPSType, spec: Optional[Variant] = None, path: Tuple[int, ...] = (), data: Optional[Union[_T, List]] = None ): super().__init__(type, spec) self.path = path self._int_size = _size = { TC.U08: +1, TC.U16: +1, TC.U32: +1, TC.S08: -1, TC.S16: -1, TC.S32: -1, TC.S64: -1, }.get((code := type.code), 0) * code.width if _size: bits = abs(_size) * 8 umax = (1 << bits) self._int_bits = bits self._int_mask = umax - 1 if _size < 0: self._int_good = range(-(umax >> 1), (umax >> 1)) else: self._int_good = range(umax) else: self._int_mask = NoMask self._int_bits = INF self._int_good = AST if data is None: def default(type: IFPSType, *sub_path): if isinstance(type, TRecord): return [Variable(t, spec, (*path, *sub_path, k)) for k, t in enumerate(type.members)] if isinstance(type, TStaticArray): t = type.type return [Variable(t, spec, (*path, *sub_path, k)) for k in range(type.size)] if isinstance(type, TArray): return [] if sub_path: return Variable(type, spec, (*path, *sub_path)) else: return type.default() self.data = default(type) else: self.set(data) def _wrap(self, value: Union[Value, _T], key: Optional[int] = None) -> _T: if (t := self.type.py_type(key)) and not isinstance(value, t): if issubclass(t, int): if isinstance(value, str) and len(value) == 1: return ord(value[0]) if isinstance(value, float): return int(value) elif isinstance(value, int): if issubclass(t, str): return chr(value) if issubclass(t, float): return float(value) raise TypeError(F'Assigning value {value!r} to variable of type {self.type}.') if s := self._int_size and value not in self._int_good: mask = self._int_mask value &= mask if s < 0 and (value >> (self._int_bits - 1)): value = -(-value & mask) return value def resize(self, n: int): t = self.type m = n - len(self.data) if t.code != TC.Array: if t.code not in (TC.StaticArray, TC.Record): raise TypeError if n == t.size: return raise ValueError(F'Attempt to resize {t} of size {t.size} to {n}.') if m <= 0: del self.data[n:] return for k in range(m): self.data.append(Variable(t.type, self.spec, (*self.path, k))) def setptr(self, var: Variable, copy: bool = False): if not self.pointer: raise TypeError if not isinstance(var, Variable): raise TypeError if copy: var = Variable(var.type, data=var.get()) self.data = var def set( self, value: Union[_T, Sequence, Variable], ): if isinstance(value, Variable): if value.container: dst = self.deref() if not dst.container: raise TypeError(F'Attempting to assign container type {value.type} to non-container {self.type}.') dst.resize(len(value)) for k, v in enumerate(value.data): dst.data[k].set(v) return if value.pointer: if self.pointer: self.data = value.data else: self.set(value.deref()) return value = value.get() if isinstance(value, (Enum, Value)): value = value.value if self.pointer: return self.deref().set(value) elif self.container: if not isinstance(value, (list, tuple)): raise TypeError self.resize(len(value)) for k, v in enumerate(value): self.data[k].set(v) else: self.data = self._wrap(value) def get(self) -> _T: if self.pointer: return self.deref().get() if self.container: data: List[Variable] = self.data return [v.get() for v in data] return self.data @property def name(self): if self.spec is None: return 'Unbound' name = F'{self.spec!s}' for k in self.path: name = F'{name}[{k}]' return name def __repr__(self): rep = self.name if (val := self.data) is None: return rep if self.type.code is TC.Set: val = F'{val:b}' elif self.pointer: val: Variable return F'{rep} -> {val.name}' elif isinstance(val, (str, int, float, list)): val = repr(self.get()) else: return rep return F'{rep} = {val}'
Ancestors
- VariableBase
- typing.Generic
Class variables
var type
var spec
var data
var path
Instance variables
var container
-
Expand source code Browse git
@property def container(self): return self.type.container
var pointer
-
Expand source code Browse git
@property def pointer(self): return self.type.code == TC.Pointer
var name
-
Expand source code Browse git
@property def name(self): if self.spec is None: return 'Unbound' name = F'{self.spec!s}' for k in self.path: name = F'{name}[{k}]' return name
Methods
def at(self, k)
-
Expand source code Browse git
def at(self, k: int): return self.deref().data[k]
def deref(var)
-
Expand source code Browse git
def deref(var): while True: val = var.data if not isinstance(val, Variable): return var var = val
def resize(self, n)
-
Expand source code Browse git
def resize(self, n: int): t = self.type m = n - len(self.data) if t.code != TC.Array: if t.code not in (TC.StaticArray, TC.Record): raise TypeError if n == t.size: return raise ValueError(F'Attempt to resize {t} of size {t.size} to {n}.') if m <= 0: del self.data[n:] return for k in range(m): self.data.append(Variable(t.type, self.spec, (*self.path, k)))
def setptr(self, var, copy=False)
-
Expand source code Browse git
def setptr(self, var: Variable, copy: bool = False): if not self.pointer: raise TypeError if not isinstance(var, Variable): raise TypeError if copy: var = Variable(var.type, data=var.get()) self.data = var
def set(self, value)
-
Expand source code Browse git
def set( self, value: Union[_T, Sequence, Variable], ): if isinstance(value, Variable): if value.container: dst = self.deref() if not dst.container: raise TypeError(F'Attempting to assign container type {value.type} to non-container {self.type}.') dst.resize(len(value)) for k, v in enumerate(value.data): dst.data[k].set(v) return if value.pointer: if self.pointer: self.data = value.data else: self.set(value.deref()) return value = value.get() if isinstance(value, (Enum, Value)): value = value.value if self.pointer: return self.deref().set(value) elif self.container: if not isinstance(value, (list, tuple)): raise TypeError self.resize(len(value)) for k, v in enumerate(value): self.data[k].set(v) else: self.data = self._wrap(value)
def get(self)
-
Expand source code Browse git
def get(self) -> _T: if self.pointer: return self.deref().get() if self.container: data: List[Variable] = self.data return [v.get() for v in data] return self.data
class NeedSymbol (*args, **kwargs)
-
Method or function hasn't been implemented yet.
Expand source code Browse git
class NeedSymbol(NotImplementedError): pass
Ancestors
- builtins.NotImplementedError
- builtins.RuntimeError
- builtins.Exception
- builtins.BaseException
class OpCodeNotImplemented (*args, **kwargs)
-
Method or function hasn't been implemented yet.
Expand source code Browse git
class OpCodeNotImplemented(NotImplementedError): pass
Ancestors
- builtins.NotImplementedError
- builtins.RuntimeError
- builtins.Exception
- builtins.BaseException
class EmulatorException (*args, **kwargs)
-
Unspecified run-time error.
Expand source code Browse git
class EmulatorException(RuntimeError): pass
Ancestors
- builtins.RuntimeError
- builtins.Exception
- builtins.BaseException
class AbortEmulation (*args, **kwargs)
-
Common base class for all non-exit exceptions.
Expand source code Browse git
class AbortEmulation(Exception): pass
Ancestors
- builtins.Exception
- builtins.BaseException
class IFPSException (msg, parent=None)
-
Unspecified run-time error.
Expand source code Browse git
class IFPSException(RuntimeError): def __init__(self, msg: str, parent: Optional[BaseException] = None): super().__init__(msg) self.parent = parent
Ancestors
- builtins.RuntimeError
- builtins.Exception
- builtins.BaseException
class EmulatorTimeout (*args, **kwargs)
-
Timeout expired.
Expand source code Browse git
class EmulatorTimeout(TimeoutError): pass
Ancestors
- builtins.TimeoutError
- builtins.OSError
- builtins.Exception
- builtins.BaseException
class EmulatorExecutionLimit (*args, **kwargs)
-
Timeout expired.
Expand source code Browse git
class EmulatorExecutionLimit(TimeoutError): pass
Ancestors
- builtins.TimeoutError
- builtins.OSError
- builtins.Exception
- builtins.BaseException
class EmulatorMaxStack (*args, **kwargs)
-
Out of memory.
Expand source code Browse git
class EmulatorMaxStack(MemoryError): pass
Ancestors
- builtins.MemoryError
- builtins.Exception
- builtins.BaseException
class EmulatorMaxCalls (*args, **kwargs)
-
Out of memory.
Expand source code Browse git
class EmulatorMaxCalls(MemoryError): pass
Ancestors
- builtins.MemoryError
- builtins.Exception
- builtins.BaseException
class ExceptionHandler (finally_one, catch_error, finally_two, handler_end, current=Try)
-
ExceptionHandler(finally_one: 'Optional[int]', catch_error: 'Optional[int]', finally_two: 'Optional[int]', handler_end: 'int', current: 'EHType' = EHType.Try)
Expand source code Browse git
class ExceptionHandler: finally_one: Optional[int] catch_error: Optional[int] finally_two: Optional[int] handler_end: int current: EHType = EHType.Try
Class variables
var finally_one
var catch_error
var finally_two
var handler_end
var current
class IFPSEmulatedFunction (call, spec, static, void=False)
-
IFPSEmulatedFunction(call, spec, static, void)
Expand source code Browse git
class IFPSEmulatedFunction(NamedTuple): call: Callable spec: List[bool] static: bool void: bool = False @property def argc(self): return len(self.spec)
Ancestors
- builtins.tuple
Instance variables
var call
-
Alias for field number 0
var spec
-
Alias for field number 1
var static
-
Alias for field number 2
var void
-
Alias for field number 3
var argc
-
Expand source code Browse git
@property def argc(self): return len(self.spec)
class IFPSEmulatorConfig (x64=True, admin=True, windows_os_version=(10, 0, 10240), windows_sp_version=(2, 0), throw_abort=False, trace_calls=False, log_passwords=True, wizard_silent=True, max_opcodes=0, max_seconds=60, start_time=<factory>, milliseconds_per_instruction=0.001, sleep_scale=0.0, max_data_stack=1000000, max_call_stack=4096, environment=<factory>, user_name='Frank', host_name='Frank-PC', inno_name='ThisInstall', language='en', executable='C:\\Install.exe', install_to='I:\\', lcid=1033)
-
IFPSEmulatorConfig(x64: 'bool' = True, admin: 'bool' = True, windows_os_version: 'Tuple[int, int, int]' = (10, 0, 10240), windows_sp_version: 'Tuple[int, int]' = (2, 0), throw_abort: 'bool' = False, trace_calls: 'bool' = False, log_passwords: 'bool' = True, wizard_silent: 'bool' = True, max_opcodes: 'int' = 0, max_seconds: 'int' = 60, start_time: 'datetime' =
, milliseconds_per_instruction: 'float' = 0.001, sleep_scale: 'float' = 0.0, max_data_stack: 'int' = 1000000, max_call_stack: 'int' = 4096, environment: 'Dict[str, str]' = , user_name: 'str' = 'Frank', host_name: 'str' = 'Frank-PC', inno_name: 'str' = 'ThisInstall', language: 'str' = 'en', executable: 'str' = 'C:\Install.exe', install_to: 'str' = 'I:\', lcid: 'int' = 1033) Expand source code Browse git
class IFPSEmulatorConfig: x64: bool = True admin: bool = True windows_os_version: Tuple[int, int, int] = (10, 0, 10240) windows_sp_version: Tuple[int, int] = (2, 0) throw_abort: bool = False trace_calls: bool = False log_passwords: bool = True wizard_silent: bool = True max_opcodes: int = 0 max_seconds: int = 60 start_time: datetime = field(default_factory=datetime.now) milliseconds_per_instruction: float = 0.001 sleep_scale: float = 0.0 max_data_stack: int = 1_000_000 max_call_stack: int = 4096 environment: Dict[str, str] = field(default_factory=dict) user_name: str = 'Frank' host_name: str = 'Frank-PC' inno_name: str = 'ThisInstall' language: str = 'en' executable: str = 'C:\\Install.exe' install_to: str = 'I:\\' lcid: int = 0x0409 @property def cwd(self): return Path(self.executable).parent
Class variables
var start_time
var environment
var x64
var admin
var windows_os_version
var windows_sp_version
var throw_abort
var trace_calls
var log_passwords
var wizard_silent
var max_opcodes
var max_seconds
var milliseconds_per_instruction
var sleep_scale
var max_data_stack
var max_call_stack
var user_name
var host_name
var inno_name
var language
var executable
var install_to
var lcid
Instance variables
var cwd
-
Expand source code Browse git
@property def cwd(self): return Path(self.executable).parent
class TSetupStep (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class TSetupStep(int, Enum): ssPreInstall = 0 ssInstall = auto() ssPostInstall = auto() ssDone = auto()
Ancestors
- builtins.int
- enum.Enum
Class variables
var ssPreInstall
var ssInstall
var ssPostInstall
var ssDone
class TSplitType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class TSplitType(int, Enum): stAll = 0 stExcludeEmpty = auto() stExcludeLastEmpty = auto()
Ancestors
- builtins.int
- enum.Enum
Class variables
var stAll
var stExcludeEmpty
var stExcludeLastEmpty
class TUninstallStep (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class TUninstallStep(int, Enum): usAppMutexCheck = 0 usUninstall = auto() usPostUninstall = auto() usDone = auto()
Ancestors
- builtins.int
- enum.Enum
Class variables
var usAppMutexCheck
var usUninstall
var usPostUninstall
var usDone
class TSetupProcessorArchitecture (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class TSetupProcessorArchitecture(int, Enum): paUnknown = 0 paX86 = auto() paX64 = auto() paArm32 = auto() paArm64 = auto()
Ancestors
- builtins.int
- enum.Enum
Class variables
var paUnknown
var paX86
var paX64
var paArm32
var paArm64
class PageID (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class PageID(int, Enum): wpWelcome = 1 wpLicense = auto() wpPassword = auto() wpInfoBefore = auto() wpUserInfo = auto() wpSelectDir = auto() wpSelectComponents = auto() wpSelectProgramGroup = auto() wpSelectTasks = auto() wpReady = auto() wpPreparing = auto() wpInstalling = auto() wpInfoAfter = auto() wpFinished = auto()
Ancestors
- builtins.int
- enum.Enum
Class variables
var wpWelcome
var wpLicense
var wpPassword
var wpInfoBefore
var wpUserInfo
var wpSelectDir
var wpSelectComponents
var wpSelectProgramGroup
var wpSelectTasks
var wpReady
var wpPreparing
var wpInstalling
var wpInfoAfter
var wpFinished
class IFPSCall (name, args)
-
IFPSCall(name, args)
Expand source code Browse git
class IFPSCall(NamedTuple): name: str args: tuple
Ancestors
- builtins.tuple
Instance variables
var name
-
Alias for field number 0
var args
-
Alias for field number 1
class FPUControl (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code Browse git
class FPUControl(IntFlag): InvalidOperation = 0b0_00_0_00_00_00_000001 # noqa DenormalizedOperand = 0b0_00_0_00_00_00_000010 # noqa ZeroDivide = 0b0_00_0_00_00_00_000100 # noqa Overflow = 0b0_00_0_00_00_00_001000 # noqa Underflow = 0b0_00_0_00_00_00_010000 # noqa PrecisionError = 0b0_00_0_00_00_00_100000 # noqa Reserved1 = 0b0_00_0_00_00_01_000000 # noqa Reserved2 = 0b0_00_0_00_00_10_000000 # noqa ExtendPrecision = 0b0_00_0_00_01_00_000000 # noqa DoublePrecision = 0b0_00_0_00_10_00_000000 # noqa MaxPrecision = 0b0_00_0_00_11_00_000000 # noqa RoundDown = 0b0_00_0_01_00_00_000000 # noqa RoundUp = 0b0_00_0_10_00_00_000000 # noqa RoundTowardZero = 0b0_00_0_11_00_00_000000 # noqa AffineInfinity = 0b0_00_1_00_00_00_000000 # noqa Reserved3 = 0b0_01_0_00_00_00_000000 # noqa Reserved4 = 0b0_10_0_00_00_00_000000 # noqa ReservedBits = 0b0_11_0_00_00_11_000000 # noqa
Ancestors
- enum.IntFlag
- builtins.int
- enum.Flag
- enum.Enum
Class variables
var InvalidOperation
var DenormalizedOperand
var ZeroDivide
var Overflow
var Underflow
var PrecisionError
var Reserved1
var Reserved2
var ExtendPrecision
var DoublePrecision
var MaxPrecision
var RoundDown
var RoundUp
var RoundTowardZero
var AffineInfinity
var Reserved3
var Reserved4
var ReservedBits
class IFPSEmulator (archive, options=None, **more)
-
Expand source code Browse git
class IFPSEmulator: def __init__( self, archive: Union[InnoArchive, IFPSFile], options: Optional[IFPSEmulatorConfig] = None, **more ): if isinstance(archive, InnoArchive): self.inno = archive self.ifps = ifps = archive.ifps else: self.inno = None self.ifps = ifps = archive self.config = options or IFPSEmulatorConfig(**more) self.globals = [Variable(v.type, v.spec) for v in ifps.globals] self.stack: List[Variable] = [] self.trace: List[IFPSCall] = [] self.passwords: Set[str] = set() self.jumpflag = False self.fpucw = FPUControl(0) self.clock = 0 self.seconds_slept = 0.0 self.mutexes: Set[str] = set() self.symbols: Dict[str, Function] = CaseInsensitiveDict() for pfn in ifps.functions: self.symbols[pfn.name] = pfn def __repr__(self): return self.__class__.__name__ def unimplemented(self, function: Function): raise NeedSymbol(function.name) def emulate_function(self, function: Function, *args): self.stack.clear() decl = function.decl if decl is None: raise NotImplementedError(F'Do not know how to call {function!s}.') if (n := len(decl.parameters)) != (m := len(args)): raise ValueError( F'Function {function!s} expects {n} arguments, only {m} were given.') for index, (argument, parameter) in enumerate(zip(args, decl.parameters), 1): variable = Variable(parameter.type, Variant(index, VariantType.Local)) variable.set(argument) self.stack.append(variable) self.stack.reverse() if not decl.void: result = Variable(decl.return_type, Variant(0, VariantType.Argument)) self.stack.append(result) self.call(function) self.stack.clear() if not decl.void: return result.get() def call(self, function: Function): def operator_div(a, b): return a // b if isinstance(a, int) and isinstance(b, int) else a / b def operator_in(a, b): return a in b def getvar(op: Union[Variant, Operand]) -> Variable: if not isinstance(op, Operand): v = op k = None elif op.type is OperandType.Value: raise TypeError('Attempting to retrieve variable for an immediate operand.') else: v = op.variant k = op.index if op.type is OperandType.IndexedByVar: k = getvar(k).get() t, i = v.type, v.index if t is VariantType.Argument: if function.decl.void: i -= 1 var = self.stack[sp - i] elif t is VariantType.Global: var = self.globals[i] elif t is VariantType.Local: var = self.stack[sp + i] else: raise TypeError if k is not None: var = var.at(k) return var def getval(op: Operand): if op.immediate: return op.value.value return getvar(op).get() def setval(op: Operand, new): if op.immediate: raise RuntimeError('attempt to assign to an immediate') getvar(op).set(new) class CallState(NamedTuple): fn: Function ip: int sp: int eh: List[ExceptionHandler] callstack: List[CallState] = [] exec_start = process_time() ip: int = 0 sp: int = len(self.stack) - 1 pending_exception = None exceptions = [] while True: if 0 < self.config.max_data_stack < len(callstack): raise EmulatorMaxCalls if function.body is None: decl = function.decl name = function.name tcls = decl and (decl.classname or decl.module) tcls = tcls or '' registry: Dict[str, IFPSEmulatedFunction] = self.external_symbols.get(tcls, {}) handler = registry.get(name) if handler: void = handler.void argc = handler.argc elif decl: void = decl.void argc = decl.argc else: void = True argc = 0 try: rpos = 0 if void else 1 args = [self.stack[~k] for k in range(rpos, argc + rpos)] except IndexError: raise EmulatorException( F'Cannot call {function!s}; {argc} arguments + {rpos} return values expected,' F' but stack size is only {len(self.stack)}.') if self.config.trace_calls: self.trace.append(IFPSCall(str(function), tuple(a.get() for a in args))) if handler is None: self.unimplemented(function) else: if decl and (decl.void != handler.void or decl.argc != handler.argc): raise RuntimeError(F'Handler for {function!s} does not match the declaration.') for k, (var, byref) in enumerate(zip(args, handler.spec)): if not byref: args[k] = var.get() if not handler.static: args.insert(0, self) try: return_value = handler.call(*args) except BaseException as b: pending_exception = IFPSException(F'Error calling {function.name}: {b!s}', b) else: if not handler.void: self.stack[-1].set(return_value) if not callstack: if pending_exception is None: return raise pending_exception function, ip, sp, exceptions = callstack.pop() continue while insn := function.code.get(ip, None): if 0 < self.config.max_seconds < process_time() - exec_start: raise EmulatorTimeout if 0 < self.config.max_opcodes < self.clock: raise EmulatorExecutionLimit if 0 < self.config.max_data_stack < len(self.stack): raise EmulatorMaxStack try: if pe := pending_exception: pending_exception = None raise pe opc = insn.opcode ip += insn.size self.clock += 1 if opc == Op.Nop: continue elif opc == Op.Assign: dst = getvar(insn.op(0)) src = insn.op(1) if src.immediate: dst.set(src.value) else: dst.set(getvar(src)) elif opc == Op.Calculate: calculate = { AOp.Add: operator.add, AOp.Sub: operator.sub, AOp.Mul: operator.mul, AOp.Div: operator_div, AOp.Mod: operator.mod, AOp.Shl: operator.lshift, AOp.Shr: operator.rshift, AOp.And: operator.and_, AOp.BOr: operator.or_, AOp.Xor: operator.xor, }[insn.operator] src = insn.op(1) dst = insn.op(0) sv = getval(src) dv = getval(dst) fpu = isinstance(sv, float) or isinstance(dv, float) try: result = calculate(dv, sv) if fpu and not isinstance(result, float): raise FloatingPointError except FloatingPointError as FPE: if not self.fpucw & FPUControl.InvalidOperation: result = float('nan') else: raise IFPSException('invalid operation', FPE) from FPE except OverflowError as OFE: if fpu and self.fpucw & FPUControl.Overflow: result = float('nan') else: raise IFPSException('arithmetic overflow', OFE) from OFE except ZeroDivisionError as ZDE: if fpu and self.fpucw & FPUControl.ZeroDivide: result = float('nan') else: raise IFPSException('division by zero', ZDE) from ZDE setval(dst, result) elif opc == Op.Push: # TODO: I do not actually know how this works self.stack.append(getval(insn.op(0))) elif opc == Op.PushVar: self.stack.append(getvar(insn.op(0))) elif opc == Op.Pop: self.temp = self.stack.pop() elif opc == Op.Call: callstack.append(CallState(function, ip, sp, exceptions)) function = insn.operands[0] ip = 0 sp = len(self.stack) - 1 exceptions = [] break elif opc == Op.Jump: ip = insn.operands[0] elif opc == Op.JumpTrue: if getval(insn.op(1)): ip = insn.operands[0] elif opc == Op.JumpFalse: if not getval(insn.op(1)): ip = insn.operands[0] elif opc == Op.Ret: del self.stack[sp + 1:] if not callstack: return function, ip, sp, exceptions = callstack.pop() break elif opc == Op.StackType: raise OpCodeNotImplemented(str(opc)) elif opc == Op.PushType: self.stack.append(Variable( insn.operands[0], Variant(len(self.stack) - sp, VariantType.Local) )) elif opc == Op.Compare: compare = { COp.GE: operator.ge, COp.LE: operator.le, COp.GT: operator.gt, COp.LT: operator.lt, COp.NE: operator.ne, COp.EQ: operator.eq, COp.IN: operator_in, COp.IS: operator.is_, }[insn.operator] d = getvar(insn.op(0)) a = getval(insn.op(1)) b = getval(insn.op(2)) d.set(compare(a, b)) elif opc == Op.CallVar: pfn = getval(insn.op(0)) if isinstance(pfn, int): pfn = self.ifps.functions[pfn] if isinstance(pfn, Function): self.call(pfn) elif opc in (Op.SetPtr, Op.SetPtrToCopy): copy = False if opc == Op.SetPtrToCopy: copy = True dst = getvar(insn.op(0)) src = getvar(insn.op(1)) dst.setptr(src, copy=copy) elif opc == Op.BooleanNot: setval(a := insn.op(0), not getval(a)) elif opc == Op.IntegerNot: setval(a := insn.op(0), ~getval(a)) elif opc == Op.Neg: setval(a := insn.op(0), -getval(a)) elif opc == Op.SetFlag: condition, negated = insn.operands self.jumpflag = getval(condition) ^ negated elif opc == Op.JumpFlag: if self.jumpflag: ip = insn.operands[0] elif opc == Op.PushEH: exceptions.append(ExceptionHandler(*insn.operands)) elif opc == Op.PopEH: tp = None et = EHType(insn.operands[0]) eh = exceptions[-1] if eh.current != et: raise RuntimeError(F'Expected {eh.current} block to end, but {et} was ended instead.') while tp is None: if et is None: raise RuntimeError tp, et = { EHType.Catch : (eh.finally_one, EHType.Finally), EHType.Try : (eh.finally_one, EHType.Finally), EHType.Finally : (eh.finally_two, EHType.SecondFinally), EHType.SecondFinally : (eh.handler_end, None), }[et] eh.current = et ip = tp if et is None: exceptions.pop() elif opc == Op.Inc: setval(a := insn.op(0), getval(a) + 1) elif opc == Op.Dec: setval(a := insn.op(0), getval(a) - 1) elif opc == Op.JumpPop1: self.stack.pop() ip = insn.operands[0] elif opc == Op.JumpPop2: self.stack.pop() self.stack.pop() ip = insn.operands[0] else: raise RuntimeError(F'Function contains invalid opcode at 0x{ip:X}.') except IFPSException as EE: try: eh = exceptions[-1] except IndexError: raise EE et = EHType.Try tp = None while tp is None: if et is None: raise RuntimeError tp, et = { EHType.Try : (eh.catch_error, EHType.Catch), EHType.Catch : (eh.finally_one, EHType.Finally), EHType.Finally : (eh.finally_two, EHType.SecondFinally), EHType.SecondFinally : (eh.handler_end, None), }[et] if et is None: raise EE eh.current = et ip = tp except AbortEmulation: raise except EmulatorException: raise # except Exception as RE: # raise EmulatorException( # F'In {function.symbol} at 0x{insn.offset:X} (cycle {cycle}), ' # F'emulation of {insn!r} failed: {RE!s}') if ip is None: raise RuntimeError(F'Instruction pointer moved out of bounds to 0x{ip:X}.') external_symbols: ClassVar[ Dict[str, # class name for methods or empty string for functions Dict[str, IFPSEmulatedFunction]] # method or function name to emulation info ] = CaseInsensitiveDict() def external(*args, static=True, __reg: dict = external_symbols, **kwargs): def decorator(pfn): signature = inspect.signature(pfn) name: str = kwargs.get('name', pfn.__name__) csep: str = '.' if csep not in name: csep = '__' classname, _, name = name.rpartition(csep) if (registry := __reg.get(classname)) is None: registry = __reg[classname] = CaseInsensitiveDict() void = kwargs.get('void', signature.return_annotation == signature.empty) parameters: List[bool] = [] specs = iter(signature.parameters.values()) if not static: next(specs) for spec in specs: try: hint = eval(spec.annotation) except Exception as E: raise RuntimeError(F'Invalid signature: {signature}') from E if not isinstance(hint, type): hint = get_origin(hint) var = isinstance(hint, type) and issubclass(hint, Variable) parameters.append(var) registry[name] = e = IFPSEmulatedFunction(pfn, parameters, static, void) aliases = kwargs.get('alias', []) if isinstance(aliases, str): aliases = [aliases] for name in aliases: registry[name] = e if static: pfn = staticmethod(pfn) return pfn return decorator(args[0]) if args else decorator @external(static=False) def TPasswordEdit__Text(self, value: str) -> str: if value: self.passwords.add(value) return value @external def kernel32__GetTickCount() -> int: return time.monotonic_ns() // 1_000_000 @external def user32__GetSystemMetrics(index: int) -> int: if index == 80: return 1 if index == 43: return 2 return 0 @external def IsX86Compatible() -> bool: return True @external(alias=[ 'sArm64', 'IsArm32Compatible', 'Debugging', 'IsUninstaller', ]) def Terminated() -> bool: return False @external(static=False) def IsAdmin(self) -> bool: return self.config.admin @external(static=False, alias='Sleep') def kernel32__Sleep(self, ms: int): seconds = ms / 1000.0 self.seconds_slept += seconds time.sleep(seconds * self.config.sleep_scale) @external def Random(top: int) -> int: return random.randrange(0, top) @external(alias='StrGet') def WStrGet(string: Variable[str], index: int) -> str: if index <= 0: raise ValueError return string[index - 1:index] @external(alias='StrSet') def WStrSet(char: str, index: int, dst: Variable[str]): old = dst.get() index -= 1 dst.set(old[:index] + char + old[index:]) @external(static=False) def GetEnv(self, name: str) -> str: return self.config.environment.get(name, F'%{name}%') @external def Beep(): pass @external(static=False) def Abort(self): if self.config.throw_abort: raise AbortEmulation @external def DirExists(path: str) -> bool: return True @external def ForceDirectories(path: str) -> bool: return True @external(alias='LoadStringFromLockedFile') def LoadStringFromFile(path: str, out: Variable[str]) -> bool: return True @external(alias='LoadStringsFromLockedFile') def LoadStringsFromFile(path: str, out: Variable[str]) -> bool: return True @cached_property def constant_map(self) -> dict[str, str]: tmp = random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ', k=5) cfg = self.config map = { 'app' : cfg.install_to, 'win' : R'C:\Windows', 'sys' : R'C:\Windows\System', 'sysnative' : R'C:\Windows\System32', 'src' : str(Path(cfg.executable).parent), 'sd' : R'C:', 'commonpf' : R'C:\Program Files', 'commoncf' : R'C:\Program Files\Common Files', 'tmp' : RF'C:\Windows\Temp\IS-{tmp}', 'commonfonts' : R'C:\Windows\Fonts', 'dao' : R'C:\Program Files\Common Files\Microsoft Shared\DAO', 'dotnet11' : R'C:\Windows\Microsoft.NET\Framework\v1.1.4322', 'dotnet20' : R'C:\Windows\Microsoft.NET\Framework\v3.0', 'dotnet2032' : R'C:\Windows\Microsoft.NET\Framework\v3.0', 'dotnet40' : R'C:\Windows\Microsoft.NET\Framework\v4.0.30319', 'dotnet4032' : R'C:\Windows\Microsoft.NET\Framework\v4.0.30319', 'group' : RF'C:\Users\{cfg.user_name}\Start Menu\Programs\{cfg.inno_name}', 'localappdata' : RF'C:\Users\{cfg.user_name}\AppData\Local', 'userappdata' : RF'C:\Users\{cfg.user_name}\AppData\Roaming', 'userdesktop' : RF'C:\Users\{cfg.user_name}\Desktop', 'userdocs' : RF'C:\Users\{cfg.user_name}\Documents', 'userfavourites' : RF'C:\Users\{cfg.user_name}\Favourites', 'usersavedgames' : RF'C:\Users\{cfg.user_name}\Saved Games', 'usersendto' : RF'C:\Users\{cfg.user_name}\SendTo', 'userstartmenu' : RF'C:\Users\{cfg.user_name}\Start Menu', 'userprograms' : RF'C:\Users\{cfg.user_name}\Start Menu\Programs', 'userstartup' : RF'C:\Users\{cfg.user_name}\Start Menu\Programs\Startup', 'usertemplates' : RF'C:\Users\{cfg.user_name}\Templates', 'commonappdata' : R'C:\ProgramData', 'commondesktop' : R'C:\ProgramData\Microsoft\Windows\Desktop', 'commondocs' : R'C:\ProgramData\Microsoft\Windows\Documents', 'commonstartmenu' : R'C:\ProgramData\Microsoft\Windows\Start Menu', 'commonprograms' : R'C:\ProgramData\Microsoft\Windows\Start Menu\Programs', 'commonstartup' : R'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup', 'commontemplates' : R'C:\ProgramData\Microsoft\Windows\Templates', 'cmd' : R'C:\Windows\System32\cmd.exe', 'computername' : cfg.host_name, 'groupname' : cfg.inno_name, 'hwnd' : '0', 'wizardhwnd' : '0', 'language' : cfg.language, 'srcexe' : cfg.executable, 'sysuserinfoname' : '{sysuserinfoname}', 'sysuserinfoorg' : '{sysuserinfoorg}', 'userinfoname' : '{userinfoname}', 'userinfoorg' : '{userinfoorg}', 'userinfoserial' : '{userinfoserial}', 'username' : cfg.user_name, 'log' : '', } if (inno := self.inno) is None or (inno.setup_info.Header.Flags & Flags.Uninstallable): map['uninstallexe'] = RF'{cfg.install_to}\unins000.exe' if cfg.x64: map['syswow64'] = R'C:\Windows\SysWOW64' map['commonpf32'] = R'C:\Program Files (x86)' map['commoncf32'] = R'C:\Program Files (x86)\Common Files' map['commonpf64'] = R'C:\Program Files' map['commoncf64'] = R'C:\Program Files\Common Files' map['dotnet2064'] = R'C:\Windows\Microsoft.NET\Framework64\v3.0' map['dotnet4064'] = R'C:\Windows\Microsoft.NET\Framework64\v4.0.30319' else: map['syswow64'] = R'C:\Windows\System32' map['commonpf32'] = R'C:\Program Files' map['commoncf32'] = R'C:\Program Files\Common Files' if cfg.windows_os_version[0] >= 10: map['userfonts'] = RF'{map["localappdata"]}\Microsoft\Windows\Fonts' if cfg.windows_os_version[0] >= 7: map['usercf'] = RF'{map["localappdata"]}\Programs\Common' map['userpf'] = RF'{map["localappdata"]}\Programs' for auto_var, admin_var, user_var in [ ('autoappdata', 'commonappdata', 'userappdata', ), # noqa ('autocf', 'commoncf', 'usercf', ), # noqa ('autocf32', 'commoncf32', 'usercf', ), # noqa ('autocf64', 'commoncf64', 'usercf', ), # noqa ('autodesktop', 'commondesktop', 'userdesktop', ), # noqa ('autodocs', 'commondocs', 'userdocs', ), # noqa ('autofonts', 'commonfonts', 'userfonts', ), # noqa ('autopf', 'commonpf', 'userpf', ), # noqa ('autopf32', 'commonpf32', 'userpf', ), # noqa ('autopf64', 'commonpf64', 'userpf', ), # noqa ('autoprograms', 'commonprograms', 'userprograms', ), # noqa ('autostartmenu', 'commonstartmenu', 'userstartmenu', ), # noqa ('autostartup', 'commonstartup', 'userstartup', ), # noqa ('autotemplates', 'commontemplates', 'usertemplates', ), # noqa ]: try: map[auto_var] = map[admin_var] if cfg.admin else map[user_var] except KeyError: continue for legacy, new in [ ('cf', 'commoncf', ), # noqa ('cf32', 'commoncf32', ), # noqa ('cf64', 'commoncf64', ), # noqa ('fonts', 'commonfonts', ), # noqa ('pf', 'commonpf', ), # noqa ('pf32', 'commonpf32', ), # noqa ('pf64', 'commonpf64', ), # noqa ('sendto', 'usersendto', ), # noqa ]: try: map[legacy] = map[new] except KeyError: continue return map @external(static=False) def ExpandConstant(self, string: str) -> str: return self.expand_constant(string) @external(static=False) def ExpandConstantEx(self, string: str, custom_var: str, custom_val: str) -> str: return self.expand_constant(string, custom_var, custom_val) def expand_constant( self, string: str, custom_var: Optional[str] = None, custom_val: Optional[str] = None, unescape: bool = False ): config = self.config expand = partial(self.expand_constant, unescape=True) with io.StringIO() as result: constants = self.constant_map formatter = Formatter() backslash = False for prefix, spec, modifier, conversion in formatter.parse(string): if backslash and prefix[:1] == '\\': prefix = prefix[1:] if unescape: prefix = unquote(prefix) result.write(prefix) if spec is None: continue elif spec == '\\': if modifier or conversion: raise IFPSException('Invalid format string.', ValueError(string)) value = spec elif spec == custom_var: value = custom_val elif spec.startswith('%'): name, p, default = spec[1:].partition('|') name = expand(name) default = expand(default) try: value = config.environment[name] except KeyError: value = default if p else F'%{name}%' elif spec == 'drive': value = self.ExtractFileDrive(expand(modifier)) elif spec == 'ini': # {ini:Filename,Section,Key|DefaultValue} _, _, default = modifier.partition('|') value = expand(default) elif spec == 'cm': # {cm:LaunchProgram,Inno Setup} # The example above translates to "Launch Inno Setup" if English is the active language. name, _, args = modifier.partition(',') value = self.CustomMessage(expand(name)) elif spec == 'reg': # {reg:HKXX\SubkeyName,ValueName|DefaultValue} _, _, default = modifier.partition('|') value = expand(default) elif spec == 'param': # {param:ParamName|DefaultValue} _, _, default = modifier.partition('|') value = expand(default) else: try: value = constants[spec] except KeyError as KE: raise IFPSException(F'invalid format field {spec}', KE) from KE backslash = value.endswith('\\') result.write(value) return result.getvalue() @external def DeleteFile(path: str) -> bool: return True @external def FileExists(file_name: str) -> bool: return False @external def Log(log: str): ... @external def Inc(p: Variable[Variable[int]]): p.set(p.get() + 1) @external def Dec(p: Variable[Variable[int]]): p.set(p.get() - 1) @external def FindFirst(file_name: str, frec: Variable) -> bool: return False @external def Trunc(x: float) -> float: return math.trunc(x) @external def GetSpaceOnDisk( path: str, in_megabytes: bool, avail: Variable[int], space: Variable[int], ) -> bool: _a = 3_000_000 _t = 5_000_000 if not in_megabytes: _a *= 1000 _t *= 1000 avail.set(_a) space.set(_t) return True @external def GetSpaceOnDisk64( path: str, avail: Variable[int], space: Variable[int], ) -> bool: avail.set(3_000_000_000) space.set(5_000_000_000) return True @external def Exec( exe: str, cmd: str, cwd: str, show: int, wait: int, out: Variable[int], ) -> bool: out.set(0) return True @external def GetCmdTail() -> str: return '' @external def ParamCount() -> int: return 0 @external def ParamStr(index: int) -> str: return '' @external def ActiveLanguage() -> str: return 'en' @external(static=False) def CustomMessage(self, msg_name: str) -> str: by_language = {} for msg in self.inno.setup_info.Messages: if msg.EncodedName == msg_name: lng = msg.Language.Name if lng == self.config.language: return msg.Value by_language[lng] = msg.Value try: return by_language[0] except KeyError: pass try: return next(iter(by_language.values())) except StopIteration: raise IFPSException(F'Custom message with name {msg_name} not found.') @external def FmtMessage(fmt: str, args: List[str]) -> str: fmt = fmt.replace('{', '{{') fmt = fmt.replace('}', '}}') fmt = '%'.join(re.sub('%(\\d+)', '{\\1}', p) for p in fmt.split('%%')) return fmt.format(*args) @external def Format(fmt: str, args: List[Union[str, int, float]]) -> str: try: formatted = fmt % tuple(args) except Exception: raise IFPSException('invalid format') else: return formatted @external(static=False) def SetupMessage(self, id: int) -> str: try: return self.inno.setup_info.Messages[id].Value except (AttributeError, IndexError): return '' @external(static=False, alias=['Is64BitInstallMode', 'IsX64Compatible', 'IsX64OS']) def IsWin64(self) -> bool: return self.config.x64 @external(static=False) def IsX86OS(self) -> bool: return not self.config.x64 @external def RaiseException(msg: str): raise IFPSException(msg) @external(static=False) def ProcessorArchitecture(self) -> int: if self.config.x64: return TSetupProcessorArchitecture.paX64.value else: return TSetupProcessorArchitecture.paX86.value @external(static=False) def GetUserNameString(self) -> str: return self.config.user_name @external(static=False) def GetComputerNameString(self) -> str: return self.config.host_name @external(static=False) def GetUILanguage(self) -> str: return self.config.lcid @external def GetArrayLength(array: Variable) -> int: array = array.deref() return len(array) @external def SetArrayLength(array: Variable, n: int): a = array.deref() a.resize(n) @external(static=False) def WizardForm(self) -> object: return self @external def Unassigned() -> None: return None @external def Null() -> None: return None @external(static=False) def Set8087CW(self, cw: int): self.fpucw = FPUControl(cw) @external(static=False) def Get8087CW(self) -> int: return self.fpucw.value @external(static=False) def GetDateTimeString( self, fmt: str, date_separator: str, time_separator: str, ) -> str: now = self.config.start_time now = now + timedelta( milliseconds=(self.config.milliseconds_per_instruction * self.clock)) now = now + timedelta(seconds=self.seconds_slept) date_separator = date_separator.lstrip('\0') time_separator = time_separator.lstrip('\0') def dt(m: re.Match[str]): spec = m[1] ampm = m[2] if ampm: am, _, pm = ampm.partition('/') spec = spec.upper() suffix = now.strftime('%p').lower() suffix = {'am': am, 'pm': pm}[suffix] else: suffix = '' if spec == 'dddddd' or spec == 'ddddd': return now.date.isoformat() if spec == 't': return now.time().isoformat('minutes') if spec == 'tt': return now.time().isoformat('seconds') if spec == 'd': return str(now.day) if spec == 'm': return str(now.month) if spec == 'h': return str(now.hour) if spec == 'n': return str(now.minute) if spec == 's': return str(now.second) if spec == 'H': return now.strftime('%I').lstrip('0') + suffix if spec == '/': return date_separator or spec if spec == ':': return time_separator or spec return now.strftime({ 'dddd' : '%A', 'ddd' : '%a', 'dd' : '%d', 'mmmm' : '%B', 'mmm' : '%b', 'mm' : '%m', 'yyyy' : '%Y', 'yy' : '%y', 'hh' : '%H', 'HH' : '%I' + suffix, 'nn' : '%M', 'ss' : '%S', }.get(spec, m[0])) split = re.split(F'({formats.string!s})', fmt) for k in range(0, len(split), 2): split[k] = re.sub('([dmyhnst]+)((?:[aA][mM]?/[pP][mM]?)?)', dt, split[k]) for k in range(1, len(split), 2): split[k] = split[k][1:-1] return ''.join(split) @external def Chr(b: int) -> str: return chr(b) @external def Ord(c: str) -> int: return ord(c) @external def Copy(string: str, index: int, count: int) -> str: index -= 1 return string[index:index + count] @external def Length(string: str) -> int: return len(string) @external(alias='AnsiLowercase') def Lowercase(string: str) -> str: return string.lower() @external(alias='AnsiUppercase') def Uppercase(string: str) -> str: return string.upper() @external def StringOfChar(c: str, count: int) -> str: return c * count @external def Delete(string: Variable[str], index: int, count: int): index -= 1 old = string.get() string.set(old[:index] + old[index + count:]) @external def Insert(string: str, dest: Variable[str], index: int): index -= 1 old = dest.get() dest.set(old[:index] + string + old[index:]) @external(static=False) def StringChange(self, string: Variable[str], old: str, new: str) -> int: return self.StringChangeEx(string, old, new, False) @external def StringChangeEx(string: Variable[str], old: str, new: str, _: bool) -> int: haystack = string.get() count = haystack.count(old) string.set(haystack.replace(old, new)) return count @external def Pos(string: str, sub: str) -> int: return string.find(sub) + 1 @external def AddQuotes(string: str) -> str: if string and (string[0] != '"' or string[~0] != '"') and ' ' in string: string = F'"{string}"' return string @external def RemoveQuotes(string: str) -> str: if string and string[0] == '"' and string[~0] == '"': string = string[1:-1] return string @external(static=False) def CompareText(self, a: str, b: str) -> int: return self.CompareStr(a.casefold(), b.casefold()) @external def CompareStr(a: str, b: str) -> int: if a > b: return +1 if a < b: return -1 return 0 @external def SameText(a: str, b: str) -> bool: return a.casefold() == b.casefold() @external def SameStr(a: str, b: str) -> bool: return a == b @external def IsWildcard(pattern: str) -> bool: return '*' in pattern or '?' in pattern @external def WildcardMatch(text: str, pattern: str) -> bool: return fnmatch.fnmatch(text, pattern) @external def Trim(string: str) -> str: return string.strip() @external def TrimLeft(string: str) -> str: return string.lstrip() @external def TrimRight(string: str) -> str: return string.rstrip() @external def StringJoin(sep: str, values: List[str]) -> str: return sep.join(values) @external def StringSplitEx(string: str, separators: List[str], quote: str, how: TSplitType) -> List[str]: if not quote: parts = [string] else: quote = re.escape(quote) parts = re.split(F'({quote}.*?{quote})', string) sep = '|'.join(re.escape(s) for s in separators) out = [] if how == TSplitType.stExcludeEmpty: sep = F'(?:{sep})+' for k in range(0, len(parts)): if k & 1 == 1: out.append(parts[k]) continue out.extend(re.split(sep, string)) if how == TSplitType.stExcludeLastEmpty: for k in reversed(range(len(out))): if not out[k]: out.pop(k) break return out @external(static=False) def StringSplit(self, string: str, separators: List[str], how: TSplitType) -> List[str]: return self.StringSplitEx(string, separators, None, how) @external(alias='StrToInt64') def StrToInt(s: str) -> int: return int(s) @external(alias='StrToInt64Def') def StrToIntDef(s: str, d: int) -> int: try: return int(s) except Exception: return d @external def StrToFloat(s: str) -> float: return float(s) @external(alias='FloatToStr') def IntToStr(i: int) -> str: return str(i) @external def StrToVersion(s: str, v: Variable[int]) -> bool: try: packed = bytes(map(int, s.split('.'))) except Exception: return False if len(packed) != 4: return False v.set(int.from_bytes(packed, 'little')) return True @external def CharLength(string: str, index: int) -> int: return 1 @external def AddBackslash(string: str) -> str: if string and string[~0] != '\\': string = F'{string}\\' return string @external def AddPeriod(string: str) -> str: if string and string[~0] != '.': string = F'{string}.' return string @external(static=False) def RemoveBackslashUnlessRoot(self, string: str) -> str: path = Path(string) if len(path.parts) == 1: return str(path) return self.RemoveBackslash(string) @external def RemoveBackslash(string: str) -> str: return string.rstrip('\\/') @external def ChangeFileExt(name: str, ext: str) -> str: if not ext.startswith('.'): ext = F'.{ext}' return str(Path(name).with_suffix(ext)) @external def ExtractFileExt(name: str) -> str: return Path(name).suffix @external(alias='ExtractFilePath') def ExtractFileDir(name: str) -> str: dirname = str(Path(name).parent) return '' if dirname == '.' else dirname @external def ExtractFileName(name: str) -> str: if name: name = Path(name).parts[-1] return name @external def ExtractFileDrive(name: str) -> str: if name: parts = Path(name).parts if len(parts) >= 2 and parts[0] == '\\' and parts[1] == '?': parts = parts[2:] if parts[0] == '\\': if len(parts) >= 3: return '\\'.join(parts[:3]) else: root = parts[0] if len(root) == 2 and root[1] == ':': return root return '' @external def ExtractRelativePath(base: str, dst: str) -> str: return str(Path(dst).relative_to(base)) @external(static=False, alias='ExpandUNCFileName') def ExpandFileName(self, name: str) -> str: if self.ExtractFileDrive(name): return name return str(self.config.cwd / name) @external def SetLength(string: Variable[str], size: int): old = string.get() old = old.ljust(size, '\0') string.set(old[:size]) @external(alias='OemToCharBuff') def CharToOemBuff(string: str) -> str: # TODO return string @external def Utf8Encode(string: str) -> str: return string.encode('utf8').decode('latin1') @external def Utf8Decode(string: str) -> str: return string.encode('latin1').decode('utf8') @external def GetMD5OfString(string: str) -> str: return hashlib.md5(string.encode('latin1')).hexdigest() @external def GetMD5OfUnicodeString(string: str) -> str: return hashlib.md5(string.encode('utf8')).hexdigest() @external def GetSHA1OfString(string: str) -> str: return hashlib.sha1(string.encode('latin1')).hexdigest() @external def GetSHA1OfUnicodeString(string: str) -> str: return hashlib.sha1(string.encode('utf8')).hexdigest() @external def GetSHA256OfString(string: str) -> str: return hashlib.sha256(string.encode('latin1')).hexdigest() @external def GetSHA256OfUnicodeString(string: str) -> str: return hashlib.sha256(string.encode('utf8')).hexdigest() @external def SysErrorMessage(code: int) -> str: return F'[description for error {code:08X}]' @external def MinimizePathName(path: str, font: object, max_len: int) -> str: return path @external(static=False) def CheckForMutexes(self, mutexes: str) -> bool: return any(m in self.mutexes for m in mutexes.split(',')) @external(static=False) def CreateMutex(self, name: str): self.mutexes.add(name) @external(static=False) def GetWinDir(self) -> str: return self.expand_constant('{win}') @external(static=False) def GetSystemDir(self) -> str: return self.expand_constant('{sys}') @external(static=False) def GetWindowsVersion(self) -> int: version = int.from_bytes( struct.pack('>BBH', *self.config.windows_os_version), 'big') return version @external(static=False) def GetWindowsVersionEx(self, tv: Variable[Union[int, bool]]): tv[0], tv[1], tv[2] = self.config.windows_os_version # noqa tv[3], tv[4] = self.config.windows_sp_version # noqa tv[5], tv[6], tv[7] = True, 0, 0 @external(static=False) def GetWindowsVersionString(self) -> str: return '{0}.{1:02d}.{2:04d}'.format(*self.config.windows_os_version) @external def CreateOleObject(name: str) -> OleObject: return OleObject(name) @external def GetActiveOleObject(name: str) -> OleObject: return OleObject(name) @external def IDispatchInvoke(ole: OleObject, prop_set: bool, name: str, value: Any) -> int: return 0 @external def FindWindowByClassName(name: str) -> int: return 0 @external(static=False) def WizardSilent(self) -> bool: return self.config.wizard_silent @external(static=False) def SizeOf(self, var: Variable) -> int: if var.pointer: return (self.config.x64 + 1) * 4 if var.container: return sum(self.SizeOf(x) for x in var.data) return var.type.code.width del external
Subclasses
Class variables
var external_symbols
Static methods
def kernel32__GetTickCount()
-
Expand source code Browse git
@external def kernel32__GetTickCount() -> int: return time.monotonic_ns() // 1_000_000
def user32__GetSystemMetrics(index)
-
Expand source code Browse git
@external def user32__GetSystemMetrics(index: int) -> int: if index == 80: return 1 if index == 43: return 2 return 0
def IsX86Compatible()
-
Expand source code Browse git
@external def IsX86Compatible() -> bool: return True
def Terminated()
-
Expand source code Browse git
@external(alias=[ 'sArm64', 'IsArm32Compatible', 'Debugging', 'IsUninstaller', ]) def Terminated() -> bool: return False
def Random(top)
-
Expand source code Browse git
@external def Random(top: int) -> int: return random.randrange(0, top)
def WStrGet(string, index)
-
Expand source code Browse git
@external(alias='StrGet') def WStrGet(string: Variable[str], index: int) -> str: if index <= 0: raise ValueError return string[index - 1:index]
def WStrSet(char, index, dst)
-
Expand source code Browse git
@external(alias='StrSet') def WStrSet(char: str, index: int, dst: Variable[str]): old = dst.get() index -= 1 dst.set(old[:index] + char + old[index:])
def Beep()
-
Expand source code Browse git
@external def Beep(): pass
def DirExists(path)
-
Expand source code Browse git
@external def DirExists(path: str) -> bool: return True
def ForceDirectories(path)
-
Expand source code Browse git
@external def ForceDirectories(path: str) -> bool: return True
def LoadStringFromFile(path, out)
-
Expand source code Browse git
@external(alias='LoadStringFromLockedFile') def LoadStringFromFile(path: str, out: Variable[str]) -> bool: return True
def LoadStringsFromFile(path, out)
-
Expand source code Browse git
@external(alias='LoadStringsFromLockedFile') def LoadStringsFromFile(path: str, out: Variable[str]) -> bool: return True
def DeleteFile(path)
-
Expand source code Browse git
@external def DeleteFile(path: str) -> bool: return True
def FileExists(file_name)
-
Expand source code Browse git
@external def FileExists(file_name: str) -> bool: return False
def Log(log)
-
Expand source code Browse git
@external def Log(log: str): ...
def Inc(p)
-
Expand source code Browse git
@external def Inc(p: Variable[Variable[int]]): p.set(p.get() + 1)
def Dec(p)
-
Expand source code Browse git
@external def Dec(p: Variable[Variable[int]]): p.set(p.get() - 1)
def FindFirst(file_name, frec)
-
Expand source code Browse git
@external def FindFirst(file_name: str, frec: Variable) -> bool: return False
def Trunc(x)
-
Expand source code Browse git
@external def Trunc(x: float) -> float: return math.trunc(x)
def GetSpaceOnDisk(path, in_megabytes, avail, space)
-
Expand source code Browse git
@external def GetSpaceOnDisk( path: str, in_megabytes: bool, avail: Variable[int], space: Variable[int], ) -> bool: _a = 3_000_000 _t = 5_000_000 if not in_megabytes: _a *= 1000 _t *= 1000 avail.set(_a) space.set(_t) return True
def GetSpaceOnDisk64(path, avail, space)
-
Expand source code Browse git
@external def GetSpaceOnDisk64( path: str, avail: Variable[int], space: Variable[int], ) -> bool: avail.set(3_000_000_000) space.set(5_000_000_000) return True
def Exec(exe, cmd, cwd, show, wait, out)
-
Expand source code Browse git
@external def Exec( exe: str, cmd: str, cwd: str, show: int, wait: int, out: Variable[int], ) -> bool: out.set(0) return True
def GetCmdTail()
-
Expand source code Browse git
@external def GetCmdTail() -> str: return ''
def ParamCount()
-
Expand source code Browse git
@external def ParamCount() -> int: return 0
def ParamStr(index)
-
Expand source code Browse git
@external def ParamStr(index: int) -> str: return ''
def ActiveLanguage()
-
Expand source code Browse git
@external def ActiveLanguage() -> str: return 'en'
def FmtMessage(fmt, args)
-
Expand source code Browse git
@external def FmtMessage(fmt: str, args: List[str]) -> str: fmt = fmt.replace('{', '{{') fmt = fmt.replace('}', '}}') fmt = '%'.join(re.sub('%(\\d+)', '{\\1}', p) for p in fmt.split('%%')) return fmt.format(*args)
def Format(fmt, args)
-
Expand source code Browse git
@external def Format(fmt: str, args: List[Union[str, int, float]]) -> str: try: formatted = fmt % tuple(args) except Exception: raise IFPSException('invalid format') else: return formatted
def RaiseException(msg)
-
Expand source code Browse git
@external def RaiseException(msg: str): raise IFPSException(msg)
def GetArrayLength(array)
-
Expand source code Browse git
@external def GetArrayLength(array: Variable) -> int: array = array.deref() return len(array)
def SetArrayLength(array, n)
-
Expand source code Browse git
@external def SetArrayLength(array: Variable, n: int): a = array.deref() a.resize(n)
def Unassigned()
-
Expand source code Browse git
@external def Unassigned() -> None: return None
def Null()
-
Expand source code Browse git
@external def Null() -> None: return None
def Chr(b)
-
Expand source code Browse git
@external def Chr(b: int) -> str: return chr(b)
def Ord(c)
-
Expand source code Browse git
@external def Ord(c: str) -> int: return ord(c)
def Copy(string, index, count)
-
Expand source code Browse git
@external def Copy(string: str, index: int, count: int) -> str: index -= 1 return string[index:index + count]
def Length(string)
-
Expand source code Browse git
@external def Length(string: str) -> int: return len(string)
def Lowercase(string)
-
Expand source code Browse git
@external(alias='AnsiLowercase') def Lowercase(string: str) -> str: return string.lower()
def Uppercase(string)
-
Expand source code Browse git
@external(alias='AnsiUppercase') def Uppercase(string: str) -> str: return string.upper()
def StringOfChar(c, count)
-
Expand source code Browse git
@external def StringOfChar(c: str, count: int) -> str: return c * count
def Delete(string, index, count)
-
Expand source code Browse git
@external def Delete(string: Variable[str], index: int, count: int): index -= 1 old = string.get() string.set(old[:index] + old[index + count:])
def Insert(string, dest, index)
-
Expand source code Browse git
@external def Insert(string: str, dest: Variable[str], index: int): index -= 1 old = dest.get() dest.set(old[:index] + string + old[index:])
def StringChangeEx(string, old, new, _)
-
Expand source code Browse git
@external def StringChangeEx(string: Variable[str], old: str, new: str, _: bool) -> int: haystack = string.get() count = haystack.count(old) string.set(haystack.replace(old, new)) return count
def Pos(string, sub)
-
Expand source code Browse git
@external def Pos(string: str, sub: str) -> int: return string.find(sub) + 1
def AddQuotes(string)
-
Expand source code Browse git
@external def AddQuotes(string: str) -> str: if string and (string[0] != '"' or string[~0] != '"') and ' ' in string: string = F'"{string}"' return string
def RemoveQuotes(string)
-
Expand source code Browse git
@external def RemoveQuotes(string: str) -> str: if string and string[0] == '"' and string[~0] == '"': string = string[1:-1] return string
def CompareStr(a, b)
-
Expand source code Browse git
@external def CompareStr(a: str, b: str) -> int: if a > b: return +1 if a < b: return -1 return 0
def SameText(a, b)
-
Expand source code Browse git
@external def SameText(a: str, b: str) -> bool: return a.casefold() == b.casefold()
def SameStr(a, b)
-
Expand source code Browse git
@external def SameStr(a: str, b: str) -> bool: return a == b
def IsWildcard(pattern)
-
Expand source code Browse git
@external def IsWildcard(pattern: str) -> bool: return '*' in pattern or '?' in pattern
def WildcardMatch(text, pattern)
-
Expand source code Browse git
@external def WildcardMatch(text: str, pattern: str) -> bool: return fnmatch.fnmatch(text, pattern)
def Trim(string)
-
Expand source code Browse git
@external def Trim(string: str) -> str: return string.strip()
def TrimLeft(string)
-
Expand source code Browse git
@external def TrimLeft(string: str) -> str: return string.lstrip()
def TrimRight(string)
-
Expand source code Browse git
@external def TrimRight(string: str) -> str: return string.rstrip()
def StringJoin(sep, values)
-
Expand source code Browse git
@external def StringJoin(sep: str, values: List[str]) -> str: return sep.join(values)
def StringSplitEx(string, separators, quote, how)
-
Expand source code Browse git
@external def StringSplitEx(string: str, separators: List[str], quote: str, how: TSplitType) -> List[str]: if not quote: parts = [string] else: quote = re.escape(quote) parts = re.split(F'({quote}.*?{quote})', string) sep = '|'.join(re.escape(s) for s in separators) out = [] if how == TSplitType.stExcludeEmpty: sep = F'(?:{sep})+' for k in range(0, len(parts)): if k & 1 == 1: out.append(parts[k]) continue out.extend(re.split(sep, string)) if how == TSplitType.stExcludeLastEmpty: for k in reversed(range(len(out))): if not out[k]: out.pop(k) break return out
def StrToInt(s)
-
Expand source code Browse git
@external(alias='StrToInt64') def StrToInt(s: str) -> int: return int(s)
def StrToIntDef(s, d)
-
Expand source code Browse git
@external(alias='StrToInt64Def') def StrToIntDef(s: str, d: int) -> int: try: return int(s) except Exception: return d
def StrToFloat(s)
-
Expand source code Browse git
@external def StrToFloat(s: str) -> float: return float(s)
def IntToStr(i)
-
Expand source code Browse git
@external(alias='FloatToStr') def IntToStr(i: int) -> str: return str(i)
def StrToVersion(s, v)
-
Expand source code Browse git
@external def StrToVersion(s: str, v: Variable[int]) -> bool: try: packed = bytes(map(int, s.split('.'))) except Exception: return False if len(packed) != 4: return False v.set(int.from_bytes(packed, 'little')) return True
def CharLength(string, index)
-
Expand source code Browse git
@external def CharLength(string: str, index: int) -> int: return 1
def AddBackslash(string)
-
Expand source code Browse git
@external def AddBackslash(string: str) -> str: if string and string[~0] != '\\': string = F'{string}\\' return string
def AddPeriod(string)
-
Expand source code Browse git
@external def AddPeriod(string: str) -> str: if string and string[~0] != '.': string = F'{string}.' return string
def RemoveBackslash(string)
-
Expand source code Browse git
@external def RemoveBackslash(string: str) -> str: return string.rstrip('\\/')
def ChangeFileExt(name, ext)
-
Expand source code Browse git
@external def ChangeFileExt(name: str, ext: str) -> str: if not ext.startswith('.'): ext = F'.{ext}' return str(Path(name).with_suffix(ext))
def ExtractFileExt(name)
-
Expand source code Browse git
@external def ExtractFileExt(name: str) -> str: return Path(name).suffix
def ExtractFileDir(name)
-
Expand source code Browse git
@external(alias='ExtractFilePath') def ExtractFileDir(name: str) -> str: dirname = str(Path(name).parent) return '' if dirname == '.' else dirname
def ExtractFileName(name)
-
Expand source code Browse git
@external def ExtractFileName(name: str) -> str: if name: name = Path(name).parts[-1] return name
def ExtractFileDrive(name)
-
Expand source code Browse git
@external def ExtractFileDrive(name: str) -> str: if name: parts = Path(name).parts if len(parts) >= 2 and parts[0] == '\\' and parts[1] == '?': parts = parts[2:] if parts[0] == '\\': if len(parts) >= 3: return '\\'.join(parts[:3]) else: root = parts[0] if len(root) == 2 and root[1] == ':': return root return ''
def ExtractRelativePath(base, dst)
-
Expand source code Browse git
@external def ExtractRelativePath(base: str, dst: str) -> str: return str(Path(dst).relative_to(base))
def SetLength(string, size)
-
Expand source code Browse git
@external def SetLength(string: Variable[str], size: int): old = string.get() old = old.ljust(size, '\0') string.set(old[:size])
def CharToOemBuff(string)
-
Expand source code Browse git
@external(alias='OemToCharBuff') def CharToOemBuff(string: str) -> str: # TODO return string
def Utf8Encode(string)
-
Expand source code Browse git
@external def Utf8Encode(string: str) -> str: return string.encode('utf8').decode('latin1')
def Utf8Decode(string)
-
Expand source code Browse git
@external def Utf8Decode(string: str) -> str: return string.encode('latin1').decode('utf8')
def GetMD5OfString(string)
-
Expand source code Browse git
@external def GetMD5OfString(string: str) -> str: return hashlib.md5(string.encode('latin1')).hexdigest()
def GetMD5OfUnicodeString(string)
-
Expand source code Browse git
@external def GetMD5OfUnicodeString(string: str) -> str: return hashlib.md5(string.encode('utf8')).hexdigest()
def GetSHA1OfString(string)
-
Expand source code Browse git
@external def GetSHA1OfString(string: str) -> str: return hashlib.sha1(string.encode('latin1')).hexdigest()
def GetSHA1OfUnicodeString(string)
-
Expand source code Browse git
@external def GetSHA1OfUnicodeString(string: str) -> str: return hashlib.sha1(string.encode('utf8')).hexdigest()
def GetSHA256OfString(string)
-
Expand source code Browse git
@external def GetSHA256OfString(string: str) -> str: return hashlib.sha256(string.encode('latin1')).hexdigest()
def GetSHA256OfUnicodeString(string)
-
Expand source code Browse git
@external def GetSHA256OfUnicodeString(string: str) -> str: return hashlib.sha256(string.encode('utf8')).hexdigest()
def SysErrorMessage(code)
-
Expand source code Browse git
@external def SysErrorMessage(code: int) -> str: return F'[description for error {code:08X}]'
def MinimizePathName(path, font, max_len)
-
Expand source code Browse git
@external def MinimizePathName(path: str, font: object, max_len: int) -> str: return path
def CreateOleObject(name)
-
Expand source code Browse git
@external def CreateOleObject(name: str) -> OleObject: return OleObject(name)
def GetActiveOleObject(name)
-
Expand source code Browse git
@external def GetActiveOleObject(name: str) -> OleObject: return OleObject(name)
def IDispatchInvoke(ole, prop_set, name, value)
-
Expand source code Browse git
@external def IDispatchInvoke(ole: OleObject, prop_set: bool, name: str, value: Any) -> int: return 0
def FindWindowByClassName(name)
-
Expand source code Browse git
@external def FindWindowByClassName(name: str) -> int: return 0
Instance variables
var constant_map
-
Expand source code
def __get__(self, instance, owner=None): if instance is None: return self if self.attrname is None: raise TypeError( "Cannot use cached_property instance without calling __set_name__ on it.") try: cache = instance.__dict__ except AttributeError: # not all objects have __dict__ (e.g. class defines slots) msg = ( f"No '__dict__' attribute on {type(instance).__name__!r} " f"instance to cache {self.attrname!r} property." ) raise TypeError(msg) from None val = cache.get(self.attrname, _NOT_FOUND) if val is _NOT_FOUND: with self.lock: # check if another thread filled cache while we awaited lock val = cache.get(self.attrname, _NOT_FOUND) if val is _NOT_FOUND: val = self.func(instance) try: cache[self.attrname] = val except TypeError: msg = ( f"The '__dict__' attribute on {type(instance).__name__!r} instance " f"does not support item assignment for caching {self.attrname!r} property." ) raise TypeError(msg) from None return val
Methods
def unimplemented(self, function)
-
Expand source code Browse git
def unimplemented(self, function: Function): raise NeedSymbol(function.name)
def emulate_function(self, function, *args)
-
Expand source code Browse git
def emulate_function(self, function: Function, *args): self.stack.clear() decl = function.decl if decl is None: raise NotImplementedError(F'Do not know how to call {function!s}.') if (n := len(decl.parameters)) != (m := len(args)): raise ValueError( F'Function {function!s} expects {n} arguments, only {m} were given.') for index, (argument, parameter) in enumerate(zip(args, decl.parameters), 1): variable = Variable(parameter.type, Variant(index, VariantType.Local)) variable.set(argument) self.stack.append(variable) self.stack.reverse() if not decl.void: result = Variable(decl.return_type, Variant(0, VariantType.Argument)) self.stack.append(result) self.call(function) self.stack.clear() if not decl.void: return result.get()
def call(self, function)
-
Expand source code Browse git
def call(self, function: Function): def operator_div(a, b): return a // b if isinstance(a, int) and isinstance(b, int) else a / b def operator_in(a, b): return a in b def getvar(op: Union[Variant, Operand]) -> Variable: if not isinstance(op, Operand): v = op k = None elif op.type is OperandType.Value: raise TypeError('Attempting to retrieve variable for an immediate operand.') else: v = op.variant k = op.index if op.type is OperandType.IndexedByVar: k = getvar(k).get() t, i = v.type, v.index if t is VariantType.Argument: if function.decl.void: i -= 1 var = self.stack[sp - i] elif t is VariantType.Global: var = self.globals[i] elif t is VariantType.Local: var = self.stack[sp + i] else: raise TypeError if k is not None: var = var.at(k) return var def getval(op: Operand): if op.immediate: return op.value.value return getvar(op).get() def setval(op: Operand, new): if op.immediate: raise RuntimeError('attempt to assign to an immediate') getvar(op).set(new) class CallState(NamedTuple): fn: Function ip: int sp: int eh: List[ExceptionHandler] callstack: List[CallState] = [] exec_start = process_time() ip: int = 0 sp: int = len(self.stack) - 1 pending_exception = None exceptions = [] while True: if 0 < self.config.max_data_stack < len(callstack): raise EmulatorMaxCalls if function.body is None: decl = function.decl name = function.name tcls = decl and (decl.classname or decl.module) tcls = tcls or '' registry: Dict[str, IFPSEmulatedFunction] = self.external_symbols.get(tcls, {}) handler = registry.get(name) if handler: void = handler.void argc = handler.argc elif decl: void = decl.void argc = decl.argc else: void = True argc = 0 try: rpos = 0 if void else 1 args = [self.stack[~k] for k in range(rpos, argc + rpos)] except IndexError: raise EmulatorException( F'Cannot call {function!s}; {argc} arguments + {rpos} return values expected,' F' but stack size is only {len(self.stack)}.') if self.config.trace_calls: self.trace.append(IFPSCall(str(function), tuple(a.get() for a in args))) if handler is None: self.unimplemented(function) else: if decl and (decl.void != handler.void or decl.argc != handler.argc): raise RuntimeError(F'Handler for {function!s} does not match the declaration.') for k, (var, byref) in enumerate(zip(args, handler.spec)): if not byref: args[k] = var.get() if not handler.static: args.insert(0, self) try: return_value = handler.call(*args) except BaseException as b: pending_exception = IFPSException(F'Error calling {function.name}: {b!s}', b) else: if not handler.void: self.stack[-1].set(return_value) if not callstack: if pending_exception is None: return raise pending_exception function, ip, sp, exceptions = callstack.pop() continue while insn := function.code.get(ip, None): if 0 < self.config.max_seconds < process_time() - exec_start: raise EmulatorTimeout if 0 < self.config.max_opcodes < self.clock: raise EmulatorExecutionLimit if 0 < self.config.max_data_stack < len(self.stack): raise EmulatorMaxStack try: if pe := pending_exception: pending_exception = None raise pe opc = insn.opcode ip += insn.size self.clock += 1 if opc == Op.Nop: continue elif opc == Op.Assign: dst = getvar(insn.op(0)) src = insn.op(1) if src.immediate: dst.set(src.value) else: dst.set(getvar(src)) elif opc == Op.Calculate: calculate = { AOp.Add: operator.add, AOp.Sub: operator.sub, AOp.Mul: operator.mul, AOp.Div: operator_div, AOp.Mod: operator.mod, AOp.Shl: operator.lshift, AOp.Shr: operator.rshift, AOp.And: operator.and_, AOp.BOr: operator.or_, AOp.Xor: operator.xor, }[insn.operator] src = insn.op(1) dst = insn.op(0) sv = getval(src) dv = getval(dst) fpu = isinstance(sv, float) or isinstance(dv, float) try: result = calculate(dv, sv) if fpu and not isinstance(result, float): raise FloatingPointError except FloatingPointError as FPE: if not self.fpucw & FPUControl.InvalidOperation: result = float('nan') else: raise IFPSException('invalid operation', FPE) from FPE except OverflowError as OFE: if fpu and self.fpucw & FPUControl.Overflow: result = float('nan') else: raise IFPSException('arithmetic overflow', OFE) from OFE except ZeroDivisionError as ZDE: if fpu and self.fpucw & FPUControl.ZeroDivide: result = float('nan') else: raise IFPSException('division by zero', ZDE) from ZDE setval(dst, result) elif opc == Op.Push: # TODO: I do not actually know how this works self.stack.append(getval(insn.op(0))) elif opc == Op.PushVar: self.stack.append(getvar(insn.op(0))) elif opc == Op.Pop: self.temp = self.stack.pop() elif opc == Op.Call: callstack.append(CallState(function, ip, sp, exceptions)) function = insn.operands[0] ip = 0 sp = len(self.stack) - 1 exceptions = [] break elif opc == Op.Jump: ip = insn.operands[0] elif opc == Op.JumpTrue: if getval(insn.op(1)): ip = insn.operands[0] elif opc == Op.JumpFalse: if not getval(insn.op(1)): ip = insn.operands[0] elif opc == Op.Ret: del self.stack[sp + 1:] if not callstack: return function, ip, sp, exceptions = callstack.pop() break elif opc == Op.StackType: raise OpCodeNotImplemented(str(opc)) elif opc == Op.PushType: self.stack.append(Variable( insn.operands[0], Variant(len(self.stack) - sp, VariantType.Local) )) elif opc == Op.Compare: compare = { COp.GE: operator.ge, COp.LE: operator.le, COp.GT: operator.gt, COp.LT: operator.lt, COp.NE: operator.ne, COp.EQ: operator.eq, COp.IN: operator_in, COp.IS: operator.is_, }[insn.operator] d = getvar(insn.op(0)) a = getval(insn.op(1)) b = getval(insn.op(2)) d.set(compare(a, b)) elif opc == Op.CallVar: pfn = getval(insn.op(0)) if isinstance(pfn, int): pfn = self.ifps.functions[pfn] if isinstance(pfn, Function): self.call(pfn) elif opc in (Op.SetPtr, Op.SetPtrToCopy): copy = False if opc == Op.SetPtrToCopy: copy = True dst = getvar(insn.op(0)) src = getvar(insn.op(1)) dst.setptr(src, copy=copy) elif opc == Op.BooleanNot: setval(a := insn.op(0), not getval(a)) elif opc == Op.IntegerNot: setval(a := insn.op(0), ~getval(a)) elif opc == Op.Neg: setval(a := insn.op(0), -getval(a)) elif opc == Op.SetFlag: condition, negated = insn.operands self.jumpflag = getval(condition) ^ negated elif opc == Op.JumpFlag: if self.jumpflag: ip = insn.operands[0] elif opc == Op.PushEH: exceptions.append(ExceptionHandler(*insn.operands)) elif opc == Op.PopEH: tp = None et = EHType(insn.operands[0]) eh = exceptions[-1] if eh.current != et: raise RuntimeError(F'Expected {eh.current} block to end, but {et} was ended instead.') while tp is None: if et is None: raise RuntimeError tp, et = { EHType.Catch : (eh.finally_one, EHType.Finally), EHType.Try : (eh.finally_one, EHType.Finally), EHType.Finally : (eh.finally_two, EHType.SecondFinally), EHType.SecondFinally : (eh.handler_end, None), }[et] eh.current = et ip = tp if et is None: exceptions.pop() elif opc == Op.Inc: setval(a := insn.op(0), getval(a) + 1) elif opc == Op.Dec: setval(a := insn.op(0), getval(a) - 1) elif opc == Op.JumpPop1: self.stack.pop() ip = insn.operands[0] elif opc == Op.JumpPop2: self.stack.pop() self.stack.pop() ip = insn.operands[0] else: raise RuntimeError(F'Function contains invalid opcode at 0x{ip:X}.') except IFPSException as EE: try: eh = exceptions[-1] except IndexError: raise EE et = EHType.Try tp = None while tp is None: if et is None: raise RuntimeError tp, et = { EHType.Try : (eh.catch_error, EHType.Catch), EHType.Catch : (eh.finally_one, EHType.Finally), EHType.Finally : (eh.finally_two, EHType.SecondFinally), EHType.SecondFinally : (eh.handler_end, None), }[et] if et is None: raise EE eh.current = et ip = tp except AbortEmulation: raise except EmulatorException: raise # except Exception as RE: # raise EmulatorException( # F'In {function.symbol} at 0x{insn.offset:X} (cycle {cycle}), ' # F'emulation of {insn!r} failed: {RE!s}') if ip is None: raise RuntimeError(F'Instruction pointer moved out of bounds to 0x{ip:X}.')
def TPasswordEdit__Text(self, value)
-
Expand source code Browse git
@external(static=False) def TPasswordEdit__Text(self, value: str) -> str: if value: self.passwords.add(value) return value
def IsAdmin(self)
-
Expand source code Browse git
@external(static=False) def IsAdmin(self) -> bool: return self.config.admin
def kernel32__Sleep(self, ms)
-
Expand source code Browse git
@external(static=False, alias='Sleep') def kernel32__Sleep(self, ms: int): seconds = ms / 1000.0 self.seconds_slept += seconds time.sleep(seconds * self.config.sleep_scale)
def GetEnv(self, name)
-
Expand source code Browse git
@external(static=False) def GetEnv(self, name: str) -> str: return self.config.environment.get(name, F'%{name}%')
def Abort(self)
-
Expand source code Browse git
@external(static=False) def Abort(self): if self.config.throw_abort: raise AbortEmulation
def ExpandConstant(self, string)
-
Expand source code Browse git
@external(static=False) def ExpandConstant(self, string: str) -> str: return self.expand_constant(string)
def ExpandConstantEx(self, string, custom_var, custom_val)
-
Expand source code Browse git
@external(static=False) def ExpandConstantEx(self, string: str, custom_var: str, custom_val: str) -> str: return self.expand_constant(string, custom_var, custom_val)
def expand_constant(self, string, custom_var=None, custom_val=None, unescape=False)
-
Expand source code Browse git
def expand_constant( self, string: str, custom_var: Optional[str] = None, custom_val: Optional[str] = None, unescape: bool = False ): config = self.config expand = partial(self.expand_constant, unescape=True) with io.StringIO() as result: constants = self.constant_map formatter = Formatter() backslash = False for prefix, spec, modifier, conversion in formatter.parse(string): if backslash and prefix[:1] == '\\': prefix = prefix[1:] if unescape: prefix = unquote(prefix) result.write(prefix) if spec is None: continue elif spec == '\\': if modifier or conversion: raise IFPSException('Invalid format string.', ValueError(string)) value = spec elif spec == custom_var: value = custom_val elif spec.startswith('%'): name, p, default = spec[1:].partition('|') name = expand(name) default = expand(default) try: value = config.environment[name] except KeyError: value = default if p else F'%{name}%' elif spec == 'drive': value = self.ExtractFileDrive(expand(modifier)) elif spec == 'ini': # {ini:Filename,Section,Key|DefaultValue} _, _, default = modifier.partition('|') value = expand(default) elif spec == 'cm': # {cm:LaunchProgram,Inno Setup} # The example above translates to "Launch Inno Setup" if English is the active language. name, _, args = modifier.partition(',') value = self.CustomMessage(expand(name)) elif spec == 'reg': # {reg:HKXX\SubkeyName,ValueName|DefaultValue} _, _, default = modifier.partition('|') value = expand(default) elif spec == 'param': # {param:ParamName|DefaultValue} _, _, default = modifier.partition('|') value = expand(default) else: try: value = constants[spec] except KeyError as KE: raise IFPSException(F'invalid format field {spec}', KE) from KE backslash = value.endswith('\\') result.write(value) return result.getvalue()
def CustomMessage(self, msg_name)
-
Expand source code Browse git
@external(static=False) def CustomMessage(self, msg_name: str) -> str: by_language = {} for msg in self.inno.setup_info.Messages: if msg.EncodedName == msg_name: lng = msg.Language.Name if lng == self.config.language: return msg.Value by_language[lng] = msg.Value try: return by_language[0] except KeyError: pass try: return next(iter(by_language.values())) except StopIteration: raise IFPSException(F'Custom message with name {msg_name} not found.')
def SetupMessage(self, id)
-
Expand source code Browse git
@external(static=False) def SetupMessage(self, id: int) -> str: try: return self.inno.setup_info.Messages[id].Value except (AttributeError, IndexError): return ''
def IsWin64(self)
-
Expand source code Browse git
@external(static=False, alias=['Is64BitInstallMode', 'IsX64Compatible', 'IsX64OS']) def IsWin64(self) -> bool: return self.config.x64
def IsX86OS(self)
-
Expand source code Browse git
@external(static=False) def IsX86OS(self) -> bool: return not self.config.x64
def ProcessorArchitecture(self)
-
Expand source code Browse git
@external(static=False) def ProcessorArchitecture(self) -> int: if self.config.x64: return TSetupProcessorArchitecture.paX64.value else: return TSetupProcessorArchitecture.paX86.value
def GetUserNameString(self)
-
Expand source code Browse git
@external(static=False) def GetUserNameString(self) -> str: return self.config.user_name
def GetComputerNameString(self)
-
Expand source code Browse git
@external(static=False) def GetComputerNameString(self) -> str: return self.config.host_name
def GetUILanguage(self)
-
Expand source code Browse git
@external(static=False) def GetUILanguage(self) -> str: return self.config.lcid
def WizardForm(self)
-
Expand source code Browse git
@external(static=False) def WizardForm(self) -> object: return self
def Set8087CW(self, cw)
-
Expand source code Browse git
@external(static=False) def Set8087CW(self, cw: int): self.fpucw = FPUControl(cw)
def Get8087CW(self)
-
Expand source code Browse git
@external(static=False) def Get8087CW(self) -> int: return self.fpucw.value
def GetDateTimeString(self, fmt, date_separator, time_separator)
-
Expand source code Browse git
@external(static=False) def GetDateTimeString( self, fmt: str, date_separator: str, time_separator: str, ) -> str: now = self.config.start_time now = now + timedelta( milliseconds=(self.config.milliseconds_per_instruction * self.clock)) now = now + timedelta(seconds=self.seconds_slept) date_separator = date_separator.lstrip('\0') time_separator = time_separator.lstrip('\0') def dt(m: re.Match[str]): spec = m[1] ampm = m[2] if ampm: am, _, pm = ampm.partition('/') spec = spec.upper() suffix = now.strftime('%p').lower() suffix = {'am': am, 'pm': pm}[suffix] else: suffix = '' if spec == 'dddddd' or spec == 'ddddd': return now.date.isoformat() if spec == 't': return now.time().isoformat('minutes') if spec == 'tt': return now.time().isoformat('seconds') if spec == 'd': return str(now.day) if spec == 'm': return str(now.month) if spec == 'h': return str(now.hour) if spec == 'n': return str(now.minute) if spec == 's': return str(now.second) if spec == 'H': return now.strftime('%I').lstrip('0') + suffix if spec == '/': return date_separator or spec if spec == ':': return time_separator or spec return now.strftime({ 'dddd' : '%A', 'ddd' : '%a', 'dd' : '%d', 'mmmm' : '%B', 'mmm' : '%b', 'mm' : '%m', 'yyyy' : '%Y', 'yy' : '%y', 'hh' : '%H', 'HH' : '%I' + suffix, 'nn' : '%M', 'ss' : '%S', }.get(spec, m[0])) split = re.split(F'({formats.string!s})', fmt) for k in range(0, len(split), 2): split[k] = re.sub('([dmyhnst]+)((?:[aA][mM]?/[pP][mM]?)?)', dt, split[k]) for k in range(1, len(split), 2): split[k] = split[k][1:-1] return ''.join(split)
def StringChange(self, string, old, new)
-
Expand source code Browse git
@external(static=False) def StringChange(self, string: Variable[str], old: str, new: str) -> int: return self.StringChangeEx(string, old, new, False)
def CompareText(self, a, b)
-
Expand source code Browse git
@external(static=False) def CompareText(self, a: str, b: str) -> int: return self.CompareStr(a.casefold(), b.casefold())
def StringSplit(self, string, separators, how)
-
Expand source code Browse git
@external(static=False) def StringSplit(self, string: str, separators: List[str], how: TSplitType) -> List[str]: return self.StringSplitEx(string, separators, None, how)
def RemoveBackslashUnlessRoot(self, string)
-
Expand source code Browse git
@external(static=False) def RemoveBackslashUnlessRoot(self, string: str) -> str: path = Path(string) if len(path.parts) == 1: return str(path) return self.RemoveBackslash(string)
def ExpandFileName(self, name)
-
Expand source code Browse git
@external(static=False, alias='ExpandUNCFileName') def ExpandFileName(self, name: str) -> str: if self.ExtractFileDrive(name): return name return str(self.config.cwd / name)
def CheckForMutexes(self, mutexes)
-
Expand source code Browse git
@external(static=False) def CheckForMutexes(self, mutexes: str) -> bool: return any(m in self.mutexes for m in mutexes.split(','))
def CreateMutex(self, name)
-
Expand source code Browse git
@external(static=False) def CreateMutex(self, name: str): self.mutexes.add(name)
def GetWinDir(self)
-
Expand source code Browse git
@external(static=False) def GetWinDir(self) -> str: return self.expand_constant('{win}')
def GetSystemDir(self)
-
Expand source code Browse git
@external(static=False) def GetSystemDir(self) -> str: return self.expand_constant('{sys}')
def GetWindowsVersion(self)
-
Expand source code Browse git
@external(static=False) def GetWindowsVersion(self) -> int: version = int.from_bytes( struct.pack('>BBH', *self.config.windows_os_version), 'big') return version
def GetWindowsVersionEx(self, tv)
-
Expand source code Browse git
@external(static=False) def GetWindowsVersionEx(self, tv: Variable[Union[int, bool]]): tv[0], tv[1], tv[2] = self.config.windows_os_version # noqa tv[3], tv[4] = self.config.windows_sp_version # noqa tv[5], tv[6], tv[7] = True, 0, 0
def GetWindowsVersionString(self)
-
Expand source code Browse git
@external(static=False) def GetWindowsVersionString(self) -> str: return '{0}.{1:02d}.{2:04d}'.format(*self.config.windows_os_version)
def WizardSilent(self)
-
Expand source code Browse git
@external(static=False) def WizardSilent(self) -> bool: return self.config.wizard_silent
def SizeOf(self, var)
-
Expand source code Browse git
@external(static=False) def SizeOf(self, var: Variable) -> int: if var.pointer: return (self.config.x64 + 1) * 4 if var.container: return sum(self.SizeOf(x) for x in var.data) return var.type.code.width
class InnoSetupEmulator (archive, options=None, **more)
-
Expand source code Browse git
class InnoSetupEmulator(IFPSEmulator): def emulate_installation(self, password=''): class SetupDispatcher: InitializeSetup: Callable InitializeWizard: Callable CurStepChanged: Callable ShouldSkipPage: Callable CurPageChanged: Callable PrepareToInstall: Callable CheckPassword: Callable NextButtonClick: Callable DeinitializeSetup: Callable def __getattr__(_, name): return (lambda *a: self.emulate_function(pfn, *a)) if ( pfn := self.symbols.get(name) ) else (lambda *_: False) Setup = SetupDispatcher() Setup.InitializeSetup() Setup.InitializeWizard() Setup.CurStepChanged(TSetupStep.ssPreInstall) for page in PageID: if not Setup.ShouldSkipPage(page): Setup.CurPageChanged(page) if page == PageID.wpPreparing: Setup.PrepareToInstall(False) if page == PageID.wpPassword: Setup.CheckPassword(password) Setup.NextButtonClick(page) if page == PageID.wpPreparing: Setup.CurStepChanged(TSetupStep.ssInstall) if page == PageID.wpInfoAfter: Setup.CurStepChanged(TSetupStep.ssPostInstall) Setup.CurStepChanged(TSetupStep.ssDone) Setup.DeinitializeSetup() def unimplemented(self, function: Function): decl = function.decl if decl is None: return if not decl.void: rc = 1 rv = self.stack[-1] if not rv.container: rt = rv.type.py_type() if isinstance(rt, type) and issubclass(rt, int): rv.set(1) else: rc = 0 for k in range(rc, rc + len(decl.parameters)): ptr: Variable[Variable] = self.stack[-k] if not ptr.pointer: continue var = ptr.deref() if var.container: continue vt = var.type.py_type() if isinstance(vt, type) and issubclass(vt, int): var.set(1)
Ancestors
Class variables
var external_symbols
Methods
def emulate_installation(self, password='')
-
Expand source code Browse git
def emulate_installation(self, password=''): class SetupDispatcher: InitializeSetup: Callable InitializeWizard: Callable CurStepChanged: Callable ShouldSkipPage: Callable CurPageChanged: Callable PrepareToInstall: Callable CheckPassword: Callable NextButtonClick: Callable DeinitializeSetup: Callable def __getattr__(_, name): return (lambda *a: self.emulate_function(pfn, *a)) if ( pfn := self.symbols.get(name) ) else (lambda *_: False) Setup = SetupDispatcher() Setup.InitializeSetup() Setup.InitializeWizard() Setup.CurStepChanged(TSetupStep.ssPreInstall) for page in PageID: if not Setup.ShouldSkipPage(page): Setup.CurPageChanged(page) if page == PageID.wpPreparing: Setup.PrepareToInstall(False) if page == PageID.wpPassword: Setup.CheckPassword(password) Setup.NextButtonClick(page) if page == PageID.wpPreparing: Setup.CurStepChanged(TSetupStep.ssInstall) if page == PageID.wpInfoAfter: Setup.CurStepChanged(TSetupStep.ssPostInstall) Setup.CurStepChanged(TSetupStep.ssDone) Setup.DeinitializeSetup()
def unimplemented(self, function)
-
Expand source code Browse git
def unimplemented(self, function: Function): decl = function.decl if decl is None: return if not decl.void: rc = 1 rv = self.stack[-1] if not rv.container: rt = rv.type.py_type() if isinstance(rt, type) and issubclass(rt, int): rv.set(1) else: rc = 0 for k in range(rc, rc + len(decl.parameters)): ptr: Variable[Variable] = self.stack[-k] if not ptr.pointer: continue var = ptr.deref() if var.container: continue vt = var.type.py_type() if isinstance(vt, type) and issubclass(vt, int): var.set(1)