// TODO: try less hard, just mem::transmute all this junk away // TODO: PSOPacketData derive #![recursion_limit="256"] extern crate proc_macro; use proc_macro::TokenStream; use syn::{parse_macro_input, ItemStruct, NestedMeta, DeriveInput, Field}; use syn::punctuated::Iter; use quote::quote; #[derive(Debug, PartialEq)] enum AttrMeta { None, Utf8, Utf16, NoDebug, LengthIs(syn::Ident), LengthOf(syn::Ident), } #[derive(Debug)] enum AttrType { Value(syn::TypePath, syn::Ident, AttrMeta), Array(syn::TypePath, syn::Ident, usize, AttrMeta) } fn generate_struct_def(name: syn::Ident, attrs: &Vec) -> proc_macro2::TokenStream { let mut struct_def = Vec::new(); for attr in attrs { let element = match attr { AttrType::Value(ty, name, _) => { quote!(pub #name: #ty) }, AttrType::Array(ty, name, len, _) => { quote!(pub #name: [#ty; #len]) } }; struct_def.push(element); } quote! { pub struct #name { #(#struct_def),* } } } fn generate_from_bytes(attrs: &Vec) -> Vec { let mut from_bytes = Vec::new(); for attr in attrs { let element = match attr { AttrType::Value(ty, name, _) => { let type_str = ty.path.segments[0].ident.to_string(); if type_str == "Vec" { let vec_type = match &ty.path.segments[0].arguments { syn::PathArguments::AngleBracketed(arg) => { match &arg.args[0] { syn::GenericArgument::Type(typ) => { match &typ { syn::Type::Path(path) => { Some(path.path.segments[0].ident.clone()) } _ => None } } _ => None, } } _ => None }.unwrap(); quote! { #name: { let mut tmp = Vec::new(); for _ in 0..flag { tmp.push(<#vec_type as PSOPacketData>::from_bytes(&mut cur)?); } tmp } } } else { quote! { #name: <#ty as PSOPacketData>::from_bytes(&mut cur)?, } } }, AttrType::Array(ty, name, len, _) => { quote! { #name: { let mut arr = [#ty::default(); #len]; for e in arr.iter_mut() { *e = #ty::from_bytes(&mut cur)? } arr }, } } }; from_bytes.push(element); } from_bytes } fn generate_as_bytes(attrs: &Vec) -> Vec { let mut as_bytes = Vec::new(); for attr in attrs { let element = match attr { AttrType::Value(ty, name, _) => { let type_str = ty.path.segments[0].ident.to_string(); if type_str == "Vec" { quote! { flag = self.#name.len() as u32; for i in self.#name.iter() { buf.extend_from_slice(&PSOPacketData::as_bytes(i)); } } } else { quote! { buf.extend_from_slice(&PSOPacketData::as_bytes(&self.#name)); } } }, AttrType::Array(_ty, name, len, _) => { quote! { for i in 0..#len { buf.extend_from_slice(&self.#name[i].as_bytes()); } } } }; as_bytes.push(element); } as_bytes } fn generate_psopacket_impl(pkt_cmd: u16, name: syn::Ident, attrs: &Vec, include_flag: bool) -> proc_macro2::TokenStream { let from_bytes = generate_from_bytes(&attrs); let as_bytes = generate_as_bytes(&attrs); quote! { impl PSOPacket for #name { fn from_bytes(data: &[u8]) -> Result<#name, PacketParseError> { let mut cur = std::io::Cursor::new(data); let mut b: [u8; 2] = [0; 2]; cur.read(&mut b).unwrap(); let len = u16::from_le_bytes(b); cur.read(&mut b).unwrap(); let cmd = u16::from_le_bytes(b); let mut f: [u8; 4] = [0; 4]; let flag = if #include_flag { cur.read(&mut f).unwrap(); u32::from_le_bytes(f) } else { 0 }; if cmd != #pkt_cmd { return Err(PacketParseError::WrongPacketCommand {expected: #pkt_cmd, got: cmd}); } if len as usize != data.len() { return Err(PacketParseError::WrongPacketSize(len, data.len())); } let result = Ok(#name { #(#from_bytes)* }); if cur.position() as usize != data.len() { return Err(PacketParseError::DataStructNotLargeEnough(cur.position(), data.len())); } result } fn as_bytes(&self) -> Vec { let mut buf = Vec::new(); let mut flag = 0; #(#as_bytes)* while buf.len() % 4 != 0 { buf.push(0); } let pkt_len = (buf.len() + if #include_flag { 8 } else { 4 }) as u16; let mut prebuf: Vec = Vec::new(); prebuf.extend_from_slice(&u16::to_le_bytes(pkt_len)); prebuf.extend_from_slice(&u16::to_le_bytes(#pkt_cmd)); if #include_flag { prebuf.extend_from_slice(&u32::to_le_bytes(flag)); } prebuf.append(&mut buf); prebuf } } } } fn generate_debug_impl(name: syn::Ident, attrs: &Vec) -> proc_macro2::TokenStream { let dbg_write = attrs .iter() .map(|attr| { match attr { AttrType::Value(ty, name, meta) => { let ident_str = name.to_string(); let type_str = ty.path.segments[0].ident.to_string(); match meta { AttrMeta::NoDebug => quote! { .field(&format!("{} [{}]", #ident_str, #type_str), &format_args!("[...]")) }, _ => quote! { .field(&format!("{} [{}]", #ident_str, #type_str), &self.#name) } } }, AttrType::Array(ty, name, len, meta) => { let ident_str = name.to_string(); let type_str = ty.path.segments[0].ident.to_string(); match meta { AttrMeta::Utf8 => quote! { .field(&format!("{} [utf8; {}]", #ident_str, #len), match std::str::from_utf8(&self.#name) { Ok(ref s) => s, Err(_) => &self.#name }) }, AttrMeta::Utf16 => quote! { .field(&format!("{} [utf16; {}]", #ident_str, #len), match std::str::from_utf16(&self.#name) { Ok(ref s) => s, Err(_) => &self.#name }) }, AttrMeta::NoDebug => quote! { .field(&format!("{} [{}; {}]", #ident_str, #type_str, #len), &format_args!("[...]")) }, _ => quote! { .field(&format!("{} [{}; {}]", #ident_str, #type_str, #len), &format_args!("{:?}", &self.#name)) } } } } }) .collect::>(); let name_str = name.to_string(); quote! { impl std::fmt::Debug for #name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct(#name_str) #(#dbg_write)* .finish() } } } } fn generate_partialeq_impl(name: syn::Ident, attrs: &Vec) -> proc_macro2::TokenStream { let mut partialeq = Vec::new(); for attr in attrs { let element = match attr { AttrType::Value(_, name, _) => { quote! { if self.#name!= other.#name { return false; } } }, AttrType::Array(_, name, _, _) => { quote! { if self.#name[..] != other.#name[..] { return false; } } } }; partialeq.push(element); } quote! { impl std::cmp::PartialEq for #name { fn eq(&self, other: &Self) -> bool { #(#partialeq)* true } } } } fn get_struct_fields(fields: Iter) -> Result, TokenStream> { let mut attrs = Vec::new(); let mut must_be_last = false; for field in fields { if must_be_last { return Err(syn::Error::new(field.ident.as_ref().unwrap().span(), "variables can not follow Vec or String").to_compile_error().into()); } let mut attr_meta = AttrMeta::None; for attr in &field.attrs { attr_meta = match attr.path.segments[0].ident.to_string().as_str() { "utf8" => AttrMeta::Utf8, "utf16" => AttrMeta::Utf16, "nodebug" => AttrMeta::NoDebug, "length_is" => AttrMeta::LengthIs(attr.parse_args::().unwrap()), "length_of" => AttrMeta::LengthOf(attr.parse_args::().unwrap()), _ => AttrMeta::None } } match &field.ty { syn::Type::Array(ty) => { if let (syn::Type::Path(ref ty), syn::Expr::Lit(ref lit)) = (&*ty.elem, &ty.len) { if let syn::Lit::Int(ref int) = lit.lit { attrs.push(AttrType::Array(ty.clone(), field.ident.as_ref().unwrap().clone(), int.base10_parse().unwrap(), attr_meta )) } } }, syn::Type::Path(ty) => { let type_str = ty.path.segments[0].ident.to_string(); if type_str == "String" || type_str == "Vec"{ must_be_last = true; } attrs.push(AttrType::Value(ty.clone(), field.ident.as_ref().unwrap().clone(), attr_meta)) }, _ => {} } } Ok(attrs) } #[proc_macro_attribute] pub fn pso_packet(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr as syn::AttributeArgs); let mut cmd = 0; let mut flag = true; let mut manual_flag = false; for a in args { match &a { NestedMeta::Lit(lit) => { if let syn::Lit::Int(litint) = lit { cmd = litint.base10_parse().unwrap(); } }, NestedMeta::Meta(k) => { if let syn::Meta::Path(syn::Path {segments, ..}) = k { match segments[0].ident.to_string().as_str() { "no_flag" => flag = false, "manual_flag" => { flag = false; manual_flag = true; }, _ => { return syn::Error::new(segments[0].ident.span(), "unknown macro param").to_compile_error().into(); } } } }, } } let pkt_struct = parse_macro_input!(item as ItemStruct); let attrs = match get_struct_fields(pkt_struct.fields.iter()) { Ok(a) => a, Err(err) => return err }; if manual_flag { match &attrs[0] { AttrType::Array(_, ident, _, _) => { if ident.to_string() != "flag" { return syn::Error::new(pkt_struct.ident.span(), "struct must have flag as the first field if manual_flag is set").to_compile_error().into(); } }, AttrType::Value(_, ident, _) => { if ident.to_string() != "flag" { return syn::Error::new(pkt_struct.ident.span(), "struct must have flag as the first field if manual_flag is set").to_compile_error().into(); } } } } let struct_def = generate_struct_def(pkt_struct.ident.clone(), &attrs); let psopacket_impl = generate_psopacket_impl(cmd, pkt_struct.ident.clone(), &attrs, flag); let debug_impl = generate_debug_impl(pkt_struct.ident.clone(), &attrs); let partialeq_impl = generate_partialeq_impl(pkt_struct.ident.clone(), &attrs); let q = quote!{ #[derive(Clone)] #struct_def #psopacket_impl #debug_impl #partialeq_impl }; q.into() } fn generate_psomessage_from_bytes(attrs: &Vec) -> (Vec, Vec) { //let mut from_bytes = Vec::new(); let mut assignment = Vec::new(); let mut structure_creation = Vec::new(); for attr in attrs { let (assignment_element, creation_element) = match attr { AttrType::Value(ty, name, meta) => { let temp_name = syn::Ident::new(&("_".to_string() + &name.to_string()), name.span()); let type_str = ty.path.segments[0].ident.to_string(); if type_str == "Vec" { let vec_type = match &ty.path.segments[0].arguments { syn::PathArguments::AngleBracketed(arg) => { match &arg.args[0] { syn::GenericArgument::Type(typ) => { match &typ { syn::Type::Path(path) => { Some(path.path.segments[0].ident.clone()) } _ => None } } _ => None, } } _ => None }.unwrap(); let length_variable = match meta { AttrMeta::LengthIs(ident) => syn::Ident::new(&("_".to_string() + &ident.to_string()), ident.span()), _ => panic!("{} does not have a length specified", name), }; let assignment = quote! { let mut #temp_name = Vec::new(); for _ in 0..#length_variable { #temp_name.push(<#vec_type as PSOPacketData>::from_bytes(&mut cur)?); } }; let creation = quote! { #name: #temp_name, }; (assignment, creation) } else { let assignment = quote! { let #temp_name = <#ty as PSOPacketData>::from_bytes(&mut cur)?; }; let creation = quote! { #name: #temp_name, }; (assignment, creation) } }, AttrType::Array(ty, name, len, _) => { let temp_name = syn::Ident::new(&("_".to_string() + &name.to_string()), name.span()); let assignment = quote! { let #temp_name = { let mut arr = [#ty::default(); #len]; for e in arr.iter_mut() { *e = #ty::from_bytes(&mut cur)? } arr }; }; let creation = quote! { #name: #temp_name, }; (assignment, creation) } }; assignment.push(assignment_element); structure_creation.push(creation_element); } (assignment, structure_creation) } fn generate_psomessage_as_bytes(attrs: &Vec) -> Vec { let mut as_bytes = Vec::new(); for attr in attrs { let element = match attr { AttrType::Value(ty, name, meta) => { let type_str = ty.path.segments[0].ident.to_string(); if type_str == "Vec" { quote! { for i in self.#name.iter() { buf.extend_from_slice(&PSOPacketData::as_bytes(i)); } } } else { if let AttrMeta::LengthOf(ident) = meta { quote! { buf.extend_from_slice(&PSOPacketData::as_bytes(&(self.#ident.len() as #ty))); } } else { quote! { buf.extend_from_slice(&PSOPacketData::as_bytes(&self.#name)); } } } }, AttrType::Array(_ty, name, len, _) => { quote! { for i in 0..#len { buf.extend_from_slice(&self.#name[i].as_bytes()); } } } }; as_bytes.push(element); } as_bytes } fn generate_psomessage_impl(msg_cmd: u8, name: syn::Ident, attrs: &Vec) -> proc_macro2::TokenStream { let (assignment, struct_creation)= generate_psomessage_from_bytes(&attrs); let as_bytes = generate_psomessage_as_bytes(&attrs); quote! { impl PSOMessage for #name { const CMD: u8 = #msg_cmd; fn from_bytes(mut cur: &mut R) -> Result<#name, PacketParseError> { let mut buf1 = [0u8; 1]; cur.read(&mut buf1).unwrap(); let cmd = buf1[0]; cur.read(&mut buf1).unwrap(); let size = buf1[0]; let mut subbuf = vec![0u8; size as usize * 4 - 2]; let len = cur.read(&mut subbuf).unwrap(); if cmd != #msg_cmd { return Err(PacketParseError::WrongMessageCommand {expected: #msg_cmd, got: cmd}); } if len != size as usize * 4 - 2 { return Err(PacketParseError::WrongPacketSize(size as u16 * 4, len)); } let mut cur = std::io::Cursor::new(subbuf); #(#assignment)* let result = Ok(#name { #(#struct_creation)* }); result } fn as_bytes(&self) -> Vec { let mut buf = Vec::new(); #(#as_bytes)* while buf.len() % 4 != 2 { buf.push(0); } let mut fullbuf = Vec::new(); fullbuf.push(#msg_cmd); fullbuf.push(((buf.len() + 2) / 4) as u8); fullbuf.extend_from_slice(&mut buf); fullbuf } } } } #[proc_macro_attribute] pub fn pso_message(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr as syn::AttributeArgs); let mut cmd = 0; for a in args { if let NestedMeta::Lit(lit) = a { if let syn::Lit::Int(litint) = lit { cmd = litint.base10_parse().unwrap(); } } } let pkt_struct = parse_macro_input!(item as ItemStruct); let mut attrs = match get_struct_fields(pkt_struct.fields.iter()) { Ok(a) => a, Err(err) => return err }; // this is a lot of work to make a `u8` token, surely this can be easier? let mut punctuated: syn::punctuated::Punctuated = syn::punctuated::Punctuated::new(); punctuated.push_value(syn::PathSegment { ident: syn::Ident::new("u8", proc_macro2::Span::call_site()), arguments: syn::PathArguments::None, }); let u8tpath = syn::TypePath { qself: None, path: syn::Path { leading_colon: None, segments: punctuated } }; attrs.insert(0, AttrType::Value(u8tpath.clone(), syn::Ident::new("target", proc_macro2::Span::call_site()), AttrMeta::None)); attrs.insert(0, AttrType::Value(u8tpath, syn::Ident::new("client", proc_macro2::Span::call_site()), AttrMeta::None)); let struct_def = generate_struct_def(pkt_struct.ident.clone(), &attrs); let psopacket_impl = generate_psomessage_impl(cmd, pkt_struct.ident.clone(), &attrs); let debug_impl = generate_debug_impl(pkt_struct.ident.clone(), &attrs); let partialeq_impl = generate_partialeq_impl(pkt_struct.ident.clone(), &attrs); let q = quote!{ #[derive(Clone)] #struct_def #psopacket_impl #debug_impl #partialeq_impl }; q.into() } #[proc_macro_derive(PSOPacketData)] pub fn pso_packet_data(input: TokenStream) -> TokenStream { let derive = parse_macro_input!(input as DeriveInput); let name = derive.ident; let fields = if let syn::Data::Struct(strct) = derive.data { strct.fields } else { return syn::Error::new(name.span(), "PSOPacketData only works on structs").to_compile_error().into(); }; let attrs = match get_struct_fields(fields.iter()) { Ok(a) => a, Err(err) => return err }; let from_bytes = generate_from_bytes(&attrs); let as_bytes = generate_as_bytes(&attrs); let impl_pso_data_packet = quote! { impl PSOPacketData for #name { fn from_bytes(mut cur: &mut R) -> Result { Ok(#name { #(#from_bytes)* }) } fn as_bytes(&self) -> Vec { let mut buf = Vec::new(); #(#as_bytes)* buf } } }; let partialeq = generate_partialeq_impl(name.clone(), &attrs); let debug = generate_debug_impl(name, &attrs); let q = quote! { #impl_pso_data_packet #partialeq #debug }; q.into() }