use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::{ self, braced, parse::{Parse, ParseStream, Result}, parse_macro_input, Data, DeriveInput, FieldsNamed, Ident, LitInt, Token, }; fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream { match data { syn::Data::Struct(syn::DataStruct { fields, .. }) => { let FieldsNamed { named, .. } = match fields { syn::Fields::Named(f) => f, _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), }; let read_fields = named .iter() .map(|f| { let field_name = &f.ident; let field_type = &f.ty; // do a different buf.write_* for each field depending on the type // if it's a string, use buf.write_string match field_type { syn::Type::Path(_) => { if f.attrs.iter().any(|a| a.path.is_ident("var")) { quote! { let #field_name = crate::mc_buf::McBufVarReadable::var_read_into(buf)?; } } else { quote! { let #field_name = crate::mc_buf::McBufReadable::read_into(buf)?; } } } _ => panic!( "Error reading field {}: {}", field_name.clone().unwrap(), field_type.to_token_stream() ), } }) .collect::>(); let read_field_names = named.iter().map(|f| &f.ident).collect::>(); quote! { impl crate::mc_buf::McBufReadable for #ident { fn read_into(buf: &mut impl std::io::Read) -> Result { #(#read_fields)* Ok(#ident { #(#read_field_names: #read_field_names),* }) } } } } syn::Data::Enum(syn::DataEnum { variants, .. }) => { let mut match_contents = quote!(); let mut variant_discrim: u32 = 0; for variant in variants { let variant_name = &variant.ident; match &variant.discriminant.as_ref() { Some(d) => { variant_discrim = match &d.1 { syn::Expr::Lit(e) => match &e.lit { syn::Lit::Int(i) => i.base10_parse().unwrap(), _ => panic!("Error parsing enum discriminant"), }, _ => panic!("Error parsing enum discriminant"), } } None => { variant_discrim += 1; } } match_contents.extend(quote! { #variant_discrim => Ok(Self::#variant_name), }); } quote! { impl crate::mc_buf::McBufReadable for #ident { fn read_into(buf: &mut impl std::io::Read) -> Result { let id = crate::mc_buf::McBufVarReadable::var_read_into(buf)?; match id { #match_contents _ => Err(format!("Unknown enum variant {}", id)), } } } } } _ => panic!("#[derive(*Packet)] can only be used on structs"), } } fn create_impl_mcbufwritable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream { match data { syn::Data::Struct(syn::DataStruct { fields, .. }) => { let FieldsNamed { named, .. } = match fields { syn::Fields::Named(f) => f, _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), }; let write_fields = named .iter() .map(|f| { let field_name = &f.ident; let field_type = &f.ty; // do a different buf.write_* for each field depending on the type // if it's a string, use buf.write_string match field_type { syn::Type::Path(_) => { if f.attrs.iter().any(|attr| attr.path.is_ident("var")) { quote! { crate::mc_buf::McBufVarWritable::var_write_into(&self.#field_name, buf)?; } } else { quote! { crate::mc_buf::McBufWritable::write_into(&self.#field_name, buf)?; } } } _ => panic!( "Error writing field {}: {}", field_name.clone().unwrap(), field_type.to_token_stream() ), } }) .collect::>(); quote! { impl crate::mc_buf::McBufWritable for #ident { fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { #(#write_fields)* Ok(()) } } } } syn::Data::Enum(syn::DataEnum { .. }) => { quote! { impl crate::mc_buf::McBufWritable for #ident { fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { crate::mc_buf::Writable::write_varint(buf, *self as i32) } } } } _ => panic!("#[derive(*Packet)] can only be used on structs"), } } #[proc_macro_derive(McBufReadable, attributes(var))] pub fn derive_mcbufreadable(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); create_impl_mcbufreadable(&ident, &data).into() } #[proc_macro_derive(McBufWritable, attributes(var))] pub fn derive_mcbufwritable(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); create_impl_mcbufwritable(&ident, &data).into() } #[proc_macro_derive(McBuf, attributes(var))] pub fn derive_mcbuf(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); let writable = create_impl_mcbufwritable(&ident, &data); let readable = create_impl_mcbufreadable(&ident, &data); quote! { #writable #readable } .into() } fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); let fields = match &data { syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, _ => panic!("#[derive(*Packet)] can only be used on structs"), }; let FieldsNamed { named: _, .. } = match fields { syn::Fields::Named(f) => f, _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), }; let _mcbufreadable_impl = create_impl_mcbufreadable(&ident, &data); let _mcbufwritable_impl = create_impl_mcbufwritable(&ident, &data); let contents = quote! { impl #ident { pub fn get(self) -> #state { #state::#ident(self) } pub fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { crate::mc_buf::McBufWritable::write_into(self, buf) } pub fn read( buf: &mut impl std::io::Read, ) -> Result<#state, String> { use crate::mc_buf::McBufReadable; Ok(Self::read_into(buf)?.get()) } } }; contents.into() } #[proc_macro_derive(GamePacket, attributes(var))] pub fn derive_game_packet(input: TokenStream) -> TokenStream { as_packet_derive(input, quote! {crate::packets::game::GamePacket}) } #[proc_macro_derive(HandshakePacket, attributes(var))] pub fn derive_handshake_packet(input: TokenStream) -> TokenStream { as_packet_derive(input, quote! {crate::packets::handshake::HandshakePacket}) } #[proc_macro_derive(LoginPacket, attributes(var))] pub fn derive_login_packet(input: TokenStream) -> TokenStream { as_packet_derive(input, quote! {crate::packets::login::LoginPacket}) } #[proc_macro_derive(StatusPacket, attributes(var))] pub fn derive_status_packet(input: TokenStream) -> TokenStream { as_packet_derive(input, quote! {crate::packets::status::StatusPacket}) } #[derive(Debug)] struct PacketIdPair { id: u32, module: Ident, name: Ident, } #[derive(Debug)] struct PacketIdMap { packets: Vec, } impl Parse for PacketIdMap { fn parse(input: ParseStream) -> Result { let mut packets = vec![]; // example: // 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket, // 0x0e while let Ok(packet_id) = input.parse::() { let packet_id = packet_id.base10_parse::()?; // : input.parse::()?; // clientbound_change_difficulty_packet let module: Ident = input.parse()?; // :: input.parse::()?; // ClientboundChangeDifficultyPacket let name: Ident = input.parse()?; packets.push(PacketIdPair { id: packet_id, module, name, }); if input.parse::().is_err() { break; } } Ok(PacketIdMap { packets }) } } #[derive(Debug)] struct DeclareStatePackets { name: Ident, serverbound: PacketIdMap, clientbound: PacketIdMap, } impl Parse for DeclareStatePackets { fn parse(input: ParseStream) -> Result { let name = input.parse()?; input.parse::()?; let serverbound_token: Ident = input.parse()?; if serverbound_token != "Serverbound" { return Err(syn::Error::new( serverbound_token.span(), "Expected `Serverbound`", )); } input.parse::]>()?; let content; braced!(content in input); let serverbound = content.parse()?; input.parse::()?; let clientbound_token: Ident = input.parse()?; if clientbound_token != "Clientbound" { return Err(syn::Error::new( clientbound_token.span(), "Expected `Clientbound`", )); } input.parse::]>()?; let content; braced!(content in input); let clientbound = content.parse()?; Ok(DeclareStatePackets { name, serverbound, clientbound, }) } } #[proc_macro] pub fn declare_state_packets(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeclareStatePackets); let state_name = input.name; let state_name_litstr = syn::LitStr::new(&state_name.to_string(), state_name.span()); let mut enum_contents = quote!(); let mut id_match_contents = quote!(); let mut write_match_contents = quote!(); let mut serverbound_read_match_contents = quote!(); let mut clientbound_read_match_contents = quote!(); for PacketIdPair { id, module, name } in input.serverbound.packets { enum_contents.extend(quote! { #name(#module::#name), }); id_match_contents.extend(quote! { #state_name::#name(_packet) => #id, }); write_match_contents.extend(quote! { #state_name::#name(packet) => packet.write(buf), }); serverbound_read_match_contents.extend(quote! { #id => #module::#name::read(buf)?, }); } for PacketIdPair { id, module, name } in input.clientbound.packets { // let name_litstr = syn::LitStr::new(&name.to_string(), name.span()); enum_contents.extend(quote! { #name(#module::#name), }); id_match_contents.extend(quote! { #state_name::#name(_packet) => #id, }); write_match_contents.extend(quote! { #state_name::#name(packet) => packet.write(buf), }); clientbound_read_match_contents.extend(quote! { #id => #module::#name::read(buf)?, }); } quote! { #[derive(Clone, Debug)] pub enum #state_name where Self: Sized, { #enum_contents } impl crate::packets::ProtocolPacket for #state_name { fn id(&self) -> u32 { match self { #id_match_contents } } fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { match self { #write_match_contents } } /// Read a packet by its id, ConnectionProtocol, and flow fn read( id: u32, flow: &crate::connect::PacketFlow, buf: &mut impl std::io::Read, ) -> Result<#state_name, String> where Self: Sized, { Ok(match flow { crate::connect::PacketFlow::ServerToClient => match id { #clientbound_read_match_contents _ => return Err(format!("Unknown ServerToClient {} packet id: {}", #state_name_litstr, id)), }, crate::connect::PacketFlow::ClientToServer => match id { #serverbound_read_match_contents _ => return Err(format!("Unknown ClientToServer {} packet id: {}", #state_name_litstr, id)), }, }) } } } .into() }