Crates.io | tdf-derive |
lib.rs | tdf-derive |
version | 0.2.0 |
source | src |
created_at | 2023-08-17 03:29:36.883018 |
updated_at | 2023-12-22 07:24:01.558332 |
description | Derive macros for working with the tdf crate |
homepage | |
repository | https://github.com/jacobtread/tdf |
max_upload_size | |
id | 946610 |
size | 35,474 |
Library for serializing and deserializing the tdf network format used by BlazeSDK (Heat1) which is present in games such as Mass Effect 3, Mass Effect Andromeda, Battlefield 3, etc (Lots of EA games).
This is a re-write of my previous library blazepk. This version provides much more accurate naming and support for the different structures along with a derive macro implementation. This implementation also seperates out routing and packet framing which should be seperately as different versions use different framing implementations (Fire and Fire2)
Using tdf
with cargo
[dependencies]
tdf = "0.1"
or
cargo add tdf
The default features are ["serde", "derive"]
Feature | Description |
---|---|
serde | Adds support for serde serialization of tdf types |
bytes | Adds support for serializing directly onto BytesMut types from the bytes crate |
derive | Adds support for using tdf-derive derive macros for deriving structures and enums |
Tdf comes with the feature flag derive
(Enabled by default) which allows deriving deserialize and serialize implementations automatically. There are 3 derive macros
that you can use they are: TdfDeserialize, TdfSerialize, and TdfTyped
This is only supported for Groups, Repr Enums, and Tagged Enums
When creating structures for example to use as the contents of a Packet you should use this section, if you would like to use a structure as a group (Structures must be groups if you would like to tag them as values) see Deriving Groups for that.
Below is an example with a simple structure:
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize)]
pub struct ExampleStructure {
#[tdf(tag = "TEST")]
pub my_value: u32
}
You can provide a lifetime to a TdfDeserialize struct (The lifetime can be whatever you like and doesn't have to be 'de)
Lifetimes can be used for values like strings and Blobs to prevent needing to copy the underlying data, instead directly borrowing from the deserialize buffer:
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize)]
pub struct ExampleStructure<'de> {
#[tdf(tag = "TEST")]
pub my_value: &'de str
}
Warning TdfDeserialize structures can NOT have more than one lifetime more than one lifetime will cause a compile time errror. However if you structure only implements TdfSerialize you can have as many lifetimes as you like
You can also provide generic parameters to your structures as long as the where
clause is specified directly on the structure:
use tdf::prelude::*;
#[derive(TdfSerialize)]
pub struct ExampleStructure<T>
where T: TdfSerialize + TdfTyped
{
#[tdf(tag = "TEST")]
pub my_value: T
}
Note Any generic types that you want to use as a tag must also implement the
TdfTyped
trait
When using generic types with TdfDeserialize you must specify the lifetime like in the lifetimes example, if your structure doesn't make use of the deserialize lifetime you will get an error for it being unused, you can solve this by using PhantomData
and marking it as skip (See Skipping fields):
use tdf::prelude::*;
use std::marker::PhantomData;
#[derive(TdfSerialize, TdfDeserialize)]
pub struct ExampleStructure<'de, T>
where T: TdfSerialize + TdfDeserialize<'de> + TdfTyped
{
#[tdf(tag = "TEST")]
pub my_value: T,
#[tdf(skip)]
pub _marker: PhantomData<&'de T>,
}
You can tell the macro to skip any fields that you don't want to include in serialization using #[tdf(skip)]
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize)]
pub struct ExampleStructure {
#[tdf(tag = "TEST")]
pub my_value: u32,
#[tdf(skip)]
pub skip_me: u32
}
Fields can also be skipped on tagged enum variants with named fields
Note If you are deriving
TdfDeserialize
any skipped types must implementDefault
because the deserializer will use theDefault::default
implementation for that type in order to fill in the struct
When serializing structures in order to represent them as a group value that can
be tagged they must have special leading and trailing data which is created if you
specify #[tdf(group)]
on your structure:
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize, TdfTyped)]
#[tdf(group)]
pub struct ExampleStructure {
#[tdf(tag = "TEST")]
pub my_value: u32
}
Once a structure is marked as a group you can derive the TdfTyped
and use it as a group in tags
For structures that require a (2) prefix you can use the #[tdf(prefix_two)]
attribute on the structure:
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize, TdfTyped)]
#[tdf(group, prefix_two)]
pub struct ExampleStructure {
#[tdf(tag = "TEST")]
pub my_value: u32
}
Warning When a structure is marked as a group structure you cannot use it as a Packet body as it contains the extra bytes that represent it as a struct which will likely cause errors when another tool/client attempts to parse it
If you want to define an enum of possible values that maps to one of the existing primitive types (u8, i8, u16, i16, u32, i32, u64, i64, usize, isize) you can define a repr enum:
Note Repr enums are serialized as variable-length integers aka TdfType::VarInt
Note Repr enums that implement TdfSerialize must also implement Clone + Copy
use tdf::prelude::*;
#[derive(Clone, Copy, TdfSerialize, TdfDeserialize, TdfTyped)]
#[repr(u8)]
pub enum ExampleReprEnum {
Test = 0x0,
Test2 = 0x1,
Test3 = 0x3
}
Don't forget to derive TdfTyped
to use this value within tags
Note When defining a repr enum you MUST use
#[repr(%TYPE%)]
otherwise the type will not be known
Warning Unlike standard repr enums you CANNOT omit the discriminant for each value, as at this stage the macro cannot infer the next values.
When using the above enum if a value you will run into a DecodeError
at runtime if a discriminant not present in the enum is deserialized.
To prevent this behavior you can specify a default variant:
use tdf::prelude::*;
#[derive(Clone, Copy, TdfSerialize, TdfDeserialize, TdfTyped)]
#[repr(u8)]
pub enum ExampleReprEnum {
Test = 0x0,
Test2 = 0x1,
Test3 = 0x3,
#[tdf(default)]
MyDefault = 0x4,
}
You can represent BlazeSDK Tagged Unions as Rust enums with fields:
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize, TdfTyped)]
pub enum ExampleTaggedEnum {
#[tdf(key = 0x0, tag = "VALU")]
Test {
#[tdf(tag = "TEST")]
test: String,
},
#[tdf(key = 0x1, tag = "VALU")]
Test2(String)
}
In the above the "key" attribute for each enum variant is the discriminant to use for that variant. The "tag" attribute on each enum variant is the tag that will be used when tagging the value of the union
In the above there is a variant with named fields and one with a single unnamed field.
Variants with named fields are treated and serialized as groups (TdfType::Group) so the values within them have all the same conditions as Deriving Groups and most of the information from Deriving Structures also carries over.
Variants with unnamed fields (You are only allowed to specify 1 unnamed field as shown in ExampleTaggedEnum::Test2
) the value is serialized as
the type of value provided, in this case it Test2 would have a tagged value of VALU with the type of string.
When using the above enum just like in the repr example you will run into a DecodeError
at runtime if a discriminant not present in the enum is deserialized.
To prevent this behavior you can specify a default variant:
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize, TdfTyped)]
pub enum ExampleTaggedEnum {
#[tdf(key = 0x0, tag = "VALU")]
Test {
#[tdf(tag = "TEST")]
test: String,
},
#[tdf(key = 0x1, tag = "VALU")]
Test2(String),
#[tdf(default)]
DefaultValue
}
For tagged enums that implement TdfSerialize, if a default value is serialized it will use the
unset
discriminant
Tdf tagged unions have a special unset type discriminant so you will run into a DecodeError
at runtime if an unset discriminant is deserialized.
To prevent this behavior you can specify a unset variant:
use tdf::prelude::*;
#[derive(TdfSerialize, TdfDeserialize, TdfTyped)]
pub enum ExampleTaggedEnum {
#[tdf(key = 0x0, tag = "VALU")]
Test {
#[tdf(tag = "TEST")]
test: String,
},
#[tdf(key = 0x1, tag = "VALU")]
Test2(String),
#[tdf(unset)]
Unset
}
Note You cannot use a default variant as an unset variant they must be seperate as the default variant handles skipping extra values that might exist which will fail
Refer to docs.rs/tdf
Refer to docs.rs/tdf