bancho_packets_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Ident, Span};
3use quote::{format_ident, quote};
4use syn::{parse_macro_input, DeriveInput};
5
6#[proc_macro_derive(ReadPacket)]
7/// This derive macro will implement the `BanchoPacketRead` trait for the struct.
8///
9/// ### Usage
10///
11/// ```ignore
12/// use bancho_packets::{ReadPacket, PacketReader, PayloadReader};
13///
14/// #[derive(Debug, Clone, ReadPacket)]
15/// /// [`BanchoMessage`] is the message structure of the bancho client.
16/// pub struct BanchoMessage {
17///     pub sender: String,
18///     pub content: String,
19///     pub target: String,
20///     pub sender_id: i32,
21/// }
22///
23/// // Now we can use [`PayloadReader`] to read the [`BanchoMessage`] from bytes.
24/// let mut reader = PacketReader::new(&[
25///     1, 0, 0, 20, 0, 0, 0, 11, 0, 11, 6, 228, 189, 160, 229, 165, 189,
26///     11, 4, 35, 111, 115, 117, 0, 0, 0, 0,
27/// ]);
28/// let packet = reader.next().unwrap();
29///
30/// let mut payload_reader = PayloadReader::new(packet.payload.unwrap());
31/// let message = payload_reader.read::<BanchoMessage>();
32///
33/// println!("{:?}: {:?}", packet.id, message);
34/// ```
35pub fn derive_read_packet(input: TokenStream) -> TokenStream {
36    let input = parse_macro_input!(input as DeriveInput);
37    let name = input.ident;
38
39    let fields = match input.data {
40        syn::Data::Struct(data) => data.fields,
41        _ => panic!("#[derive(ReadPacket)] is only defined for structs."),
42    };
43
44    let field_names = fields.iter().map(|field| &field.ident);
45
46    let expanded = quote! {
47        impl BanchoPacketRead<#name> for #name {
48            #[inline]
49            fn read(reader: &mut PayloadReader) -> Option<#name> {
50                Some(#name {
51                    #(#field_names: reader.read()?,)*
52                })
53            }
54        }
55    };
56
57    expanded.into()
58}
59
60#[proc_macro_derive(WritePacket)]
61/// This derive macro will implement the `BanchoPacketWrite` trait for the struct.
62///
63/// ### Usage
64///
65/// ```ignore
66/// use bancho_packets::{ReadPacket, WritePacket, PacketLength};
67///
68/// #[derive(Debug, Clone, ReadPacket, WritePacket, PacketLength)]
69/// /// The [`ScoreFrame`] uploaded by the bancho client during multiplayer games.
70/// pub struct ScoreFrame {
71///     pub timestamp: i32,
72///     pub id: u8,
73///     pub n300: u16,
74///     pub n100: u16,
75///     pub n50: u16,
76///     pub geki: u16,
77///     pub katu: u16,
78///     pub miss: u16,
79///     pub score: i32,
80///     pub combo: u16,
81///     pub max_combo: u16,
82///     pub perfect: bool,
83///     pub hp: u8,
84///     pub tag_byte: u8,
85///     pub score_v2: bool,
86/// }
87/// ```
88pub fn derive_write_packet(input: TokenStream) -> TokenStream {
89    let input = parse_macro_input!(input as DeriveInput);
90    let name = input.ident;
91
92    let fields = match input.data {
93        syn::Data::Struct(data) => data.fields,
94        _ => panic!("#[derive(WritePacket)] is only defined for structs."),
95    };
96
97    let field_names = fields.iter().map(|field| &field.ident);
98
99    let expanded = quote! {
100        impl BanchoPacketWrite for #name {
101            #[inline]
102            fn write_into_buf(self, buf: &mut Vec<u8>) {
103                buf.extend(data!(
104                    #(self.#field_names,)*
105                ));
106            }
107        }
108    };
109
110    expanded.into()
111}
112
113#[proc_macro_derive(PacketLength, attributes(length))]
114/// This derive macro will implement the `BanchoPacketLength` trait for the struct.
115///
116/// ### Usage
117///
118/// ```ignore
119/// use bancho_packets::{PacketLength};
120///
121/// #[derive(Debug, Clone, PacketLength)]
122/// /// [`MatchData`] is the data of bancho client multiplayer game room.
123/// pub struct MatchData {
124///     pub match_id: i32,
125///     pub in_progress: bool,
126///     pub match_type: i8,
127///     pub play_mods: u32,
128///     pub match_name: String,
129///     #[length(self.password.as_ref().map(|pw| pw.packet_len()).unwrap_or(2))]
130///     pub password: Option<String>,
131///     pub beatmap_name: String,
132///     pub beatmap_id: i32,
133///     pub beatmap_md5: String,
134///     pub slot_status: Vec<u8>,
135///     pub slot_teams: Vec<u8>,
136///     pub slot_players: Vec<i32>,
137///     pub host_player_id: i32,
138///     pub match_game_mode: u8,
139///     pub win_condition: u8,
140///     pub team_type: u8,
141///     pub freemods: bool,
142///     pub player_mods: Vec<i32>,
143///     pub match_seed: i32,
144/// }
145/// ```
146pub fn derive_packet_length(input: TokenStream) -> TokenStream {
147    let input = parse_macro_input!(input as DeriveInput);
148    let name = input.ident;
149
150    let mut fields = match input.data {
151        syn::Data::Struct(data) => data.fields,
152        _ => panic!("#[derive(PacketLength)] is only defined for structs."),
153    };
154
155    let mut extra = Vec::new();
156
157    let field_names = fields
158        .iter_mut()
159        .filter(|field| {
160            for attr in field.attrs.iter() {
161                if attr.path.is_ident(&format_ident!("length")) {
162                    extra.push(attr.tokens.clone());
163                    return false;
164                }
165            }
166            true
167        })
168        .enumerate()
169        .map(|(i, field)| match &field.ident {
170            Some(ident) => ident.clone(),
171            None => Ident::new(&i.to_string(), Span::call_site()),
172        })
173        .collect::<Vec<_>>();
174
175    let expanded = quote! {
176        impl BanchoPacketLength for #name {
177            #[inline]
178            fn packet_len(&self) -> usize {
179                let mut len = 0;
180                #(len += self.#field_names.packet_len();)*
181                #(len += #extra;)*
182                len
183            }
184        }
185    };
186
187    expanded.into()
188}