Source code for memobj.object

from typing import TYPE_CHECKING, Callable, Union

from memobj.property import MemoryProperty, Pointer

if TYPE_CHECKING:
    from .process import Process, WindowsProcess


class MemoryObjectMeta(type):
    # TODO: move to __init_subclass__?
    # noinspection PyMethodParameters
    def __new__(
        cls,
        class_name: str,
        superclasses: tuple[type],
        attributed_dict: dict,
        *args,
        **kwargs,
    ):
        if not superclasses:
            return super().__new__(cls, class_name, superclasses, attributed_dict)

        new_instance = super().__new__(cls, class_name, superclasses, attributed_dict)
        memory_object = superclasses[-1]

        replace = kwargs.pop("replace", False)

        memory_object._register_string_class_lookup(new_instance, replace)  # type: ignore

        __memory_objects__ = {}
        __memory_properties__ = {}
        for name, _type in attributed_dict.items():
            if isinstance(_type, MemoryObject):
                __memory_objects__[name] = _type
            elif isinstance(_type, MemoryProperty):
                __memory_properties__[name] = _type

        new_instance.__memory_objects__ = __memory_objects__  # type: ignore
        new_instance.__memory_properties__ = __memory_properties__  # type: ignore

        return new_instance


[docs] class MemoryObject(metaclass=MemoryObjectMeta): __memory_object_instances__: dict[str, type["MemoryObject"]] = {} __memory_properties__: dict[str, MemoryProperty] = {} def __init__( self, offset: Union[int, None] = None, *, address: Union[int, None] = None, address_provider: Union[Callable[[], int], None] = None, process: Union["Process", "WindowsProcess", None] = None, ): if address is not None and address_provider is not None: raise ValueError("both address and address_provider cannot be provided") if (address is not None or address_provider is not None) and process is None: raise ValueError("process must be provided if address/address_provider are") if process is not None and (address is None and address_provider is None): raise ValueError("address/address_provider must be provided if process is") self._offset = offset self._base_address = address self._address_provider = address_provider self.memobj_process = process # TODO: should this be named something else to prevent collisions with properties? @property def base_address(self) -> int: if self._address_provider is not None: return self._address_provider() # TODO: deprecate this, the Pointer property can hold the offset i.e. player = Pointer(0x10, Player) # the property-like usage if self._base_address is None: raise ValueError("Uninitialized memoryobject did not receieve an address") return self._base_address @staticmethod def _resolve_string_class_lookup(class_name: str) -> type["MemoryObject"]: try: return MemoryObject.__memory_object_instances__[class_name] except KeyError: raise ValueError(f"No registered MemoryObject named {class_name}") @staticmethod def _register_string_class_lookup( type_instance: type["MemoryObject"], replace: bool = False ): class_name = type_instance.__name__ if MemoryObject.__memory_object_instances__.get(class_name) and not replace: raise NameError( f"You can only have one MemoryObject named {type_instance.__name__}" ) MemoryObject.__memory_object_instances__[class_name] = type_instance if not TYPE_CHECKING: def __getattribute__(self, name): attr = super().__getattribute__(name) if not isinstance(attr, MemoryObject): return attr if isinstance(self.__memory_properties__[name], Pointer): return attr if attr._offset is None: raise ValueError("Offset not set") attr._base_address = self.base_address + attr._offset attr.memobj_process = self.memobj_process return attr