diff --git a/protocol-generator/Cargo.toml b/protocol-generator/Cargo.toml index b8c85ae..9b325a4 100644 --- a/protocol-generator/Cargo.toml +++ b/protocol-generator/Cargo.toml @@ -15,3 +15,4 @@ serde = "1.0.120" serde_json = "1.0" handlebars = "3.5.2" heck = "0.3.2" +linked-hash-map = { version = "0.5.4", features = ["serde_impl"] } diff --git a/protocol-generator/src/data/input.rs b/protocol-generator/src/data/input.rs index 5918a30..68ca055 100644 --- a/protocol-generator/src/data/input.rs +++ b/protocol-generator/src/data/input.rs @@ -1,37 +1,38 @@ +use linked_hash_map::LinkedHashMap; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; #[derive(Debug, Deserialize)] pub struct Protocol { - handshaking: ProtocolState, - status: ProtocolState, - login: ProtocolState, + pub handshaking: ProtocolState, + pub status: ProtocolState, + pub login: ProtocolState, #[serde(rename = "play")] - game: ProtocolState, + pub game: ProtocolState, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ProtocolState { - to_client: ProtocolData, - to_server: ProtocolData, + pub to_client: ProtocolData, + pub to_server: ProtocolData, } #[derive(Debug, Deserialize)] -struct ProtocolData { - types: HashMap>, +pub struct ProtocolData { + pub types: LinkedHashMap>, } #[derive(Debug, Deserialize)] #[serde(untagged)] -enum Data { +pub enum Data { Type(String), Container(Vec), Mapper { #[serde(rename = "type")] mappings_type: String, - mappings: HashMap, + mappings: LinkedHashMap, }, Switch(Switch), List(Box), @@ -40,13 +41,13 @@ enum Data { #[derive(Debug, Deserialize)] #[serde(untagged)] -enum Container { +pub enum Container { Value { name: String, #[serde(rename = "type")] data: Data, }, - Array { + List { name: Option, #[serde(rename = "type")] data: Vec, @@ -55,7 +56,7 @@ enum Container { #[derive(Debug, Deserialize)] #[serde(untagged)] -enum Switch { +pub enum Switch { Empty { #[serde(rename = "compareTo")] compare_to: String, @@ -63,18 +64,18 @@ enum Switch { Value { #[serde(rename = "compareTo")] compare_to: String, - fields: HashMap, + fields: LinkedHashMap, }, List { #[serde(rename = "compareTo")] compare_to: String, - fields: HashMap>, + fields: LinkedHashMap>, }, } #[derive(Debug, Deserialize)] #[serde(untagged)] -enum List { +pub enum List { Empty { #[serde(rename = "countType")] count_type: String, @@ -85,7 +86,7 @@ enum List { #[serde(rename = "type")] list_type: Data, }, - Array { + List { #[serde(rename = "countType")] count_type: String, #[serde(rename = "type")] @@ -94,7 +95,7 @@ enum List { } #[derive(Debug, Deserialize)] -struct BitField { +pub struct BitField { name: String, size: usize, signed: bool, diff --git a/protocol-generator/src/data/output.rs b/protocol-generator/src/data/output.rs index 0f85027..a5b5680 100644 --- a/protocol-generator/src/data/output.rs +++ b/protocol-generator/src/data/output.rs @@ -52,13 +52,15 @@ impl Display for Bound { #[derive(Serialize)] pub struct Packet { + pub id: u8, pub name: String, pub fields: Vec, } impl Packet { - pub fn new(name: impl ToString, fields: Vec) -> Packet { + pub fn new(id: u8, name: impl ToString, fields: Vec) -> Packet { Packet { + id, name: name.to_string(), fields, } diff --git a/protocol-generator/src/main.rs b/protocol-generator/src/main.rs index 187011d..e1fb3f6 100644 --- a/protocol-generator/src/main.rs +++ b/protocol-generator/src/main.rs @@ -2,12 +2,15 @@ mod data; use crate::data::input; use handlebars::*; -use heck::SnakeCase; +use heck::{CamelCase, KebabCase, MixedCase, SnakeCase, TitleCase}; +use crate::data::input::{Container, Data, ProtocolData, ProtocolState}; use crate::data::output; -use crate::data::output::Bound; +use crate::data::output::{Bound, Packet, State}; +use linked_hash_map::LinkedHashMap; use serde::Serialize; use serde_json::json; +use std::collections::HashMap; use std::fs::File; use std::io::Write; use structopt::StructOpt; @@ -31,10 +34,35 @@ pub fn main() { let protocol_data_file = File::open(protocol_data_file_name).expect("Failed to open protocol data file"); - let protocol_data: input::Protocol = + let protocol_input: input::Protocol = serde_json::from_reader(protocol_data_file).expect("Failed to parse protocol data"); - println!("{:#?}", protocol_data) + let protocols = vec![ + ( + transform_protocol_state(State::Handshake, &protocol_input.handshaking), + State::Handshake, + ), + ( + transform_protocol_state(State::Status, &protocol_input.status), + State::Status, + ), + ( + transform_protocol_state(State::Login, &protocol_input.login), + State::Login, + ), + ( + transform_protocol_state(State::Game, &protocol_input.game), + State::Game, + ), + ]; + + for (protocol, state) in protocols { + let file_name = format!("{}.rs", state.to_string().to_lowercase()); + let file = File::create(file_name).expect("Failed to create file"); + + generate_rust_file(&protocol, &template_engine, &file) + .expect("Failed to generate rust file"); + } } fn create_template_engine() -> Handlebars<'static> { @@ -68,13 +96,81 @@ fn create_template_engine() -> Handlebars<'static> { template_engine } +fn transform_protocol_state(state: State, protocol_state: &ProtocolState) -> output::Protocol { + let server_bound_packets = transform_protocol_data(&protocol_state.to_server); + let client_bound_packets = transform_protocol_data(&protocol_state.to_client); + + output::Protocol { + state, + server_bound_packets, + client_bound_packets, + } +} + +fn transform_protocol_data(protocol_data: &ProtocolData) -> Vec { + let mut packets = vec![]; + + let reversed_packet_ids = protocol_data + .types + .get("packet") + .and_then(|d| d.get(1)) + .and_then(|d| match d { + Data::Container(data) => data.get(0), + _ => None, + }) + .and_then(|c| match c { + Container::List { data, .. } => data.get(1), + _ => None, + }) + .and_then(|d| match d { + Data::Mapper { mappings, .. } => Some(mappings), + _ => None, + }) + .expect("Failed to get packet ids"); + + let packet_ids: HashMap = reversed_packet_ids + .into_iter() + .map(|(k, v)| { + ( + v.clone(), + u8::from_str_radix(k.trim_start_matches("0x"), 16).expect("Invalid packet id"), + ) + }) + .collect(); + + for (unformatted_name, data) in protocol_data.types.iter() { + if !unformatted_name.starts_with("packet_") + || unformatted_name == "packet_legacy_server_list_ping" + { + continue; + } + + let no_prefix_name = unformatted_name.trim_start_matches("packet_"); + + let id = *packet_ids + .get(no_prefix_name) + .expect("Failed to get packet id"); + let name = no_prefix_name.to_camel_case(); + + let packet = Packet { + id, + name, + fields: vec![], + }; + + packets.push(packet); + } + + packets +} + #[derive(Serialize)] struct GenerateContext<'a> { packet_enum_name: String, packets: &'a Vec, } -pub fn generate_rust_file( +fn generate_rust_file( protocol: &output::Protocol, template_engine: &Handlebars, mut writer: W, diff --git a/protocol-generator/templates/packet_enum.hbs b/protocol-generator/templates/packet_enum.hbs index 7915ecc..5cc3ed9 100644 --- a/protocol-generator/templates/packet_enum.hbs +++ b/protocol-generator/templates/packet_enum.hbs @@ -1,15 +1,15 @@ - -pub enum {{protocol_enum_name}} { +{{~#if packets}} +pub enum {{packet_enum_name}} { {{~#each packets as |p|}} {{p.name}}{{#if p.fields}}({{p.name}}){{/if}}{{#unless @last}},{{/unless}} {{~/each}} } -impl {{protocol_enum_name}} { +impl {{packet_enum_name}} { pub fn get_type_id(&self) -> u8 { match self { {{~#each packets as |p|}} - Self::{{p.name}}{{#if p.fields}}(_){{/if}} => {{packet_id @index}}{{#unless @last}},{{/unless}} + Self::{{p.name}}{{#if p.fields}}(_){{/if}} => {{packet_id p.id}}{{#unless @last}},{{/unless}} {{~/each}} } } @@ -17,7 +17,7 @@ impl {{protocol_enum_name}} { pub fn decode(type_id: u8, reader: &mut R) -> Result { match type_id { {{~#each packets as |p|}} - {{@index}} => { + {{packet_id p.id}} => { {{~#if p.fields}} let {{snake_case p.name}} = {{p.name}}::decode(reader)?; @@ -48,3 +48,4 @@ impl {{protocol_enum_name}} { } {{/each~}} } +{{~/if}} \ No newline at end of file