From 5a2b7e81fa590b0fe78430136e453ed2c39cbcd0 Mon Sep 17 00:00:00 2001 From: Vladislavs Golubs Date: Sun, 14 Feb 2021 17:24:10 +0300 Subject: [PATCH] Add packet struct write --- Cargo.toml | 1 + protocol-generator/Cargo.toml | 17 +++ protocol-generator/src/error.rs | 12 ++ protocol-generator/src/frontend.rs | 138 ++++++++++++++++++ protocol-generator/src/main.rs | 5 + protocol-generator/src/templates.rs | 87 +++++++++++ .../templates/packet_struct.hbs | 19 +++ protocol-generator/test/packet_struct.txt | 27 ++++ 8 files changed, 306 insertions(+) create mode 100644 protocol-generator/Cargo.toml create mode 100644 protocol-generator/src/error.rs create mode 100644 protocol-generator/src/frontend.rs create mode 100644 protocol-generator/src/main.rs create mode 100644 protocol-generator/src/templates.rs create mode 100644 protocol-generator/templates/packet_struct.hbs create mode 100644 protocol-generator/test/packet_struct.txt diff --git a/Cargo.toml b/Cargo.toml index 4436a00..4faed3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ members = [ "protocol", "protocol-derive", + "protocol-generator", ] \ No newline at end of file diff --git a/protocol-generator/Cargo.toml b/protocol-generator/Cargo.toml new file mode 100644 index 0000000..5aa9928 --- /dev/null +++ b/protocol-generator/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "minecraft-protocol-generator" +version = "0.0.0" +authors = ["vagola "] +edition = "2018" +description = "Minecraft protocol generator" +license = "MIT" +homepage = "https://github.com/eihwaz/minecraft-protocol" +repository = "https://github.com/eihwaz/minecraft-protocol" +keywords = ["minecraft", "protocol", "packet", "io"] + +[dependencies] +serde = "1.0.120" +serde_json = "1.0" +handlebars = "3.5.2" +heck = "0.3.2" +protodef-parser = "0.1.0" diff --git a/protocol-generator/src/error.rs b/protocol-generator/src/error.rs new file mode 100644 index 0000000..0725b49 --- /dev/null +++ b/protocol-generator/src/error.rs @@ -0,0 +1,12 @@ +use handlebars::RenderError; + +#[derive(Debug)] +pub enum FrontendError { + TemplateRenderError { render_error: RenderError }, +} + +impl From for FrontendError { + fn from(render_error: RenderError) -> Self { + FrontendError::TemplateRenderError { render_error } + } +} diff --git a/protocol-generator/src/frontend.rs b/protocol-generator/src/frontend.rs new file mode 100644 index 0000000..38aac17 --- /dev/null +++ b/protocol-generator/src/frontend.rs @@ -0,0 +1,138 @@ +use crate::error::FrontendError; +use handlebars::Handlebars; +use serde::Serialize; +use std::io::Write; + +#[derive(Debug, Eq, PartialEq, Serialize)] +#[serde(tag = "type")] +pub enum DataType { + #[serde(rename(serialize = "bool"))] + Boolean, + #[serde(rename(serialize = "i8"))] + Byte, + #[serde(rename(serialize = "u8"))] + UnsignedByte, + #[serde(rename(serialize = "i16"))] + Short, + #[serde(rename(serialize = "u16"))] + UnsignedShort, + #[serde(rename(serialize = "i32"))] + Int { + var_int: bool, + }, + #[serde(rename(serialize = "u32"))] + UnsignedInt, + #[serde(rename(serialize = "i64"))] + Long { + var_long: bool, + }, + #[serde(rename(serialize = "u64"))] + UnsignedLong, + #[serde(rename(serialize = "f32"))] + Float, + #[serde(rename(serialize = "f64"))] + Double, + String { + max_length: u16, + }, + Uuid { + hyphenated: bool, + }, + #[serde(rename(serialize = "Vec"))] + ByteArray { + rest: bool, + }, + CompoundTag, + RefType { + ref_name: String, + }, +} + +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct PacketStruct { + pub name: String, + pub fields: Vec, +} + +impl PacketStruct { + pub fn new(name: impl ToString, fields: Vec) -> PacketStruct { + PacketStruct { + name: name.to_string(), + fields, + } + } +} + +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct Field { + pub name: String, + #[serde(flatten)] + pub data_type: DataType, +} + +impl Field { + pub fn new(name: impl ToString, data_type: DataType) -> Field { + Field { + name: name.to_string(), + data_type, + } + } +} + +fn write_packet_struct( + template_engine: &Handlebars, + packet_struct: PacketStruct, + write: &mut W, +) -> Result<(), FrontendError> { + template_engine.render_to_write("packet_struct", &packet_struct, write)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::frontend::{write_packet_struct, DataType, Field, PacketStruct}; + use crate::templates; + + #[test] + fn test_write_packet_struct() { + let template_engine = templates::create_template_engine("templates"); + + let fields = vec![ + Field::new("boolean", DataType::Boolean), + Field::new("byte", DataType::Byte), + Field::new("unsigned_byte", DataType::UnsignedByte), + Field::new("short", DataType::Short), + Field::new("unsigned_short", DataType::UnsignedShort), + Field::new("int", DataType::Int { var_int: false }), + Field::new("varint", DataType::Int { var_int: true }), + Field::new("unsigned_int", DataType::UnsignedInt), + Field::new("long", DataType::Long { var_long: false }), + Field::new("varlong", DataType::Long { var_long: true }), + Field::new("unsigned_long", DataType::UnsignedLong), + Field::new("float", DataType::Float), + Field::new("double", DataType::Double), + Field::new("string", DataType::String { max_length: 20 }), + Field::new("uuid", DataType::Uuid { hyphenated: false }), + Field::new("hyphenated", DataType::Uuid { hyphenated: true }), + Field::new("byte_array", DataType::ByteArray { rest: false }), + Field::new("rest", DataType::ByteArray { rest: true }), + Field::new("compound_tag", DataType::CompoundTag), + Field::new( + "ref", + DataType::RefType { + ref_name: "Chat".to_string(), + }, + ), + ]; + let packet_struct = PacketStruct::new("TestPacket", fields); + let mut vec = vec![]; + + write_packet_struct(&template_engine, packet_struct, &mut vec) + .expect("Failed to write packet struct"); + + let result = String::from_utf8(vec).expect("Failed to convert vec to string"); + + assert_eq!(result, include_str!("../test/packet_struct.txt")); + } +} diff --git a/protocol-generator/src/main.rs b/protocol-generator/src/main.rs new file mode 100644 index 0000000..a6bcd58 --- /dev/null +++ b/protocol-generator/src/main.rs @@ -0,0 +1,5 @@ +mod error; +mod frontend; +mod templates; + +pub fn main() {} diff --git a/protocol-generator/src/templates.rs b/protocol-generator/src/templates.rs new file mode 100644 index 0000000..738e35a --- /dev/null +++ b/protocol-generator/src/templates.rs @@ -0,0 +1,87 @@ +use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError}; +use heck::SnakeCase; + +pub fn create_template_engine(templates_folder: &str) -> Handlebars<'static> { + let mut template_engine = Handlebars::new(); + + template_engine.register_helper("snake_case", Box::new(format_snake_case)); + template_engine.register_helper("packet_id", Box::new(format_packet_id)); + template_engine.register_helper( + "protocol_version_module", + Box::new(format_protocol_version_module), + ); + template_engine.register_escape_fn(|s| s.to_owned()); + + register_template_file(&mut template_engine, templates_folder, "packet_struct"); + + template_engine +} + +fn register_template_file(template_engine: &mut Handlebars, templates_folder: &str, name: &str) { + let tpl_path = format!("{}/{}.hbs", templates_folder, name); + + template_engine + .register_template_file(name, tpl_path) + .expect("Failed to register template"); +} + +fn format_snake_case( + h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output, +) -> Result<(), RenderError> { + let str = h + .param(0) + .and_then(|v| v.value().as_str()) + .ok_or(RenderError::new( + "Param 0 with str type is required for snake case helper.", + ))? as &str; + + let snake_case_str = str.to_snake_case(); + + out.write(snake_case_str.as_ref())?; + Ok(()) +} + +fn format_packet_id( + h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output, +) -> Result<(), RenderError> { + let id = h + .param(0) + .and_then(|v| v.value().as_u64()) + .ok_or(RenderError::new( + "Param 0 with u64 type is required for packet id helper.", + ))? as u64; + + let packet_id_str = format!("{:#04X}", id); + + out.write(packet_id_str.as_ref())?; + Ok(()) +} + +fn format_protocol_version_module( + h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output, +) -> Result<(), RenderError> { + let version = h + .param(0) + .and_then(|v| v.value().as_str()) + .ok_or(RenderError::new( + "Param 0 with str type is required for packet id helper.", + ))? as &str; + + let formatted_protocol_module_version = + format!("v_{}", version.replace(".", "_").replace("-", "_")); + + out.write(formatted_protocol_module_version.as_ref())?; + Ok(()) +} diff --git a/protocol-generator/templates/packet_struct.hbs b/protocol-generator/templates/packet_struct.hbs new file mode 100644 index 0000000..0e42fb9 --- /dev/null +++ b/protocol-generator/templates/packet_struct.hbs @@ -0,0 +1,19 @@ +pub struct {{name}} { +{{~#each fields as |f|}} + {{~#if f.var_int}} + #[packet(with = "var_int")]{{/if}} + {{~#if f.var_long}} + #[packet(with = "var_long")]{{/if}} + {{~#if f.rest}} + #[packet(with = "rest")]{{/if}} + {{~#if f.hyphenated}} + #[packet(with = "uuid_hyp_str")]{{/if}} + {{~#if f.max_length}} + #[packet(max_length = {{f.max_length}})]{{/if}} + {{~#if (ne f.type "RefType")}} + pub {{f.name}}: {{f.type}}, + {{~else}} + pub {{f.name}}: {{f.ref_name}}, + {{~/if}} +{{~/each}} +} \ No newline at end of file diff --git a/protocol-generator/test/packet_struct.txt b/protocol-generator/test/packet_struct.txt new file mode 100644 index 0000000..0a20f6d --- /dev/null +++ b/protocol-generator/test/packet_struct.txt @@ -0,0 +1,27 @@ +pub struct TestPacket { + pub boolean: bool, + pub byte: i8, + pub unsigned_byte: u8, + pub short: i16, + pub unsigned_short: u16, + pub int: i32, + #[packet(with = "var_int")] + pub varint: i32, + pub unsigned_int: u32, + pub long: i64, + #[packet(with = "var_long")] + pub varlong: i64, + pub unsigned_long: u64, + pub float: f32, + pub double: f64, + #[packet(max_length = 20)] + pub string: String, + pub uuid: Uuid, + #[packet(with = "uuid_hyp_str")] + pub hyphenated: Uuid, + pub byte_array: Vec, + #[packet(with = "rest")] + pub rest: Vec, + pub compound_tag: CompoundTag, + pub ref: Chat, +} \ No newline at end of file