Jake Probst
6 years ago
6 changed files with 343 additions and 6 deletions
-
1Cargo.toml
-
12psopacket/Cargo.toml
-
255psopacket/src/lib.rs
-
19src/lib.rs
-
2src/patch/mod.rs
-
58src/patch/packet.rs
@ -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" |
@ -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
|
|||
}
|
@ -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>;
|
|||
}
|
|||
|
|||
|
|||
|
@ -0,0 +1,2 @@ |
|||
|
|||
mod packet;
|
@ -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,
|
|||
}))
|
|||
}
|
|||
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue