SMSG UPDATE OBJECT
When the server wants to notify the client that something (an object, a player, whatever) spawned, moved or disappeared from the world, it sends a SMSG_UPDATE_OBJECT packet with only the relevant data, to save bandwidth.
A note about object GUIDs
In a lot of packets (but unfortunately, not all of them, albeit almost), Blizzard sends object GUIDs in a "packed" form. Seeing as GUIDs are uint64, they chose to pack information so that only the bytes that are set are sent. They first send an uint8 where every bit defines whether or not the corresponding byte is sent. This field is commonly called a mask. Then all the bytes are sent in that order.
Assuming the mask is 0b01101001, then they will send 0-th, 3-rd, 5-th and 6-th bytes of the guid, in that order, and only those.
It is worth noting that at some point (WoD or MoP, i'm not sure - Warpten), they decided to make GUIDs 128 bits. They still pack them, except that this time, they send two masks before the actual bytes: the first one for the GUID lopart, and the second one for the GUID hipart.
Header
Name | Value |
---|---|
PARTIAL | 0 |
MOVEMENT | 1 |
CREATE_OBJECT | 2 |
FAR_OBJECTS | 3 |
NEAR_OBJECTS | 4 |
Update types can be grouped as follows:
- Update data of an object (PARTIAL)
- Spawn or move an object (MOVEMENT, CREATE_OBJECT)
- destroy object (FAR_OBJECTS, NEAR_OBJECTS)
Type 1 includes only update fields, types 2 include movement information block and update fields, and types 3 are just list of GUIDs.
Name | Value |
---|---|
OBJECT | 0 |
ITEM | 1 |
CONTAINER | 2 |
UNIT | 3 |
PLAYER | 4 |
GAME_OBJECT | 5 |
DYNAMIC_OBJECT | 6 |
CORPSE | 7 |
Type | Name | Description |
---|---|---|
uint32 | count | |
uint8 | hasTransport | |
uint8 | update_type (see UpdateType) | |
uint64 | guid |
About GUID: Although depicted as an uint64 in the above table, from 1.12 (or earlier?), GUIDs are packed. See the note about object GUIDs section for more informations.
If the update type needs an object_type (e.g. CREATE_OBJECT needs it):
Type | Name | Description |
---|---|---|
uint8 | object_type (see ObjectType) |
Movement block
For all update types that need movement information:
Type | Name | Description |
---|---|---|
uint32 | flags | |
int32 | unk | |
float[4] | position | position + orientation |
uint64 | tr_guid | (flags & ON_TRANSPORT) transport GUID |
float[4] | tr_position | (flags & ON_TRANSPORT) transport position and orientation |
uint32 | swim_pitch | (flags & IS_SWIMMING) |
uint32 | time | (flags & IS_FALLING) |
float | velocity | (flags & IS_FALLING) |
float | sin | (flags & IS_FALLING) |
float | cos | (flags & IS_FALLING) |
float | xy_speed | (flags & IS_FALLING) |
float | unk | (flags & SPLINE_ELEVATION) |
float[6] | speeds | walk, run, run backward, swim, swim backward and turn |
There can be more data due to flags but I haven't look into that. These are sufficient for basic emulation though.
Name | Value |
---|---|
FORWARD | 0x00000001 |
BACKWARD | 0x00000002 |
STRAFE_LEFT | 0x00000004 |
STRAFE_RIGHT | 0x00000008 |
TURN_LEFT | 0x00000010 |
TURN_RIGHT | 0x00000020 |
IS_FALLING | 0x00002000 |
IS_SWIMMING | 0x00200000 |
ON_TRANSPORT | 0x02000000 |
SPLINE_ELEVATION | 0x04000000 |
Update fields
Before the update fields, for some update types (e.g. CREATE_OBJECT), there is information about the unit being updated:
Type | Name | Description |
---|---|---|
uint32 | is_player | 1 if this object is in fact the player |
uint32 | attack_cycle | |
uint32 | timer_id | |
uint64 | victim_guid |
Then come the update fields:
Type | Name | Description |
---|---|---|
uint8 | num_mask_blocks | hard coded limit at 0x1C |
uint32[] | mask_blocks | |
uint32[] | update_blocks |
Every object has a lot of different characteristics, sometimes specific to their type. All objects have a GUID, but only an item has durability field; both players and units have a health field, but only players have a guild ID field.
There a lot of different fields (around a thousand), so to send this data to clients without dumping a full table of fields, only relevant fields are sent in an update packet, as update_blocks. And to know what fields are present in the update_blocks, the index of each field is set in the bitmasks of mask_blocks.
To not have to worry about the size of each field, they're all sent as uint32. Beware though, some fields like GUID are uint64, so they take 2 update blocks. The client knows how to parse each field.
Example with field values from 1.1.2.4125 (look in your binary for values): I have a Player, so it has object, unit and player fields, and I want to send to a client his GUID (id: 0), his health (id: 0x16) and his experience (id: 0x9E). I will have to send 2 update blocks for the GUID, one for the health and one for the experience: update_blocks has 4 elements. The indices to set are 0, 22 and 158, so we set bits 0 and 22 of the 0-th uint32 mask block, and the bit 30 of the 4-th mask block (because 158 // 4 = 30 and 158 % 32 = 4). The num_mask_blocks value will have to be 5 because we have at least 5 mask blocks.
Object type | Fields |
---|---|
OBJECT | object |
ITEM | object, item |
CONTAINER | object, item, container |
UNIT | object, unit |
PLAYER | object, unit, player |
GAME_OBJECT | object, game_object |
DYNAMIC_OBJECT | object, dynamic_object |
CORPSE | object, corpse |