Skip to main content

meta_packet/
lib.rs

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}