diff --git a/hippolyzer/lib/base/message/data_packer.py b/hippolyzer/lib/base/message/data_packer.py index 601c771..02507d0 100644 --- a/hippolyzer/lib/base/message/data_packer.py +++ b/hippolyzer/lib/base/message/data_packer.py @@ -29,7 +29,10 @@ PACKER = Callable[[Any], bytes] UNPACKER = Callable[[bytes], Any] +LLSD_PACKER = Callable[[Any], Any] +LLSD_UNPACKER = Callable[[Any], Any] SPEC = Tuple[UNPACKER, PACKER] +LLSD_SPEC = Tuple[LLSD_UNPACKER, LLSD_PACKER] def _pack_string(pack_string): @@ -64,6 +67,21 @@ def _packer(x): return lambda x: typ(*struct_obj.unpack(x)), _packer +def _make_llsd_tuplecoord_spec(typ: Type[TupleCoord], needed_elems: Optional[int] = None): + if needed_elems is None: + # Number of elems needed matches the number in the coord type + def _packer(x): + return list(x) + else: + # Special case, we only want to pack some of the components. + # Mostly for Quaternion since we don't actually need to send W. + def _packer(x): + if isinstance(x, TupleCoord): + x = x.data() + return list(x.data(needed_elems)) + return lambda x: typ(*x), _packer + + def _unpack_specs(cls): cls.UNPACKERS = {k: v[0] for (k, v) in cls.SPECS.items()} cls.PACKERS = {k: v[1] for (k, v) in cls.SPECS.items()} @@ -110,10 +128,15 @@ def pack(cls, data, data_type): class LLSDDataPacker(TemplateDataPacker): # Some template var types aren't directly representable in LLSD, so they # get encoded to binary fields. - SPECS = { + SPECS: Dict[MsgType, LLSD_SPEC] = { MsgType.MVT_IP_ADDR: (socket.inet_ntoa, socket.inet_aton), # LLSD ints are technically bound to S32 range. MsgType.MVT_U32: _make_struct_spec('!I'), MsgType.MVT_U64: _make_struct_spec('!Q'), MsgType.MVT_S64: _make_struct_spec('!q'), + # These are arrays in LLSD, we need to turn them into coords. + MsgType.MVT_LLVector3: _make_llsd_tuplecoord_spec(Vector3), + MsgType.MVT_LLVector3d: _make_llsd_tuplecoord_spec(Vector3), + MsgType.MVT_LLVector4: _make_llsd_tuplecoord_spec(Vector4), + MsgType.MVT_LLQuaternion: _make_llsd_tuplecoord_spec(Quaternion, needed_elems=3) } diff --git a/hippolyzer/lib/base/message/msgtypes.py b/hippolyzer/lib/base/message/msgtypes.py index 2eb074d..e646d98 100644 --- a/hippolyzer/lib/base/message/msgtypes.py +++ b/hippolyzer/lib/base/message/msgtypes.py @@ -55,6 +55,8 @@ class PacketFlags(enum.IntFlag): RELIABLE = 0x40 RESENT = 0x20 ACK = 0x10 + # Not a real flag, just used for display. + EQ = 1 << 10 # frequency for messages diff --git a/hippolyzer/lib/base/templates.py b/hippolyzer/lib/base/templates.py index b1c3dbf..dba7cbc 100644 --- a/hippolyzer/lib/base/templates.py +++ b/hippolyzer/lib/base/templates.py @@ -1952,7 +1952,7 @@ class AvatarPropertiesFlags(IntFlag): @se.flag_field_serializer("AvatarGroupsReply", "GroupData", "GroupPowers") -@se.flag_field_serializer("AvatarGroupDataUpdate", "GroupData", "GroupPowers") +@se.flag_field_serializer("AgentGroupDataUpdate", "GroupData", "GroupPowers") @se.flag_field_serializer("AgentDataUpdate", "AgentData", "GroupPowers") @se.flag_field_serializer("GroupProfileReply", "GroupData", "PowersMask") @se.flag_field_serializer("GroupRoleDataReply", "RoleData", "Powers") @@ -2134,6 +2134,43 @@ class ScriptPermissions(IntFlag): CHANGE_ENVIRONMENT = 1 << 18 +@se.flag_field_serializer("ParcelProperties", "ParcelData", "ParcelFlags") +class ParcelFlags(IntFlag): + ALLOW_FLY = 1 << 0 # Can start flying + ALLOW_OTHER_SCRIPTS = 1 << 1 # Scripts by others can run. + FOR_SALE = 1 << 2 # Can buy this land + FOR_SALE_OBJECTS = 1 << 7 # Can buy all objects on this land + ALLOW_LANDMARK = 1 << 3 # Always true/deprecated + ALLOW_TERRAFORM = 1 << 4 + ALLOW_DAMAGE = 1 << 5 + CREATE_OBJECTS = 1 << 6 + # 7 is moved above + USE_ACCESS_GROUP = 1 << 8 + USE_ACCESS_LIST = 1 << 9 + USE_BAN_LIST = 1 << 10 + USE_PASS_LIST = 1 << 11 + SHOW_DIRECTORY = 1 << 12 + ALLOW_DEED_TO_GROUP = 1 << 13 + CONTRIBUTE_WITH_DEED = 1 << 14 + SOUND_LOCAL = 1 << 15 # Hear sounds in this parcel only + SELL_PARCEL_OBJECTS = 1 << 16 # Objects on land are included as part of the land when the land is sold + ALLOW_PUBLISH = 1 << 17 # Allow publishing of parcel information on the web + MATURE_PUBLISH = 1 << 18 # The information on this parcel is mature + URL_WEB_PAGE = 1 << 19 # The "media URL" is an HTML page + URL_RAW_HTML = 1 << 20 # The "media URL" is a raw HTML string like

