6 Commits

Author SHA1 Message Date
Salad Dais
da0117db1b v0.13.1 2023-07-05 20:29:40 +00:00
Salad Dais
4dbf01a604 Blacklist new versions of recordclass 2023-07-05 20:27:05 +00:00
Salad Dais
36858ed3e2 Fix flake error 2023-06-18 18:37:14 +00:00
Salad Dais
370c586582 Decode more flags fields 2023-06-18 18:33:52 +00:00
Salad Dais
fdfffd96c9 Fix UUID serialization with invalid AIS LLSD payloads 2023-06-18 18:33:26 +00:00
Salad Dais
6da9f58b23 Pass original Message through to objectupdate hooks 2023-06-18 18:29:51 +00:00
15 changed files with 56 additions and 33 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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):

View File

@@ -78,7 +78,7 @@ class TemplateDataPacker:
MsgType.MVT_S8: _make_struct_spec('b'),
MsgType.MVT_U8: _make_struct_spec('B'),
MsgType.MVT_BOOL: _make_struct_spec('B'),
MsgType.MVT_LLUUID: (lambda x: UUID(bytes=bytes(x)), lambda x: x.bytes),
MsgType.MVT_LLUUID: (lambda x: UUID(bytes=bytes(x)), lambda x: UUID(x).bytes),
MsgType.MVT_IP_ADDR: (socket.inet_ntoa, socket.inet_aton),
MsgType.MVT_IP_PORT: _make_struct_spec('!H'),
MsgType.MVT_U16: _make_struct_spec('<H'),

View File

@@ -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

View File

@@ -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__ = ()

View File

@@ -1859,6 +1859,8 @@ class AvatarPropertiesFlags(IntFlag):
@se.flag_field_serializer("AvatarGroupsReply", "GroupData", "GroupPowers")
@se.flag_field_serializer("AvatarGroupDataUpdate", "GroupData", "GroupPowers")
@se.flag_field_serializer("AvatarDataUpdate", "AgentDataData", "GroupPowers")
@se.flag_field_serializer("GroupProfileReply", "GroupData", "PowersMask")
@se.flag_field_serializer("GroupRoleDataReply", "RoleData", "Powers")
class GroupPowerFlags(IntFlag):
MEMBER_INVITE = 1 << 1 # Invite member
MEMBER_EJECT = 1 << 2 # Eject member from group
@@ -1948,6 +1950,15 @@ class GroupPowerFlags(IntFlag):
GROUP_BAN_ACCESS = 1 << 51 # Allows access to ban / un-ban agents from a group.
@se.flag_field_serializer("GrantUserRights", "Rights", "RelatedRights")
@se.flag_field_serializer("ChangeUserRights", "Rights", "RelatedRights")
class UserRelatedRights(IntFlag):
"""See lluserrelations.h for definitions"""
ONLINE_STATUS = 1
MAP_LOCATION = 1 << 1
MODIFY_OBJECTS = 1 << 2
@se.flag_field_serializer("RequestObjectPropertiesFamily", "ObjectData", "RequestFlags")
@se.flag_field_serializer("ObjectPropertiesFamily", "ObjectData", "RequestFlags")
class ObjectPropertiesFamilyRequestFlags(IntFlag):

View File

@@ -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,11 +564,16 @@ 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 region_state:
region_state.resolve_futures(obj, update_type)
else:
LOG.warning(f"{obj} not tied to a region state")
if obj.PCode == PCode.AVATAR and "NameValue" in updated_props:
if obj.NameValue:
self.name_cache.update(obj.FullID, obj.NameValue.to_dict())

View File

@@ -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):

View File

@@ -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):

View File

@@ -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)

View File

@@ -25,7 +25,7 @@ from setuptools import setup, find_packages
here = path.abspath(path.dirname(__file__))
version = '0.13.0'
version = '0.13.1'
with open(path.join(here, 'README.md')) as readme_fh:
readme = readme_fh.read()
@@ -86,7 +86,8 @@ setup(
'outleap<1.0',
'defusedxml',
'aiohttp<4.0.0',
'recordclass>0.15,<0.19',
# Newer recordclasses break!
'recordclass>0.15,<0.18.3',
'lazy-object-proxy',
'arpeggio',
# requests breaks with newer idna

View File

@@ -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))

View File

@@ -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):