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}