Display templated EQ messages as templated messages

This makes them less annoying to read, and allows us to use
subfield serializers to pretty-print their contents.
This commit is contained in:
Salad Dais
2024-01-04 18:00:14 +00:00
parent 582cfea47c
commit e7764c1665
4 changed files with 82 additions and 2 deletions

View File

@@ -29,7 +29,10 @@ from hippolyzer.lib.base.message.msgtypes import MsgType
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 _make_tuplecoord_spec(typ: Type[TupleCoord], struct_fmt: str,
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 @@ class TemplateDataPacker:
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)
}

View File

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

View File

@@ -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 <H1>Foo</H1>
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

View File

@@ -16,10 +16,14 @@ import weakref
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 @@ class EQMessageLogEntry(AbstractMessageLogEntry):
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