#silkroad #serde #serialization #packet #online #depending #deserialize

skrillax-serde

A serialization/deserialization library for Silkroad Online packets

3 unstable releases

0.2.0 Oct 27, 2024
0.1.1 Apr 28, 2024
0.1.0 Apr 21, 2024

#524 in Encoding

Download history 3/week @ 2024-09-16 33/week @ 2024-09-23 30/week @ 2024-09-30 1/week @ 2024-10-07 85/week @ 2024-10-21 88/week @ 2024-10-28 4/week @ 2024-11-04 2/week @ 2024-11-11 13/week @ 2024-11-18 39/week @ 2024-11-25

62 downloads per month
Used in 3 crates

MIT license

22KB
338 lines

skrillax-serde

Crates.io Docs.rs

serde is a well known and respected serialization and deserialization framework for Rust. And while we could make it work for our context, Silkroad is unfortunately a little annoying in a few respects, which would make working with serde a little cumbersome. Thus, this provides a more focused approach to serialization/deserialization in the Silkroad context.

Silkroad does a few things that make it hard to generally serialize the packets:

  • They're not self describing - which is, however, expected
  • Lists can be represented in different ways, depending on the specific operation
  • Attributes may appear or be hidden depending on previous values
  • Attributes may appear or be hidden depending on external information

For example, there are three types if list representations in Silkroad: Length based, Break based, or Continue based. The first is quite obvious; a length is prepended and then all elements follow without any separator. "Break based" and "Continue based" are similar in that there's a separator in between each element which differs on the last element. Both use 1 as the separator, but will use 2 or 0 as the end element, respectively. Why do you need multiple ways to encode a list of values? I don't know. Other weirdness includes some strings using wide character (2 byte) encoding or the content completely changing depending on what type of item it belongs to.

This is not impossible to implement with serde, in fact, the implementation would be easy, but would be more exhausting to use given you'd have to constantly reach for deserialize_with and remembering the exact function that does what you want. Especially once you have to serialize/deserialize a field that depends on a previous field, you're going to have a hard time (Example: a ChatMessage packet has an optional field that depends on the type of message which has been encoded in a previous, non-adjacent field). To make it easier to work with and make these quirks "implementable", this custom 'serde' exists.

Silkroad Operation Serialization/Deserialization

Most of the serialization/deserialization logic is implemented in the -derive crate. This crate only includes ser/de definitions for primitive values, such as time, u8 and the like. However, I'd like to explain the basics of how data is serialized - in general - in the Silkroad world.

Let's begin with the basics: Silkroad encodes data (mostly) in little endian byte order, so a packet length of 256, which is a u16, will show up as 0x00 0x01 instead of 0x01 0x00. There are no separators between data fields, so values are present directly adjacent to each other. If you have three fields of varying size, the serialized size will always match the total space the fields would use up in memory. Arrays of constant length also encode only the data it contains (the number of elements is know and so is the size of each element). Varying length lists (or in Rust terms: Vecs) have, as previously mentioned, three different ways of being encoded. Either the length is prepended, which is usually of size u8, but may also be u16, and then all elements will follow without any further separators. Alternatively, a u8 is used to show if there's more (with a value of 1) followed by the next element or if it's the end with either 0 or a 2, depending on where it's used. Enum-like elements (for example, error codes or, generally, different variants of something) often contain a "variant id" denoted as a u8, followed by the specific fields for that variant. As such, variants that don't contain any data would only have the variant id and nothing else. Lastly, there are optional fields, which contain a u8 specifying if data is present (value of 1) or no data (value of 0). Everything else uses any combination of the previously defined atoms.

Dependencies

~1.4–2.2MB
~40K SLoC