diff --git a/protocol-generator/src/data/input.rs b/protocol-generator/src/backend.rs similarity index 88% rename from protocol-generator/src/data/input.rs rename to protocol-generator/src/backend.rs index b7a2c30..d8b3ced 100644 --- a/protocol-generator/src/data/input.rs +++ b/protocol-generator/src/backend.rs @@ -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>, } diff --git a/protocol-generator/src/data/mod.rs b/protocol-generator/src/data/mod.rs deleted file mode 100644 index 7d1a548..0000000 --- a/protocol-generator/src/data/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod input; -pub mod output; diff --git a/protocol-generator/src/main.rs b/protocol-generator/src/main.rs index 4820043..ffbd346 100644 --- a/protocol-generator/src/main.rs +++ b/protocol-generator/src/main.rs @@ -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 { - 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 { - 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 { - 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 { - 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".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, + packets: &'a Vec, } fn generate_rust_file( - 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, }; diff --git a/protocol-generator/src/mappings.rs b/protocol-generator/src/mappings.rs new file mode 100644 index 0000000..5716322 --- /dev/null +++ b/protocol-generator/src/mappings.rs @@ -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, + } + } +} diff --git a/protocol-generator/src/transformer.rs b/protocol-generator/src/transformer.rs new file mode 100644 index 0000000..20cb518 --- /dev/null +++ b/protocol-generator/src/transformer.rs @@ -0,0 +1,196 @@ +use crate::mappings::Mappings; +use crate::{backend, frontend}; +use heck::{CamelCase, SnakeCase}; +use std::collections::HashMap; + +pub fn transform_protocol( + 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( + mappings: &M, + protocol: &backend::Protocol, + packets: &backend::Packets, + bound: frontend::Bound, +) -> Vec { + 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 { + 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 { + 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 { + 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".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() + } +} diff --git a/protocol/src/data/game.rs b/protocol/src/data/game.rs index 95e2fcb..cf94310 100644 --- a/protocol/src/data/game.rs +++ b/protocol/src/data/game.rs @@ -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(&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::((encoded_x << 38) | (encoded_z << 12) | encoded_y)?; - Ok(()) - } -} - -impl Decoder for Position { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - let encoded = reader.read_i64::()?; - - 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 { - fn encode(&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 { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - if reader.read_bool()? { - Ok(Some(Slot::decode(reader)?)) - } else { - Ok(None) - } - } -} - -impl Encoder for Slot { - fn encode(&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(reader: &mut R) -> Result { - 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(&self, writer: &mut W) -> Result<(), EncodeError> { - unimplemented!() - } -} - -impl Decoder for Metadata { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - unimplemented!() - } -} - #[derive(Debug)] pub struct TagsMap {} - -impl Encoder for TagsMap { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - unimplemented!() - } -} - -impl Decoder for TagsMap { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - unimplemented!() - } -} diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index fdcbbc2..cb858c1 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -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(&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::((encoded_x << 38) | (encoded_z << 12) | encoded_y)?; + Ok(()) + } +} + +impl Decoder for Position { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + let encoded = reader.read_i64::()?; + + 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 { + fn encode(&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 { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + if reader.read_bool()? { + Ok(Some(Slot::decode(reader)?)) + } else { + Ok(None) + } + } +} + +impl Encoder for Slot { + fn encode(&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(reader: &mut R) -> Result { + 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(&self, writer: &mut W) -> Result<(), EncodeError> { + unimplemented!() + } +} + +impl Decoder for Metadata { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + unimplemented!() + } +} + +impl Encoder for TagsMap { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + unimplemented!() + } +} + +impl Decoder for TagsMap { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + unimplemented!() + } +} + mod var_int { use std::io::{Read, Write};