From 53ef5a8efc2e7e52b944d003ca8d70d7bf475b66 Mon Sep 17 00:00:00 2001 From: Jake Probst Date: Thu, 6 Jun 2019 16:46:23 -0700 Subject: [PATCH] pso_packet proc_macro for misc things --- Cargo.toml | 1 + psopacket/Cargo.toml | 12 ++ psopacket/src/lib.rs | 255 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 21 +++- src/patch/mod.rs | 2 + src/patch/packet.rs | 58 ++++++++++ 6 files changed, 343 insertions(+), 6 deletions(-) create mode 100644 psopacket/Cargo.toml create mode 100644 psopacket/src/lib.rs create mode 100644 src/patch/mod.rs create mode 100644 src/patch/packet.rs diff --git a/Cargo.toml b/Cargo.toml index 018f605..7fb578b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,4 @@ authors = ["Jake Probst "] edition = "2018" [dependencies] +psopacket = { path = "psopacket" } \ No newline at end of file diff --git a/psopacket/Cargo.toml b/psopacket/Cargo.toml new file mode 100644 index 0000000..72cb2bd --- /dev/null +++ b/psopacket/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "psopacket" +version = "1.0.0" +authors = ["Jake Probst "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +syn = {version = "0.15", features=["full", "extra-traits", "parsing"]} +quote = "0.6" diff --git a/psopacket/src/lib.rs b/psopacket/src/lib.rs new file mode 100644 index 0000000..c6cc653 --- /dev/null +++ b/psopacket/src/lib.rs @@ -0,0 +1,255 @@ +#![recursion_limit="128"] + +extern crate proc_macro; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, ItemStruct}; +use quote::quote; + +#[proc_macro_attribute] +pub fn pso_packet(attr: TokenStream, item: TokenStream) -> TokenStream { + let arg = parse_macro_input!(attr as syn::LitInt); + let pkt_cmd = arg.value() as u16; + + let parsed = parse_macro_input!(item as ItemStruct); + + let mut has_flag: bool = false; + let mut from_bytes = Vec::new(); + let mut as_bytes = Vec::new(); + let mut dbg_write_vars = Vec::new(); + let mut partialeq = Vec::new(); + + for (i, f) in parsed.fields.iter().enumerate() { + if let Some(ident) = &f.ident { + let ident_str = ident.to_string(); + + match &f.ty { + syn::Type::Array(arr) => { + let array_length = if let syn::Expr::Lit(lit) = &arr.len { + if let syn::Lit::Int(int) = &lit.lit { + int.value() as usize + } + else { + return syn::Error::new(arr.bracket_token.span, "unknown array size").to_compile_error().into(); + } + } else { + return syn::Error::new(arr.bracket_token.span, "unknown array size").to_compile_error().into(); + }; + match *arr.elem { + syn::Type::Path(ref path) => { + dbg_write_vars.push(quote! { + write!(f, " {}: {:?}\n", #ident_str, self.#ident.iter()).unwrap(); + }); + as_bytes.push(quote! { + for f in self.#ident.iter() { + buf.extend_from_slice(&f.to_le_bytes()) + } + }); + let ty = path.path.segments[0].ident.to_string(); + match ty.as_str() { + "u8" => { + from_bytes.push(quote! { + #ident: { + let mut b: [u8; #array_length] = [0; #array_length]; + if let Ok(len) = cur.read(&mut b) { + if len != #array_length { + return Err(PacketParseError::NotEnoughBytes); + } + } + else { + return Err(PacketParseError::NotEnoughBytes); + }; + b + }, + }); + }, + _ => { + return syn::Error::new(path.path.segments[0].ident.span(), "type not supported") + .to_compile_error().into(); + } + } + partialeq.push(quote! { + if self.#ident[..] != other.#ident[..] { + return false; + } + }); + } + _ => { + panic!("why"); + } + } + }, + syn::Type::Path(path) => { + dbg_write_vars.push(quote! { + write!(f, " {}: {:?}\n", #ident_str, self.#ident).unwrap(); + }); + as_bytes.push(quote! { + buf.extend_from_slice(&self.#ident.to_le_bytes()); + }); + let ty = path.path.segments[0].ident.to_string(); + match ty.as_str() { + "u8" => { + from_bytes.push(quote! { + #ident: { + let mut b: [u8; 1] = [0; 1]; + if let Ok(len) = cur.read(&mut b) { + if len != 1 { + return Err(PacketParseError::NotEnoughBytes); + } + } + else { + return Err(PacketParseError::NotEnoughBytes); + }; + b[0] + }, + }); + }, + "u16" => { + from_bytes.push(quote! { + #ident: { + let mut b: [u8; 2] = [0; 2]; + if let Ok(len) = cur.read(&mut b) { + if len != 2 { + return Err(PacketParseError::NotEnoughBytes); + } + } + else { + return Err(PacketParseError::NotEnoughBytes); + }; + u16::from_le_bytes(b) + }, + }); + }, + "u32" => { + if ident_str == "flag" { + if i != 0 { + return syn::Error::new(ident.span(), "flag must be first member of struct").to_compile_error().into(); + } + has_flag = true; + continue; + } + from_bytes.push(quote! { + #ident: { + let mut b: [u8; 4] = [0; 4]; + if let Ok(len) = cur.read(&mut b) { + if len != 4 { + return Err(PacketParseError::NotEnoughBytes); + } + } + else { + return Err(PacketParseError::NotEnoughBytes); + }; + u32::from_le_bytes(b) + }, + }); + }, + _ => { + return syn::Error::new(path.path.segments[0].ident.span(), "type not supported") + .to_compile_error().into(); + } + } + partialeq.push(quote! { + if self.#ident != other.#ident { + return false; + } + }); + } + _ => { + } + } + } + } + + let this_struct = parsed.ident.clone(); + let this_struct_str = this_struct.to_string(); + + let flag_write = if has_flag { + quote! { + buf.extend_from_slice(&u32::to_le_bytes(self.flag)); + } + } + else { + quote! { + buf.extend_from_slice(&u32::to_le_bytes(0)); + } + }; + + let psopacket = quote! { + impl PSOPacket for #this_struct { + fn from_bytes(data: &Vec) -> Result<#this_struct, PacketParseError> { + // TODO: assert cmd is correct + let mut cur = std::io::Cursor::new(data); + cur.seek(SeekFrom::Start(2)); + let mut b: [u8; 2] = [0; 2]; + cur.read(&mut b); + let cmd = u16::from_le_bytes(b); + + if cmd != #pkt_cmd { + return Err(PacketParseError::WrongPacketCommand); + } + + if #has_flag { + cur.seek(SeekFrom::Start(4)); + } + else { + cur.seek(SeekFrom::Start(8)); + } + Ok(#this_struct { + #(#from_bytes)* + }) + } + fn as_bytes(&self) -> Vec { + let mut buf: Vec = Vec::new(); + #flag_write + #(#as_bytes)* + + let pkt_len = buf.len() 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)); + prebuf.append(&mut buf); + + prebuf + } + } + }; + + + let psopacket_debug = quote! { + impl std::fmt::Debug for #this_struct { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "packet {} {{\n", #this_struct_str).unwrap(); + #(#dbg_write_vars)* + write!(f, "}}") + } + } + }; + + + let psopacket_partialeq = quote! { + impl std::cmp::PartialEq for #this_struct { + fn eq(&self, other: &Self) -> bool { + #(#partialeq)* + true + } + } + }; + + let q = quote! { + #parsed + #psopacket + #psopacket_debug + #psopacket_partialeq + }; + + //println!("[[[{}]]]", q.to_string()); + + q.into() +} + + +#[proc_macro_attribute] +pub fn game_command(attr: TokenStream, item: TokenStream) -> TokenStream { + item +} diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..3a99b69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,16 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } +mod patch; + +#[derive(Debug, PartialEq)] +pub enum PacketParseError { + NotEnoughBytes, + WrongPacketCommand, } + + +pub trait PSOPacket { + fn from_bytes(data: &Vec) -> Result where Self: Sized; + fn as_bytes(&self) -> Vec; +} + + + diff --git a/src/patch/mod.rs b/src/patch/mod.rs new file mode 100644 index 0000000..3b40f1c --- /dev/null +++ b/src/patch/mod.rs @@ -0,0 +1,2 @@ + +mod packet; diff --git a/src/patch/packet.rs b/src/patch/packet.rs new file mode 100644 index 0000000..768bea5 --- /dev/null +++ b/src/patch/packet.rs @@ -0,0 +1,58 @@ +use psopacket::pso_packet; +use crate::{PSOPacket, PacketParseError}; + +use std::io::{Read, Seek, SeekFrom}; + + +#[pso_packet(0x02)] +struct PatchWelcome { + copyright: [u8; 44], + padding: [u8; 20], + server_key: u32, + client_key: u32, +} + +impl PatchWelcome { + fn new(server_key: u32, client_key: u32) -> PatchWelcome { + PatchWelcome { + copyright: b"Patch Server. Copyright SonicTeam, LTD. 2001".clone(), + padding: [0; 20], + server_key: server_key, + client_key: client_key, + } + } +} + +enum PatchPackets { + PatchWelcome(PatchWelcome) +} + +#[cfg(test)] +mod tests { + #[test] + fn patch_welcome() { + use super::PSOPacket; + + let pkt = super::PatchWelcome::new(123, 456); + + assert!(pkt.as_bytes() == vec![0x4C, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x74, 0x63, 0x68, 0x20, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x2E, 0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, + 0x53, 0x6F, 0x6E, 0x69, 0x63, 0x54, 0x65, 0x61, 0x6D, 0x2C, 0x20, 0x4C, 0x54, 0x44, 0x2E, 0x20, + 0x32, 0x30, 0x30, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0xC8, 0x01, 0x00, 0x00, + ]); + + + let mut bytes = pkt.as_bytes(); + bytes.splice(32..41, b"Elsewhere".iter().cloned()); + + let new_pkt = super::PatchWelcome::from_bytes(&bytes); + + assert!(new_pkt == Ok(super::PatchWelcome { + copyright: b"Patch Server. Copyright Elsewhere, LTD. 2001".clone(), + padding: [0; 20], + server_key: 123, + client_key: 456, + })) + } +}