1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 parse_macro_input,
5 Attribute, DeriveInput, Error, Expr, Ident, Result,
6};
7
8#[proc_macro_derive(Packet, attributes(name, opcode))]
9pub fn derive_packet(input: TokenStream) -> TokenStream {
10 let input = parse_macro_input!(input as DeriveInput);
11
12 let opts = match PacketOptions::from_attrs(&input.attrs) {
13 Ok(o) => o,
14 Err(e) => return e.to_compile_error().into(),
15 };
16
17 let ident = &input.ident;
18 let generics = &input.generics;
19 let where_clause = &generics.where_clause;
20
21 let unpack_impl = if opts.has_br {
22 quote! {
23 impl #generics #ident #where_clause {
24 pub fn unpack(
25 packet: &mut crate::client::packet::Packet
26 ) -> ::anyhow::Result<Self>
27 where
28 Self: ::binrw::BinRead
29 + ::serde::Serialize
30 + crate::client::packet::ExtractMetadata,
31 {
32 let mut cursor =
33 ::std::io::Cursor::new(&packet.content.body);
34
35 let instance =
36 <Self as ::binrw::BinRead>::read(&mut cursor)?;
37
38 packet.set_offset_info(
39 crate::client::packet::ExtractMetadata
40 ::extract_metadata(&instance)
41 );
42
43 let json =
44 crate::client::packet
45 ::serialize_packet_json(&instance)?;
46
47 packet.set_json(json);
48 Ok(instance)
49 }
50 }
51 }
52 } else {
53 quote! {}
54 };
55
56 let pack_impl = if opts.has_bw {
57 let name = match &opts.name {
58 Some(n) => n.clone(),
59 None => {
60 return Error::new_spanned(
61 ident,
62 "Missing #[name(...)] for outgoing packet",
63 )
64 .to_compile_error()
65 .into();
66 }
67 };
68
69 let opcode = match &opts.opcode {
70 Some(o) => normalize_opcode(o.clone()),
71 None => {
72 return Error::new_spanned(
73 ident,
74 "Missing #[opcode(...)] for outgoing packet",
75 )
76 .to_compile_error()
77 .into();
78 }
79 };
80
81 let name_lit =
82 syn::LitStr::new(&name.to_string(), name.span());
83
84 quote! {
85 impl #generics #ident #where_clause {
86 pub const PACKET_NAME: &'static str = #name_lit;
87
88 pub fn pack_with(
89 &self,
90 name: &str,
91 opcode: crate::client::packet::PacketOpcode,
92 ) -> ::anyhow::Result<
93 crate::client::packet::Packet
94 >
95 where
96 Self: ::binrw::BinWrite
97 + ::serde::Serialize
98 + crate::client::packet::ExtractMetadata,
99 for<'a>
100 <Self as ::binrw::BinWrite>
101 ::Args<'a>: Default,
102 {
103 let mut buffer =
104 ::std::io::Cursor::new(Vec::new());
105
106 ::binrw::BinWrite::write_args(
107 self,
108 &mut buffer,
109 <<Self as ::binrw::BinWrite>
110 ::Args<'_>>::default(),
111 )?;
112
113 let json =
114 crate::client::packet
115 ::serialize_packet_json(self)?;
116
117 let mut packet =
118 crate::client::packet::Packet::default();
119
120 packet.set_opcode(opcode);
121 packet.set_type(
122 crate::client::packet::PacketType::Outgoing
123 );
124 packet.set_offset_info(
125 crate::client::packet::ExtractMetadata
126 ::extract_metadata(self)
127 );
128 packet.set_packet_name(name.to_string());
129 packet.set_body(buffer.into_inner());
130 packet.set_json(json);
131
132 Ok(packet)
133 }
134
135 pub fn pack(
136 &self
137 ) -> ::anyhow::Result<
138 crate::client::packet::Packet
139 > {
140 self.pack_with(
141 Self::PACKET_NAME,
142 #opcode
143 )
144 }
145 }
146 }
147 } else {
148 quote! {}
149 };
150
151 quote! {
152 #unpack_impl
153 #pack_impl
154 }
155 .into()
156}
157
158struct PacketOptions {
159 has_br: bool,
160 has_bw: bool,
161 name: Option<Ident>,
162 opcode: Option<Expr>,
163}
164
165impl PacketOptions {
166 fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
167 let mut has_br = false;
168 let mut has_bw = false;
169 let mut name = None;
170 let mut opcode = None;
171
172 for attr in attrs {
173 if attr.path().is_ident("br") {
174 has_br = true;
175 }
176
177 if attr.path().is_ident("bw") {
178 has_bw = true;
179 }
180
181 if attr.path().is_ident("name") {
182 name = Some(attr.parse_args()?);
183 }
184
185 if attr.path().is_ident("opcode") {
186 opcode = Some(attr.parse_args()?);
187 }
188 }
189
190 if has_br && has_bw {
191 return Err(Error::new(
192 proc_macro2::Span::call_site(),
193 "Packet cannot be both #[br(...)] and #[bw(...)]",
194 ));
195 }
196
197 if !has_br && !has_bw {
198 return Err(Error::new(
199 proc_macro2::Span::call_site(),
200 "Packet requires either #[br(...)] or #[bw(...)]",
201 ));
202 }
203
204 Ok(Self {
205 has_br,
206 has_bw,
207 name,
208 opcode,
209 })
210 }
211}
212
213fn normalize_opcode(expr: Expr) -> Expr {
214 if let Expr::Call(call) = &expr {
215 if let Expr::Path(path) = &*call.func {
216 if let Some(id) = path.path.get_ident() {
217 let name = id.to_string();
218 let ok = matches!(
219 name.as_str(),
220 "U8" | "U16" | "U32" | "U64" | "Text" | "Raw"
221 );
222
223 if ok && call.args.len() == 1 {
224 let arg = call.args.first().cloned().unwrap();
225 let v = Ident::new(&name, id.span());
226 return syn::parse_quote! {
227 crate::client::packet
228 ::PacketOpcode::#v(#arg)
229 };
230 }
231 }
232 }
233 }
234
235 expr
236}