pso_packet proc_macro for misc things
This commit is contained in:
		
							parent
							
								
									ffca6f8b58
								
							
						
					
					
						commit
						53ef5a8efc
					
				| @ -5,3 +5,4 @@ authors = ["Jake Probst <jake.probst@gmail.com>"] | ||||
| edition = "2018" | ||||
| 
 | ||||
| [dependencies] | ||||
| psopacket = { path = "psopacket" } | ||||
							
								
								
									
										12
									
								
								psopacket/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								psopacket/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| [package] | ||||
| name = "psopacket" | ||||
| version = "1.0.0" | ||||
| authors = ["Jake Probst <jake.probst@gmail.com>"] | ||||
| edition = "2018" | ||||
| 
 | ||||
| [lib] | ||||
| proc-macro = true | ||||
| 
 | ||||
| [dependencies] | ||||
| syn = {version = "0.15", features=["full", "extra-traits", "parsing"]} | ||||
| quote = "0.6" | ||||
							
								
								
									
										255
									
								
								psopacket/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								psopacket/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<u8>) -> 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<u8> { | ||||
|                 let mut buf: Vec<u8> = Vec::new(); | ||||
|                 #flag_write | ||||
|                 #(#as_bytes)* | ||||
| 
 | ||||
|                 let pkt_len = buf.len() as u16; | ||||
|                 let mut prebuf: Vec<u8> = 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 | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								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<u8>) -> Result<Self, PacketParseError> where Self: Sized; | ||||
|     fn as_bytes(&self) -> Vec<u8>; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										2
									
								
								src/patch/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/patch/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| 
 | ||||
| mod packet; | ||||
							
								
								
									
										58
									
								
								src/patch/packet.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/patch/packet.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||
|         })) | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user