This commit is contained in:
Vladislavs Golubs 2021-02-08 23:37:01 +03:00
parent 5245cdf16d
commit ebd8f5ce22
7 changed files with 406 additions and 381 deletions

View File

@ -2,23 +2,23 @@ use linked_hash_map::LinkedHashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Protocol {
pub handshaking: ProtocolState,
pub status: ProtocolState,
pub login: ProtocolState,
pub struct ProtocolHandler {
pub handshaking: Protocol,
pub status: Protocol,
pub login: Protocol,
#[serde(rename = "play")]
pub game: ProtocolState,
pub game: Protocol,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProtocolState {
pub to_client: ProtocolData,
pub to_server: ProtocolData,
pub struct Protocol {
pub to_client: Packets,
pub to_server: Packets,
}
#[derive(Debug, Deserialize)]
pub struct ProtocolData {
pub struct Packets {
pub types: LinkedHashMap<String, Vec<Data>>,
}

View File

@ -1,2 +0,0 @@
pub mod input;
pub mod output;

View File

@ -1,18 +1,19 @@
mod data;
use crate::data::input;
use handlebars::*;
use heck::{CamelCase, SnakeCase};
use crate::data::input::{Container, Data, ProtocolData, ProtocolState};
use crate::data::output;
use serde::Serialize;
use serde_json::json;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use crate::mappings::CodeMappings;
use crate::transformer::transform_protocol;
use handlebars::*;
use heck::SnakeCase;
use serde::Serialize;
use serde_json::json;
use structopt::StructOpt;
pub mod backend;
pub mod frontend;
pub mod mappings;
pub mod transformer;
#[derive(StructOpt)]
#[structopt(name = "protocol-generator")]
struct Opt {
@ -32,25 +33,31 @@ pub fn main() {
let protocol_data_file =
File::open(protocol_data_file_name).expect("Failed to open protocol data file");
let protocol_input: input::Protocol =
let protocol_input: backend::ProtocolHandler =
serde_json::from_reader(protocol_data_file).expect("Failed to parse protocol data");
let mappings = CodeMappings {};
let protocols = vec![
(
transform_protocol_state(output::State::Handshake, &protocol_input.handshaking),
output::State::Handshake,
transform_protocol(
&mappings,
frontend::State::Handshake,
&protocol_input.handshaking,
),
frontend::State::Handshake,
),
(
transform_protocol_state(output::State::Status, &protocol_input.status),
output::State::Status,
transform_protocol(&mappings, frontend::State::Status, &protocol_input.status),
frontend::State::Status,
),
(
transform_protocol_state(output::State::Login, &protocol_input.login),
output::State::Login,
transform_protocol(&mappings, frontend::State::Login, &protocol_input.login),
frontend::State::Login,
),
(
transform_protocol_state(output::State::Game, &protocol_input.game),
output::State::Game,
transform_protocol(&mappings, frontend::State::Game, &protocol_input.game),
frontend::State::Game,
),
];
@ -97,264 +104,24 @@ fn create_template_engine() -> Handlebars<'static> {
template_engine
}
fn transform_protocol_state(
state: output::State,
protocol_state: &ProtocolState,
) -> output::Protocol {
let server_bound_packets = transform_protocol_data(
protocol_state,
&protocol_state.to_server,
output::Bound::Server,
);
let client_bound_packets = transform_protocol_data(
protocol_state,
&protocol_state.to_client,
output::Bound::Client,
);
output::Protocol {
state,
server_bound_packets,
client_bound_packets,
}
}
fn transform_protocol_data(
protocol_state: &ProtocolState,
protocol_data: &ProtocolData,
bound: output::Bound,
) -> Vec<output::Packet> {
let packet_ids = get_packet_ids(protocol_data);
let mut packets = vec![];
for (unformatted_name, data_vec) in protocol_data.types.iter() {
if !unformatted_name.starts_with("packet_")
|| unformatted_name == "packet_legacy_server_list_ping"
{
continue;
}
let no_prefix_unformatted = unformatted_name.trim_start_matches("packet_");
let id = *packet_ids
.get(no_prefix_unformatted)
.expect("Failed to get packet id");
let packet_name = rename_packet(
unformatted_name,
&no_prefix_unformatted.to_camel_case(),
&bound,
protocol_state,
);
let mut fields = vec![];
for data in data_vec {
if let Data::Container(container_vec) = data {
for container in container_vec {
match container {
Container::Value { name, data } => match transform_field(name, data) {
Some(field) => fields.push(modify_field(&packet_name, field)),
None => println!(
"[{}] Field \"{}\" are skipped ({:?}",
packet_name, name, data
),
},
Container::List { name, data_vec } => {
if let Some(name) = name {
for data in data_vec {
match transform_field(name, data) {
Some(field) => {
fields.push(modify_field(&packet_name, field))
}
None => println!(
"[{}] Field \"{}\" are skipped ({:?})",
packet_name, name, data_vec
),
}
}
}
}
}
}
}
}
let packet = output::Packet {
id,
name: packet_name,
fields,
};
packets.push(packet);
}
packets
}
fn get_packet_ids(protocol_data: &ProtocolData) -> HashMap<String, u8> {
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_vec, .. } => data_vec.get(1),
_ => None,
})
.and_then(|d| match d {
Data::Mapper { mappings, .. } => Some(mappings),
_ => None,
})
.expect("Failed to get packet ids");
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()
}
fn transform_field(unformatted_field_name: &str, data: &Data) -> Option<output::Field> {
match data {
Data::Type(name) => match transform_data_type(name) {
Some(data_type) => Some(output::Field {
name: format_field_name(unformatted_field_name),
data_type,
}),
None => None,
},
_ => None,
}
}
fn transform_data_type(name: &str) -> Option<output::DataType> {
match name {
"bool" => Some(output::DataType::Boolean),
"i8" => Some(output::DataType::Byte),
"i16" => Some(output::DataType::Short),
"i32" => Some(output::DataType::Int { var_int: false }),
"i64" => Some(output::DataType::Long { var_long: false }),
"u8" => Some(output::DataType::UnsignedByte),
"u16" => Some(output::DataType::UnsignedShort),
"f32" => Some(output::DataType::Float),
"f64" => Some(output::DataType::Double),
"varint" => Some(output::DataType::Int { var_int: true }),
"varlong" => Some(output::DataType::Long { var_long: true }),
"string" => Some(output::DataType::String { max_length: 0 }),
"nbt" | "optionalNbt" => Some(output::DataType::CompoundTag),
"UUID" => Some(output::DataType::Uuid { hyphenated: false }),
"buffer" => Some(output::DataType::ByteArray { rest: false }),
"restBuffer" => Some(output::DataType::ByteArray { rest: true }),
"position" => Some(output::DataType::RefType {
ref_name: "Position".to_string(),
}),
"slot" => Some(output::DataType::RefType {
ref_name: "Option<Slot>".to_string(),
}),
"entityMetadata" => Some(output::DataType::RefType {
ref_name: "Metadata".to_string(),
}),
"tags" => Some(output::DataType::RefType {
ref_name: "TagsMap".to_string(),
}),
"option" => None,
_ => {
println!("Unknown data type \"{}\"", name);
None
}
}
}
fn format_field_name(unformatted_field_name: &str) -> String {
if unformatted_field_name == "type" {
String::from("type_")
} else {
unformatted_field_name.to_snake_case()
}
}
fn rename_packet(
unformatted_name: &str,
name: &str,
bound: &output::Bound,
protocol_state: &ProtocolState,
) -> String {
let new_name = match (name, bound) {
("EncryptionBegin", output::Bound::Server) => "EncryptionResponse",
("EncryptionBegin", output::Bound::Client) => "EncryptionRequest",
("PingStart", output::Bound::Server) => "StatusRequest",
("Ping", output::Bound::Server) => "PingRequest",
("ServerInfo", output::Bound::Client) => "StatusResponse",
("Ping", output::Bound::Client) => "PingResponse",
("Login", output::Bound::Client) => "JoinGame",
_ => name,
}
.to_owned();
if new_name == name
&& protocol_state
.to_client
.types
.contains_key(unformatted_name)
&& protocol_state
.to_server
.types
.contains_key(unformatted_name)
{
bidirectional(&new_name, bound)
} else {
new_name.to_owned()
}
}
fn bidirectional(name: &str, bound: &output::Bound) -> String {
match bound {
output::Bound::Server => format!("ServerBound{}", name),
output::Bound::Client => format!("ClientBound{}", name),
}
}
fn modify_field(packet_name: &str, field: output::Field) -> output::Field {
match (packet_name, field.name.as_str()) {
("StatusResponse", "response") => field.change_type(output::DataType::RefType {
ref_name: "ServerStatus".to_owned(),
}),
("Success", "uuid") => field.change_type(output::DataType::Uuid { hyphenated: true }),
("Disconnect", "reason") => field.change_type(output::DataType::Chat),
("ClientBoundChat", "message") => field.change_type(output::DataType::Chat),
("ClientBoundChat", "position") => field.change_type(output::DataType::RefType {
ref_name: "MessagePosition".to_owned(),
}),
_ => field,
}
}
#[derive(Serialize)]
struct GenerateContext<'a> {
packet_enum_name: String,
packets: &'a Vec<output::Packet>,
packets: &'a Vec<frontend::Packet>,
}
fn generate_rust_file<W: Write>(
protocol: &output::Protocol,
protocol: &frontend::Protocol,
template_engine: &Handlebars,
mut writer: W,
) -> Result<(), TemplateRenderError> {
let server_bound_ctx = GenerateContext {
packet_enum_name: format!("{}{}BoundPacket", &protocol.state, output::Bound::Server),
packet_enum_name: format!("{}{}BoundPacket", &protocol.state, frontend::Bound::Server),
packets: &protocol.server_bound_packets,
};
let client_bound_ctx = GenerateContext {
packet_enum_name: format!("{}{}BoundPacket", &protocol.state, output::Bound::Client),
packet_enum_name: format!("{}{}BoundPacket", &protocol.state, frontend::Bound::Client),
packets: &protocol.client_bound_packets,
};

View File

@ -0,0 +1,65 @@
use crate::backend;
use crate::frontend;
pub trait Mappings {
fn rename_packet(
&self,
unformatted_name: &str,
name: &str,
bound: &frontend::Bound,
protocol: &backend::Protocol,
) -> String;
fn change_field_type(&self, packet_name: &str, field: frontend::Field) -> frontend::Field;
}
pub struct CodeMappings {}
impl Mappings for CodeMappings {
fn rename_packet(
&self,
unformatted_name: &str,
name: &str,
bound: &frontend::Bound,
protocol: &backend::Protocol,
) -> String {
let new_name = match (name, bound) {
("EncryptionBegin", frontend::Bound::Server) => "EncryptionResponse",
("EncryptionBegin", frontend::Bound::Client) => "EncryptionRequest",
("PingStart", frontend::Bound::Server) => "StatusRequest",
("Ping", frontend::Bound::Server) => "PingRequest",
("ServerInfo", frontend::Bound::Client) => "StatusResponse",
("Ping", frontend::Bound::Client) => "PingResponse",
("Login", frontend::Bound::Client) => "JoinGame",
_ => name,
}
.to_owned();
if new_name == name
&& protocol.to_client.types.contains_key(unformatted_name)
&& protocol.to_server.types.contains_key(unformatted_name)
{
match bound {
frontend::Bound::Server => format!("ServerBound{}", name),
frontend::Bound::Client => format!("ClientBound{}", name),
}
} else {
new_name.to_owned()
}
}
fn change_field_type(&self, packet_name: &str, field: frontend::Field) -> frontend::Field {
match (packet_name, field.name.as_str()) {
("StatusResponse", "response") => field.change_type(frontend::DataType::RefType {
ref_name: "ServerStatus".to_owned(),
}),
("Success", "uuid") => field.change_type(frontend::DataType::Uuid { hyphenated: true }),
("Disconnect", "reason") => field.change_type(frontend::DataType::Chat),
("ClientBoundChat", "message") => field.change_type(frontend::DataType::Chat),
("ClientBoundChat", "position") => field.change_type(frontend::DataType::RefType {
ref_name: "MessagePosition".to_owned(),
}),
_ => field,
}
}
}

View File

@ -0,0 +1,196 @@
use crate::mappings::Mappings;
use crate::{backend, frontend};
use heck::{CamelCase, SnakeCase};
use std::collections::HashMap;
pub fn transform_protocol<M: Mappings>(
mappings: &M,
state: frontend::State,
protocol: &backend::Protocol,
) -> frontend::Protocol {
let server_bound_packets = transform_packets(
mappings,
protocol,
&protocol.to_server,
frontend::Bound::Server,
);
let client_bound_packets = transform_packets(
mappings,
protocol,
&protocol.to_client,
frontend::Bound::Client,
);
frontend::Protocol {
state,
server_bound_packets,
client_bound_packets,
}
}
fn transform_packets<M: Mappings>(
mappings: &M,
protocol: &backend::Protocol,
packets: &backend::Packets,
bound: frontend::Bound,
) -> Vec<frontend::Packet> {
let packet_ids = get_packet_ids(packets);
let mut output_packets = vec![];
for (unformatted_name, data_vec) in packets.types.iter() {
if !unformatted_name.starts_with("packet_")
|| unformatted_name == "packet_legacy_server_list_ping"
{
continue;
}
let no_prefix_unformatted = unformatted_name.trim_start_matches("packet_");
let id = *packet_ids
.get(no_prefix_unformatted)
.expect("Failed to get packet id");
let packet_name = mappings.rename_packet(
unformatted_name,
&no_prefix_unformatted.to_camel_case(),
&bound,
protocol,
);
let mut fields = vec![];
for data in data_vec {
if let backend::Data::Container(container_vec) = data {
for container in container_vec {
match container {
backend::Container::Value { name, data } => {
match transform_field(&name, &data) {
Some(field) => {
fields.push(mappings.change_field_type(&packet_name, field))
}
None => println!(
"[{}] Field \"{}\" are skipped ({:?}",
packet_name, name, data
),
}
}
backend::Container::List { name, data_vec } => {
if let Some(name) = name {
for data in data_vec {
match transform_field(&name, &data) {
Some(field) => fields
.push(mappings.change_field_type(&packet_name, field)),
None => println!(
"[{}] Field \"{}\" are skipped ({:?})",
packet_name, name, data_vec
),
}
}
}
}
}
}
}
}
let packet = frontend::Packet {
id,
name: packet_name,
fields,
};
output_packets.push(packet);
}
output_packets
}
fn get_packet_ids(packets: &backend::Packets) -> HashMap<String, u8> {
let reversed_packet_ids = packets
.types
.get("packet")
.and_then(|d| d.get(1))
.and_then(|d| match d {
backend::Data::Container(data) => data.get(0),
_ => None,
})
.and_then(|c| match c {
backend::Container::List { data_vec, .. } => data_vec.get(1),
_ => None,
})
.and_then(|d| match d {
backend::Data::Mapper { mappings, .. } => Some(mappings),
_ => None,
})
.expect("Failed to get packet ids");
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()
}
fn transform_field(unformatted_field_name: &str, data: &backend::Data) -> Option<frontend::Field> {
match data {
backend::Data::Type(name) => match transform_data_type(name) {
Some(data_type) => Some(frontend::Field {
name: format_field_name(unformatted_field_name),
data_type,
}),
None => None,
},
_ => None,
}
}
fn transform_data_type(name: &str) -> Option<frontend::DataType> {
match name {
"bool" => Some(frontend::DataType::Boolean),
"i8" => Some(frontend::DataType::Byte),
"i16" => Some(frontend::DataType::Short),
"i32" => Some(frontend::DataType::Int { var_int: false }),
"i64" => Some(frontend::DataType::Long { var_long: false }),
"u8" => Some(frontend::DataType::UnsignedByte),
"u16" => Some(frontend::DataType::UnsignedShort),
"f32" => Some(frontend::DataType::Float),
"f64" => Some(frontend::DataType::Double),
"varint" => Some(frontend::DataType::Int { var_int: true }),
"varlong" => Some(frontend::DataType::Long { var_long: true }),
"string" => Some(frontend::DataType::String { max_length: 0 }),
"nbt" | "optionalNbt" => Some(frontend::DataType::CompoundTag),
"UUID" => Some(frontend::DataType::Uuid { hyphenated: false }),
"buffer" => Some(frontend::DataType::ByteArray { rest: false }),
"restBuffer" => Some(frontend::DataType::ByteArray { rest: true }),
"position" => Some(frontend::DataType::RefType {
ref_name: "Position".to_string(),
}),
"slot" => Some(frontend::DataType::RefType {
ref_name: "Option<Slot>".to_string(),
}),
"entityMetadata" => Some(frontend::DataType::RefType {
ref_name: "Metadata".to_string(),
}),
"tags" => Some(frontend::DataType::RefType {
ref_name: "TagsMap".to_string(),
}),
"option" => None,
_ => {
println!("Unknown data type \"{}\"", name);
None
}
}
}
fn format_field_name(unformatted_field_name: &str) -> String {
if unformatted_field_name == "type" {
String::from("type_")
} else {
unformatted_field_name.to_snake_case()
}
}

View File

@ -1,6 +1,4 @@
use crate::error::{DecodeError, EncodeError};
use crate::{impl_enum_encoder_decoder, Decoder, DecoderReadExt, Encoder, EncoderWriteExt};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::impl_enum_encoder_decoder;
use nbt::CompoundTag;
use num_derive::{FromPrimitive, ToPrimitive};
use std::io::{Read, Write};
@ -32,31 +30,6 @@ pub struct Position {
pub z: i32,
}
impl Encoder for Position {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
let encoded_x = (self.x & 0x3FFFFFF) as i64;
let encoded_y = (self.y & 0xFFF) as i64;
let encoded_z = (self.z & 0x3FFFFFF) as i64;
writer.write_i64::<BigEndian>((encoded_x << 38) | (encoded_z << 12) | encoded_y)?;
Ok(())
}
}
impl Decoder for Position {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
let encoded = reader.read_i64::<BigEndian>()?;
let x = (encoded >> 38) as i32;
let y = (encoded & 0xFFF) as i16;
let z = (encoded << 26 >> 38) as i32;
Ok(Position { x, y, z })
}
}
#[derive(Debug)]
pub struct Slot {
pub id: i32,
@ -64,86 +37,8 @@ pub struct Slot {
pub compound_tag: CompoundTag,
}
impl Encoder for Option<Slot> {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
match self {
Some(slot) => {
writer.write_bool(true)?;
slot.encode(writer)
}
None => writer.write_bool(false),
}
}
}
impl Decoder for Option<Slot> {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
if reader.read_bool()? {
Ok(Some(Slot::decode(reader)?))
} else {
Ok(None)
}
}
}
impl Encoder for Slot {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
writer.write_var_i32(self.id)?;
writer.write_u8(self.amount)?;
writer.write_compound_tag(&self.compound_tag)?;
Ok(())
}
}
impl Decoder for Slot {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
let id = reader.read_var_i32()?;
let amount = reader.read_u8()?;
let compound_tag = reader.read_compound_tag()?;
Ok(Slot {
id,
amount,
compound_tag,
})
}
}
#[derive(Debug)]
pub struct Metadata {}
impl Encoder for Metadata {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
unimplemented!()
}
}
impl Decoder for Metadata {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
unimplemented!()
}
}
#[derive(Debug)]
pub struct TagsMap {}
impl Encoder for TagsMap {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
unimplemented!()
}
}
impl Decoder for TagsMap {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
unimplemented!()
}
}

