import abc import dataclasses import enum import importlib import logging import zlib from typing import * import hippolyzer.lib.base.serialization as se from hippolyzer.lib.base import llsd from hippolyzer.lib.base.datatypes import UUID from hippolyzer.lib.base.namevalue import NameValuesSerializer try: importlib.reload(se) # type: ignore except: logging.exception("Failed to reload serialization lib") @se.enum_field_serializer("RequestXfer", "XferID", "VFileType") @se.enum_field_serializer("AssetUploadRequest", "AssetBlock", "Type") @se.enum_field_serializer("AssetUploadComplete", "AssetBlock", "Type") @se.enum_field_serializer("UpdateCreateInventoryItem", "InventoryData", "Type") @se.enum_field_serializer("CreateInventoryItem", "InventoryBlock", "Type") @se.enum_field_serializer("RezObject", "InventoryData", "Type") @se.enum_field_serializer("RezScript", "InventoryBlock", "Type") @se.enum_field_serializer("UpdateTaskInventory", "InventoryData", "Type") class AssetType(enum.IntEnum): TEXTURE = 0 SOUND = 1 CALLINGCARD = 2 LANDMARK = 3 SCRIPT = 4 CLOTHING = 5 OBJECT = 6 NOTECARD = 7 CATEGORY = 8 LSL_TEXT = 10 LSL_BYTECODE = 11 TEXTURE_TGA = 12 BODYPART = 13 SOUND_WAV = 17 IMAGE_TGA = 18 IMAGE_JPEG = 19 ANIMATION = 20 GESTURE = 21 SIMSTATE = 22 LINK = 24 LINK_FOLDER = 25 MARKETPLACE_FOLDER = 26 WIDGET = 40 PERSON = 45 MESH = 49 RESERVED_1 = 50 RESERVED_2 = 51 RESERVED_3 = 52 RESERVED_4 = 53 RESERVED_5 = 54 RESERVED_6 = 55 SETTINGS = 56 UNKNOWN = 255 NONE = -1 @property def human_name(self): lower = self.name.lower() return { "animation": "animatn", "callingcard": "callcard", "texture_tga": "txtr_tga", "image_tga": "img_tga", "sound_wav": "snd_wav", }.get(lower, lower) @property def inventory_type(self): return { AssetType.TEXTURE: InventoryType.TEXTURE, AssetType.SOUND: InventoryType.SOUND, AssetType.CALLINGCARD: InventoryType.CALLINGCARD, AssetType.LANDMARK: InventoryType.LANDMARK, AssetType.SCRIPT: InventoryType.LSL, AssetType.CLOTHING: InventoryType.WEARABLE, AssetType.OBJECT: InventoryType.OBJECT, AssetType.NOTECARD: InventoryType.NOTECARD, AssetType.CATEGORY: InventoryType.CATEGORY, AssetType.LSL_TEXT: InventoryType.LSL, AssetType.LSL_BYTECODE: InventoryType.LSL, AssetType.TEXTURE_TGA: InventoryType.TEXTURE, AssetType.BODYPART: InventoryType.WEARABLE, AssetType.SOUND_WAV: InventoryType.SOUND, AssetType.ANIMATION: InventoryType.ANIMATION, AssetType.GESTURE: InventoryType.GESTURE, AssetType.WIDGET: InventoryType.WIDGET, AssetType.PERSON: InventoryType.PERSON, AssetType.MESH: InventoryType.MESH, AssetType.SETTINGS: InventoryType.SETTINGS, }.get(self, AssetType.NONE) @se.enum_field_serializer("UpdateCreateInventoryItem", "InventoryData", "InvType") @se.enum_field_serializer("CreateInventoryItem", "InventoryBlock", "InvType") @se.enum_field_serializer("RezObject", "InventoryData", "InvType") @se.enum_field_serializer("RezScript", "InventoryBlock", "InvType") @se.enum_field_serializer("UpdateTaskInventory", "InventoryData", "InvType") class InventoryType(enum.IntEnum): TEXTURE = 0 SOUND = 1 CALLINGCARD = 2 LANDMARK = 3 SCRIPT = 4 CLOTHING = 5 OBJECT = 6 NOTECARD = 7 CATEGORY = 8 ROOT_CATEGORY = 9 LSL = 10 LSL_BYTECODE = 11 TEXTURE_TGA = 12 BODYPART = 13 TRASH = 14 SNAPSHOT = 15 LOST_AND_FOUND = 16 ATTACHMENT = 17 WEARABLE = 18 ANIMATION = 19 GESTURE = 20 MESH = 22 WIDGET = 23 PERSON = 24 SETTINGS = 25 UNKNOWN = 255 NONE = -1 @property def human_name(self): lower = self.name.lower() return { "callingcard": "callcard", }.get(lower, lower) @se.enum_field_serializer("AgentIsNowWearing", "WearableData", "WearableType") @se.enum_field_serializer("AgentWearablesUpdate", "WearableData", "WearableType") @se.enum_field_serializer("CreateInventoryItem", "InventoryBlock", "WearableType") class WearableType(enum.IntEnum): SHAPE = 0 SKIN = 1 HAIR = 2 EYES = 3 SHIRT = 4 PANTS = 5 SHOES = 6 SOCKS = 7 JACKET = 8 GLOVES = 9 UNDERSHIRT = 10 UNDERPANTS = 11 SKIRT = 12 ALPHA = 13 TATTOO = 14 PHYSICS = 15 UNIVERSAL = 16 def _register_permissions_flags(message_name, block_name): def _wrapper(flag_cls): for flag_type in {"EveryoneMask", "BaseMask", "OwnerMask", "GroupMask", "NextOwnerMask"}: se.flag_field_serializer(message_name, block_name, flag_type)(flag_cls) return flag_cls return _wrapper @se.flag_field_serializer("ObjectPermissions", "ObjectData", "Mask") @_register_permissions_flags("ObjectProperties", "ObjectData") @_register_permissions_flags("UpdateCreateInventoryItem", "InventoryData") @_register_permissions_flags("UpdateTaskInventory", "InventoryData") @_register_permissions_flags("CreateInventoryItem", "InventoryBlock") @_register_permissions_flags("RezObject", "RezData") @_register_permissions_flags("RezObject", "InventoryData") @_register_permissions_flags("RezScript", "InventoryBlock") @_register_permissions_flags("RezMultipleAttachmentsFromInv", "ObjectData") class Permissions(enum.IntFlag): TRANSFER = (1 << 13) MODIFY = (1 << 14) COPY = (1 << 15) # OpenSim export permission, per Firestorm. Deprecated parcel entry flag EXPORT_OR_PARCEL_ENTER = (1 << 16) # parcels, allow terraform, deprecated TERRAFORM = (1 << 17) # deprecated OWNER_DEBIT = (1 << 18) # objects, can grab/translate/rotate MOVE = (1 << 19) # parcels, avatars take damage, deprecated DAMAGE = (1 << 20) RESERVED = 1 << 31 @se.flag_field_serializer("RezObject", "RezData", "ItemFlags") @se.flag_field_serializer("RezMultipleAttachmentsFromInv", "ObjectData", "ItemFlags") @se.flag_field_serializer("RezObject", "InventoryData", "Flags") @se.flag_field_serializer("UpdateCreateInventoryItem", "InventoryData", "Flags") @se.flag_field_serializer("UpdateTaskInventory", "InventoryData", "Flags") class InventoryItemFlags(enum.IntFlag): # The asset has only one reference in the system. If the # inventory item is deleted, or the assetid updated, then we # can remove the old reference. SHARED_SINGLE_REFERENCE = 0x40000000 # Object permissions should have next owner perm be more # restrictive on rez. We bump this into the second byte of the # flags since the low byte is used to track attachment points. OBJECT_SLAM_PERM = 0x100 # The object sale information has been changed. OBJECT_SLAM_SALE = 0x1000 # Specify which permissions masks to overwrite # upon rez. Normally, if no permissions slam (above) or # overwrite flags are set, the asset's permissions are # used and the inventory's permissions are ignored. If # any of these flags are set, the inventory's permissions # take precedence. OBJECT_PERM_OVERWRITE_BASE = 0x010000 OBJECT_PERM_OVERWRITE_OWNER = 0x020000 OBJECT_PERM_OVERWRITE_GROUP = 0x040000 OBJECT_PERM_OVERWRITE_EVERYONE = 0x080000 OBJECT_PERM_OVERWRITE_NEXT_OWNER = 0x100000 # Whether a returned object is composed of multiple items. OBJECT_HAS_MULTIPLE_ITEMS = 0x200000 @property def attachment_point(self): return self & 0xFF @se.enum_field_serializer("ObjectPermissions", "ObjectData", "Field") class PermissionType(enum.IntEnum): BASE = 0x01 OWNER = 0x02 GROUP = 0x04 EVERYONE = 0x08 NEXT_OWNER = 0x10 @se.enum_field_serializer("TransferRequest", "TransferInfo", "SourceType") class TransferSourceType(enum.IntEnum): UNKNOWN = 0 FILE = enum.auto() ASSET = enum.auto() SIM_INV_ITEM = enum.auto() SIM_ESTATE = enum.auto() class EstateAssetType(enum.IntEnum): NONE = -1 COVENANT = 0 @dataclasses.dataclass class TransferRequestParamsBase(abc.ABC): pass @dataclasses.dataclass class TransferRequestParamsFile(TransferRequestParamsBase): FileName: str = se.dataclass_field(se.CStr()) Delete: bool = se.dataclass_field(se.BOOL) @dataclasses.dataclass class TransferRequestParamsAsset(TransferRequestParamsFile): AssetID: UUID = se.dataclass_field(se.UUID) AssetType: "AssetType" = se.dataclass_field(se.IntEnum(AssetType, se.U32)) @dataclasses.dataclass class TransferRequestParamsSimInvItem(TransferRequestParamsBase): # Will never be None on the wire, just set this way so # code can fill them in when creating these ourselves. AgentID: Optional[UUID] = se.dataclass_field(se.UUID, default=None) SessionID: Optional[UUID] = se.dataclass_field(se.UUID, default=None) OwnerID: UUID = se.dataclass_field(se.UUID) TaskID: UUID = se.dataclass_field(se.UUID) ItemID: UUID = se.dataclass_field(se.UUID) AssetID: UUID = se.dataclass_field(se.UUID) AssetType: "AssetType" = se.dataclass_field(se.IntEnum(AssetType, se.U32)) @dataclasses.dataclass class TransferRequestParamsSimEstate(TransferRequestParamsBase): # See above RE Optional AgentID: Optional[UUID] = se.dataclass_field(se.UUID, default=None) SessionID: Optional[UUID] = se.dataclass_field(se.UUID, default=None) EstateAssetType: "EstateAssetType" = se.dataclass_field(se.IntEnum(EstateAssetType, se.U32)) @se.subfield_serializer("TransferRequest", "TransferInfo", "Params") class TransferParamsSerializer(se.EnumSwitchedSubfieldSerializer): ENUM_FIELD = "SourceType" TEMPLATES = { TransferSourceType.FILE: se.Dataclass(TransferRequestParamsFile), TransferSourceType.ASSET: se.Dataclass(TransferRequestParamsAsset), TransferSourceType.SIM_INV_ITEM: se.Dataclass(TransferRequestParamsSimInvItem), TransferSourceType.SIM_ESTATE: se.Dataclass(TransferRequestParamsSimEstate), } @se.enum_field_serializer("TransferAbort", "TransferInfo", "ChannelType") @se.enum_field_serializer("TransferPacket", "TransferData", "ChannelType") @se.enum_field_serializer("TransferRequest", "TransferInfo", "ChannelType") @se.enum_field_serializer("TransferInfo", "TransferInfo", "ChannelType") class TransferChannelType(enum.IntEnum): UNKNOWN = 0 MISC = enum.auto() ASSET = enum.auto() @se.enum_field_serializer("TransferInfo", "TransferInfo", "TargetType") class TransferTargetType(enum.IntEnum): UNKNOWN = 0 FILE = enum.auto() VFILE = enum.auto() @se.enum_field_serializer("TransferInfo", "TransferInfo", "Status") @se.enum_field_serializer("TransferPacket", "TransferData", "Status") class TransferStatus(enum.IntEnum): OK = 0 DONE = 1 SKIP = 2 ABORT = 3 ERROR = -1 # generic error UNKNOWN_SOURCE = -2 # not found INSUFFICIENT_PERMISSIONS = -3 @se.subfield_serializer("TransferInfo", "TransferInfo", "Params") class TransferInfoSerializer(se.BaseSubfieldSerializer): TEMPLATES = { TransferTargetType.FILE: se.Template({ "FileName": se.CStr(), "Delete": se.BOOL, }), TransferTargetType.VFILE: se.Template({ "AgentID": se.UUID, "SessionID": se.UUID, "OwnerID": se.UUID, "TaskID": se.UUID, "ItemID": se.UUID, "AssetID": se.UUID, "AssetType": se.IntEnum(AssetType, se.U32), }), } @classmethod def _get_target_template(cls, block, val): target_type = block["TargetType"] if target_type != TransferTargetType.UNKNOWN: return cls.TEMPLATES[target_type] # Hard to tell what format the payload uses without tracking # which request this info message corresponds to. Brute force it. templates = TransferParamsSerializer.TEMPLATES.values() return cls._fuzzy_guess_template(templates, val) @classmethod def deserialize(cls, ctx_obj, val, pod=False): if not val: return se.UNSERIALIZABLE template = cls._get_target_template(ctx_obj, val) if not template: return se.UNSERIALIZABLE return cls._deserialize_template(template, val, pod) @classmethod def serialize(cls, ctx_obj, vals): template = cls._get_target_template(ctx_obj, vals) if not template: return se.UNSERIALIZABLE return cls._serialize_template(template, vals) @se.enum_field_serializer("RequestXfer", "XferID", "FilePath") class XferFilePath(enum.IntEnum): NONE = 0 USER_SETTINGS = 1 APP_SETTINGS = 2 PER_SL_ACCOUNT = 3 CACHE = 4 CHARACTER = 5 HELP = 6 LOGS = 7 TEMP = 8 SKINS = 9 TOP_SKIN = 10 CHAT_LOGS = 11 PER_ACCOUNT_CHAT_LOGS = 12 USER_SKIN = 14 LOCAL_ASSETS = 15 EXECUTABLE = 16 DEFAULT_SKIN = 17 FONTS = 18 DUMP = 19 @se.enum_field_serializer("AbortXfer", "XferID", "Result") class XferError(enum.IntEnum): FILE_EMPTY = -44 FILE_NOT_FOUND = -43 CANNOT_OPEN_FILE = -42 EOF = -39 @dataclasses.dataclass class XferPacket(se.BitfieldDataclass): PacketID: int = se.bitfield_field(bits=31) IsEOF: bool = se.bitfield_field(bits=1, adapter=se.BoolAdapter()) @se.subfield_serializer("SendXferPacket", "XferID", "Packet") class SendXferPacketIDSerializer(se.AdapterSubfieldSerializer): ORIG_INLINE = True ADAPTER = se.BitfieldDataclass(XferPacket) @se.enum_field_serializer("ViewerEffect", "Effect", "Type") class ViewerEffectType(enum.IntEnum): TEXT = 0 ICON = enum.auto() CONNECTOR = enum.auto() FLEXIBLE_OBJECT = enum.auto() ANIMAL_CONTROLS = enum.auto() LOCAL_ANIMATION_OBJECT = enum.auto() CLOTH = enum.auto() EFFECT_BEAM = enum.auto() EFFECT_GLOW = enum.auto() EFFECT_POINT = enum.auto() EFFECT_TRAIL = enum.auto() EFFECT_SPHERE = enum.auto() EFFECT_SPIRAL = enum.auto() EFFECT_EDIT = enum.auto() EFFECT_LOOKAT = enum.auto() EFFECT_POINTAT = enum.auto() EFFECT_VOICE_VISUALIZER = enum.auto() NAME_TAG = enum.auto() EFFECT_BLOB = enum.auto() class LookAtTarget(enum.IntEnum): NONE = 0 IDLE = enum.auto() AUTO_LISTEN = enum.auto() FREELOOK = enum.auto() RESPOND = enum.auto() HOVER = enum.auto() CONVERSATION = enum.auto() SELECT = enum.auto() FOCUS = enum.auto() MOUSELOOK = enum.auto() CLEAR = enum.auto() class PointAtTarget(enum.IntEnum): NONE = 0 SELECT = enum.auto() GRAB = enum.auto() CLEAR = enum.auto() @se.subfield_serializer("ViewerEffect", "Effect", "TypeData") class ViewerEffectDataSerializer(se.EnumSwitchedSubfieldSerializer): ENUM_FIELD = "Type" # A lot of effect types that seem to have their own payload # actually use spiral's. Weird. SPIRAL_TEMPLATE = se.Template({ "SourceID": se.UUID, "TargetID": se.UUID, "TargetPos": se.Vector3D, }) TEMPLATES = { ViewerEffectType.EFFECT_POINTAT: se.Template({ "SourceID": se.UUID, "TargetID": se.UUID, "TargetPos": se.Vector3D, "PointTargetType": se.IntEnum(PointAtTarget, se.U8), }), ViewerEffectType.EFFECT_BEAM: SPIRAL_TEMPLATE, ViewerEffectType.EFFECT_EDIT: SPIRAL_TEMPLATE, ViewerEffectType.EFFECT_SPHERE: SPIRAL_TEMPLATE, ViewerEffectType.EFFECT_POINT: SPIRAL_TEMPLATE, ViewerEffectType.EFFECT_LOOKAT: se.Template({ "SourceID": se.UUID, "TargetID": se.UUID, "TargetPos": se.Vector3D, "LookTargetType": se.IntEnum(LookAtTarget, se.U8), }), ViewerEffectType.EFFECT_SPIRAL: SPIRAL_TEMPLATE, } @se.enum_field_serializer("MoneyTransferRequest", "MoneyData", "TransactionType") @se.enum_field_serializer("MoneyBalanceReply", "TransactionInfo", "TransactionType") class MoneyTransactionType(enum.IntEnum): # _many_ of these codes haven't been used in decades. # Money transaction failure codes NULL = 0 # Error conditions FAIL_SIMULATOR_TIMEOUT = 1 FAIL_DATASERVER_TIMEOUT = 2 FAIL_APPLICATION = 3 # Everything past this is actual transaction types OBJECT_CLAIM = 1000 LAND_CLAIM = 1001 GROUP_CREATE = 1002 OBJECT_PUBLIC_CLAIM = 1003 GROUP_JOIN = 1004 TELEPORT_CHARGE = 1100 UPLOAD_CHARGE = 1101 LAND_AUCTION = 1102 CLASSIFIED_CHARGE = 1103 OBJECT_TAX = 2000 LAND_TAX = 2001 LIGHT_TAX = 2002 PARCEL_DIR_FEE = 2003 GROUP_TAX = 2004 CLASSIFIED_RENEW = 2005 RECURRING_GENERIC = 2100 GIVE_INVENTORY = 3000 OBJECT_SALE = 5000 GIFT = 5001 LAND_SALE = 5002 REFER_BONUS = 5003 INVENTORY_SALE = 5004 REFUND_PURCHASE = 5005 LAND_PASS_SALE = 5006 DWELL_BONUS = 5007 PAY_OBJECT = 5008 OBJECT_PAYS = 5009 RECURRING_GENERIC_USER = 5100 GROUP_JOIN_RESERVED = 6000 GROUP_LAND_DEED = 6001 GROUP_OBJECT_DEED = 6002 GROUP_LIABILITY = 6003 GROUP_DIVIDEND = 6004 MEMBERSHIP_DUES = 6005 OBJECT_RELEASE = 8000 LAND_RELEASE = 8001 OBJECT_DELETE = 8002 OBJECT_PUBLIC_DECAY = 8003 OBJECT_PUBLIC_DELETE = 8004 LINDEN_ADJUSTMENT = 9000 LINDEN_GRANT = 9001 LINDEN_PENALTY = 9002 EVENT_FEE = 9003 EVENT_PRIZE = 9004 STIPEND_BASIC = 10000 STIPEND_DEVELOPER = 10001 STIPEND_ALWAYS = 10002 STIPEND_DAILY = 10003 STIPEND_RATING = 10004 STIPEND_DELTA = 10005 @se.flag_field_serializer("MoneyTransferRequest", "MoneyData", "Flags") class MoneyTransactionFlags(enum.IntFlag): SOURCE_GROUP = 1 DEST_GROUP = 1 << 1 OWNER_GROUP = 1 << 2 SIMULTANEOUS_CONTRIBUTION = 1 << 3 SIMULTANEOUS_CONTRIBUTION_REMOVAL = 1 << 4 @se.enum_field_serializer("ImprovedInstantMessage", "MessageBlock", "Dialog") class IMDialogType(enum.IntEnum): NOTHING_SPECIAL = 0 MESSAGEBOX = 1 GROUP_INVITATION = 3 INVENTORY_OFFERED = 4 INVENTORY_ACCEPTED = 5 INVENTORY_DECLINED = 6 GROUP_VOTE = 7 GROUP_MESSAGE_DEPRECATED = 8 TASK_INVENTORY_OFFERED = 9 TASK_INVENTORY_ACCEPTED = 10 TASK_INVENTORY_DECLINED = 11 NEW_USER_DEFAULT = 12 SESSION_INVITE = 13 SESSION_P2P_INVITE = 14 SESSION_GROUP_START = 15 SESSION_CONFERENCE_START = 16 SESSION_SEND = 17 SESSION_LEAVE = 18 FROM_TASK = 19 DO_NOT_DISTURB_AUTO_RESPONSE = 20 CONSOLE_AND_CHAT_HISTORY = 21 LURE_USER = 22 LURE_ACCEPTED = 23 LURE_DECLINED = 24 GODLIKE_LURE_USER = 25 TELEPORT_REQUEST = 26 GROUP_ELECTION_DEPRECATED = 27 GOTO_URL = 28 FROM_TASK_AS_ALERT = 31 GROUP_NOTICE = 32 GROUP_NOTICE_INVENTORY_ACCEPTED = 33 GROUP_NOTICE_INVENTORY_DECLINED = 34 GROUP_INVITATION_ACCEPT = 35 GROUP_INVITATION_DECLINE = 36 GROUP_NOTICE_REQUESTED = 37 FRIENDSHIP_OFFERED = 38 FRIENDSHIP_ACCEPTED = 39 FRIENDSHIP_DECLINED_DEPRECATED = 40 TYPING_START = 41 TYPING_STOP = 42 @se.subfield_serializer("ImprovedInstantMessage", "MessageBlock", "BinaryBucket") class InstantMessageBucketSerializer(se.EnumSwitchedSubfieldSerializer): ENUM_FIELD = "Dialog" # Aliases for clarity EMPTY_BUCKET = se.UNSERIALIZABLE PLAIN_STRING = se.UNSERIALIZABLE ACCEPTED_INVENTORY = se.Template({ "TargetCategoryID": se.UUID, }) # This is in a different, string format if it comes in through the # ReadOfflineMsgs Cap but we don't parse those. GROUP_NOTICE = se.Template({ "HasInventory": se.BOOL, "AssetType": se.IntEnum(AssetType, se.U8), "GroupID": se.UUID, "ItemName": se.CStr(), }) TEMPLATES = { IMDialogType.SESSION_GROUP_START: EMPTY_BUCKET, IMDialogType.INVENTORY_OFFERED: se.Template({ "AssetType": se.IntEnum(AssetType, se.U8), "ItemID": se.UUID, # Greedy, no length field. "Children": se.Collection( None, se.Template({ "AssetType": se.IntEnum(AssetType, se.U8), "ItemID": se.UUID, }), ), }), # Either binary AssetType or serialized string if from when offline. WTF? IMDialogType.TASK_INVENTORY_OFFERED: se.Template({ "AssetType": se.IntEnum(AssetType, se.U8), }), IMDialogType.TASK_INVENTORY_ACCEPTED: ACCEPTED_INVENTORY, IMDialogType.GROUP_NOTICE_INVENTORY_ACCEPTED: ACCEPTED_INVENTORY, IMDialogType.INVENTORY_ACCEPTED: ACCEPTED_INVENTORY, IMDialogType.INVENTORY_DECLINED: EMPTY_BUCKET, IMDialogType.GROUP_NOTICE_INVENTORY_DECLINED: EMPTY_BUCKET, IMDialogType.TASK_INVENTORY_DECLINED: EMPTY_BUCKET, IMDialogType.NOTHING_SPECIAL: EMPTY_BUCKET, IMDialogType.TYPING_START: EMPTY_BUCKET, IMDialogType.TYPING_STOP: EMPTY_BUCKET, # It's a string, just read it as-is. IMDialogType.LURE_USER: PLAIN_STRING, IMDialogType.TELEPORT_REQUEST: PLAIN_STRING, IMDialogType.GODLIKE_LURE_USER: PLAIN_STRING, IMDialogType.SESSION_LEAVE: EMPTY_BUCKET, IMDialogType.DO_NOT_DISTURB_AUTO_RESPONSE: EMPTY_BUCKET, IMDialogType.FRIENDSHIP_OFFERED: EMPTY_BUCKET, IMDialogType.FRIENDSHIP_ACCEPTED: EMPTY_BUCKET, IMDialogType.FRIENDSHIP_DECLINED_DEPRECATED: EMPTY_BUCKET, IMDialogType.GROUP_NOTICE: GROUP_NOTICE, IMDialogType.GROUP_NOTICE_REQUESTED: GROUP_NOTICE, IMDialogType.GROUP_INVITATION: se.Template({ "JoinCost": se.S32, "RoleID": se.UUID, }), # Just a string IMDialogType.FROM_TASK: PLAIN_STRING, # Session name IMDialogType.SESSION_SEND: PLAIN_STRING, # What? Is this even used? IMDialogType.GOTO_URL: PLAIN_STRING, } @se.subfield_serializer("ObjectUpdate", "ObjectData", "ObjectData") class ObjectUpdateDataSerializer(se.SimpleSubfieldSerializer): # http://wiki.secondlife.com/wiki/ObjectUpdate#ObjectData_Format REGION_SIZE = 256.0 MIN_HEIGHT = -REGION_SIZE MAX_HEIGHT = 4096.0 POSITION_COMPONENT_SCALES = ( (-0.5 * REGION_SIZE, 1.5 * REGION_SIZE), (-0.5 * REGION_SIZE, 1.5 * REGION_SIZE), (MIN_HEIGHT, MAX_HEIGHT), ) FLOAT_TEMPLATE = { "Position": se.Vector3, "Velocity": se.Vector3, "Acceleration": se.Vector3, "Rotation": se.PackedQuat(se.Vector3), "AngularVelocity": se.Vector3, } HALF_PRECISION_TEMPLATE = { "Position": se.Vector3U16(component_scales=POSITION_COMPONENT_SCALES), "Velocity": se.Vector3U16(-REGION_SIZE, REGION_SIZE), "Acceleration": se.Vector3U16(-REGION_SIZE, REGION_SIZE), "Rotation": se.PackedQuat(se.Vector4U16(-1.0, 1.0)), "AngularVelocity": se.Vector3U16(-REGION_SIZE, REGION_SIZE), } LOW_PRECISION_TEMPLATE = { "Position": se.Vector3U8(component_scales=POSITION_COMPONENT_SCALES), "Velocity": se.Vector3U8(-REGION_SIZE, REGION_SIZE), "Acceleration": se.Vector3U8(-REGION_SIZE, REGION_SIZE), "Rotation": se.PackedQuat(se.Vector4U8(-1.0, 1.0)), "AngularVelocity": se.Vector3U8(-REGION_SIZE, REGION_SIZE), } TEMPLATE = se.LengthSwitch({ 76: se.Template({"FootCollisionPlane": se.Vector4, **FLOAT_TEMPLATE}), 60: se.Template(FLOAT_TEMPLATE), 48: se.Template({"FootCollisionPlane": se.Vector4, **HALF_PRECISION_TEMPLATE}), 32: se.Template(HALF_PRECISION_TEMPLATE), 16: se.Template(LOW_PRECISION_TEMPLATE), }) @se.enum_field_serializer("ObjectUpdate", "ObjectData", "PCode") @se.enum_field_serializer("ObjectAdd", "ObjectData", "PCode") class PCode(enum.IntEnum): # Should actually be a bitmask, these are just some common ones. PRIMITIVE = 9 AVATAR = 47 GRASS = 95 NEW_TREE = 111 PARTICLE_SYSTEM = 143 TREE = 255 @se.flag_field_serializer("ObjectUpdate", "ObjectData", "UpdateFlags") @se.flag_field_serializer("ObjectUpdateCompressed", "ObjectData", "UpdateFlags") @se.flag_field_serializer("ObjectUpdateCached", "ObjectData", "UpdateFlags") @se.flag_field_serializer("ObjectAdd", "ObjectData", "AddFlags") class ObjectUpdateFlags(enum.IntFlag): USE_PHYSICS = 1 << 0 CREATE_SELECTED = 1 << 1 OBJECT_MODIFY = 1 << 2 OBJECT_COPY = 1 << 3 OBJECT_ANY_OWNER = 1 << 4 OBJECT_YOU_OWNER = 1 << 5 SCRIPTED = 1 << 6 HANDLE_TOUCH = 1 << 7 OBJECT_MOVE = 1 << 8 TAKES_MONEY = 1 << 9 PHANTOM = 1 << 10 INVENTORY_EMPTY = 1 << 11 AFFECTS_NAVMESH = 1 << 12 CHARACTER = 1 << 13 VOLUME_DETECT = 1 << 14 INCLUDE_IN_SEARCH = 1 << 15 ALLOW_INVENTORY_DROP = 1 << 16 OBJECT_TRANSFER = 1 << 17 OBJECT_GROUP_OWNED = 1 << 18 OBJECT_YOU_OFFICER_DEPRECATED = 1 << 19 CAMERA_DECOUPLED = 1 << 20 ANIM_SOURCE = 1 << 21 CAMERA_SOURCE = 1 << 22 CAST_SHADOWS_DEPRECATED = 1 << 23 UNUSED_002 = 1 << 24 UNUSED_003 = 1 << 25 UNUSED_004 = 1 << 26 UNUSED_005 = 1 << 27 OBJECT_OWNER_MODIFY = 1 << 28 TEMPORARY_ON_REZ = 1 << 29 TEMPORARY_DEPRECATED = 1 << 30 ZLIB_COMPRESSED_REPRECATED = 1 << 31 class AttachmentStateAdapter(se.Adapter): # Encoded attachment point ID for attached objects # nibbles are swapped around because old attachment nums only used to live # in the high bits, I guess. OFFSET = 4 MASK = 0xF << OFFSET def _rotate_nibbles(self, val: int): return ((val & self.MASK) >> self.OFFSET) | ((val & ~self.MASK) << self.OFFSET) def decode(self, val: Any, ctx: Optional[se.ParseContext], pod: bool = False) -> Any: return self._rotate_nibbles(val) def encode(self, val: Any, ctx: Optional[se.ParseContext]) -> Any: # f(f(x)) = x return self._rotate_nibbles(val) @se.flag_field_serializer("AgentUpdate", "AgentData", "State") class AgentState(enum.IntFlag): TYPING = 1 << 3 EDITING = 1 << 4 class ObjectStateAdapter(se.ContextAdapter): # State has a different meaning depending on PCode def __init__(self, child_spec: Optional[se.SERIALIZABLE_TYPE]): super().__init__( lambda ctx: ctx.PCode, child_spec, { PCode.AVATAR: se.IntFlag(AgentState), PCode.PRIMITIVE: AttachmentStateAdapter(None), # Other cases are probably just a number (tree species ID or something.) dataclasses.MISSING: se.IdentityAdapter(), } ) @se.subfield_serializer("ObjectUpdate", "ObjectData", "State") @se.subfield_serializer("ObjectAdd", "ObjectData", "State") class ObjectStateSerializer(se.AdapterSubfieldSerializer): ADAPTER = ObjectStateAdapter(None) ORIG_INLINE = True @se.subfield_serializer("ImprovedTerseObjectUpdate", "ObjectData", "Data") class ImprovedTerseObjectUpdateDataSerializer(se.SimpleSubfieldSerializer): TEMPLATE = se.Template({ "ID": se.U32, # No inline PCode, so can't make sense of State at the message level "State": se.U8, "FootCollisionPlane": se.OptionalPrefixed(se.Vector4), "Position": se.Vector3, "Velocity": se.Vector3U16(-128.0, 128.0), "Acceleration": se.Vector3U16(-64.0, 64.0), "Rotation": se.PackedQuat(se.Vector4U16(-1.0, 1.0)), "AngularVelocity": se.Vector3U16(-64.0, 64.0), }) class ShineLevel(enum.IntEnum): OFF = 0 LOW = 1 MEDIUM = 2 HIGH = 3 @dataclasses.dataclass class BasicMaterials: # Meaning is technically implementation-dependent, these are in LL data files Bump: int = se.bitfield_field(bits=5) FullBright: bool = se.bitfield_field(bits=1, adapter=se.BoolAdapter()) Shiny: int = se.bitfield_field(bits=2, adapter=se.IntEnum(ShineLevel)) BUMP_SHINY_FULLBRIGHT = se.BitfieldDataclass(BasicMaterials, se.U8) class TexGen(enum.IntEnum): DEFAULT = 0 PLANAR = 0x2 # These are unused / not supported SPHERICAL = 0x4 CYLINDRICAL = 0x6 @dataclasses.dataclass class MediaFlags: WebPage: bool = se.bitfield_field(bits=1, adapter=se.BoolAdapter()) TexGen: "TexGen" = se.bitfield_field(bits=2, adapter=se.IntEnum(TexGen)) # Probably unused but show it just in case _Unused: int = se.bitfield_field(bits=5) # Not shifted so enum definitions can match indra MEDIA_FLAGS = se.BitfieldDataclass(MediaFlags, se.U8, shift=False) class Color4(se.SerializableBase): def __init__(self, invert_bytes=False, invert_alpha=False): # There's several different ways of representing colors, presumably # to allow for more efficient zerocoding in common cases. self.invert_bytes = invert_bytes self.invert_alpha = invert_alpha self._bytes_templ = se.BytesFixed(4) def _invert(self, val: bytes) -> bytes: if self.invert_bytes: val = bytes(~x & 0xFF for x in val) if self.invert_alpha: val = val[:3] + bytes((~val[4] & 0xFF,)) return val def serialize(self, val, writer: se.BufferWriter, ctx=None): self._bytes_templ.serialize(self._invert(val), writer, ctx) def deserialize(self, reader: se.BufferReader, ctx=None): return self._invert(self._bytes_templ.deserialize(reader, ctx)) class TEFaceBitfield(se.SerializableBase): """ Arbitrary-length bitfield of faces 0x80 bit indicates bitfield has more bytes. Each byte can represent on / off for 7 faces. """ @classmethod def deserialize(cls, reader: se.BufferReader, ctx=None): have_next = True val = 0 while have_next: char = reader.read(se.U8, ctx=ctx) have_next = char & 0x80 val |= char & 0x7F if have_next: val <<= 7 # Bitfield of faces reconstructed, convert to tuple i = 0 face_list = [] while val: if val & 1: face_list.append(i) i += 1 val >>= 1 return tuple(face_list) @classmethod def serialize(cls, faces, writer: se.BufferWriter, ctx=None): packed = 0 for face in faces: packed |= 1 << face char_arr = [] while packed: # 7 faces per byte val = packed & 0x7F packed >>= 7 char_arr.append(val) char_arr.reverse() while char_arr: val = char_arr.pop(0) # need a continuation if char_arr: val |= 0x80 writer.write(se.U8, val, ctx=ctx) class TEExceptionField(se.SerializableBase): """ TextureEntry field with a "default" value and trailing "exception" values Each value that deviates from the default value is added as an "exception," prefixed with a bitfield of face numbers it applies to. A field is terminated with a NUL, so long as it's not the last field. In that case, EOF implicitly terminates. """ def __init__(self, spec, optional=False, first=False): self._spec = spec self._first = first self._optional = optional def serialize(self, vals, writer: se.BufferWriter, ctx=None): if self._optional and not vals: return # NUL needed to mark the start of a field if this isn't the first one if not self._first: writer.write_bytes(b"\x00") ctx = se.ParseContext(vals, parent=ctx) default = vals[None] writer.write(self._spec, default, ctx=ctx) for faces, val in vals.items(): if faces is None: continue writer.write(TEFaceBitfield, faces, ctx=ctx) writer.write(self._spec, val, ctx=ctx) def deserialize(self, reader: se.BufferReader, ctx=None): # No bytes left and this is an optional field if self._optional and not reader: return None # Technically there's nothing preventing an implementation from # repeating a face bitfield. You can use a MultiDict to preserve # any duplicate keys if you care, but it's incredibly slow. vals = {} ctx = se.ParseContext(vals, parent=ctx) vals[None] = reader.read(self._spec, ctx=ctx) while reader: faces = reader.read(TEFaceBitfield, ctx=ctx) if not faces: break # Key will be a tuple of face numbers vals[faces] = reader.read(self._spec, ctx=ctx) return vals def default_value(self): return dict def _te_dataclass_field(spec: se.SERIALIZABLE_TYPE, first=False, optional=False): return se.dataclass_field(TEExceptionField(spec, first=first, optional=optional)) _T = TypeVar("_T") TE_FIELD_TYPE = Dict[Optional[Sequence[int]], _T] @dataclasses.dataclass class TextureEntry: Textures: TE_FIELD_TYPE[UUID] = _te_dataclass_field(se.UUID, first=True) # Bytes are inverted so fully opaque white is \x00\x00\x00\x00 Color: TE_FIELD_TYPE[bytes] = _te_dataclass_field(Color4(invert_bytes=True)) ScalesS: TE_FIELD_TYPE[float] = _te_dataclass_field(se.F32) ScalesT: TE_FIELD_TYPE[float] = _te_dataclass_field(se.F32) OffsetsS: TE_FIELD_TYPE[int] = _te_dataclass_field(se.S16) OffsetsT: TE_FIELD_TYPE[int] = _te_dataclass_field(se.S16) Rotation: TE_FIELD_TYPE[int] = _te_dataclass_field(se.S16) BasicMaterials: TE_FIELD_TYPE["BasicMaterials"] = _te_dataclass_field(BUMP_SHINY_FULLBRIGHT) MediaFlags: TE_FIELD_TYPE["MediaFlags"] = _te_dataclass_field(MEDIA_FLAGS) Glow: TE_FIELD_TYPE[int] = _te_dataclass_field(se.U8) Materials: TE_FIELD_TYPE[UUID] = _te_dataclass_field(se.UUID, optional=True) TE_SERIALIZER = se.Dataclass(TextureEntry) @se.subfield_serializer("ObjectUpdate", "ObjectData", "TextureEntry") @se.subfield_serializer("AvatarAppearance", "ObjectData", "TextureEntry") @se.subfield_serializer("AgentSetAppearance", "ObjectData", "TextureEntry") @se.subfield_serializer("ObjectImage", "ObjectData", "TextureEntry") class TextureEntrySubfieldSerializer(se.SimpleSubfieldSerializer): EMPTY_IS_NONE = True TEMPLATE = TE_SERIALIZER DATA_PACKER_TE_TEMPLATE = se.TypedByteArray( se.U32, TE_SERIALIZER, empty_is_none=True, # TODO: Let Readers have lazy=False prop and let addons call # out what subfield serializers should not be lazy. Lazy is way # more expensive if you're going to deserialize every TE anyway lazy=True, ) @se.subfield_serializer("ImprovedTerseObjectUpdate", "ObjectData", "TextureEntry") class DPTextureEntrySubfieldSerializer(se.SimpleSubfieldSerializer): EMPTY_IS_NONE = True TEMPLATE = DATA_PACKER_TE_TEMPLATE class TextureAnimMode(enum.IntFlag): ON = 0x01 LOOP = 0x02 REVERSE = 0x04 PING_PONG = 0x08 SMOOTH = 0x10 ROTATE = 0x20 SCALE = 0x40 @dataclasses.dataclass class TextureAnim: Mode: TextureAnimMode = se.dataclass_field(se.IntFlag(TextureAnimMode, se.U8)) Face: int = se.dataclass_field(se.S8) SizeX: int = se.dataclass_field(se.U8) SizeY: int = se.dataclass_field(se.U8) Start: float = se.dataclass_field(se.F32) Length: float = se.dataclass_field(se.F32) Rate: float = se.dataclass_field(se.F32) TA_TEMPLATE = se.Dataclass(TextureAnim) @se.subfield_serializer("ObjectUpdate", "ObjectData", "TextureAnim") class TextureAnimSerializer(se.SimpleSubfieldSerializer): EMPTY_IS_NONE = True TEMPLATE = TA_TEMPLATE @se.subfield_serializer("ObjectProperties", "ObjectData", "TextureID") class TextureIDListSerializer(se.SimpleSubfieldSerializer): EMPTY_IS_NONE = True TEMPLATE = se.Collection(None, se.UUID) class ParticleDataFlags(enum.IntFlag): INTERP_COLOR = 0x001 INTERP_SCALE = 0x002 BOUNCE = 0x004 WIND = 0x008 FOLLOW_SRC = 0x010 FOLLOW_VELOCITY = 0x020 TARGET_POS = 0x040 TARGET_LINEAR = 0x080 EMISSIVE = 0x100 BEAM = 0x200 RIBBON = 0x400 DATA_GLOW = 0x10000 DATA_BLEND = 0x20000 class ParticleFlags(enum.IntFlag): OBJECT_RELATIVE = 0x1 USE_NEW_ANGLE = 0x2 class ParticleBlendFunc(enum.IntEnum): ONE = 0 ZERO = 1 DEST_COLOR = 2 SOURCE_COLOR = 3 ONE_MINUS_DEST_COLOR = 4 ONE_MINUS_SOURCE_COLOR = 5 DEST_ALPHA = 6 SOURCE_ALPHA = 7 ONE_MINUS_DEST_ALPHA = 8 ONE_MINUS_SOURCE_ALPHA = 9 PARTDATA_FLAGS = se.IntFlag(ParticleDataFlags, se.U32) class PartDataOption(se.OptionalFlagged): def __init__(self, flag_val, spec): super().__init__("PDataFlags", PARTDATA_FLAGS, flag_val, spec) PDATA_BLOCK_TEMPLATE = se.Template({ "PDataFlags": PARTDATA_FLAGS, "PDataMaxAge": se.FixedPoint(se.U16, 8, 8), "StartColor": Color4(), "EndColor": Color4(), "StartScaleX": se.FixedPoint(se.U8, 3, 5), "StartScaleY": se.FixedPoint(se.U8, 3, 5), "EndScaleX": se.FixedPoint(se.U8, 3, 5), "EndScaleY": se.FixedPoint(se.U8, 3, 5), "StartGlow": PartDataOption(ParticleDataFlags.DATA_GLOW, se.QuantizedFloat(se.U8, 0.0, 1.0)), "EndGlow": PartDataOption(ParticleDataFlags.DATA_GLOW, se.QuantizedFloat(se.U8, 0.0, 1.0)), "BlendSource": PartDataOption(ParticleDataFlags.DATA_BLEND, se.IntEnum(ParticleBlendFunc, se.U8)), "BlendDest": PartDataOption(ParticleDataFlags.DATA_BLEND, se.IntEnum(ParticleBlendFunc, se.U8)), }) class PartPattern(enum.IntFlag): NONE = 0 DROP = 0x1 EXPLODE = 0x2 ANGLE = 0x4 ANGLE_CONE = 0x8 ANGLE_CONE_EMPTY = 0x10 PSYS_BLOCK_TEMPLATE = se.Template({ "CRC": se.U32, "PSysFlags": se.IntFlag(ParticleFlags, se.U32), "Pattern": se.IntFlag(PartPattern, se.U8), "PSysMaxAge": se.FixedPoint(se.U16, 8, 8), "StartAge": se.FixedPoint(se.U16, 8, 8), "InnerAngle": se.FixedPoint(se.U8, 3, 5), "OuterAngle": se.FixedPoint(se.U8, 3, 5), "BurstRate": se.FixedPoint(se.U16, 8, 8), "BurstRadius": se.FixedPoint(se.U16, 8, 8), "BurstSpeedMin": se.FixedPoint(se.U16, 8, 8), "BurstSpeedMax": se.FixedPoint(se.U16, 8, 8), "BurstPartCount": se.U8, "VelX": se.FixedPoint(se.U16, 8, 7, signed=True), "VelY": se.FixedPoint(se.U16, 8, 7, signed=True), "VelZ": se.FixedPoint(se.U16, 8, 7, signed=True), "AccelX": se.FixedPoint(se.U16, 8, 7, signed=True), "AccelY": se.FixedPoint(se.U16, 8, 7, signed=True), "AccelZ": se.FixedPoint(se.U16, 8, 7, signed=True), "Texture": se.UUID, "Target": se.UUID, }) PSBLOCK_TEMPLATE = se.LengthSwitch({ 0: se.Null, 86: se.Template({"PSys": PSYS_BLOCK_TEMPLATE, "PData": PDATA_BLOCK_TEMPLATE}), # Catch-all, this is a variable-length psblock None: se.Template({ "PSys": se.TypedByteArray(se.S32, PSYS_BLOCK_TEMPLATE), "PData": se.TypedByteArray(se.S32, PDATA_BLOCK_TEMPLATE), }) }) @se.subfield_serializer("ObjectUpdate", "ObjectData", "PSBlock") class PSBlockSerializer(se.SimpleSubfieldSerializer): TEMPLATE = PSBLOCK_TEMPLATE @se.enum_field_serializer("ObjectExtraParams", "ObjectData", "ParamType") class ExtraParamType(enum.IntEnum): FLEXIBLE = 0x10 LIGHT = 0x20 SCULPT = 0x30 LIGHT_IMAGE = 0x40 RESERVED = 0x50 MESH = 0x60 EXTENDED_MESH = 0x70 class ExtendedMeshFlags(enum.IntFlag): ANIMATED_MESH = 0x1 class SculptType(enum.IntEnum): NONE = 0 SPHERE = 1 TORUS = 2 PLANE = 3 CYLINDER = 4 MESH = 5 @dataclasses.dataclass class SculptTypeData: Type: SculptType = se.bitfield_field(bits=6, adapter=se.IntEnum(SculptType)) Invert: bool = se.bitfield_field(bits=1, adapter=se.BoolAdapter()) Mirror: bool = se.bitfield_field(bits=1, adapter=se.BoolAdapter()) EXTRA_PARAM_TEMPLATES = { ExtraParamType.FLEXIBLE: se.Template({ "Tension": se.BitField(se.U8, {"Tension": 6, "Softness1": 2}), "Drag": se.BitField(se.U8, {"Drag": 7, "Softness2": 1}), "Gravity": se.U8, "Wind": se.U8, "UserForce": se.IfPresent(se.Vector3), }), ExtraParamType.LIGHT: se.Template({ "Color": Color4(), "Radius": se.F32, "Cutoff": se.F32, "Falloff": se.F32, }), ExtraParamType.SCULPT: se.Template({ "Texture": se.UUID, "TypeData": se.BitfieldDataclass(SculptTypeData, se.U8), }), ExtraParamType.LIGHT_IMAGE: se.Template({ "Texture": se.UUID, "FOV": se.F32, "Focus": se.F32, "Ambiance": se.F32, }), ExtraParamType.MESH: se.Template({ "Asset": se.UUID, "TypeData": se.BitfieldDataclass(SculptTypeData, se.U8), }), ExtraParamType.EXTENDED_MESH: se.Template({ "Flags": se.IntFlag(ExtendedMeshFlags, se.U32), }), } @se.subfield_serializer("ObjectExtraParams", "ObjectData", "ParamData") class ObjectExtraParamsDataSerializer(se.EnumSwitchedSubfieldSerializer): ENUM_FIELD = "ParamType" TEMPLATES = EXTRA_PARAM_TEMPLATES EXTRA_PARAM_COLLECTION = se.DictAdapter(se.Collection( length=se.U8, entry_ser=se.EnumSwitch(se.IntEnum(ExtraParamType, se.U16), { t: se.TypedByteArray(se.U32, v) for t, v in EXTRA_PARAM_TEMPLATES.items() }), )) @se.subfield_serializer("ObjectUpdate", "ObjectData", "ExtraParams") class ObjectUpdateExtraParamsSerializer(se.SimpleSubfieldSerializer): TEMPLATE = EXTRA_PARAM_COLLECTION EMPTY_IS_NONE = True @se.enum_field_serializer("ObjectUpdate", "ObjectData", "Flags") class SoundFlags(enum.IntEnum): LOOP = 1 << 0 SYNC_MASTER = 1 << 1 SYNC_SLAVE = 1 << 2 SYNC_PENDING = 1 << 3 QUEUE = 1 << 4 STOP = 1 << 5 class CompressedFlags(enum.IntFlag): SCRATCHPAD = 1 TREE = 1 << 1 TEXT = 1 << 2 PARTICLES = 1 << 3 SOUND = 1 << 4 PARENT_ID = 1 << 5 TEXTURE_ANIM = 1 << 6 ANGULAR_VELOCITY = 1 << 7 NAME_VALUES = 1 << 8 MEDIA_URL = 1 << 9 PARTICLES_NEW = 1 << 10 UPDATE_COMPRESSED_FLAGS = se.IntFlag(CompressedFlags, se.U32) class CompressedOption(se.OptionalFlagged): def __init__(self, flag_val, spec): super().__init__("Flags", UPDATE_COMPRESSED_FLAGS, flag_val, spec) NAMEVALUES_TERMINATED_TEMPLATE = se.TypedBytesTerminated( NameValuesSerializer, terminators=(b"\x00",), empty_is_none=True) @se.subfield_serializer("ObjectUpdateCompressed", "ObjectData", "Data") class ObjectUpdateCompressedDataSerializer(se.SimpleSubfieldSerializer): TEMPLATE = se.Template({ "FullID": se.UUID, "ID": se.U32, "PCode": se.IntEnum(PCode, se.U8), # Meaning of State is PCode dependent. Could be a bitfield of flags + shifted attachment # point if an object with parents set to an avatar. "State": ObjectStateAdapter(se.U8), "CRC": se.U32, "Material": se.U8, "ClickAction": se.U8, "Scale": se.Vector3, "Position": se.Vector3, "Rotation": se.PackedQuat(se.Vector3), "Flags": UPDATE_COMPRESSED_FLAGS, # Only non-null if there's an attached sound "OwnerID": se.UUID, "AngularVelocity": CompressedOption(CompressedFlags.ANGULAR_VELOCITY, se.Vector3), # Note: missing section specifically means ParentID = 0 "ParentID": CompressedOption(CompressedFlags.PARENT_ID, se.U32), # This field is strange. State == TreeSpecies in other ObjectUpdate types "TreeSpecies": CompressedOption(CompressedFlags.TREE, se.U8), # Technically only allowed if TREE not set, but I'm not convinced this is ever # used, or that any of the official unpackers would work correctly even if it was. "ScratchPad": CompressedOption(CompressedFlags.SCRATCHPAD, se.ByteArray(se.U32)), "Text": CompressedOption(CompressedFlags.TEXT, se.CStr()), "TextColor": CompressedOption(CompressedFlags.TEXT, Color4()), "MediaURL": CompressedOption(CompressedFlags.MEDIA_URL, se.CStr()), "PSBlock": CompressedOption(CompressedFlags.PARTICLES, se.TypedBytesFixed(86, PSBLOCK_TEMPLATE)), "ExtraParams": EXTRA_PARAM_COLLECTION, "Sound": CompressedOption(CompressedFlags.SOUND, se.UUID), "SoundGain": CompressedOption(CompressedFlags.SOUND, se.F32), "SoundFlags": CompressedOption(CompressedFlags.SOUND, se.U8), "SoundRadius": CompressedOption(CompressedFlags.SOUND, se.F32), "NameValue": CompressedOption(CompressedFlags.NAME_VALUES, NAMEVALUES_TERMINATED_TEMPLATE), # Intentionally not de-quantizing to preserve their real ranges. "PathCurve": se.U8, "ProfileCurve": se.U8, "PathBegin": se.U16, # 0 to 1, quanta = 0.01 "PathEnd": se.U16, # 0 to 1, quanta = 0.01 "PathScaleX": se.U8, # 0 to 1, quanta = 0.01 "PathScaleY": se.U8, # 0 to 1, quanta = 0.01 "PathShearX": se.U8, # -.5 to .5, quanta = 0.01 "PathShearY": se.U8, # -.5 to .5, quanta = 0.01 "PathTwist": se.S8, # -1 to 1, quanta = 0.01 "PathTwistBegin": se.S8, # -1 to 1, quanta = 0.01 "PathRadiusOffset": se.S8, # -1 to 1, quanta = 0.01 "PathTaperX": se.S8, # -1 to 1, quanta = 0.01 "PathTaperY": se.S8, # -1 to 1, quanta = 0.01 "PathRevolutions": se.U8, # 0 to 3, quanta = 0.015 "PathSkew": se.S8, # -1 to 1, quanta = 0.01 "ProfileBegin": se.U16, # 0 to 1, quanta = 0.01 "ProfileEnd": se.U16, # 0 to 1, quanta = 0.01 "ProfileHollow": se.U16, # 0 to 1, quanta = 0.01 "TextureEntry": DATA_PACKER_TE_TEMPLATE, "TextureAnim": CompressedOption(CompressedFlags.TEXTURE_ANIM, se.TypedByteArray(se.U32, TA_TEMPLATE)), "PSBlockNew": CompressedOption(CompressedFlags.PARTICLES_NEW, PSBLOCK_TEMPLATE), }) @se.flag_field_serializer("MultipleObjectUpdate", "ObjectData", "Type") class MultipleObjectUpdateFlags(enum.IntFlag): POSITION = 0x01 ROTATION = 0x02 SCALE = 0x04 LINKED_SETS = 0x08 UNIFORM = 0x10 @se.subfield_serializer("MultipleObjectUpdate", "ObjectData", "Data") class MultipleObjectUpdateDataSerializer(se.FlagSwitchedSubfieldSerializer): FLAG_FIELD = "Type" TEMPLATES = { MultipleObjectUpdateFlags.POSITION: se.Vector3, MultipleObjectUpdateFlags.ROTATION: se.PackedQuat(se.Vector3), MultipleObjectUpdateFlags.SCALE: se.Vector3, } @se.flag_field_serializer("AgentUpdate", "AgentData", "ControlFlags") @se.flag_field_serializer("ScriptControlChange", "Data", "Controls") class AgentControlFlags(enum.IntFlag): AT_POS = 1 AT_NEG = 1 << 1 LEFT_POS = 1 << 2 LEFT_NEG = 1 << 3 UP_POS = 1 << 4 UP_NEG = 1 << 5 PITCH_POS = 1 << 6 PITCH_NEG = 1 << 7 YAW_POS = 1 << 8 YAW_NEG = 1 << 9 FAST_AT = 1 << 10 FAST_LEFT = 1 << 11 FAST_UP = 1 << 12 FLY = 1 << 13 STOP = 1 << 14 FINISH_ANIM = 1 << 15 STAND_UP = 1 << 16 SIT_ON_GROUND = 1 << 17 MOUSELOOK = 1 << 18 NUDGE_AT_POS = 1 << 19 NUDGE_AT_NEG = 1 << 20 NUDGE_LEFT_POS = 1 << 21 NUDGE_LEFT_NEG = 1 << 22 NUDGE_UP_POS = 1 << 23 NUDGE_UP_NEG = 1 << 24 TURN_LEFT = 1 << 25 TURN_RIGHT = 1 << 26 AWAY = 1 << 27 LBUTTON_DOWN = 1 << 28 LBUTTON_UP = 1 << 29 ML_LBUTTON_DOWN = 1 << 30 ML_LBUTTON_UP = 1 << 31 @se.flag_field_serializer("AgentUpdate", "AgentData", "Flags") class AgentUpdateFlags(enum.IntFlag): HIDE_TITLE = 1 CLIENT_AUTOPILOT = 1 << 1 @se.enum_field_serializer("ChatFromViewer", "ChatData", "Type") @se.enum_field_serializer("ChatFromSimulator", "ChatData", "ChatType") class ChatType(enum.IntEnum): WHISPER = 0 NORMAL = 1 SHOUT = 2 OOC = 3 TYPING_START = 4 TYPING_STOP = 5 DEBUG_MSG = 6 REGION = 7 OWNER = 8 DIRECT = 9 IM = 10 IM_GROUP = 11 RADAR = 12 @se.enum_field_serializer("ChatFromSimulator", "ChatData", "SourceType") class ChatSourceType(enum.IntEnum): SYSTEM = 0 AGENT = 1 OBJECT = 2 UNKNOWN = 3 @se.subfield_serializer("AgentThrottle", "Throttle", "Throttles") class AgentThrottlesSerializer(se.SimpleSubfieldSerializer): TEMPLATE = se.Collection(None, se.F32) @se.subfield_serializer("ObjectUpdate", "ObjectData", "NameValue") class NameValueSerializer(se.SimpleSubfieldSerializer): TEMPLATE = NAMEVALUES_TERMINATED_TEMPLATE @se.enum_field_serializer("SetFollowCamProperties", "CameraProperty", "Type") class CameraPropertyType(enum.IntEnum): PITCH = 0 FOCUS_OFFSET = enum.auto() FOCUS_OFFSET_X = enum.auto() FOCUS_OFFSET_Y = enum.auto() FOCUS_OFFSET_Z = enum.auto() POSITION_LAG = enum.auto() FOCUS_LAG = enum.auto() DISTANCE = enum.auto() BEHINDNESS_ANGLE = enum.auto() BEHINDNESS_LAG = enum.auto() POSITION_THRESHOLD = enum.auto() FOCUS_THRESHOLD = enum.auto() ACTIVE = enum.auto() POSITION = enum.auto() POSITION_X = enum.auto() POSITION_Y = enum.auto() POSITION_Z = enum.auto() FOCUS = enum.auto() FOCUS_X = enum.auto() FOCUS_Y = enum.auto() FOCUS_Z = enum.auto() POSITION_LOCKED = enum.auto() FOCUS_LOCKED = enum.auto() @se.enum_field_serializer("DeRezObject", "AgentBlock", "Destination") class DeRezObjectDestination(enum.IntEnum): SAVE_INTO_AGENT_INVENTORY = 0 # deprecated, disabled ACQUIRE_TO_AGENT_INVENTORY = 1 # try to leave copy in world SAVE_INTO_TASK_INVENTORY = 2 ATTACHMENT = 3 # deprecated TAKE_INTO_AGENT_INVENTORY = 4 # delete from world FORCE_TO_GOD_INVENTORY = 5 # force take copy TRASH = 6 ATTACHMENT_TO_INV = 7 # deprecated ATTACHMENT_EXISTS = 8 # deprecated RETURN_TO_OWNER = 9 # back to owner's inventory RETURN_TO_LAST_OWNER = 10 # deeded object back to last owner's inventory, deprecated @se.flag_field_serializer("RegionHandshake", "RegionInfo", "RegionFlags") @se.flag_field_serializer("RegionHandshake", "RegionInfo4", "RegionFlagsExtended") @se.flag_field_serializer("SimStats", "Region", "RegionFlags") @se.flag_field_serializer("SimStats", "RegionInfo", "RegionFlagsExtended") @se.flag_field_serializer("RegionInfo", "RegionInfo", "RegionFlags") @se.flag_field_serializer("RegionInfo", "RegionInfo3", "RegionFlagsExtended") class RegionFlags(enum.IntFlag): ALLOW_DAMAGE = 1 << 0 ALLOW_LANDMARK = 1 << 1 ALLOW_SET_HOME = 1 << 2 # Do we reset the home position when someone teleports away from here? RESET_HOME_ON_TELEPORT = 1 << 3 SUN_FIXED = 1 << 4 # Does the sun move? # Does the estate owner allow private parcels? ALLOW_ACCESS_OVERRIDE = 1 << 5 BLOCK_TERRAFORM = 1 << 6 BLOCK_LAND_RESELL = 1 << 7 SANDBOX = 1 << 8 # All content wiped once per night ALLOW_ENVIRONMENT_OVERRIDE = 1 << 9 SKIP_COLLISIONS = 1 << 12 # Pin all non agent rigid bodies SKIP_SCRIPTS = 1 << 13 SKIP_PHYSICS = 1 << 14 EXTERNALLY_VISIBLE = 1 << 15 ALLOW_RETURN_ENCROACHING_OBJECT = 1 << 16 ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT = 1 << 17 BLOCK_DWELL = 1 << 18 BLOCK_FLY = 1 << 19 ALLOW_DIRECT_TELEPORT = 1 << 20 # Is there an administrative override on scripts in the region at the # moment. This is the similar skip scripts, except this flag is # presisted in the database on an estate level. ESTATE_SKIP_SCRIPTS = 1 << 21 RESTRICT_PUSHOBJECT = 1 << 22 DENY_ANONYMOUS = 1 << 23 ALLOW_PARCEL_CHANGES = 1 << 26 BLOCK_FLYOVER = 1 << 27 ALLOW_VOICE = 1 << 28 BLOCK_PARCEL_SEARCH = 1 << 29 DENY_AGEUNVERIFIED = 1 << 30 @se.flag_field_serializer("RegionHandshakeReply", "RegionInfo", "Flags") class RegionHandshakeReplyFlags(enum.IntFlag): VOCACHE_CULLING_ENABLED = 0x1 # ask sim to send all cacheable objects. VOCACHE_IS_EMPTY = 0x2 # the cache file is empty, no need to send cache probes. SUPPORTS_SELF_APPEARANCE = 0x4 # inbound AvatarAppearance for self is ok @se.http_serializer("RenderMaterials") class RenderMaterialsSerializer(se.BaseHTTPSerializer): @classmethod def deserialize_resp_body(cls, method: str, body: bytes): deser = llsd.unzip_llsd(llsd.parse_xml(body)["Zipped"]) return deser @classmethod def deserialize_req_body(cls, method: str, body: bytes): if not body: return se.UNSERIALIZABLE deser = llsd.unzip_llsd(llsd.parse_xml(body)["Zipped"]) return deser @classmethod def serialize_req_body(cls, method: str, body): if body == b"": return body return llsd.format_xml({"Zipped": llsd.zip_llsd(body)}) @se.http_serializer("RetrieveNavMeshSrc") class RetrieveNavMeshSrcSerializer(se.BaseHTTPSerializer): @classmethod def deserialize_resp_body(cls, method: str, body: bytes): deser = llsd.parse_xml(body) # 15 bit window size, gzip wrapped deser["navmesh_data"] = zlib.decompress(deser["navmesh_data"], wbits=15 | 32) return deser @dataclasses.dataclass class CAPTemplate: cap_name: str method: str body: Any query: Set[str] = dataclasses.field(default_factory=set) path: str = "" # Cap request templates for message builder prefills CAP_TEMPLATES: List[CAPTemplate] = [ CAPTemplate(cap_name='Seed', method='POST', body=b'\n\n\n \n \n\n', query=set()), CAPTemplate(cap_name='SimulatorFeatures', method='GET', body=b'', query=set()), CAPTemplate(cap_name='EventQueueGet', method='POST', body=b'\n\n\n ack\n \n done\n \n \n\n', query=set()), CAPTemplate(cap_name='NavMeshGenerationStatus', method='GET', body=b'', query=set()), CAPTemplate(cap_name='AgentState', method='GET', body=b'', query=set()), CAPTemplate(cap_name='AgentPreferences', method='POST', body=b'\n\n\n default_object_perm_masks\n \n Everyone\n \n Group\n \n NextOwner\n \n \n \n\n', query=set()), CAPTemplate(cap_name='ExtEnvironment', method='GET', body=b'', query={'parcelid'}), CAPTemplate(cap_name='UpdateAgentLanguage', method='POST', body=b'\n\n\n language\n en\n language_is_public\n 1\n \n\n', query=set()), CAPTemplate(cap_name='DirectDelivery', method='GET', body=b'', query=set(), path="/listings"), CAPTemplate(cap_name='ViewerStats', method='POST', body=b'\n\n\n AgentPositionSnaps\n \n DisplayNamesEnabled\n \n DisplayNamesShowUsername\n \n MinimalSkin\n \n agent\n \n agents_in_view\n \n fps\n \n language\n \n mem_use\n \n meters_traveled\n \n ping\n \n regions_visited\n \n run_time\n \n sim_fps\n \n start_time\n \n version\n \n \n downloads\n \n mesh_kbytes\n \n object_kbytes\n \n texture_kbytes\n \n world_kbytes\n \n \n misc\n \n Version\n \n Vertex Buffers Enabled\n \n \n session_id\n \n stats\n \n failures\n \n dropped\n \n failed_resends\n \n invalid\n \n missing_updater\n \n off_circuit\n \n resent\n \n send_packet\n \n \n misc\n \n int_1\n \n int_2\n \n string_1\n \n string_2\n \n \n net\n \n in\n \n compressed_packets\n \n kbytes\n \n packets\n \n savings\n \n \n out\n \n compressed_packets\n \n kbytes\n \n packets\n \n savings\n \n \n \n voice\n \n connect_attempts\n \n connect_cycles\n \n connect_time\n \n establish_attempts\n \n establish_time\n \n provision_attempts\n \n provision_time\n \n \n \n system\n \n address_size\n \n cpu\n \n gl\n \n ati_offset_vertical_lines\n \n ati_old_driver\n \n debug_gpu\n \n gl_renderer\n \n gpu_vendor\n \n gpu_version\n \n has_anisotropic\n \n has_arb_env_combine\n \n has_ati_mem_info\n \n has_blend_func_separate\n \n has_compressed_textures\n \n has_cube_map\n \n has_debug_output\n \n has_depth_clamp\n \n has_draw_buffers\n \n has_flush_buffer_range\n \n has_fragment_shader\n \n has_framebuffer_object\n \n has_map_buffer_range\n \n has_mip_map_generation\n \n has_multitexture\n \n has_nvx_mem_info\n \n has_occlusion_query\n \n has_occlusion_query2\n \n has_pbuffer\n \n has_point_parameters\n \n has_requirements\n \n has_separate_specular_color\n \n has_shader_objects\n \n has_srgb_framebuffer\n \n has_srgb_texture\n \n has_sync\n \n has_texture_multisample\n \n has_texture_rectangle\n \n has_texture_srgb_decode\n \n has_timer_query\n \n has_transform_feedback\n \n has_vertex_array_object\n \n has_vertex_buffer_object\n \n has_vertex_shader\n \n is_ati\n \n is_gf2or4mx\n \n is_gf3\n \n is_gf_gfx\n \n is_intel\n \n is_nvidia\n \n max_color_texture_samples\n \n max_depth_texture_samples\n \n max_index_range\n \n max_integer_samples\n \n max_sample_mask_words\n \n max_samples\n \n max_texture_size\n \n max_vertex_range\n \n num_texture_image_units\n \n num_texture_units\n \n opengl_version\n \n vram\n \n \n gpu\n \n gpu_class\n \n gpu_vendor\n \n gpu_version\n \n mac_address\n \n opengl_version\n \n os\n \n ram\n \n serial_number\n \n shader_level\n \n \n \n\n', query=set()), CAPTemplate(cap_name='GetDisplayNames', method='GET', body=b'', query={'ids'}), CAPTemplate(cap_name='FetchInventory2', method='POST', body=b'\n\n\n agent_id\n \n cap_name\n \n items\n \n \n item_id\n \n owner_id\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='FetchLib2', method='POST', body=b'\n\n\n agent_id\n \n cap_name\n \n items\n \n \n item_id\n \n owner_id\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='FetchInventoryDescendents2', method='POST', body=b'\n\n\n folders\n \n \n fetch_folders\n \n fetch_items\n \n folder_id\n \n owner_id\n \n sort_order\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='ProvisionVoiceAccountRequest', method='POST', body=b'\n\n\n\n', query=set()), CAPTemplate(cap_name='AvatarRenderInfo', method='GET', body=b'', query=set()), CAPTemplate(cap_name='ReadOfflineMsgs', method='GET', body=b'', query=set()), CAPTemplate(cap_name='MapImageService', method='GET', body=b'', query=set()), CAPTemplate(cap_name='RenderMaterials', method='GET', body=b'', query=set()), CAPTemplate(cap_name='AvatarRenderInfo', method='POST', body=b'\n\n\n agents\n \n \n \n tooComplex\n \n weight\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='ParcelVoiceInfoRequest', method='POST', body=b'\n\n\n\n', query=set()), CAPTemplate(cap_name='ProductInfoRequest', method='GET', body=b'', query=set()), CAPTemplate(cap_name='ObjectMedia', method='POST', body=b'\n\n\n object_id\n \n verb\n GET\n \n\n', query=set()), CAPTemplate(cap_name='GetObjectCost', method='POST', body=b'\n\n\n object_ids\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='GetObjectPhysicsData', method='POST', body=b'\n\n\n object_ids\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='RenderMaterials', method='PUT', body=b'\n\n\n Zipped\n \n \n\n', query=set()), CAPTemplate(cap_name='RenderMaterials', method='POST', body=b'\n\n\n Zipped\n \n \n\n', query=set()), CAPTemplate(cap_name='GetExperienceInfo', method='GET', body=b'', query={'public_id', 'page_size'}, path="/id/"), CAPTemplate(cap_name='LSLSyntax', method='GET', body=b'', query=set()), CAPTemplate(cap_name='GetCreatorExperiences', method='GET', body=b'', query=set()), CAPTemplate(cap_name='GetMetadata', method='POST', body=b'\n\n\n fields\n \n experience\n \n item-id\n \n object-id\n \n \n\n', query=set()), CAPTemplate(cap_name='UpdateScriptTask', method='POST', body=b'\n\n\n experience\n \n is_script_running\n 1\n item_id\n \n target\n mono\n task_id\n \n \n\n', query=set()), CAPTemplate(cap_name='EstateAccess', method='GET', body=b'', query=set()), CAPTemplate(cap_name='RegionExperiences', method='GET', body=b'', query=set()), CAPTemplate(cap_name='SimConsoleAsync', method='POST', body=b'\n\n\n\n', query=set()), CAPTemplate(cap_name='FindExperienceByName', method='GET', body=b'', query={'query', 'page', 'page_size'}), CAPTemplate(cap_name='ViewerMetrics', method='POST', body=b'\n\n\n agent_id\n \n break\n \n duration\n \n initial\n \n message\n \n nearby\n \n cloud\n \n downloading\n \n full\n \n gray\n \n \n rez_status\n \n sequence\n \n session_id\n \n timers\n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n central_bake_version\n \n completed\n \n grid_x\n \n grid_y\n \n is_self\n \n is_using_server_bakes\n \n stats\n \n count\n \n max\n \n mean\n \n min\n \n std_dev\n \n \n timer_name\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='InventoryAPIv3', method='PATCH', body=b'\n\n\n name\n \n \n\n', query={'tid'}, path="/item/SOME_ID"), CAPTemplate(cap_name='RemoteParcelRequest', method='POST', body=b'\n\n\n location\n \n \n \n \n \n region_handle\n \n region_id\n \n \n\n', query=set()), CAPTemplate(cap_name='ParcelPropertiesUpdate', method='POST', body=b'\n\n \n local_id\n \n any_av_sounds\n true\n auth_buyer_id\n \n auto_scale\n 0\n category\n 0\n description\n \n flags\n AAAAAQ==\n group_av_sounds\n true\n group_id\n \n landing_type\n 2\n media_allow_navigate\n 1\n media_current_url\n \n media_desc\n \n media_height\n 0\n media_id\n \n media_loop\n 0\n media_prevent_camera_zoom\n 0\n media_type\n none/none\n media_url\n \n media_url_timeout\n 0.0\n media_width\n 0\n music_url\n \n name\n \n obscure_media\n false\n obscure_music\n false\n parcel_flags\n fiQASw==\n pass_hours\n 1.0\n pass_price\n 10\n sale_price\n 10000\n see_avs\n true\n snapshot_id\n \n user_location\n \n 0.0\n 0.0\n 0.0\n \n user_look_at\n \n 0.0\n 0.0\n 0.0\n \n \n\n', query=set()), CAPTemplate(cap_name='UpdateScriptAgent', method='POST', body=b'\n\n\n item_id\n \n target\n mono\n \n\n', query=set()), CAPTemplate(cap_name='NewFileAgentInventory', method='POST', body=b'\n\n\n asset_type\n \n description\n \n everyone_mask\n \n folder_id\n \n group_mask\n \n inventory_type\n \n name\n \n next_owner_mask\n \n \n\n', query=set()), CAPTemplate(cap_name='UpdateNotecardAgentInventory', method='POST', body=b'\n\n\n item_id\n \n \n\n', query=set()), CAPTemplate(cap_name='UpdateNotecardTaskInventory', method='POST', body=b'\n\n\n item_id\n \n task_id\n \n \n\n', query=set()), CAPTemplate(cap_name='UpdateGestureAgentInventory', method='POST', body=b'\n\n\n item_id\n \n \n\n', query=set()), CAPTemplate(cap_name='UpdateGestureTaskInventory', method='POST', body=b'\n\n\n item_id\n \n task_id\n \n \n\n', query=set()), CAPTemplate(cap_name='MeshUploadFlag', method='GET', body=b'', query=set()), CAPTemplate(cap_name='UntrustedSimulatorMessage', method='POST', body=b'\n\n\n message\n ChatFromViewer\n body\n \n AgentData\n \n \n AgentID\n \n SessionID\n \n \n \n ChatData\n \n \n Channel\n 0\n Message\n test \n Type\n 1\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='UpdateAvatarAppearance', method='POST', body=b'\n\n\n cof_version\n \n \n\n', query=set()), CAPTemplate(cap_name='ServerReleaseNotes', method='GET', body=b'', query=set()), CAPTemplate(cap_name='UserInfo', method='GET', body=b'', query=set()), CAPTemplate(cap_name='SendPostcard', method='POST', body=b'\n\n\n msg\n \n name\n \n pos-global\n \n \n \n \n \n subject\n \n to\n \n \n\n', query=set()), CAPTemplate(cap_name='GetAdminExperiences', method='GET', body=b'', query=set()), CAPTemplate(cap_name='GetExperiences', method='GET', body=b'', query=set()), CAPTemplate(cap_name='AgentExperiences', method='GET', body=b'', query=set()), CAPTemplate(cap_name='ExperiencePreferences', method='GET', body=b'', query={'SOME_ID'}), CAPTemplate(cap_name='IsExperienceAdmin', method='GET', body=b'', query={'experience_id'}), CAPTemplate(cap_name='UpdateExperience', method='POST', body=b'\n\n\n description\n \n extended_metadata\n \n group_id\n \n slurl\n \n \n\n', query=set()), CAPTemplate(cap_name='AttachmentResources', method='GET', body=b'', query=set()), CAPTemplate(cap_name='DispatchRegionInfo', method='POST', body=b'\n\n\n agent_limit\n \n allow_damage\n \n allow_land_resell\n \n allow_parcel_changes\n \n block_fly\n \n block_fly_over\n \n block_parcel_search\n \n block_terraform\n \n prim_bonus\n \n restrict_pushobject\n \n sim_access\n \n \n\n', query=set()), CAPTemplate(cap_name='AvatarPickerSearch', method='GET', body=b'', query={'names', 'page_size'}), CAPTemplate(cap_name='LandResources', method='POST', body=b'\n\n\n parcel_id\n \n \n\n', query=set()), CAPTemplate(cap_name='ResourceCostSelected', method='POST', body=b'\n\n\n selected_roots\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='GroupMemberData', method='POST', body=b'\n\n\n group_id\n \n \n\n', query=set()), CAPTemplate(cap_name='GroupExperiences', method='GET', body=b'', query={'SOME_ID'}), CAPTemplate(cap_name='CopyInventoryFromNotecard', method='POST', body=b'\n\n\n callback-id\n \n folder-id\n \n item-id\n \n notecard-id\n \n object-id\n \n \n\n', query=set()), CAPTemplate(cap_name='HomeLocation', method='POST', body=b'\n\n\n HomeLocation\n \n LocationId\n 1\n LocationLookAt\n \n X\n \n Y\n \n Z\n \n \n LocationPos\n \n X\n \n Y\n \n Z\n \n \n \n \n\n', query=set()), CAPTemplate(cap_name='CharacterProperties', method='GET', body=b'', query=set()), CAPTemplate(cap_name='ObjectNavMeshProperties', method='GET', body=b'', query=set()), CAPTemplate(cap_name='TerrainNavMeshProperties', method='GET', body=b'', query=set()), CAPTemplate(cap_name='ObjectNavMeshProperties', method='PUT', body=b'\n\n\n \n \n A\n \n B\n \n C\n \n D\n \n navmesh_category\n \n phantom\n \n \n \n\n', query=set()), CAPTemplate(cap_name='RetrieveNavMeshSrc', method='POST', body=b'\n\n\n\n', query=set()), CAPTemplate(cap_name='MapLayer', method='POST', body=b'\n\n\n\n', query=set()), CAPTemplate(cap_name='GroupAPIv1', method='GET', body=b'', query={'group_id'}), CAPTemplate(cap_name='ChatSessionRequest', method='POST', body=b'\n\n\n method\n \n session-id\n \n \n\n', query=set()), CAPTemplate(cap_name='ViewerBenefits', method='GET', body=b'', query=set(), path=''), CAPTemplate(cap_name='SetDisplayName', method='POST', body=b'\n\n\n display_name\n \n OLD_DISPLAY_NAME\n NEW_DISPLAY_NAME\n \n \n\n', query=set(), path=''), CAPTemplate(cap_name='ObjectMediaNavigate', method='POST', body=b'\n\n\n current_url\n \n object_id\n \n texture_index\n \n \n\n', query=set(), path=''), ]