From 257c64b705d4c46213a744c0eabe6e86b0686e99 Mon Sep 17 00:00:00 2001 From: oupson Date: Sun, 17 Jul 2022 11:02:20 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 2 + Cargo.toml | 6 + minecraft-bot-framework/Cargo.toml | 18 + minecraft-bot-framework/examples/simple.rs | 16 + minecraft-bot-framework/src/lib.rs | 221 +++++++++ minecraft-protocol-derive/Cargo.toml | 13 + minecraft-protocol-derive/src/lib.rs | 55 +++ minecraft-protocol/Cargo.toml | 21 + minecraft-protocol/examples/ping.rs | 169 +++++++ minecraft-protocol/src/error.rs | 11 + minecraft-protocol/src/lib.rs | 31 ++ minecraft-protocol/src/types.rs | 477 ++++++++++++++++++++ minecraft-protocol/src/v1_18/clientbound.rs | 13 + minecraft-protocol/src/v1_18/mod.rs | 4 + minecraft-protocol/src/v1_18/serverbound.rs | 14 + 15 files changed, 1071 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 minecraft-bot-framework/Cargo.toml create mode 100644 minecraft-bot-framework/examples/simple.rs create mode 100644 minecraft-bot-framework/src/lib.rs create mode 100644 minecraft-protocol-derive/Cargo.toml create mode 100644 minecraft-protocol-derive/src/lib.rs create mode 100644 minecraft-protocol/Cargo.toml create mode 100644 minecraft-protocol/examples/ping.rs create mode 100644 minecraft-protocol/src/error.rs create mode 100644 minecraft-protocol/src/lib.rs create mode 100644 minecraft-protocol/src/types.rs create mode 100644 minecraft-protocol/src/v1_18/clientbound.rs create mode 100644 minecraft-protocol/src/v1_18/mod.rs create mode 100644 minecraft-protocol/src/v1_18/serverbound.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a088746 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "minecraft-protocol", + "minecraft-protocol-derive", + "minecraft-bot-framework" +] diff --git a/minecraft-bot-framework/Cargo.toml b/minecraft-bot-framework/Cargo.toml new file mode 100644 index 0000000..7f4754d --- /dev/null +++ b/minecraft-bot-framework/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "minecraft-bot-framework" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1" +base64 = "0.13.0" +hex = "0.4.3" +minecraft-protocol = { path = "../minecraft-protocol", features = [ "tokio-support" ] } +openssl = "0.10" +rand = "0.8.4" +reqwest = "0.11" +thiserror = "1.0" +tokio = { version = "1.16", features = ["full"] } +tracing = "0.1" diff --git a/minecraft-bot-framework/examples/simple.rs b/minecraft-bot-framework/examples/simple.rs new file mode 100644 index 0000000..209486e --- /dev/null +++ b/minecraft-bot-framework/examples/simple.rs @@ -0,0 +1,16 @@ +use minecraft_bot_framework::{Auth, Bot}; + +struct EventListener; + +impl minecraft_bot_framework::EventListener for EventListener {} + +#[tokio::main] +async fn main() { + let mut bot = Bot::new(EventListener); + bot.connect("home.dyn.oupsman.fr", 25565, Auth{ + username: "AymDroid", + uuid: "583204e9426447c79bbeebc42f4c0224", + token: "eyJhbGciOiJIUzI1NiJ9.eyJ4dWlkIjoiMjUzNTQ2NzA1NzEzNjgxMCIsImFnZyI6IkFkdWx0Iiwic3ViIjoiYzRjZTgwMDItMTI2Ny00MmQ2LTgyMjYtNGVkYjIwZmJhN2E2IiwibmJmIjoxNjQwMDgyNDQ4LCJhdXRoIjoiWEJPWCIsInJvbGVzIjpbXSwiaXNzIjoiYXV0aGVudGljYXRpb24iLCJleHAiOjE2NDAxNjg4NDgsImlhdCI6MTY0MDA4MjQ0OCwicGxhdGZvcm0iOiJQQ19MQVVOQ0hFUiIsInl1aWQiOiIyZjkwNjI0ZGUwZjY0ZDQ5OTExYTU3ODYyNThmZmYyYyJ9.ZetvhYW7gEmk28jTM1P7813RP7qdwjeuMXey5gS1vIM" + }).await.unwrap(); + bot.run().await.unwrap(); +} diff --git a/minecraft-bot-framework/src/lib.rs b/minecraft-bot-framework/src/lib.rs new file mode 100644 index 0000000..e1cc002 --- /dev/null +++ b/minecraft-bot-framework/src/lib.rs @@ -0,0 +1,221 @@ +use std::{fmt::Debug, io::Cursor}; + +use minecraft_protocol::{ + types::VarInt, + v1_18::{clientbound::EncryptionRequest, serverbound::Handshake, PROTOCOL_VERSION}, + AsyncPacketContent, PacketContent, +}; +use openssl::sha::Sha1; +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + net::TcpStream, +}; + +pub use minecraft_protocol as protocol; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("IO Error")] + Disconnect(#[from] std::io::Error), + #[error("Protocol Error")] + ProtocolError(#[from] minecraft_protocol::Error), + #[error("Request Error")] + RequestError(#[from] reqwest::Error), +} + +pub type Result = std::result::Result; + +pub struct Auth<'s> { + pub username: &'s str, + pub uuid: &'s str, + pub token: &'s str, +} + +pub struct Bot +where + E: EventListener, +{ + event_listener: E, +} + +impl Bot +where + E: EventListener, +{ + pub fn new(event_listener: E) -> Self { + Self { + event_listener: event_listener, + } + } + + pub async fn connect(&mut self, address: &str, port: u16, auth: Auth<'_>) -> Result<()> { + let mut stream = TcpStream::connect(format!("{}:{}", address, port)).await?; + let packet = Packet { + packet_id: VarInt(0x00), + content: Handshake { + protocol_version: VarInt(PROTOCOL_VERSION), + server_address: String::from(address), + server_port: port, + next_state: VarInt(0x02), + }, + }; + packet.write_to(&mut stream).await?; + + let packet = Packet { + packet_id: VarInt(0x00), + content: auth.username.to_string(), + }; + packet.write_to(&mut stream).await?; + + let res: Packet = Packet::read_from(&mut stream).await?; + + self.setup_encryption(res.content, &auth).await?; + + Ok(()) + } + + pub async fn run(&mut self) -> Result<()> { + Ok(()) + } + + async fn setup_encryption( + &mut self, + request: EncryptionRequest, + auth: &Auth<'_>, + ) -> Result<()> { + let secret: [u8; 16] = rand::random(); + let server_hash = calc_hash(&secret, &request); + println!("{}", server_hash); + println!( + r#"{{"accessToken": "{}","selectedProfile": {{"id": "{}", "name": "{}"}},"serverId": "{}"}}"#, + auth.token, + auth.uuid.replace('-', ""), + auth.username, + server_hash + ); + + let res = reqwest::Client::new() + .post("https://sessionserver.mojang.com/session/minecraft/join") + .header("content-type", "application/json") + .header("user-agent", "minecraft-bot") + .body(format!( + r#"{{"accessToken": "{}","selectedProfile": {{"id": "{}", "name": "{}"}},"serverId": "{}"}}"#, + auth.token, + auth.uuid.replace('-', ""), + auth.username, + server_hash + )) + .send() + .await? + .text() + .await?; + + println!("{}", res); + + Ok(()) + } +} + +fn calc_hash(secret: &[u8], request: &EncryptionRequest) -> String { + let mut sha1 = Sha1::new(); + sha1.update(request.server_id.as_bytes()); + sha1.update(secret); + sha1.update(&request.public_key); + let mut hex = sha1.finish(); + + let negative = (hex[0] & 0x80) == 0x80; + + if negative { + two_complement(&mut hex); + + format!("-{}", hex::encode(hex).trim_start_matches('0')) + } else { + hex::encode(hex).trim_start_matches('0').to_string() + } +} + +fn two_complement(bytes: &mut [u8]) { + let mut carry = true; + for i in (0..bytes.len()).rev() { + bytes[i] = !bytes[i] & 0xff; + if carry { + carry = bytes[i] == 0xff; + bytes[i] = bytes[i] + 1; + } + } +} + +#[async_trait::async_trait] +pub trait EventListener: Send + Sync {} + +struct Packet +where + T: PacketContent, +{ + packet_id: VarInt, + content: T, +} + +impl Debug for Packet +where + T: PacketContent + Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Packet") + .field("packet_id", &self.packet_id) + .field("content", &self.content) + .finish() + } +} + +impl Packet +where + T: PacketContent, +{ + pub async fn read_from(reader: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin, + { + let mut packet = Vec::new(); + let mut buffer = [0u8; 8092]; + + let size = reader.read(&mut buffer).await.unwrap(); + + let packet_size = { + let mut cursor = Cursor::new(&buffer); + let packet_size = VarInt::read_from(&mut cursor).unwrap(); + packet.extend_from_slice(&buffer[cursor.position() as usize..size]); + packet_size + }; + + while packet.len() < packet_size.0 as usize { + let size = reader.read(&mut buffer).await.unwrap(); + packet.extend_from_slice(&buffer[0..size]); + } + + let mut cursor = Cursor::new(&packet); + let packet_id = VarInt::read_from(&mut cursor).unwrap(); + + let content = T::read_from(&mut cursor)?; + + Ok(Packet { packet_id, content }) + } + + pub async fn write_to(&self, writer: &mut W) -> Result<()> + where + W: AsyncWrite + std::marker::Unpin + std::marker::Send, + { + let mut write_buffer = Cursor::new(Vec::new()); + self.packet_id.write_to(&mut write_buffer)?; + self.content.write_to(&mut write_buffer)?; + + let write_buffer = write_buffer.get_ref(); + + VarInt(write_buffer.len() as i32) + .async_write_to(writer) + .await?; + writer.write_all(write_buffer).await?; + + Ok(()) + } +} diff --git a/minecraft-protocol-derive/Cargo.toml b/minecraft-protocol-derive/Cargo.toml new file mode 100644 index 0000000..887f485 --- /dev/null +++ b/minecraft-protocol-derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "minecraft-protocol-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +quote = "1.0.10" +syn = { version = "1.0.82", features = ["extra-traits"] } + +[lib] +proc-macro = true diff --git a/minecraft-protocol-derive/src/lib.rs b/minecraft-protocol-derive/src/lib.rs new file mode 100644 index 0000000..a48bfbc --- /dev/null +++ b/minecraft-protocol-derive/src/lib.rs @@ -0,0 +1,55 @@ +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput}; + +#[proc_macro_derive(PacketContent)] +pub fn derive_packet_content(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let fields = match input.data { + Data::Struct(st) => st.fields, + _ => { + unimplemented!() + } + }; + + let parse_all = fields.iter().map(|f| { + let name = &f.ident; + let ty = &f.ty; + quote_spanned! { f.span() => + #name : <#ty as minecraft_protocol::PacketContent>::read_from(reader)?, + } + }); + + let write_all = fields.iter().map(|f| { + let name = &f.ident; + let ty = &f.ty; + quote_spanned! { f.span() => + <#ty as minecraft_protocol::PacketContent>::write_to(&self.#name, writer)?; + } + }); + + let name = &input.ident; + + let expanded = quote! { + impl minecraft_protocol::PacketContent for #name { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read { + Ok(Self { + #(#parse_all)* + }) + } + + fn write_to(&self, writer: &mut W) -> Result<(), minecraft_protocol::Error> + where + W: std::io::Write{ + #(#write_all)* + + Ok(()) + } + } + }; + + TokenStream::from(expanded) +} diff --git a/minecraft-protocol/Cargo.toml b/minecraft-protocol/Cargo.toml new file mode 100644 index 0000000..8d33022 --- /dev/null +++ b/minecraft-protocol/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "minecraft-protocol" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +tokio-support = ["tokio", "async-trait"] + +[dependencies] +thiserror = "1.0" +byteorder = "1" +minecraft-protocol-derive = { path = "../minecraft-protocol-derive" } +tokio = { version = "1.16", optional = true } +async-trait = { version = "0.1", optional = true } + +[dev-dependencies] +log = "0.4" +env_logger = "0.9.0" +serde_json = "1.0.73" \ No newline at end of file diff --git a/minecraft-protocol/examples/ping.rs b/minecraft-protocol/examples/ping.rs new file mode 100644 index 0000000..de9de29 --- /dev/null +++ b/minecraft-protocol/examples/ping.rs @@ -0,0 +1,169 @@ +use std::{env, io::Cursor, net::TcpStream}; + +use minecraft_protocol::{types::VarInt, v1_18::{serverbound::Handshake, PROTOCOL_VERSION}, PacketContent}; + +use log::debug; + +struct Packet +where + T: PacketContent, +{ + packet_id: VarInt, + content: T, +} + +impl Packet +where + T: PacketContent, +{ + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + let mut packet = Vec::new(); + let mut buffer = [0u8; 8092]; + + let size = reader.read(&mut buffer).unwrap(); + + let packet_size = { + let mut cursor = Cursor::new(&buffer); + let packet_size = VarInt::read_from(&mut cursor).unwrap(); + packet.extend_from_slice(&buffer[cursor.position() as usize..size]); + packet_size + }; + + while packet.len() < packet_size.0 as usize { + let size = reader.read(&mut buffer).unwrap(); + packet.extend_from_slice(&buffer[0..size]); + } + + let mut cursor = Cursor::new(&packet); + let packet_id = VarInt::read_from(&mut cursor).unwrap(); + + let content = T::read_from(&mut cursor)?; + + Ok(Packet { packet_id, content }) + } + + fn write_to(&self, writer: &mut W) -> Result<(), minecraft_protocol::Error> + where + W: std::io::Write, + { + let mut write_buffer = Cursor::new(Vec::new()); + self.packet_id.write_to(&mut write_buffer)?; + self.content.write_to(&mut write_buffer)?; + + let write_buffer = write_buffer.get_ref(); + + VarInt(write_buffer.len() as i32).write_to(writer)?; + writer.write_all(write_buffer)?; + + Ok(()) + } +} + +fn main() { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let mut args = env::args().skip(1); + + let address = if let Some(ad) = args.next() { + ad + } else { + panic!("missing server address") + }; + + let port = if let Some(port) = args.next() { + port.parse::().expect("failed to get port") + } else { + 25565 + }; + + ping(&address, port); +} + +fn ping(address: &str, port: u16) { + let mut stream = TcpStream::connect(format!("{}:{}", address, port)).unwrap(); + + { + debug!("writting ..."); + + let packet = Packet { + packet_id: VarInt(0x00), + content: Handshake { + protocol_version: VarInt(PROTOCOL_VERSION), + server_address: String::from(address), + server_port: port, + next_state: VarInt(1), + }, + }; + packet.write_to(&mut stream).unwrap(); + + let packet = Packet { + packet_id: VarInt(0x00), + content: (), + }; + packet.write_to(&mut stream).unwrap(); + } + + let ping_value: serde_json::Value = { + debug!("reading ..."); + + let p: Packet = Packet::read_from(&mut stream).unwrap(); + log::trace!("{}", p.content); + + serde_json::from_str(&p.content).expect("failed to decode as json") + }; + + debug!("{:#?}", ping_value); + + println!("result for {}:{}", address, port); + let ping_value = ping_value.as_object().unwrap(); + if let Some(version) = ping_value.get("version").and_then(|s| s.as_object()) { + println!( + "\tversion: {} ({})", + version + .get("name") + .and_then(|m| m.as_str()) + .expect("failed to get version name"), + version + .get("protocol") + .and_then(|m| m.as_i64()) + .expect("failed to get protocol") + ); + } + + if let Some(description) = ping_value.get("description") { + println!( + "\tdescription : {}", + description.get("text").unwrap().as_str().unwrap() + ); + } + + if let Some(players) = ping_value.get("players").and_then(|o| o.as_object()) { + println!("\tplayers"); + println!( + "\t\tnumbers : {}/{}", + players + .get("online") + .and_then(|m| m.as_i64()) + .expect("failed to get online players count"), + players + .get("max") + .and_then(|m| m.as_i64()) + .expect("failed to get max player") + ); + + if let Some(sample) = players.get("sample").and_then(|s| s.as_array()) { + println!("\t\tsample:"); + for player in sample { + println!( + "\t\t\t- {}", + player + .get("name") + .and_then(|m| m.as_str()) + .expect("failed to get name of player") + ); + } + } + } +} diff --git a/minecraft-protocol/src/error.rs b/minecraft-protocol/src/error.rs new file mode 100644 index 0000000..d9da73a --- /dev/null +++ b/minecraft-protocol/src/error.rs @@ -0,0 +1,11 @@ +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("`{0}`")] + IOError(#[from] std::io::Error), + #[error("failed to get `{0}`")] + MissingValue(&'static str), + #[error("data must be {0} bytes long less")] + DataTooLong(u8), + #[error("Failed to decode string")] + FromUtf8Error(#[from] std::string::FromUtf8Error), +} diff --git a/minecraft-protocol/src/lib.rs b/minecraft-protocol/src/lib.rs new file mode 100644 index 0000000..45e28f7 --- /dev/null +++ b/minecraft-protocol/src/lib.rs @@ -0,0 +1,31 @@ +mod error; +pub mod types; + +pub use error::Error; +pub mod derive { + pub use minecraft_protocol_derive::PacketContent; +} + +pub mod v1_18; + +pub trait PacketContent: Sized { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read; + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write; +} + +#[cfg(feature = "tokio")] +#[async_trait::async_trait] +pub trait AsyncPacketContent: Sized { + async fn async_read_from(reader: &mut R) -> Result + where + R: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send; + + async fn async_write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: tokio::io::AsyncWrite + std::marker::Unpin + std::marker::Send; +} diff --git a/minecraft-protocol/src/types.rs b/minecraft-protocol/src/types.rs new file mode 100644 index 0000000..9575d2f --- /dev/null +++ b/minecraft-protocol/src/types.rs @@ -0,0 +1,477 @@ +//! Contain definitions for all minecraft protocol primitive data types.\ +//! See [https://wiki.vg/Protocol#Data_types](wiki.vg) + +use std::io::Read; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use crate::{AsyncPacketContent, Error, PacketContent}; + +/// Either false or true +pub type Boolean = bool; + +impl PacketContent for Boolean { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + match reader.read_u8()? { + 0 => Ok(false), + 1 => Ok(true), + value => { + panic!("unkown value : {}", value) + } + } + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_i8(if *self { 1 } else { 0 })?; + Ok(()) + } +} + +/// An integer between -128 and 127 +pub type Byte = i8; + +impl PacketContent for Byte { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_i8()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_i8(*self)?; + Ok(()) + } +} + +/// An integer between 0 and 255 +pub type UnsignedByte = u8; + +impl PacketContent for UnsignedByte { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_u8()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_u8(*self)?; + Ok(()) + } +} + +/// An integer between -32768 and 32767 +pub type Short = i16; + +impl PacketContent for Short { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_i16::()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_i16::(*self)?; + Ok(()) + } +} + +/// An integer between 0 and 65535 +pub type UnsignedShort = u16; + +impl PacketContent for UnsignedShort { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_u16::()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_u16::(*self)?; + Ok(()) + } +} + +/// An integer between -2147483648 and 2147483647 +pub type Int = i32; + +impl PacketContent for Int { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_i32::()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_i32::(*self)?; + Ok(()) + } +} + +/// An integer between -9223372036854775808 and 9223372036854775807 +pub type Long = i64; + +impl PacketContent for Long { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_i64::()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_i64::(*self)?; + Ok(()) + } +} + +/// A single-precision [32-bit IEEE 754 floating point number](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) +pub type Float = f32; + +impl PacketContent for Float { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_f32::()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_f32::(*self)?; + Ok(()) + } +} + +/// A double-precision [64-bit IEEE 754 floating point number](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +pub type Double = f64; + +impl PacketContent for Double { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + Ok(reader.read_f64::()?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + writer.write_f64::(*self)?; + Ok(()) + } +} + +impl PacketContent for () { + fn read_from(_: &mut R) -> Result + where + R: std::io::Read, + { + Ok(()) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + Ok(()) + } +} + +#[repr(transparent)] +#[derive(Debug)] +pub struct VarInt(pub i32); + +impl PacketContent for VarInt { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + let mut value = 0; + let mut length = 0; + + let mut buf = [0u8; 1]; + + loop { + reader.read_exact(&mut buf)?; + + value |= ((buf[0] as i32) & 0x7F) << (length * 7); + + length += 1; + + if length > 5 { + return Err(Error::DataTooLong(5)); + } + + if (buf[0] & 0x80) != 0x80 { + break; + } + } + + Ok(VarInt(value)) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + let mut value = self.0; + loop { + if (value & !0x7F) == 0 { + writer.write_u8(value as u8)?; + break; + } + + writer.write_u8(((value & 0x7F) | 0x80) as u8)?; + + // TODO BETTER WAY ? + value = unsafe { std::mem::transmute(std::mem::transmute::(value) >> 7) }; + } + Ok(()) + } +} + +#[cfg(feature = "tokio-support")] +#[async_trait::async_trait] +impl AsyncPacketContent for VarInt { + async fn async_read_from(reader: &mut R) -> Result + where + R: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send, + { + let mut value = 0; + let mut length = 0; + + let mut buf = [0u8; 1]; + + loop { + reader.read_exact(&mut buf).await.unwrap(); // TODO + + value |= ((buf[0] as i32) & 0x7F) << (length * 7); + + length += 1; + + if length > 5 { + return Err(Error::DataTooLong(5)); + } + + if (buf[0] & 0x80) != 0x80 { + break; + } + } + + Ok(VarInt(value)) + } + + async fn async_write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: tokio::io::AsyncWrite + std::marker::Unpin + std::marker::Send, + { + let mut value = self.0; + loop { + if (value & !0x7F) == 0 { + writer.write_u8(value as u8).await?; + break; + } + + writer.write_u8(((value & 0x7F) | 0x80) as u8).await?; + + // TODO BETTER WAY ? + value = unsafe { std::mem::transmute(std::mem::transmute::(value) >> 7) }; + } + Ok(()) + } +} + +#[repr(transparent)] +#[derive(Debug)] +pub struct VarLong(pub i64); + +impl PacketContent for VarLong { + fn read_from(_reader: &mut R) -> Result + where + R: std::io::Read, + { + unimplemented!() + } + + fn write_to(&self, _writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + unimplemented!() + } +} + +/// A sequence of [Unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value) +pub type String = std::string::String; + +impl PacketContent for String { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + let size = VarInt::read_from(reader)?; + + let mut string_reader = reader.take(size.0 as u64); + + let mut buf = Vec::with_capacity(size.0 as usize); + + string_reader.read_to_end(&mut buf)?; + + Ok(String::from_utf8(buf)?) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + let buf = self.as_bytes(); + + VarInt(buf.len() as i32).write_to(writer)?; + + writer.write_all(buf)?; + + Ok(()) + } +} + +// TODO +pub type ByteArray = Vec; + +impl PacketContent for ByteArray { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + { + let size = VarInt::read_from(reader)?; + + let mut string_reader = reader.take(size.0 as u64); + + let mut buf = Vec::with_capacity(size.0 as usize); + + string_reader.read_to_end(&mut buf)?; + + Ok(buf) + } + + fn write_to(&self, writer: &mut W) -> Result<(), Error> + where + W: std::io::Write, + { + + VarInt(self.len() as i32).write_to(writer)?; + + writer.write_all(self)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use crate::{types::VarInt, PacketContent}; + + #[test] + fn read_varint() { + assert_eq!(VarInt::read_from(&mut Cursor::new([0x00])).unwrap().0, 0); + assert_eq!(VarInt::read_from(&mut Cursor::new([0x01])).unwrap().0, 1); + assert_eq!(VarInt::read_from(&mut Cursor::new([0x02])).unwrap().0, 2); + assert_eq!(VarInt::read_from(&mut Cursor::new([0x7f])).unwrap().0, 127); + + assert_eq!( + VarInt::read_from(&mut Cursor::new([0x80, 0x01])).unwrap().0, + 128 + ); + assert_eq!( + VarInt::read_from(&mut Cursor::new([0xff, 0x01])).unwrap().0, + 255 + ); + + assert_eq!( + VarInt::read_from(&mut Cursor::new([0xdd, 0xc7, 0x01])) + .unwrap() + .0, + 25565 + ); + assert_eq!( + VarInt::read_from(&mut Cursor::new([0xff, 0xff, 0x7f])) + .unwrap() + .0, + 2097151 + ); + + assert_eq!( + VarInt::read_from(&mut Cursor::new([255, 255, 255, 255, 7])) + .unwrap() + .0, + 2147483647 + ); + assert_eq!( + VarInt::read_from(&mut Cursor::new([0xff, 0xff, 0xff, 0xff, 0x0f])) + .unwrap() + .0, + -1 + ); + assert_eq!( + VarInt::read_from(&mut Cursor::new([128, 128, 128, 128, 8])) + .unwrap() + .0, + -2147483648 + ); + } + + #[test] + fn write_varint() { + let mut buffer = Cursor::new(vec![0u8; 3]); + VarInt(25565).write_to(&mut buffer).unwrap(); + + assert_eq!(buffer.get_ref(), &[0xdd, 0xc7, 0x01]); + + let mut buffer = Cursor::new(vec![0u8; 5]); + VarInt(-2147483648).write_to(&mut buffer).unwrap(); + + assert_eq!(buffer.get_ref(), &[128, 128, 128, 128, 8]); + } + + #[test] + fn read_varlong() {} + + #[test] + fn write_varlong() { + unimplemented!() + } +} diff --git a/minecraft-protocol/src/v1_18/clientbound.rs b/minecraft-protocol/src/v1_18/clientbound.rs new file mode 100644 index 0000000..af4750e --- /dev/null +++ b/minecraft-protocol/src/v1_18/clientbound.rs @@ -0,0 +1,13 @@ +use crate as minecraft_protocol; +use crate::types::ByteArray; +use crate::{ + derive::PacketContent, +}; + +/// See [https://wiki.vg/Protocol#Encryption_Request](https://wiki.vg/Protocol#Encryption_Request) +#[derive(PacketContent, Debug)] +pub struct EncryptionRequest { + pub server_id: String, + pub public_key: ByteArray, + pub verify_token: ByteArray, +} diff --git a/minecraft-protocol/src/v1_18/mod.rs b/minecraft-protocol/src/v1_18/mod.rs new file mode 100644 index 0000000..7a53543 --- /dev/null +++ b/minecraft-protocol/src/v1_18/mod.rs @@ -0,0 +1,4 @@ +pub mod clientbound; +pub mod serverbound; + +pub const PROTOCOL_VERSION : i32 = 757; \ No newline at end of file diff --git a/minecraft-protocol/src/v1_18/serverbound.rs b/minecraft-protocol/src/v1_18/serverbound.rs new file mode 100644 index 0000000..9f8f144 --- /dev/null +++ b/minecraft-protocol/src/v1_18/serverbound.rs @@ -0,0 +1,14 @@ +use crate as minecraft_protocol; +use crate::{ + derive::PacketContent, + types::{UnsignedShort, VarInt}, +}; + +/// See [https://wiki.vg/Protocol#Handshake](https://wiki.vg/Protocol#Handshake) +#[derive(PacketContent)] +pub struct Handshake { + pub protocol_version: VarInt, + pub server_address: String, + pub server_port: UnsignedShort, + pub next_state: VarInt, +}