View File

@ -10,6 +10,7 @@ use uuid::Uuid;
use data::chat::Message;
use crate::data::game::{Metadata, Position, Slot, TagsMap};
use crate::error::{DecodeError, EncodeError};
pub mod data;
@ -509,6 +510,109 @@ macro_rules! impl_json_encoder_decoder (
);
);
impl Encoder for Position {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
let encoded_x = (self.x & 0x3FFFFFF) as i64;
let encoded_y = (self.y & 0xFFF) as i64;
let encoded_z = (self.z & 0x3FFFFFF) as i64;
writer.write_i64::<BigEndian>((encoded_x << 38) | (encoded_z << 12) | encoded_y)?;
Ok(())
}
}
impl Decoder for Position {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
let encoded = reader.read_i64::<BigEndian>()?;
let x = (encoded >> 38) as i32;
let y = (encoded & 0xFFF) as i16;
let z = (encoded << 26 >> 38) as i32;
Ok(Position { x, y, z })
}
}
impl Encoder for Option<Slot> {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
match self {
Some(slot) => {
writer.write_bool(true)?;
slot.encode(writer)
}
None => writer.write_bool(false),
}
}
}
impl Decoder for Option<Slot> {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
if reader.read_bool()? {
Ok(Some(Slot::decode(reader)?))
} else {
Ok(None)
}
}
}
impl Encoder for Slot {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
writer.write_var_i32(self.id)?;
writer.write_u8(self.amount)?;
writer.write_compound_tag(&self.compound_tag)?;
Ok(())
}
}
impl Decoder for Slot {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
let id = reader.read_var_i32()?;
let amount = reader.read_u8()?;
let compound_tag = reader.read_compound_tag()?;
Ok(Slot {
id,
amount,
compound_tag,
})
}
}
impl Encoder for Metadata {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
unimplemented!()
}
}
impl Decoder for Metadata {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
unimplemented!()
}
}
impl Encoder for TagsMap {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), EncodeError> {
unimplemented!()
}
}
impl Decoder for TagsMap {
type Output = Self;
fn decode<R: Read>(reader: &mut R) -> Result<Self::Output, DecodeError> {
unimplemented!()
}
}
mod var_int {
use std::io::{Read, Write};