SMSG UPDATE OBJECT
SMSG_UPDATE_OBJECT is a World Packet that is sent in order to update the state of objects in the game world.
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.
Large SMSG_UPDATE_OBJECT packets should be sent as compressed SMSG_COMPRESSED_UPDATE_OBJECT packets instead.
Packet Layout
Offset | Size / Endianness | Type | Name | Description |
---|---|---|---|---|
0x0 | 2 / Big | uint16 | size | Size of the packet including the opcode field. |
0x2 | 2 / Little | uint16 | opcode | Opcode for the packet. Determines the structure of the body. Always 0x0A9. |
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) | |
Packed GUID | guid |
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 |
Example Packets
The following two are valid packets for logging in with a (1.12.1) client. Before these packets the client expects valid:
- SMSG_LOGIN_VERIFY_WORLD packet with map id 0 (Easter Kingdoms). The coordinates do not matter.
- SMSG_TUTORIAL_FLAGS packet with all fields set to 0xFF (disable all tutorials).
If either of those packets are missing the client will SEGFAULT unless they have been cached from previous connections.
The GUID values do not appear to need to be the same as the character shown on the character screen, but it could be a source of issues. If you are unable to move but can still use the menu, the keybindings can be reset through the system menu.
The first is the absolute minimum required to log into the world.
char SMSG_UPDATE_OBJECT[] = { 1, 0, 0, 0, // Block Count (1) 0, // Has transport 3, // Update type = CREATE_NEW_OBJECT2 1, 4, // Packed GUID. GUID is 4, the mask byte indicates that only the first (least significant) byte is sent. 4, // OBJECT_TYPE = WO_PLAYER 113, // Update flags 0x71 0, 0, 0, 0, // Movement flags 0, 0, 0, 0, // Timestamp 205, 215, 11, 198, // Position x (-8949.95) 53, 126, 4, 195, // Position y (-132.493) 249, 15, 167, 66, // Position z (83.5312) Human start position (Northshire Abbey) 0, 0, 0, 0, // Orientation (0.0) 0, 0, 0, 0, // Fall time (0) 0, 0, 128, 63, // Walk speed (1.0) 0, 0, 140, 66, // Run speed (70.0) 0, 0, 144, 64, // Run back speed (4.5) 0, 0, 0, 0, // Swim speed (0.0) 0, 0, 0, 0, // Swim back speed (0.0) 219, 15, 73, 64, // Turn speed (3.1415 (pi)) 1, // Is player 1, 0, 0, // Unknown hardcoded 2, // Amount of u32 mask blocks that will follow // Mask blocks 7, 0, 64, 0, 16, 0, 0, 0, // End of mask blocks 4, 0, 0, 0, 0, 0, 0, 0, // OBJECT_FIELD_GUID (4) (notice unpacked u64) 25, 0, 0, 0, // OBJECT_FIELD_TYPE (16 | 8 | 1) (TYPE_PLAYER | TYPE_UNIT | TYPE_OBJECT) 100, 0, 0, 0, // UNIT_FIELD_HEALTH (100) 1, // UNIT_FIELD_BYTES[0] // Race (Human) 1, // UNIT_FIELD_BYTES[1] // Class (Warrior) 1, // UNIT_FIELD_BYTES[2] // Gender (Female) 1, // UNIT_FIELD_BYTES[3] // Power (Rage) };
The second adds slightly more fields and spawns an level 1 human female warrior with 100/100 health.
char SMSG_UPDATE_OBJECT[] = { 1, 0, 0, 0, // Block Count 0, // Has transport 3, // Update type = CREATE_NEW_OBJECT2 1, 4, // Packed GUID. GUID is 4, mask byte indicates that only the first (least significant) byte is sent. 4, // Object Type WO_PLAYER 113, // Update flags 0x71 0, 0, 0, 0, // Movement flags 0, 0, 0, 0, // Timestamp 205, 215, 11, 198, // Position x (-8949.95) 53, 126, 4, 195, // Position y (-132.493) 249, 15, 167, 66, // Position z (83.5312) Human start position (Northshire Abbey) 0, 0, 0, 0, // Orientation (0.0) 0, 0, 0, 0, // Fall time (0) 0, 0, 128, 63, // Walk speed (1.0) 0, 0, 140, 66, // Run speed (70.0) 0, 0, 144, 64, // Run back speed (4.5) 0, 0, 0, 0, // Swim speed (0.0) 0, 0, 0, 0, // Swim back speed (0.0) 219, 15, 73, 64, // Turn speed (3.1415 (pi)) 1, // Is player 1, 0, 0, // Unknown hardcoded 5, // Amount of u32 mask blocks that will follow // Mask blocks 23, 0, 64, 16, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, // End of mask blocks 4, 0, 0, 0, 0, 0, 0, 0, // OBJECT_FIELD_GUID (4) (notice unpacked u64) 25, 0, 0, 0, // OBJECT_FIELD_TYPE (16 | 8 | 1) (TYPE_PLAYER | TYPE_UNIT | TYPE_OBJECT) 0, 0, 128, 63, // Scale (1.0) 100, 0, 0, 0, // UNIT_FIELD_HEALTH (100) 100, 0, 0, 0, // UNIT_FIELD_MAXHEALTH (100) 1, 0, 0, 0, // UNIT_FIELD_LEVEL (1) 1, 0, 0, 0, // UNIT_FIELD_FACTIONTEMPLATE (1) 1, // UNIT_FIELD_BYTES[0] // Race (Human) 1, // UNIT_FIELD_BYTES[1] // Class (Warrior) 1, // UNIT_FIELD_BYTES[2] // Gender (Female) 1, // UNIT_FIELD_BYTES[3] // Power (Rage) 50, 0, 0, 0, // UNIT_FIELD_DISPLAYD (50, Human Female) 50, 0, 0, 0, // UNIT_FIELD_NATIVEDISPLAYID (50, Human Female) };
Wrath
The following is a valid packet for logging in with a (3.3.5) client.
char SMSG_UPDATE_OBJECT[] = { // header 0x00, 0x6f, // size (111) 0xa9, 0x00, // opcode (169) 0x01, 0x00, 0x00, 0x00, // number of objects // start of object 0x03, // CREATE_OBJECT2 (trinity uses this for creating new objects // and CREATE_OBJECT for existing objects? not sure) 0x01, 0x08, // packed guid (id: 8) 0x04, // player object type // movement block 0x21, 0x00, // update flag self | update flag living 0x00, 0x00, 0x00, 0x00, // movement flags 0x00, 0x00, // movement flags cont. 0x00, 0x00, 0x00, 0x00, // timestamp (trinity uses unix epoch, caching related?) 0xcd, 0xd7, 0x0b, 0xc6, // x pos (-8949.95) 0x35, 0x7e, 0x04, 0xc3, // y pos (-132.493) 0xf9, 0x0f, 0xa7, 0x42, // z pos (83.5312) 0x00, 0x00, 0x00, 0x00, // orientation 0x00, 0x00, 0x00, 0x00, // fall time 0x00, 0x00, 0x80, 0x3f, // walk speed (1.0f) 0x00, 0x00, 0x8c, 0x42, // run speed (70.0f) 0x00, 0x00, 0x90, 0x40, // reverse speed (4.5f) 0x00, 0x00, 0x00, 0x00, // swim speed (0.0f) 0x00, 0x00, 0x00, 0x00, // swim reverse speed (0.0f) 0x00, 0x00, 0x00, 0x00, // flight speed (0.0f) 0x00, 0x00, 0x00, 0x00, // flight reverse speed (0.0f) 0xd0, 0x0f, 0x49, 0x40, // turn speed (3.14159) 0x00, 0x00, 0x00, 0x00, // pitch rate (0.0f) // update/values block 0x03, // size of mask (measured in uint32, 3*4 = 12 byte mask) // mask 0x03, 0x00, 0x00, 0x00, // OBJECT_GUID (size=2) 0x00, 0x00, 0x80, 0x00, // UNIT_FACTIONTEMPLATE 0x18, 0x00, 0x00, 0x00, // UNIT_DISPLAYID | UNIT_NATIVEDISPLAYID // OBJECT_GUID 0x08, 0x00, 0x00, 0x00, // low guid (id: 8) 0x00, 0x00, 0x00, 0x00, // high guid (2 MSB = 0x0000 for player, haven't tested this much) // not including any of these seems to cause a segfault 0x01, 0x00, 0x00, 0x00, // UNIT_FACTIONTEMPLATE (race, 0x1 = human) 0x0c, 0x4d, 0x00, 0x00, // UNIT_DISPLAYID (0x4D0C = human female) 0x0c, 0x4d, 0x00, 0x00, // UNIT_NATIVEDISPLAYID (same as above) };