1693 lines
88 KiB
Python
1693 lines
88 KiB
Python
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'<?xml version="1.0" ?>\n<llsd>\n<array>\n <string></string>\n </array>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='SimulatorFeatures', method='GET', body=b'', query=set()),
|
|
CAPTemplate(cap_name='EventQueueGet', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>ack</key>\n <undef />\n <key>done</key>\n <boolean></boolean>\n </map>\n</llsd>\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'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>default_object_perm_masks</key>\n <map>\n <key>Everyone</key>\n <integer></integer>\n <key>Group</key>\n <integer></integer>\n <key>NextOwner</key>\n <integer></integer>\n </map>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='ExtEnvironment', method='GET', body=b'', query={'parcelid'}),
|
|
CAPTemplate(cap_name='UpdateAgentLanguage', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>language</key>\n <string>en</string>\n <key>language_is_public</key>\n <integer>1</integer>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='DirectDelivery', method='GET', body=b'', query=set(), path="/listings"),
|
|
CAPTemplate(cap_name='ViewerStats', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>AgentPositionSnaps</key>\n <real></real>\n <key>DisplayNamesEnabled</key>\n <integer></integer>\n <key>DisplayNamesShowUsername</key>\n <integer></integer>\n <key>MinimalSkin</key>\n <boolean></boolean>\n <key>agent</key>\n <map>\n <key>agents_in_view</key>\n <integer></integer>\n <key>fps</key>\n <real></real>\n <key>language</key>\n <string></string>\n <key>mem_use</key>\n <real></real>\n <key>meters_traveled</key>\n <real></real>\n <key>ping</key>\n <real></real>\n <key>regions_visited</key>\n <integer></integer>\n <key>run_time</key>\n <real></real>\n <key>sim_fps</key>\n <real></real>\n <key>start_time</key>\n <integer></integer>\n <key>version</key>\n <string></string>\n </map>\n <key>downloads</key>\n <map>\n <key>mesh_kbytes</key>\n <real></real>\n <key>object_kbytes</key>\n <real></real>\n <key>texture_kbytes</key>\n <real></real>\n <key>world_kbytes</key>\n <real></real>\n </map>\n <key>misc</key>\n <map>\n <key>Version</key>\n <integer></integer>\n <key>Vertex Buffers Enabled</key>\n <real></real>\n </map>\n <key>session_id</key>\n <uuid></uuid>\n <key>stats</key>\n <map>\n <key>failures</key>\n <map>\n <key>dropped</key>\n <integer></integer>\n <key>failed_resends</key>\n <integer></integer>\n <key>invalid</key>\n <integer></integer>\n <key>missing_updater</key>\n <integer></integer>\n <key>off_circuit</key>\n <integer></integer>\n <key>resent</key>\n <integer></integer>\n <key>send_packet</key>\n <integer></integer>\n </map>\n <key>misc</key>\n <map>\n <key>int_1</key>\n <integer></integer>\n <key>int_2</key>\n <integer></integer>\n <key>string_1</key>\n <string></string>\n <key>string_2</key>\n <string></string>\n </map>\n <key>net</key>\n <map>\n <key>in</key>\n <map>\n <key>compressed_packets</key>\n <integer></integer>\n <key>kbytes</key>\n <real></real>\n <key>packets</key>\n <integer></integer>\n <key>savings</key>\n <real></real>\n </map>\n <key>out</key>\n <map>\n <key>compressed_packets</key>\n <integer></integer>\n <key>kbytes</key>\n <real></real>\n <key>packets</key>\n <integer></integer>\n <key>savings</key>\n <real></real>\n </map>\n </map>\n <key>voice</key>\n <map>\n <key>connect_attempts</key>\n <integer></integer>\n <key>connect_cycles</key>\n <integer></integer>\n <key>connect_time</key>\n <real></real>\n <key>establish_attempts</key>\n <integer></integer>\n <key>establish_time</key>\n <real></real>\n <key>provision_attempts</key>\n <integer></integer>\n <key>provision_time</key>\n <real></real>\n </map>\n </map>\n <key>system</key>\n <map>\n <key>address_size</key>\n <integer></integer>\n <key>cpu</key>\n <string></string>\n <key>gl</key>\n <map>\n <key>ati_offset_vertical_lines</key>\n <integer></integer>\n <key>ati_old_driver</key>\n <integer></integer>\n <key>debug_gpu</key>\n <integer></integer>\n <key>gl_renderer</key>\n <string></string>\n <key>gpu_vendor</key>\n <string></string>\n <key>gpu_version</key>\n <string></string>\n <key>has_anisotropic</key>\n <integer></integer>\n <key>has_arb_env_combine</key>\n <integer></integer>\n <key>has_ati_mem_info</key>\n <integer></integer>\n <key>has_blend_func_separate</key>\n <integer></integer>\n <key>has_compressed_textures</key>\n <integer></integer>\n <key>has_cube_map</key>\n <integer></integer>\n <key>has_debug_output</key>\n <integer></integer>\n <key>has_depth_clamp</key>\n <integer></integer>\n <key>has_draw_buffers</key>\n <integer></integer>\n <key>has_flush_buffer_range</key>\n <integer></integer>\n <key>has_fragment_shader</key>\n <integer></integer>\n <key>has_framebuffer_object</key>\n <integer></integer>\n <key>has_map_buffer_range</key>\n <integer></integer>\n <key>has_mip_map_generation</key>\n <integer></integer>\n <key>has_multitexture</key>\n <integer></integer>\n <key>has_nvx_mem_info</key>\n <integer></integer>\n <key>has_occlusion_query</key>\n <integer></integer>\n <key>has_occlusion_query2</key>\n <integer></integer>\n <key>has_pbuffer</key>\n <integer></integer>\n <key>has_point_parameters</key>\n <integer></integer>\n <key>has_requirements</key>\n <integer></integer>\n <key>has_separate_specular_color</key>\n <integer></integer>\n <key>has_shader_objects</key>\n <integer></integer>\n <key>has_srgb_framebuffer</key>\n <integer></integer>\n <key>has_srgb_texture</key>\n <integer></integer>\n <key>has_sync</key>\n <integer></integer>\n <key>has_texture_multisample</key>\n <integer></integer>\n <key>has_texture_rectangle</key>\n <integer></integer>\n <key>has_texture_srgb_decode</key>\n <integer></integer>\n <key>has_timer_query</key>\n <integer></integer>\n <key>has_transform_feedback</key>\n <integer></integer>\n <key>has_vertex_array_object</key>\n <integer></integer>\n <key>has_vertex_buffer_object</key>\n <integer></integer>\n <key>has_vertex_shader</key>\n <integer></integer>\n <key>is_ati</key>\n <integer></integer>\n <key>is_gf2or4mx</key>\n <integer></integer>\n <key>is_gf3</key>\n <integer></integer>\n <key>is_gf_gfx</key>\n <integer></integer>\n <key>is_intel</key>\n <integer></integer>\n <key>is_nvidia</key>\n <integer></integer>\n <key>max_color_texture_samples</key>\n <integer></integer>\n <key>max_depth_texture_samples</key>\n <integer></integer>\n <key>max_index_range</key>\n <integer></integer>\n <key>max_integer_samples</key>\n <integer></integer>\n <key>max_sample_mask_words</key>\n <integer></integer>\n <key>max_samples</key>\n <integer></integer>\n <key>max_texture_size</key>\n <integer></integer>\n <key>max_vertex_range</key>\n <integer></integer>\n <key>num_texture_image_units</key>\n <integer></integer>\n <key>num_texture_units</key>\n <integer></integer>\n <key>opengl_version</key>\n <string></string>\n <key>vram</key>\n <integer></integer>\n </map>\n <key>gpu</key>\n <string></string>\n <key>gpu_class</key>\n <integer></integer>\n <key>gpu_vendor</key>\n <string></string>\n <key>gpu_version</key>\n <string></string>\n <key>mac_address</key>\n <string></string>\n <key>opengl_version</key>\n <string></string>\n <key>os</key>\n <string></string>\n <key>ram</key>\n <integer></integer>\n <key>serial_number</key>\n <string></string>\n <key>shader_level</key>\n <integer></integer>\n </map>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='GetDisplayNames', method='GET', body=b'', query={'ids'}),
|
|
CAPTemplate(cap_name='FetchInventory2', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>agent_id</key>\n <uuid><!HIPPOREPL[[AGENT_ID]]></uuid>\n <key>cap_name</key>\n <string></string>\n <key>items</key>\n <array>\n <map>\n <key>item_id</key>\n <uuid></uuid>\n <key>owner_id</key>\n <uuid><!HIPPOREPL[[AGENT_ID]]></uuid>\n </map>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='FetchLib2', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>agent_id</key>\n <uuid></uuid>\n <key>cap_name</key>\n <string></string>\n <key>items</key>\n <array>\n <map>\n <key>item_id</key>\n <uuid></uuid>\n <key>owner_id</key>\n <uuid></uuid>\n </map>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='FetchInventoryDescendents2', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>folders</key>\n <array>\n <map>\n <key>fetch_folders</key>\n <boolean></boolean>\n <key>fetch_items</key>\n <boolean></boolean>\n <key>folder_id</key>\n <string></string>\n <key>owner_id</key>\n <uuid><!HIPPOREPL[[AGENT_ID]]></uuid>\n <key>sort_order</key>\n <integer></integer>\n </map>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='ProvisionVoiceAccountRequest', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<undef />\n</llsd>\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'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>agents</key>\n <map>\n <key><!HIPPOREPL[[AGENT_ID]]></key>\n <map>\n <key>tooComplex</key>\n <boolean></boolean>\n <key>weight</key>\n <integer></integer>\n </map>\n </map>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='ParcelVoiceInfoRequest', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<undef />\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='ProductInfoRequest', method='GET', body=b'', query=set()),
|
|
CAPTemplate(cap_name='ObjectMedia', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>object_id</key>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n <key>verb</key>\n <string>GET</string>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='GetObjectCost', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>object_ids</key>\n <array>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='GetObjectPhysicsData', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>object_ids</key>\n <array>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='RenderMaterials', method='PUT', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>Zipped</key>\n <binary></binary>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='RenderMaterials', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>Zipped</key>\n <binary></binary>\n </map>\n</llsd>\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'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>fields</key>\n <array>\n <string>experience</string>\n </array>\n <key>item-id</key>\n <uuid><!HIPPOREPL[[SELECTED_SCRIPT_ITEM]]></uuid>\n <key>object-id</key>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='UpdateScriptTask', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>experience</key>\n <uuid></uuid>\n <key>is_script_running</key>\n <integer>1</integer>\n <key>item_id</key>\n <uuid><!HIPPOREPL[[SELECTED_SCRIPT_ITEM]]></uuid>\n <key>target</key>\n <string>mono</string>\n <key>task_id</key>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n </map>\n</llsd>\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'<?xml version="1.0" ?>\n<llsd>\n<string></string>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='FindExperienceByName', method='GET', body=b'', query={'query', 'page', 'page_size'}),
|
|
CAPTemplate(cap_name='ViewerMetrics', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>agent_id</key>\n <uuid></uuid>\n <key>break</key>\n <boolean></boolean>\n <key>duration</key>\n <real></real>\n <key>initial</key>\n <boolean></boolean>\n <key>message</key>\n <string></string>\n <key>nearby</key>\n <map>\n <key>cloud</key>\n <integer></integer>\n <key>downloading</key>\n <integer></integer>\n <key>full</key>\n <integer></integer>\n <key>gray</key>\n <integer></integer>\n </map>\n <key>rez_status</key>\n <string></string>\n <key>sequence</key>\n <integer></integer>\n <key>session_id</key>\n <uuid></uuid>\n <key>timers</key>\n <array>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n <map>\n <key>central_bake_version</key>\n <integer></integer>\n <key>completed</key>\n <boolean></boolean>\n <key>grid_x</key>\n <integer></integer>\n <key>grid_y</key>\n <integer></integer>\n <key>is_self</key>\n <boolean></boolean>\n <key>is_using_server_bakes</key>\n <boolean></boolean>\n <key>stats</key>\n <map>\n <key>count</key>\n <integer></integer>\n <key>max</key>\n <real></real>\n <key>mean</key>\n <real></real>\n <key>min</key>\n <real></real>\n <key>std_dev</key>\n <real></real>\n </map>\n <key>timer_name</key>\n <string></string>\n </map>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='InventoryAPIv3', method='PATCH', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>name</key>\n <string></string>\n </map>\n</llsd>\n', query={'tid'}, path="/item/SOME_ID"),
|
|
CAPTemplate(cap_name='RemoteParcelRequest', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>location</key>\n <array>\n <real></real>\n <real></real>\n <real></real>\n </array>\n <key>region_handle</key>\n <binary></binary>\n <key>region_id</key>\n <uuid></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='ParcelPropertiesUpdate', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n <map>\n <key>local_id</key>\n <integer><!HIPPOREPL[[SELECTED_PARCEL_LOCAL]]></integer>\n <key>any_av_sounds</key>\n <boolean>true</boolean>\n <key>auth_buyer_id</key>\n <uuid></uuid>\n <key>auto_scale</key>\n <integer>0</integer>\n <key>category</key>\n <integer>0</integer>\n <key>description</key>\n <string></string>\n <key>flags</key>\n <binary>AAAAAQ==</binary>\n <key>group_av_sounds</key>\n <boolean>true</boolean>\n <key>group_id</key>\n <uuid></uuid>\n <key>landing_type</key>\n <integer>2</integer>\n <key>media_allow_navigate</key>\n <integer>1</integer>\n <key>media_current_url</key>\n <string></string>\n <key>media_desc</key>\n <string></string>\n <key>media_height</key>\n <integer>0</integer>\n <key>media_id</key>\n <uuid></uuid>\n <key>media_loop</key>\n <integer>0</integer>\n <key>media_prevent_camera_zoom</key>\n <integer>0</integer>\n <key>media_type</key>\n <string>none/none</string>\n <key>media_url</key>\n <string></string>\n <key>media_url_timeout</key>\n <real>0.0</real>\n <key>media_width</key>\n <integer>0</integer>\n <key>music_url</key>\n <string></string>\n <key>name</key>\n <string></string>\n <key>obscure_media</key>\n <boolean>false</boolean>\n <key>obscure_music</key>\n <boolean>false</boolean>\n <key>parcel_flags</key>\n <binary>fiQASw==</binary>\n <key>pass_hours</key>\n <real>1.0</real>\n <key>pass_price</key>\n <integer>10</integer>\n <key>sale_price</key>\n <integer>10000</integer>\n <key>see_avs</key>\n <boolean>true</boolean>\n <key>snapshot_id</key>\n <uuid></uuid>\n <key>user_location</key>\n <array>\n <real>0.0</real>\n <real>0.0</real>\n <real>0.0</real>\n </array>\n <key>user_look_at</key>\n <array>\n <real>0.0</real>\n <real>0.0</real>\n <real>0.0</real>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='UpdateScriptAgent', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>item_id</key>\n <uuid></uuid>\n <key>target</key>\n <string>mono</string>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='NewFileAgentInventory', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>asset_type</key>\n <string></string>\n <key>description</key>\n <string></string>\n <key>everyone_mask</key>\n <integer></integer>\n <key>folder_id</key>\n <uuid></uuid>\n <key>group_mask</key>\n <integer></integer>\n <key>inventory_type</key>\n <string></string>\n <key>name</key>\n <string></string>\n <key>next_owner_mask</key>\n <integer></integer>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='UpdateNotecardAgentInventory', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>item_id</key>\n <uuid></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='UpdateNotecardTaskInventory', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>item_id</key>\n <uuid><!HIPPOREPL[[SELECTED_TASK_ITEM]]></uuid>\n <key>task_id</key>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='UpdateGestureAgentInventory', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>item_id</key>\n <uuid></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='UpdateGestureTaskInventory', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>item_id</key>\n <uuid><!HIPPOREPL[[SELECTED_TASK_ITEM]]></uuid>\n <key>task_id</key>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='MeshUploadFlag', method='GET', body=b'', query=set()),
|
|
CAPTemplate(cap_name='UntrustedSimulatorMessage', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>message</key>\n <string>ChatFromViewer</string>\n <key>body</key>\n <map>\n <key>AgentData</key>\n <array>\n <map>\n <key>AgentID</key>\n <uuid><!HIPPOREPL[[AGENT_ID]]></uuid>\n <key>SessionID</key>\n <uuid><!HIPPOREPL[[SESSION_ID]]></uuid>\n </map>\n </array>\n <key>ChatData</key>\n <array>\n <map>\n <key>Channel</key>\n <integer>0</integer>\n <key>Message</key>\n <string>test <!HIPPOEVAL[[\n 1 + 1\n ]]></string>\n <key>Type</key>\n <integer>1</integer>\n </map>\n </array>\n </map>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='UpdateAvatarAppearance', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>cof_version</key>\n <integer></integer>\n </map>\n</llsd>\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'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>msg</key>\n <string></string>\n <key>name</key>\n <string></string>\n <key>pos-global</key>\n <array>\n <real></real>\n <real></real>\n <real></real>\n </array>\n <key>subject</key>\n <string></string>\n <key>to</key>\n <string></string>\n </map>\n</llsd>\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'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>description</key>\n <string></string>\n <key>extended_metadata</key>\n <string></string>\n <key>group_id</key>\n <uuid></uuid>\n <key>slurl</key>\n <string></string>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='AttachmentResources', method='GET', body=b'', query=set()),
|
|
CAPTemplate(cap_name='DispatchRegionInfo', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>agent_limit</key>\n <real></real>\n <key>allow_damage</key>\n <integer></integer>\n <key>allow_land_resell</key>\n <integer></integer>\n <key>allow_parcel_changes</key>\n <integer></integer>\n <key>block_fly</key>\n <integer></integer>\n <key>block_fly_over</key>\n <integer></integer>\n <key>block_parcel_search</key>\n <integer></integer>\n <key>block_terraform</key>\n <integer></integer>\n <key>prim_bonus</key>\n <real></real>\n <key>restrict_pushobject</key>\n <integer></integer>\n <key>sim_access</key>\n <string></string>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='AvatarPickerSearch', method='GET', body=b'', query={'names', 'page_size'}),
|
|
CAPTemplate(cap_name='LandResources', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>parcel_id</key>\n <uuid><!HIPPOREPL[[SELECTED_PARCEL_FULL]]></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='ResourceCostSelected', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>selected_roots</key>\n <array>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n </array>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='GroupMemberData', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>group_id</key>\n <uuid />\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='GroupExperiences', method='GET', body=b'', query={'SOME_ID'}),
|
|
CAPTemplate(cap_name='CopyInventoryFromNotecard', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>callback-id</key>\n <integer></integer>\n <key>folder-id</key>\n <uuid></uuid>\n <key>item-id</key>\n <uuid></uuid>\n <key>notecard-id</key>\n <uuid></uuid>\n <key>object-id</key>\n <uuid />\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='HomeLocation', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>HomeLocation</key>\n <map>\n <key>LocationId</key>\n <integer>1</integer>\n <key>LocationLookAt</key>\n <map>\n <key>X</key>\n <real></real>\n <key>Y</key>\n <real></real>\n <key>Z</key>\n <real></real>\n </map>\n <key>LocationPos</key>\n <map>\n <key>X</key>\n <real></real>\n <key>Y</key>\n <real></real>\n <key>Z</key>\n <real></real>\n </map>\n </map>\n </map>\n</llsd>\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'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key><!HIPPOREPL[[SELECTED_FULL]]></key>\n <map>\n <key>A</key>\n <integer></integer>\n <key>B</key>\n <integer></integer>\n <key>C</key>\n <integer></integer>\n <key>D</key>\n <integer></integer>\n <key>navmesh_category</key>\n <integer></integer>\n <key>phantom</key>\n <boolean></boolean>\n </map>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='RetrieveNavMeshSrc', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<undef />\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='MapLayer', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<undef />\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='GroupAPIv1', method='GET', body=b'', query={'group_id'}),
|
|
CAPTemplate(cap_name='ChatSessionRequest', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>method</key>\n <string></string>\n <key>session-id</key>\n <uuid></uuid>\n </map>\n</llsd>\n', query=set()),
|
|
CAPTemplate(cap_name='ViewerBenefits', method='GET', body=b'', query=set(), path=''),
|
|
CAPTemplate(cap_name='SetDisplayName', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>display_name</key>\n <array>\n <string>OLD_DISPLAY_NAME</string>\n <string>NEW_DISPLAY_NAME</string>\n </array>\n </map>\n</llsd>\n', query=set(), path=''),
|
|
CAPTemplate(cap_name='ObjectMediaNavigate', method='POST', body=b'<?xml version="1.0" ?>\n<llsd>\n<map>\n <key>current_url</key>\n <string></string>\n <key>object_id</key>\n <uuid><!HIPPOREPL[[SELECTED_FULL]]></uuid>\n <key>texture_index</key>\n <integer></integer>\n </map>\n</llsd>\n', query=set(), path=''),
|
|
]
|