-
-
Notifications
You must be signed in to change notification settings - Fork 4
How packet (de ) serialization works
To implement serializer, you should apply LoginPacket
or WorldPacket
traits to your struct:
#[derive(LoginPacket, Serialize, Debug)]
pub struct Incoming {
unknown: u8,
code: u8,
#[serde(serialize_with = "crate::primary::serializers::serialize_array")]
server_ephemeral: [u8; 32],
g_len: u8,
#[depends_on(g_len)]
g: Vec<u8>,
n_len: u8,
#[depends_on(n_len)]
n: Vec<u8>,
#[serde(serialize_with = "crate::primary::serializers::serialize_array")]
salt: [u8; 32],
version_challenge: [u8; 16],
unknown2: u8,
}
The example above describes fields of the incoming data for LoginChallenge
response from server.
You can apply #[depends_on(prev_field_names)]
attribute, if current field is dynamic and depends on data from previous fields (any field which is above the current field).
Also you can use custom serializer #[serde(serialize_with = "crate::primary::serializers::serialize_array")]
to change the format of byte-array output (AA BB FF 01 etc)
There some fields, which can be read or not. For those fields you should use #[conditional]
attrubute and next you should implement method with same name as field name:
// example from features/wotlk_realm/object/update_object.rs
#[derive(WorldPacket, Serialize, Debug)]
pub struct Incoming {
pub blocks_amount: u32,
#[depends_on(blocks_amount)]
pub blocks: Vec<Block>,
}
fn is_zero(&x: &u32) -> bool {
x == 0
}
#[derive(Serialize, Segment, Debug, Clone, Default)]
pub struct Block {
pub block_type: BlockType,
#[conditional]
#[serde(skip_serializing_if = "PackedGuid::is_default")]
pub guid: PackedGuid,
#[conditional]
#[serde(skip_serializing_if = "ObjectTypeID::is_none")]
pub object_type_id: ObjectTypeID,
#[conditional]
#[serde(skip_serializing_if = "Movement::is_default")]
pub movement: Movement,
#[conditional]
#[serde(skip_serializing_if = "UpdateData::is_default")]
pub update_data: UpdateData,
#[conditional]
#[serde(skip_serializing_if = "is_zero")]
pub guid_count: u32,
#[depends_on(guid_count)]
#[conditional]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub guids: Vec<PackedGuid>
}
impl Block {
fn guid(instance: &mut Self) -> bool {
matches!(
instance.block_type.0,
BlockType::VALUES |
BlockType::MOVEMENT |
BlockType::CREATE_OBJECT |
BlockType::CREATE_OBJECT2
)
}
fn object_type_id(instance: &mut Self) -> bool {
matches!(
instance.block_type.0,
BlockType::CREATE_OBJECT |
BlockType::CREATE_OBJECT2
)
}
fn movement(instance: &mut Self) -> bool {
matches!(
instance.block_type.0,
BlockType::MOVEMENT |
BlockType::CREATE_OBJECT |
BlockType::CREATE_OBJECT2
)
}
fn update_data(instance: &mut Self) -> bool {
matches!(
instance.block_type.0,
BlockType::VALUES |
BlockType::CREATE_OBJECT |
BlockType::CREATE_OBJECT2
)
}
fn guid_count(instance: &mut Self) -> bool {
matches!(
instance.block_type.0,
BlockType::NEAR_OBJECTS |
BlockType::OUT_OF_RANGE_OBJECTS
)
}
fn guids(instance: &mut Self) -> bool {
matches!(
instance.block_type.0,
BlockType::NEAR_OBJECTS |
BlockType::OUT_OF_RANGE_OBJECTS
)
}
}
In the code above each field which marked as conditional
should be paired with method which returns bool to mark if field should be parsed or not. If not, default value will be set for this field and no data will be consumed from buffer.
In the code above you probably noticed a new proc-macro Segment
:
#[derive(Serialize, Segment, Debug, Clone, Default)]
pub struct Block {
// ...
}
This macro is useful when you want to move some fields set into separate field; or if you need serializer for some fields to turn them into byte-array but do not need to build the whole packet for it; or if you have fields set which is common for multiple packets (so you can just import).
So, the segmented struct can be used as field-type for serializer.