diff --git a/addon_examples/blueish_object_list.py b/addon_examples/blueish_object_list.py index dacd454..42b9ea5 100644 --- a/addon_examples/blueish_object_list.py +++ b/addon_examples/blueish_object_list.py @@ -114,7 +114,7 @@ class BlueishObjectListGUIAddon(BaseAddon): region.objects.request_missing_objects() def handle_object_updated(self, session: Session, region: ProxiedRegion, - obj: Object, updated_props: Set[str]): + obj: Object, updated_props: Set[str], msg: Optional[Message]): if self.blueish_model is None: return diff --git a/addon_examples/local_mesh.py b/addon_examples/local_mesh.py index b8b7b43..d4ed498 100644 --- a/addon_examples/local_mesh.py +++ b/addon_examples/local_mesh.py @@ -230,7 +230,7 @@ class MeshUploadInterceptingAddon(BaseAddon): show_message("Mangled upload request") def handle_object_updated(self, session: Session, region: ProxiedRegion, - obj: Object, updated_props: Set[str]): + obj: Object, updated_props: Set[str], msg: Optional[Message]): if obj.LocalID not in self.local_mesh_target_locals: return if "Name" not in updated_props or obj.Name is None: diff --git a/addon_examples/objectupdate_blame.py b/addon_examples/objectupdate_blame.py index 7900a42..65c7750 100644 --- a/addon_examples/objectupdate_blame.py +++ b/addon_examples/objectupdate_blame.py @@ -10,6 +10,7 @@ before you start tracking can help too. from typing import * from hippolyzer.lib.base.datatypes import UUID +from hippolyzer.lib.base.message.message import Message from hippolyzer.lib.base.objects import Object from hippolyzer.lib.base.templates import PCode from hippolyzer.lib.proxy.addon_utils import BaseAddon, show_message, SessionProperty @@ -57,7 +58,7 @@ class ObjectUpdateBlameAddon(BaseAddon): print(f"{obj_id} ({name!r}): {count}") def handle_object_updated(self, session: Session, region: ProxiedRegion, - obj: Object, updated_props: Set[str]): + obj: Object, updated_props: Set[str], msg: Optional[Message]): if not self.should_track_update_blame: return if region != session.main_region: diff --git a/hippolyzer/lib/base/exc.py b/hippolyzer/lib/base/exc.py index 504491a..506de27 100644 --- a/hippolyzer/lib/base/exc.py +++ b/hippolyzer/lib/base/exc.py @@ -176,7 +176,7 @@ class MessageTemplateNotFound(MessageSystemError): self.template = template def __str__(self): - return "No message template found, context: '%s'" % self.context + return "No message template found for %s, context: '%s'" % (self.template, self.context) class MessageTemplateParsingError(MessageSystemError): diff --git a/hippolyzer/lib/base/message/udpdeserializer.py b/hippolyzer/lib/base/message/udpdeserializer.py index c4c109b..dc5507d 100644 --- a/hippolyzer/lib/base/message/udpdeserializer.py +++ b/hippolyzer/lib/base/message/udpdeserializer.py @@ -126,7 +126,7 @@ class UDPMessageDeserializer: frequency, num = _parse_msg_num(reader) current_template = self.template_dict.get_template_by_pair(frequency, num) if current_template is None: - raise exc.MessageTemplateNotFound("deserializing data") + raise exc.MessageTemplateNotFound("deserializing data", f"{frequency}:{num}") msg.name = current_template.name # extra field, see note regarding msg.offset diff --git a/hippolyzer/lib/base/network/transport.py b/hippolyzer/lib/base/network/transport.py index 245dbd8..6b446ce 100644 --- a/hippolyzer/lib/base/network/transport.py +++ b/hippolyzer/lib/base/network/transport.py @@ -46,6 +46,9 @@ class UDPPacket: return self.dst_addr return self.src_addr + def __repr__(self): + return f"<{self.__class__.__name__} src_addr={self.src_addr!r} dst_addr={self.dst_addr!r} data={self.data!r}>" + class AbstractUDPTransport(abc.ABC): __slots__ = () diff --git a/hippolyzer/lib/client/object_manager.py b/hippolyzer/lib/client/object_manager.py index dd345a3..477d9ea 100644 --- a/hippolyzer/lib/client/object_manager.py +++ b/hippolyzer/lib/client/object_manager.py @@ -297,7 +297,8 @@ class ClientWorldObjectManager: self._rebuild_avatar_objects() self._region_managers.clear() - def _update_existing_object(self, obj: Object, new_properties: dict, update_type: ObjectUpdateType): + def _update_existing_object(self, obj: Object, new_properties: dict, update_type: ObjectUpdateType, + msg: Optional[Message]): old_parent_id = obj.ParentID new_parent_id = new_properties.get("ParentID", obj.ParentID) old_local_id = obj.LocalID @@ -340,23 +341,23 @@ class ClientWorldObjectManager: LOG.warning(f"Tried to move object {obj!r} to unknown region {new_region_handle}") if obj.PCode == PCode.AVATAR: - # `Avatar` instances are handled separately. Update all Avatar objects so - # we can deal with the RegionHandle change. + # `Avatar` instances are handled separately. Update all Avatar objects, + # so we can deal with the RegionHandle change. self._rebuild_avatar_objects() elif new_parent_id != old_parent_id: # Parent ID changed, but we're in the same region new_region_state.handle_object_reparented(obj, old_parent_id=old_parent_id) if actually_updated_props and new_region_state is not None: - self._run_object_update_hooks(obj, actually_updated_props, update_type) + self._run_object_update_hooks(obj, actually_updated_props, update_type, msg) - def _track_new_object(self, region: RegionObjectsState, obj: Object): + def _track_new_object(self, region: RegionObjectsState, obj: Object, msg: Message): region.track_object(obj) self._fullid_lookup[obj.FullID] = obj if obj.PCode == PCode.AVATAR: self._avatar_objects[obj.FullID] = obj self._rebuild_avatar_objects() - self._run_object_update_hooks(obj, set(obj.to_dict().keys()), ObjectUpdateType.OBJECT_UPDATE) + self._run_object_update_hooks(obj, set(obj.to_dict().keys()), ObjectUpdateType.OBJECT_UPDATE, msg) def _kill_object_by_local_id(self, region_state: RegionObjectsState, local_id: int): obj = region_state.lookup_localid(local_id) @@ -408,11 +409,11 @@ class ClientWorldObjectManager: # our view of the world then we want to move it to this region. obj = self.lookup_fullid(object_data["FullID"]) if obj: - self._update_existing_object(obj, object_data, ObjectUpdateType.OBJECT_UPDATE) + self._update_existing_object(obj, object_data, ObjectUpdateType.OBJECT_UPDATE, msg) else: if region_state is None: continue - self._track_new_object(region_state, Object(**object_data)) + self._track_new_object(region_state, Object(**object_data), msg) msg.meta["ObjectUpdateIDs"] = tuple(seen_locals) def _handle_terse_object_update(self, msg: Message): @@ -432,7 +433,7 @@ class ClientWorldObjectManager: # Need the Object as context because decoding state requires PCode. state_deserializer = ObjectStateSerializer.deserialize object_data["State"] = state_deserializer(ctx_obj=obj, val=object_data["State"]) - self._update_existing_object(obj, object_data, ObjectUpdateType.OBJECT_UPDATE) + self._update_existing_object(obj, object_data, ObjectUpdateType.OBJECT_UPDATE, msg) else: if region_state: region_state.missing_locals.add(object_data["LocalID"]) @@ -460,7 +461,7 @@ class ClientWorldObjectManager: self._update_existing_object(obj, { "UpdateFlags": update_flags, "RegionHandle": handle, - }, ObjectUpdateType.OBJECT_UPDATE) + }, ObjectUpdateType.OBJECT_UPDATE, msg) continue cached_obj_data = self._lookup_cache_entry(handle, block["ID"], block["CRC"]) @@ -468,7 +469,7 @@ class ClientWorldObjectManager: cached_obj = normalize_object_update_compressed_data(cached_obj_data) cached_obj["UpdateFlags"] = update_flags cached_obj["RegionHandle"] = handle - self._track_new_object(region_state, Object(**cached_obj)) + self._track_new_object(region_state, Object(**cached_obj), msg) continue # Don't know about it and wasn't cached. @@ -499,11 +500,11 @@ class ClientWorldObjectManager: LOG.warning(f"Got ObjectUpdateCompressed for unknown region {handle}: {object_data!r}") obj = self.lookup_fullid(object_data["FullID"]) if obj: - self._update_existing_object(obj, object_data, ObjectUpdateType.OBJECT_UPDATE) + self._update_existing_object(obj, object_data, ObjectUpdateType.OBJECT_UPDATE, msg) else: if region_state is None: continue - self._track_new_object(region_state, Object(**object_data)) + self._track_new_object(region_state, Object(**object_data), msg) msg.meta["ObjectUpdateIDs"] = tuple(seen_locals) def _handle_object_properties_generic(self, packet: Message): @@ -516,7 +517,7 @@ class ClientWorldObjectManager: obj = self.lookup_fullid(block["ObjectID"]) if obj: seen_locals.append(obj.LocalID) - self._update_existing_object(obj, object_properties, ObjectUpdateType.PROPERTIES) + self._update_existing_object(obj, object_properties, ObjectUpdateType.PROPERTIES, packet) else: LOG.debug(f"Received {packet.name} for unknown {block['ObjectID']}") packet.meta["ObjectUpdateIDs"] = tuple(seen_locals) @@ -563,9 +564,10 @@ class ClientWorldObjectManager: LOG.debug(f"Received ObjectCost for unknown {object_id}") continue obj.ObjectCosts.update(object_costs) - self._run_object_update_hooks(obj, {"ObjectCosts"}, ObjectUpdateType.COSTS) + self._run_object_update_hooks(obj, {"ObjectCosts"}, ObjectUpdateType.COSTS, None) - def _run_object_update_hooks(self, obj: Object, updated_props: Set[str], update_type: ObjectUpdateType): + def _run_object_update_hooks(self, obj: Object, updated_props: Set[str], update_type: ObjectUpdateType, + msg: Optional[Message]): region_state = self._get_region_state(obj.RegionHandle) region_state.resolve_futures(obj, update_type) if obj.PCode == PCode.AVATAR and "NameValue" in updated_props: diff --git a/hippolyzer/lib/proxy/addon_utils.py b/hippolyzer/lib/proxy/addon_utils.py index f4b5a20..b5503ac 100644 --- a/hippolyzer/lib/proxy/addon_utils.py +++ b/hippolyzer/lib/proxy/addon_utils.py @@ -172,7 +172,7 @@ class BaseAddon(metaclass=MetaBaseAddon): pass def handle_object_updated(self, session: Session, region: ProxiedRegion, - obj: Object, updated_props: Set[str]): + obj: Object, updated_props: Set[str], msg: Optional[Message]): pass def handle_object_killed(self, session: Session, region: ProxiedRegion, obj: Object): diff --git a/hippolyzer/lib/proxy/addons.py b/hippolyzer/lib/proxy/addons.py index 4281f13..e213b5c 100644 --- a/hippolyzer/lib/proxy/addons.py +++ b/hippolyzer/lib/proxy/addons.py @@ -561,9 +561,9 @@ class AddonManager: @classmethod def handle_object_updated(cls, session: Session, region: ProxiedRegion, - obj: Object, updated_props: Set[str]): + obj: Object, updated_props: Set[str], msg: Optional[Message]): with addon_ctx.push(session, region): - return cls._call_all_addon_hooks("handle_object_updated", session, region, obj, updated_props) + return cls._call_all_addon_hooks("handle_object_updated", session, region, obj, updated_props, msg) @classmethod def handle_object_killed(cls, session: Session, region: ProxiedRegion, obj: Object): diff --git a/hippolyzer/lib/proxy/lludp_proxy.py b/hippolyzer/lib/proxy/lludp_proxy.py index 74488f6..3be8d19 100644 --- a/hippolyzer/lib/proxy/lludp_proxy.py +++ b/hippolyzer/lib/proxy/lludp_proxy.py @@ -3,6 +3,7 @@ import logging import weakref from typing import Optional, Tuple +from hippolyzer.lib.base.exc import MessageTemplateNotFound from hippolyzer.lib.base.message.message_dot_xml import MessageDotXML from hippolyzer.lib.base.message.udpdeserializer import UDPMessageDeserializer from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer diff --git a/hippolyzer/lib/proxy/object_manager.py b/hippolyzer/lib/proxy/object_manager.py index 5ebfac6..cfd1a55 100644 --- a/hippolyzer/lib/proxy/object_manager.py +++ b/hippolyzer/lib/proxy/object_manager.py @@ -133,8 +133,9 @@ class ProxyWorldObjectManager(ClientWorldObjectManager): region_mgr.queued_cache_misses |= missing_locals region_mgr.request_missed_cached_objects_soon() - def _run_object_update_hooks(self, obj: Object, updated_props: Set[str], update_type: ObjectUpdateType): - super()._run_object_update_hooks(obj, updated_props, update_type) + def _run_object_update_hooks(self, obj: Object, updated_props: Set[str], update_type: ObjectUpdateType, + msg: Optional[Message]): + super()._run_object_update_hooks(obj, updated_props, update_type, msg) region = self._session.region_by_handle(obj.RegionHandle) if self._settings.ALLOW_AUTO_REQUEST_OBJECTS: if obj.PCode == PCode.AVATAR and "ParentID" in updated_props: @@ -145,7 +146,7 @@ class ProxyWorldObjectManager(ClientWorldObjectManager): # have no way to get a sitting agent's true region location, even if it's ourselves. region.objects.queued_cache_misses.add(obj.ParentID) region.objects.request_missed_cached_objects_soon() - AddonManager.handle_object_updated(self._session, region, obj, updated_props) + AddonManager.handle_object_updated(self._session, region, obj, updated_props, msg) def _run_kill_object_hooks(self, obj: Object): super()._run_kill_object_hooks(obj) diff --git a/tests/proxy/integration/test_lludp.py b/tests/proxy/integration/test_lludp.py index 41a4c89..1b8a600 100644 --- a/tests/proxy/integration/test_lludp.py +++ b/tests/proxy/integration/test_lludp.py @@ -36,7 +36,7 @@ class MockAddon(BaseAddon): return True def handle_object_updated(self, session: Session, region: ProxiedRegion, - obj: Object, updated_props: Set[str]): + obj: Object, updated_props: Set[str], msg: Optional[Message]): self.events.append(("object_update", session.id, region.circuit_addr, obj.LocalID, updated_props)) diff --git a/tests/proxy/test_object_manager.py b/tests/proxy/test_object_manager.py index 17f6873..35c69d5 100644 --- a/tests/proxy/test_object_manager.py +++ b/tests/proxy/test_object_manager.py @@ -48,7 +48,7 @@ class ObjectTrackingAddon(BaseAddon): super().__init__() self.events = [] - def handle_object_updated(self, session, region, obj: Object, updated_props: Set[str]): + def handle_object_updated(self, session, region, obj: Object, updated_props: Set[str], msg: Optional[Message]): self.events.append(("update", obj, updated_props)) def handle_object_killed(self, session, region, obj: Object):