Foo

+ RESTRICT_PUSHOBJECT = 1 << 21 # Restrict push object to either on agent or on scripts owned by parcel owner + DENY_ANONYMOUS = 1 << 22 # Deny all non identified/transacted accounts + # DENY_IDENTIFIED = 1 << 23 # Deny identified accounts + # DENY_TRANSACTED = 1 << 24 # Deny identified accounts + ALLOW_GROUP_SCRIPTS = 1 << 25 # Allow scripts owned by group + CREATE_GROUP_OBJECTS = 1 << 26 # Allow object creation by group members or objects + ALLOW_ALL_OBJECT_ENTRY = 1 << 27 # Allow all objects to enter a parcel + ALLOW_GROUP_OBJECT_ENTRY = 1 << 28 # Only allow group (and owner) objects to enter the parcel + ALLOW_VOICE_CHAT = 1 << 29 # Allow residents to use voice chat on this parcel + USE_ESTATE_VOICE_CHAN = 1 << 30 + DENY_AGEUNVERIFIED = 1 << 31 # Prevent residents who aren't age-verified + + @se.enum_field_serializer("UpdateMuteListEntry", "MuteData", "MuteType") class MuteType(IntEnum): BY_NAME = 0 @@ -2193,6 +2230,7 @@ class CreationDateSerializer(se.AdapterSubfieldSerializer): @se.subfield_serializer("MeanCollisionAlert", "MeanCollision", "Time") +@se.subfield_serializer("ParcelProperties", "ParcelData", "ClaimDate") class DateSerializer(se.AdapterSubfieldSerializer): ADAPTER = DateAdapter(1) ORIG_INLINE = True diff --git a/hippolyzer/lib/proxy/message_logger.py b/hippolyzer/lib/proxy/message_logger.py index f4ec38c..1a90ed5 100644 --- a/hippolyzer/lib/proxy/message_logger.py +++ b/hippolyzer/lib/proxy/message_logger.py @@ -16,10 +16,14 @@ from defusedxml import minidom from hippolyzer.lib.base import serialization as se, llsd +from hippolyzer.lib.base.message.llsd_msg_serializer import LLSDMessageSerializer from hippolyzer.lib.base.message.message import Message from hippolyzer.lib.base.datatypes import TaggedUnion, UUID, TupleCoord from hippolyzer.lib.base.helpers import bytes_escape from hippolyzer.lib.base.message.message_formatting import HumanMessageSerializer +from hippolyzer.lib.base.message.msgtypes import PacketFlags +from hippolyzer.lib.base.message.template_dict import DEFAULT_TEMPLATE_DICT +from hippolyzer.lib.base.network.transport import Direction from hippolyzer.lib.proxy.message_filter import MetaFieldSpecifier, compile_filter, BaseFilterNode, MessageFilterNode, \ EnumFieldSpecifier, MatchResult from hippolyzer.lib.proxy.http_flow import HippoHTTPFlow @@ -614,6 +618,19 @@ def type(self): return "EQ" def request(self, beautify=False, replacements=None): + # TODO: This is a bit of a hack! Templated messages can be sent over the EQ, so let's + # display them as template messages if that's what they are. + if self.event['message'] in DEFAULT_TEMPLATE_DICT.message_templates: + msg = LLSDMessageSerializer().deserialize(self.event) + msg.synthetic = True + msg.send_flags = PacketFlags.EQ + msg.direction = Direction.IN + # Annoyingly, templated messages sent over the EQ can have extra fields not specified + # in the template, and this is often the case. ParcelProperties has fields that aren't + # in the template. Luckily, we don't really care about extra fields, we just may not + # be able to automatically decode U32 and friends without the hint from the template + # that that is what they are. + return HumanMessageSerializer.to_human_string(msg, replacements, beautify) return f'EQ {self.event["message"]}\n\n{self._format_llsd(self.event["body"])}' @property