Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

/.cache/
/dist/
/build/
/?venv/

*.egg-info/
Expand Down
1 change: 1 addition & 0 deletions src/zenkit/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
9 changes: 9 additions & 0 deletions src/zenkit/daedalus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
16 changes: 13 additions & 3 deletions src/zenkit/daedalus/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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}"
61 changes: 56 additions & 5 deletions src/zenkit/daedalus_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]] = [
Expand Down Expand Up @@ -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))
Expand All @@ -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()

Expand Down
14 changes: 11 additions & 3 deletions src/zenkit/daedalus_vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down