diff --git a/psopacket/src/lib.rs b/psopacket/src/lib.rs index c89fb31..a767a66 100644 --- a/psopacket/src/lib.rs +++ b/psopacket/src/lib.rs @@ -404,6 +404,108 @@ pub fn pso_packet(attr: TokenStream, item: TokenStream) -> TokenStream { q.into() } +fn generate_psomessage_impl(msg_cmd: u8, name: syn::Ident, attrs: &Vec) -> proc_macro2::TokenStream { + let from_bytes = generate_from_bytes(&attrs); + let as_bytes = generate_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::WrongPacketCommand); + } + + if len != size as usize * 4 - 2 { + return Err(PacketParseError::WrongPacketSize(size as u16 * 4, len)); + } + + let mut cur = std::io::Cursor::new(subbuf); + let result = Ok(#name { + #(#from_bytes)* + }); + + 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() as u8 + 2) / 4); + 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); diff --git a/src/lib.rs b/src/lib.rs index 6b63ce3..f43ba13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ +#![allow(incomplete_features)] #![feature(const_generics)] +#![feature(seek_convenience)] pub mod crypto; pub mod packet; @@ -117,7 +119,8 @@ pub trait PSOPacket: std::fmt::Debug { #[cfg(test)] mod test { use super::*; - use psopacket::{pso_packet, PSOPacketData}; + use psopacket::{pso_packet, pso_message, PSOPacketData}; + use crate::packet::messages::PSOMessage; #[test] fn test_basic_pso_packet() { @@ -462,4 +465,63 @@ mod test { b: 456, }); } + + #[test] + fn test_pso_message() { + #[pso_message(0x23)] + struct Test { + a: u32, + b: f32, + } + + let test = Test { + client: 1, + target: 2, + a: 123, + b: 4.56, + }; + + let mut bytes = test.as_bytes(); + assert!(bytes == vec![35, 3, 1, 2, 123, 0, 0, 0, 133, 235, 145, 64]); + + bytes[6] = 2; + let test2 = Test::from_bytes(&mut std::io::Cursor::new(bytes)).unwrap(); + assert!(test2 == Test { + client: 1, + target: 2, + a: 131195, + b: 4.56, + }); + } + + #[test] + fn test_pso_message_non_4_byte_size() { + #[pso_message(0x23)] + struct Test { + a: u32, + b: f32, + c: u8, + } + + let test = Test { + client: 1, + target: 2, + a: 123, + b: 4.56, + c: 5, + }; + + let mut bytes = test.as_bytes(); + assert!(bytes == vec![35, 4, 1, 2, 123, 0, 0, 0, 133, 235, 145, 64, 5, 0, 0, 0]); + + bytes[6] = 2; + let test2 = Test::from_bytes(&mut std::io::Cursor::new(bytes)).unwrap(); + assert!(test2 == Test { + client: 1, + target: 2, + a: 131195, + b: 4.56, + c: 5, + }); + } } diff --git a/src/packet/messages.rs b/src/packet/messages.rs new file mode 100644 index 0000000..e8d4903 --- /dev/null +++ b/src/packet/messages.rs @@ -0,0 +1,45 @@ +use std::io::{Seek, SeekFrom}; + +use psopacket::pso_message; +use crate::{PSOPacketData, PacketParseError}; + + +pub trait PSOMessage { + const CMD: u8; + fn from_bytes(cur: &mut R) -> Result where Self: Sized; + fn as_bytes(&self) -> Vec; +} + + +#[pso_message(0x40)] +pub struct PlayerWalking { + x: f32, + y: f32, + z: f32, +} + + + +pub enum Message { + PlayerWalking(PlayerWalking), +} + +impl PSOPacketData for Message { + fn from_bytes(mut cur: &mut R) -> Result { + let mut byte = [0u8; 1]; + cur.read(&mut byte); + cur.seek(SeekFrom::Current(-1)); // Cursor doesn't implement Peek? + match byte[0] { + PlayerWalking::CMD => Ok(Message::PlayerWalking(PlayerWalking::from_bytes(&mut cur)?)), + _ => Err(PacketParseError::WrongPacketCommand), + } + } + fn as_bytes(&self) -> Vec { + Vec::new() + } +} + + + + + diff --git a/src/packet/mod.rs b/src/packet/mod.rs index 8ff687a..ff98604 100644 --- a/src/packet/mod.rs +++ b/src/packet/mod.rs @@ -1,3 +1,4 @@ pub mod login; pub mod patch; pub mod ship; +pub mod messages;