diff --git a/.gitignore b/.gitignore index c88fa4f..889eb1d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.cache/ /dist/ +/build/ /?venv/ *.egg-info/ diff --git a/src/zenkit/_core.py b/src/zenkit/_core.py index c8121b5..00c9c3f 100644 --- a/src/zenkit/_core.py +++ b/src/zenkit/_core.py @@ -54,6 +54,7 @@ DLL: Final[CDLL] = CDLL(str(_PATH)) PathOrFileLike = Union[str, PathLike, "Read", bytes, bytearray, "VfsNode"] +DaedalusSymbolValue = Union[float, int, str, "DaedalusInstance", None] class GameVersion(IntEnum): diff --git a/src/zenkit/daedalus/__init__.py b/src/zenkit/daedalus/__init__.py index 173a95c..47d8637 100644 --- a/src/zenkit/daedalus/__init__.py +++ b/src/zenkit/daedalus/__init__.py @@ -44,3 +44,12 @@ DaedalusInstanceType.SOUND_EFFECT: SoundEffectInstance, DaedalusInstanceType.SOUND_SYSTEM: SoundSystemInstance, } + +_CLASS_TYPES = { + "C_NPC": DaedalusInstanceType.NPC, + "C_MISSION": DaedalusInstanceType.MISSION, + "C_ITEM": DaedalusInstanceType.ITEM, + "C_INFO": DaedalusInstanceType.INFO, + "C_ITEMREACT": DaedalusInstanceType.ITEM_REACT, + "C_FOCUS": DaedalusInstanceType.FOCUS, +} diff --git a/src/zenkit/daedalus/base.py b/src/zenkit/daedalus/base.py index 8cead1f..62f759c 100644 --- a/src/zenkit/daedalus/base.py +++ b/src/zenkit/daedalus/base.py @@ -42,22 +42,25 @@ class DaedalusInstance: def __init__(self, **kwargs: Any) -> None: self._handle = None + self._sym = None if "_handle" in kwargs: self._handle = kwargs.pop("_handle") + if "_sym" in kwargs: + self._sym = kwargs.pop("_sym") + @staticmethod - def from_native(handle: c_void_p | None) -> "DaedalusInstance | None": + def from_native(handle: c_void_p | None, sym: "DaedalusSymbol | None" = None) -> "DaedalusInstance | None": from zenkit.daedalus import _INSTANCES - print(handle, handle.value if handle is not None else None) if handle is None or handle.value is None or handle.value == 0 or (isinstance(handle.value, c_void_p) and handle.value.value == None): return None DLL.ZkDaedalusInstance_getType.restype = c_int typ = DaedalusInstanceType(DLL.ZkDaedalusInstance_getType(handle)) - return _INSTANCES.get(typ, DaedalusInstance)(_handle=handle) + return _INSTANCES.get(typ, DaedalusInstance)(_handle=handle, _sym=sym) @property def handle(self) -> c_void_p: @@ -72,3 +75,10 @@ def type(self) -> DaedalusInstanceType: def index(self) -> int: DLL.ZkDaedalusInstance_getIndex.restype = c_uint32 return DLL.ZkDaedalusInstance_getIndex(self._handle) + + def __str__(self) -> str: + sym_part = f"({self._sym.name})" if self._sym else "" + if not sym_part: + sym_part = f"({self.index})" if self.index else "" + name_part = f"={self.name}" if hasattr(self, "name") else "" + return f"{self.__class__.__name__}{sym_part}{name_part}" diff --git a/src/zenkit/daedalus_script.py b/src/zenkit/daedalus_script.py index b40ac40..5e07290 100644 --- a/src/zenkit/daedalus_script.py +++ b/src/zenkit/daedalus_script.py @@ -7,6 +7,7 @@ ] from abc import abstractmethod +from collections.abc import Generator from ctypes import Structure from ctypes import c_float from ctypes import c_int @@ -20,8 +21,10 @@ from typing import ClassVar from zenkit import _native +from zenkit.daedalus import _CLASS_TYPES from zenkit._core import DLL from zenkit._core import PathOrFileLike +from zenkit._core import DaedalusSymbolValue from zenkit._native import ZkPointer from zenkit._native import ZkString from zenkit.daedalus.base import DaedalusInstance @@ -125,20 +128,31 @@ def set_string(self, val: str, i: int = 0, ctx: DaedalusInstance | None = None) ) def get_int(self, i: int = 0, ctx: DaedalusInstance | None = None) -> int: - return DLL.ZkDaedalusSymbol_getInt(self._handle, c_uint16(i), ctx.handle if ctx else None).value + return DLL.ZkDaedalusSymbol_getInt(self._handle, c_uint16(i), ctx.handle if ctx else None) def set_int(self, val: int, i: int = 0, ctx: DaedalusInstance | None = None) -> None: DLL.ZkDaedalusSymbol_setInt(self._handle, c_int32(val), c_uint16(i), ctx.handle if ctx else None) def get_float(self, i: int = 0, ctx: DaedalusInstance | None = None) -> float: - return DLL.ZkDaedalusSymbol_getFloat(self._handle, c_uint16(i), ctx.handle if ctx else None).value + return DLL.ZkDaedalusSymbol_getFloat(self._handle, c_uint16(i), ctx.handle if ctx else None) def set_float(self, val: float, i: int = 0, ctx: DaedalusInstance | None = None) -> None: DLL.ZkDaedalusSymbol_setFloat(self._handle, c_float(val), c_uint16(i), ctx.handle if ctx else None) def get_instance(self) -> DaedalusInstance: value = DLL.ZkDaedalusSymbol_getInstance(self._handle) - return DaedalusInstance.from_native(value) + return DaedalusInstance.from_native(value, self) + + def get_parent_as_symbol(self, find_root: bool = False) -> "DaedalusSymbol | None": + if self.parent < 0: + return None + + handle = self._keepalive.get_symbol_by_index(self.parent) + + while find_root and handle and handle.parent >= 0: + handle = self._keepalive.get_symbol_by_index(handle.parent) + + return handle @property def is_const(self) -> bool: @@ -192,9 +206,35 @@ def index(self) -> int: def return_type(self) -> DaedalusDataType: return DaedalusDataType(DLL.ZkDaedalusSymbol_getReturnType(self._handle)) + @property + def value(self) -> DaedalusSymbolValue: + if self.type == DaedalusDataType.FLOAT: + return self.get_float() + if self.type == DaedalusDataType.INT: + return self.get_int() + if self.type == DaedalusDataType.STRING: + return self.get_string() + if self.type == DaedalusDataType.INSTANCE: + return self.get_instance() + return None + + @value.setter + def value(self, value: DaedalusSymbolValue): + if self.type == DaedalusDataType.FLOAT: + self.set_float(value) + elif self.type == DaedalusDataType.INT: + self.set_int(value) + elif self.type == DaedalusDataType.STRING: + self.set_string(value) + else: + raise ValueError(f"Symbol of type {self.type.name} doesn't support value assignment") + def __repr__(self) -> str: return f"<{self.__class__.__name__} handle={self._handle} name={self.name!r} type={self.type.name}>" + def __str__(self) -> str: + return str(self.value) if self.value is not None else self.name + class DaedalusInstruction(Structure): _fields_: ClassVar[tuple[str, Any]] = [ @@ -253,9 +293,9 @@ def load(path_or_file_like: PathOrFileLike) -> "DaedalusScript": return DaedalusScript(_handle=handle, _delete=True) @property - def symbols(self) -> list[DaedalusSymbol]: + def symbols(self) -> Generator[DaedalusSymbol]: count = DLL.ZkDaedalusScript_getSymbolCount(self._handle) - return [self.get_symbol_by_index(i) for i in range(count)] + return (self.get_symbol_by_index(i) for i in range(count)) def get_instruction(self, address: int) -> DaedalusInstruction: return DLL.ZkDaedalusScript_getInstruction(self._handle, c_size_t(address)) @@ -278,6 +318,17 @@ def get_symbol_by_name(self, name: str) -> DaedalusSymbol | None: return None return DaedalusSymbol(_handle=handle, _keepalive=self) + def get_parent_symbol(self, child: DaedalusSymbol, find_root: bool = False) -> DaedalusSymbol | None: + if child.parent < 0: + return None + + symbol = self.get_symbol_by_index(child.parent) + + while find_root and symbol and symbol.parent >= 0: + symbol = self.get_symbol_by_index(symbol.parent) + + return symbol + def __del__(self) -> None: self._deleter() diff --git a/src/zenkit/daedalus_vm.py b/src/zenkit/daedalus_vm.py index 0ed3eaa..f05af11 100644 --- a/src/zenkit/daedalus_vm.py +++ b/src/zenkit/daedalus_vm.py @@ -17,6 +17,7 @@ from zenkit._core import PathOrFileLike from zenkit._native import ZkPointer from zenkit._native import ZkString +from zenkit.daedalus import _CLASS_TYPES from zenkit.daedalus.base import DaedalusInstance from zenkit.daedalus.base import DaedalusInstanceType from zenkit.daedalus_script import DaedalusScript @@ -102,7 +103,7 @@ def push(self, val: DaedalusType) -> None: elif isinstance(val, str): DLL.ZkDaedalusVm_pushString(self._handle, val.encode("windows-1252")) else: - raise TypeError("Unsupported type: " + type(val)) + raise TypeError(f"Unsupported type: {type(val)}") def pop(self, typ: type[DaedalusTypeGeneric]) -> DaedalusTypeGeneric: if typ == DaedalusInstance: @@ -141,14 +142,21 @@ def alloc_instance(self, sym: DaedalusSymbol | str, typ: DaedalusInstanceType) - handle = DLL.ZkDaedalusVm_allocInstance(self._handle, sym.handle, typ.value).value return DaedalusInstance.from_native(handle) - def init_instance(self, sym: DaedalusSymbol | str, typ: DaedalusInstanceType) -> DaedalusInstance: + def init_instance(self, sym: DaedalusSymbol | str, typ: DaedalusInstanceType = None) -> DaedalusInstance: DLL.ZkDaedalusVm_initInstance.restype = ZkPointer if isinstance(sym, str): sym = self.get_symbol_by_name(sym) + if typ is None: + class_sym = self.get_parent_symbol(sym, find_root=True) + if class_sym and class_sym.name in _CLASS_TYPES: + typ = _CLASS_TYPES[class_sym.name] + else: + raise ValueError(f"Failed to guess DaedalusInstanceType to init {sym.name}") + handle = DLL.ZkDaedalusVm_initInstance(self._handle, sym.handle, typ.value).value - return DaedalusInstance.from_native(handle) + return DaedalusInstance.from_native(handle, sym) def init_instance_direct(self, sym: DaedalusInstance) -> None: DLL.ZkDaedalusVm_initInstanceDirect(self._handle, sym.handle)