Expand description
Generally it should be enough to simply #[derive(Deserialize)] or
whichever trait you need. Just like the more general serde crate, this
will handle most common things, like fields of different types, including
references to other structures. However, there are a few things to
keep in mind. Silkroad Online packets are not self-specifying, and thus we
often need to provide just a little bit of help to serialize/deserialize
some kinds of data. In general, you can provide additional options through
the #[silkroad] tag. Which options are available for which elements will
be explained in the following section.
§Enums
Enums are generally serialized as one byte discriminant, followed by the
content of that variant without further details. Currently, we don’t
automatically map the index of the enum variant to the discriminant. As
such, you need to define a value manually. This can be done using
#[silkroad(value = 1)] to set the variants byte value to 1:
#[derive(Serialize, Deserialize)]
enum Hello {
#[silkroad(value = 1)]
ClientHello(String),
#[silkroad(value = 2)]
ServerHello(String),
}In some cases it may be necessary for the discriminant to be two bytes wide,
which you can specify using #[silkroad(size = 2)] on the enum itself:
#[derive(Serialize, Deserialize)]
#[silkroad(size = 2)]
enum Hello {
#[silkroad(value = 0x400D)]
ClientHello(String),
}§Structs
Structs are always serialized/deserialized by serializing/deserializing their fields. A unit struct therefor has length zero. There are also no options currently to alter the behavior for structs themselves, only their fields.
#[derive(Serialize, Deserialize)]
struct Hello(String);§Fields
The serialization/deserialization of fields is identical between structs and enums. Each field is serialized one after another without any separators. Therefor, it is necessary to match the size exactly to the consumed bytes. Fields are serialized and deserialized in the order they are defined.
#[derive(Serialize, Deserialize)]
struct Hello {
one_byte: u8,
two_bytes: u16,
}§Collections
Collections (i.e. vectors) are encoded using one byte length followed by the
elements of the collection without a separator. If the size is larger, this
needs to be denoted using the #[silkroad(size = 2)] attribute.
#[derive(Serialize, Deserialize)]
struct Hello {
#[silkroad(size = 2)]
greetings: Vec<String>,
}The default size is 1 with a size of up to 4 being supported.
Additionally, you may change the type of encoding for a collection using the
list_type attribute. This accepts one of three options: length
(default), break, and has-more. break and has-more specify before
each element if another element will follow using different values. break
uses 1 for ‘has more values’ and 2 for finished, while has-more
uses 1 for more elements and 0 for being finished.
#[derive(Serialize, Deserialize)]
struct Hello {
#[silkroad(list_type = "break")]
greetings: Vec<String>,
}§Strings
Generally a string is encoded using two bytes length and then the UTF-8
representation of that string. In some cases, Silkroad however uses two byte
wide characters (UTF-16) in strings. This can be configured by using a
size of 2.
#[derive(Serialize, Deserialize)]
struct Hello {
#[silkroad(size = 2)]
greeting: String,
}§Optional
Optional values will be encoded using a byte denoting the presence (1) or absence (0), following the underlying value if it is present. In some cases, due to previous knowledge, optional values may just appear (or be missing) without the presence indicator. This makes them impossible to deserialize (currently), but this is unfortunately current necessary. To achieve this, you can set the size of the field to 0.
#[derive(Serialize)]
struct Hello {
#[silkroad(size = 0)]
greeting: Option<String>,
}Alternatively, if there is an indication in the data whether the value will
be present or not, you can use the when attribute to specify a condition.
In that case the presence byte will be omitted as well, but makes it
possible to be deserialized. This does not make any checks for serialization
and will always append a present value, ignoring the condition. The
condition in when should denote an expression which returns a boolean,
showing if the values is present in the packet or not. It is possible to
access any previous values, but is currently limited to expressions without
imports.
#[derive(Deserialize, Serialize)]
struct Hello {
condition: u8
#[silkroad(when = "condition == 1")]
greeting: Option<String>
}