mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-24 06:51:30 +11:00
227 lines
6.8 KiB
Rust
227 lines
6.8 KiB
Rust
|
use std::{collections::HashMap, env, fs, path::Path, process::Command};
|
||
|
|
||
|
use anyhow::Context;
|
||
|
use proc_macro2::TokenStream;
|
||
|
use quote::quote;
|
||
|
use serde::Deserialize;
|
||
|
|
||
|
#[derive(Deserialize)]
|
||
|
struct Packet {
|
||
|
name: String,
|
||
|
side: String,
|
||
|
state: String,
|
||
|
id: i32,
|
||
|
}
|
||
|
|
||
|
pub fn main() -> anyhow::Result<()> {
|
||
|
let packets: Vec<Packet> = serde_json::from_str(include_str!("../../extracted/packets.json"))?;
|
||
|
|
||
|
write_packets(&packets)?;
|
||
|
write_transformer(&packets)?;
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn write_packets(packets: &Vec<Packet>) -> anyhow::Result<()> {
|
||
|
let mut consts = TokenStream::new();
|
||
|
|
||
|
let len = packets.len();
|
||
|
|
||
|
let mut p: Vec<TokenStream> = Vec::new();
|
||
|
|
||
|
for packet in packets {
|
||
|
let name = packet.name.strip_suffix("Packet").unwrap_or(&packet.name);
|
||
|
// lowercase the last character of name
|
||
|
let name = {
|
||
|
let mut chars = name.chars();
|
||
|
let last_char = chars.next_back().unwrap();
|
||
|
let last_char = last_char.to_lowercase().to_string();
|
||
|
let mut name = chars.collect::<String>();
|
||
|
name.push_str(&last_char);
|
||
|
name
|
||
|
};
|
||
|
|
||
|
// if the packet is clientbound, but the name does not ends with S2c, add it
|
||
|
let name = if packet.side == "clientbound" && !name.ends_with("S2c") {
|
||
|
format!("{}S2c", name)
|
||
|
} else {
|
||
|
name
|
||
|
};
|
||
|
|
||
|
// same for serverbound
|
||
|
let name = if packet.side == "serverbound" && !name.ends_with("C2s") {
|
||
|
format!("{}C2s", name)
|
||
|
} else {
|
||
|
name
|
||
|
};
|
||
|
|
||
|
let id = packet.id;
|
||
|
let side = match &packet.side {
|
||
|
s if s == "clientbound" => quote! { crate::packet_registry::PacketSide::Clientbound },
|
||
|
s if s == "serverbound" => quote! { crate::packet_registry::PacketSide::Serverbound },
|
||
|
_ => unreachable!(),
|
||
|
};
|
||
|
|
||
|
let state = match &packet.state {
|
||
|
s if s == "handshaking" => quote! { crate::packet_registry::PacketState::Handshaking },
|
||
|
s if s == "status" => quote! { crate::packet_registry::PacketState::Status },
|
||
|
s if s == "login" => quote! { crate::packet_registry::PacketState::Login },
|
||
|
s if s == "play" => quote! { crate::packet_registry::PacketState::Play },
|
||
|
_ => unreachable!(),
|
||
|
};
|
||
|
|
||
|
// const STD_PACKETS = [PacketSide::Client(PacketState::Handshaking(Packet{..})), ..];
|
||
|
p.push(quote! {
|
||
|
crate::packet_registry::Packet {
|
||
|
id: #id,
|
||
|
side: #side,
|
||
|
state: #state,
|
||
|
timestamp: None,
|
||
|
name: #name,
|
||
|
data: None,
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
consts.extend([quote! {
|
||
|
pub const STD_PACKETS: [crate::packet_registry::Packet; #len] = [
|
||
|
#(#p),*
|
||
|
];
|
||
|
}]);
|
||
|
|
||
|
write_generated_file(consts, "packets.rs")?;
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn write_transformer(packets: &[Packet]) -> anyhow::Result<()> {
|
||
|
// HashMap<side, HashMap<state, Vec<name>>>
|
||
|
let grouped_packets = HashMap::<String, HashMap<String, Vec<String>>>::new();
|
||
|
|
||
|
let mut grouped_packets = packets.iter().fold(grouped_packets, |mut acc, packet| {
|
||
|
let side = match packet.side.as_str() {
|
||
|
"serverbound" => "Serverbound".to_string(),
|
||
|
"clientbound" => "Clientbound".to_string(),
|
||
|
_ => panic!("Invalid side"),
|
||
|
};
|
||
|
|
||
|
let state = match packet.state.as_str() {
|
||
|
"handshaking" => "Handshaking".to_string(),
|
||
|
"status" => "Status".to_string(),
|
||
|
"login" => "Login".to_string(),
|
||
|
"play" => "Play".to_string(),
|
||
|
_ => panic!("Invalid state"),
|
||
|
};
|
||
|
|
||
|
let name = packet
|
||
|
.name
|
||
|
.strip_suffix("Packet")
|
||
|
.unwrap_or(&packet.name)
|
||
|
.to_string();
|
||
|
|
||
|
// lowercase the last character of name
|
||
|
let name = {
|
||
|
let mut chars = name.chars();
|
||
|
let last_char = chars.next_back().unwrap();
|
||
|
let last_char = last_char.to_lowercase().to_string();
|
||
|
let mut name = chars.collect::<String>();
|
||
|
name.push_str(&last_char);
|
||
|
name
|
||
|
};
|
||
|
|
||
|
// if the packet is clientbound, but the name does not ends with S2c, add it
|
||
|
let name = if side == "Clientbound" && !name.ends_with("S2c") {
|
||
|
format!("{}S2c", name)
|
||
|
} else {
|
||
|
name
|
||
|
};
|
||
|
|
||
|
// same for serverbound
|
||
|
let name = if side == "Serverbound" && !name.ends_with("C2s") {
|
||
|
format!("{}C2s", name)
|
||
|
} else {
|
||
|
name
|
||
|
};
|
||
|
|
||
|
let state_map = acc.entry(side).or_insert_with(HashMap::new);
|
||
|
let id_map = state_map.entry(state).or_insert_with(Vec::new);
|
||
|
id_map.push(name);
|
||
|
|
||
|
acc
|
||
|
});
|
||
|
|
||
|
let mut generated = TokenStream::new();
|
||
|
|
||
|
for (side, state_map) in grouped_packets.iter_mut() {
|
||
|
let mut side_arms = TokenStream::new();
|
||
|
for (state, id_map) in state_map.iter_mut() {
|
||
|
let mut match_arms = TokenStream::new();
|
||
|
|
||
|
for name in id_map.iter_mut() {
|
||
|
let name = syn::parse_str::<syn::Ident>(name).unwrap();
|
||
|
|
||
|
match_arms.extend(quote! {
|
||
|
#name::ID => {
|
||
|
format!("{:#?}", #name::decode(&mut data).unwrap())
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
let state = syn::parse_str::<syn::Ident>(state).unwrap();
|
||
|
|
||
|
side_arms.extend(quote! {
|
||
|
PacketState::#state => match packet.id {
|
||
|
#match_arms
|
||
|
_ => NOT_AVAILABLE.to_string(),
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if side == "Clientbound" {
|
||
|
side_arms.extend(quote! {
|
||
|
_ => NOT_AVAILABLE.to_string(),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
let side = syn::parse_str::<syn::Ident>(side).unwrap();
|
||
|
|
||
|
generated.extend(quote! {
|
||
|
PacketSide::#side => match packet.state {
|
||
|
#side_arms
|
||
|
},
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// wrap generated in a function definition
|
||
|
let generated = quote! {
|
||
|
const NOT_AVAILABLE: &str = "Not yet implemented";
|
||
|
|
||
|
pub fn packet_to_string(packet: &ProxyPacket) -> String {
|
||
|
let bytes = packet.data.as_ref().unwrap();
|
||
|
let mut data = &bytes.clone()[..];
|
||
|
|
||
|
match packet.side {
|
||
|
#generated
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
write_generated_file(generated, "packet_to_string.rs")?;
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
pub fn write_generated_file(content: TokenStream, out_file: &str) -> anyhow::Result<()> {
|
||
|
let out_dir = env::var_os("OUT_DIR").context("failed to get OUT_DIR env var")?;
|
||
|
let path = Path::new(&out_dir).join(out_file);
|
||
|
let code = content.to_string();
|
||
|
|
||
|
fs::write(&path, code)?;
|
||
|
|
||
|
// Try to format the output for debugging purposes.
|
||
|
// Doesn't matter if rustfmt is unavailable.
|
||
|
let _ = Command::new("rustfmt").arg(path).output();
|
||
|
|
||
|
Ok(())
|
||
|
}
|