commit 277c84c5afbc05b81eea6434763c340fafe056b2 Author: Kalle Struik Date: Sun Apr 9 00:08:03 2023 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9af5975 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,150 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "minecraft-rust-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "byteorder", + "itertools", + "serde", + "serde_json", + "thiserror", + "uuid", +] + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "uuid" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +dependencies = [ + "serde", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..41bfc79 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "minecraft-rust-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.68" +byteorder = "1.4.3" +itertools = "0.10.5" +serde = { version = "1.0.152", features = ["serde_derive", "derive"] } +serde_json = "1.0.91" +thiserror = "1.0.38" +uuid = { version = "1.2.2", features = ["serde"] } diff --git a/src/datatypes/lenght_prefixed_string.rs b/src/datatypes/lenght_prefixed_string.rs new file mode 100644 index 0000000..0c9dd05 --- /dev/null +++ b/src/datatypes/lenght_prefixed_string.rs @@ -0,0 +1,45 @@ +use std::{io::{Cursor, Seek, SeekFrom}, fmt::Display}; + +use anyhow::Result; + +use crate::packets::{Decode, Encode}; + +use super::varint::VarInt; + +#[derive(Debug, PartialEq, Eq)] +pub struct LenghtPrefixedString { + pub value: String, +} + +impl Decode for LenghtPrefixedString { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let lenght: i32 = VarInt::decode(cursor)?.into(); + let value = String::from_utf8(cursor.remaining_slice()[..lenght as usize].to_owned())?; + cursor.seek(SeekFrom::Current(lenght as i64))?; + + Ok(LenghtPrefixedString { value }) + } +} + +impl Encode for LenghtPrefixedString { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + let lenght = VarInt {value: self.value.len() as i32}; + lenght.encode(buffer)?; + + buffer.extend_from_slice(self.value.as_bytes()); + Ok(()) + } +} + + +impl From for String { + fn from(val: LenghtPrefixedString) -> Self { + val.value + } +} + +impl Display for LenghtPrefixedString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.value.fmt(f) + } +} diff --git a/src/datatypes/mod.rs b/src/datatypes/mod.rs new file mode 100644 index 0000000..8bdb2cf --- /dev/null +++ b/src/datatypes/mod.rs @@ -0,0 +1,2 @@ +pub mod varint; +pub mod lenght_prefixed_string; diff --git a/src/datatypes/varint.rs b/src/datatypes/varint.rs new file mode 100644 index 0000000..f824f32 --- /dev/null +++ b/src/datatypes/varint.rs @@ -0,0 +1,130 @@ +use std::{io::{Read, Cursor}, fmt::Display}; +use anyhow::Result; + +use crate::packets::{Decode, Encode}; + +#[derive(Debug, PartialEq, Eq)] +pub struct VarInt { + pub value: i32, +} + +impl Decode for VarInt { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let mut value: i32 = 0; + let mut pos = 0; + let mut current_byte = vec![0u8; 1]; + + loop { + cursor.read_exact(&mut current_byte)?; + + value |= (current_byte[0] as i32 & 0x7F) << pos; + if current_byte[0] & 0x80 == 0 { + break; + } + pos += 7; + } + + Ok(VarInt { value }) + } +} + +impl Encode for VarInt { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + let mut value = self.value as u32; + + loop { + if value & !0x7F == 0 { + buffer.push(value as u8); + break; + } + + buffer.push(((value & 0x7F) | 0x80) as u8); + + value >>= 7; + } + + Ok(()) + } +} + + +impl From for i32 { + fn from(val: VarInt) -> Self { + val.value + } +} + +impl Display for VarInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.value.fmt(f) + } +} + +#[cfg(test)] +mod test { + use std::io::Cursor; + + use crate::datatypes::varint::VarInt; + use crate::packets::{Decode, Encode}; + + #[test] + fn small_number_decode_works() { + let data = vec![0x10]; + let mut cursor = Cursor::new(&data[..]); + let result = VarInt::decode(&mut cursor).unwrap(); + + assert_eq!(16, result.value); + assert_eq!(1, cursor.position()); + } + + #[test] + fn large_number_decode_works() { + let data = vec![0xff, 0xff, 0x7f]; + let mut cursor = Cursor::new(&data[..]); + let result = VarInt::decode(&mut cursor).unwrap(); + + assert_eq!(2097151, result.value); + assert_eq!(3, cursor.position()); + } + + #[test] + fn negative_number_decode_works() { + let data = vec![0xff, 0xff, 0xff, 0xff, 0x0f]; + let mut cursor = Cursor::new(&data[..]); + let result = VarInt::decode(&mut cursor).unwrap(); + + assert_eq!(-1, result.value); + assert_eq!(5, cursor.position()); + } + + #[test] + fn small_number_encode_works() { + let mut data = Vec::new(); + let to_encode = VarInt {value: 12}; + + to_encode.encode(&mut data); + + assert_eq!(vec![12], data); + } + + + #[test] + fn large_number_encode_works() { + let mut data = Vec::new(); + let to_encode = VarInt {value: 2097151}; + + to_encode.encode(&mut data); + + assert_eq!(vec![0xff, 0xff, 0x7f], data); + } + + #[test] + fn negative_number_encode_works() { + let mut data = Vec::new(); + let to_encode = VarInt {value: -1}; + + to_encode.encode(&mut data); + + assert_eq!(vec![0xff, 0xff, 0xff, 0xff, 0x0f], data); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b27df52 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,238 @@ +#![feature(cursor_remaining)] + +use std::fmt::Display; +use std::io::{Read, ErrorKind, Cursor, Write}; +use std::net::{TcpListener, TcpStream, SocketAddr}; +use std::thread; + +use anyhow::{Result, bail}; +use datatypes::lenght_prefixed_string::LenghtPrefixedString; +use itertools::Itertools; +use packets::serverbound::login::LoginStartPacket; +use packets::serverbound::status::StatusPingRequestPacket; +use packets::{Packet, Encode}; +use packets::serverbound::handshake::HandshakePacket; +use packets::clientbound::status::{StatusResponsePacket, StatusPongResponsePacket}; +use packets::clientbound::login::LoginSuccessPacket; +use serde::Serialize; +use thiserror::Error; +use uuid::Uuid; + +use crate::datatypes::varint::VarInt; +use crate::packets::Decode; + +pub mod datatypes; +pub mod packets; + +fn main() { + let listener = TcpListener::bind("127.0.0.1:25565").unwrap(); + println!("listening started, ready to accept"); + for stream in listener.incoming() { + match stream { + Err(e) => println!("Accept ERROR: {}", e), + Ok(stream) => drop(thread::spawn(|| handle_client(stream))), + } + } +} + +fn handle_client(stream: TcpStream) -> Result<()> { + let mut client = Client::new(stream)?; + + loop { + client.read_packet()?; + } +} + +#[derive(Debug, Clone, Copy)] +enum ConnectionState { + Handshake, + Status, + Login, + Play, +} + +impl Display for ConnectionState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConnectionState::Handshake => f.write_str("Handshake"), + ConnectionState::Status => f.write_str("Status"), + ConnectionState::Login => f.write_str("Login"), + ConnectionState::Play => f.write_str("Play"), + } + } +} + +struct Client { + state: ConnectionState, + address: SocketAddr, + stream: TcpStream, +} + +impl Client { + fn new(stream: TcpStream) -> Result { + let address = stream.peer_addr()?; + + Ok(Client { + state: ConnectionState::Handshake, + address, stream, + }) + } + + fn send_packet(&mut self, packet: T) -> Result<()> { + let mut data: Vec = Vec::new(); + // Encode packet ID + VarInt {value: packet.get_id()}.encode(&mut data)?; + // Encode packet data + packet.encode(&mut data)?; + + // Prepend packet data + let mut lenght: Vec = Vec::new(); + VarInt {value: data.len() as i32}.encode(&mut lenght)?; + lenght.append(&mut data); + // Data is empty now + let data = lenght; + + self.stream.write_all(&data)?; + Ok(()) + } + + fn read_packet(&mut self) -> Result<()> { + let mut buffer = vec![0u8; 256]; + let read_bytes = self.stream.read(&mut buffer)?; + if read_bytes == 0 { + return Err(std::io::Error::new(ErrorKind::UnexpectedEof, "Read 0 bytes").into()); + } + let bytes = &buffer[..read_bytes]; + let mut cursor = Cursor::new(bytes); + + let length: i32 = VarInt::decode(&mut cursor)?.into(); + let id: i32 = VarInt::decode(&mut cursor)?.into(); + + + self.handle_packet(id, length, cursor) + } + + fn handle_packet(&mut self, id: i32, length: i32, cursor: Cursor<&[u8]>) -> Result<()> { + match self.state { + ConnectionState::Handshake => self.handle_handshake_packets(id, length, cursor), + ConnectionState::Status => self.handle_status_packets(id, length, cursor), + ConnectionState::Login => self.handle_login_packet(id, length, cursor), + ConnectionState::Play => todo!(), + } + } + + fn handle_handshake_packets(&mut self, id: i32, length: i32, mut cursor: Cursor<&[u8]>) -> Result<()> { + match id { + 0 => { + let packet = HandshakePacket::decode(&mut cursor)?; + println!("[HANDSHAKE] <- {}: Handshake request: IP: {}:{}", self.address, packet.server_address, packet.server_port); + self.change_state(packet.next_state.into()) + }, + _ => { + println!("[HANDSHAKE] <- {}: [ID: {}, Length: {}] {:02x}", self.address, id, length, cursor.remaining_slice().iter().format(" ")); + Ok(()) + //bail!(Error::InvalidPacketIdError {id, state: self.state}), + } + } + } + + fn handle_status_packets(&mut self, id: i32, length: i32, mut cursor: Cursor<&[u8]>) -> Result<()> { + match id { + 0 => { + let response_data = ServerStatusResponseData { + version: ServerStatusResponseDataVersion { name: "Rust 1.19".to_owned(), protocol: 761 }, + players: ServerStatusResponseDataPlayers { max: 100, online: 0 }, + description: ServerStatusResponseDataDescription { text: "A rust server!".to_owned() }, + }; + + let response = StatusResponsePacket {json: LenghtPrefixedString {value: serde_json::to_string(&response_data)?}}; + + self.send_packet(response)?; + + Ok(()) + }, + 1 => { + let packet = StatusPingRequestPacket::decode(&mut cursor)?; + let response = StatusPongResponsePacket {payload: packet.payload}; + + self.send_packet(response)?; + + Ok(()) + }, + _ => { + println!("[STATUS] <- {}: [ID: {}, Length: {}] {:02x}", self.address, id, length, cursor.remaining_slice().iter().format(" ")); + Ok(()) + //bail!(Error::InvalidPacketIdError {id, state: self.state}), + } + } + + } + + fn handle_login_packet(&mut self, id: i32, length: i32, mut cursor: Cursor<&[u8]>) -> Result<()> { + match id { + 0 => { + let packet = LoginStartPacket::decode(&mut cursor)?; + let response = LoginSuccessPacket { + uuid: Uuid::from_u128(0xd15b7518f4d747f8b385f10bffedf643), + username: LenghtPrefixedString {value: "test".to_owned()}, + property_length: VarInt {value: 0}, + }; + + self.send_packet(response)?; + self.change_state(3)?; + + Ok(()) + }, + _ => { + println!("[LOGIN] <- {}: [ID: {}, Length: {}] {:02x}", self.address, id, length, cursor.remaining_slice().iter().format(" ")); + Ok(()) + //bail!(Error::InvalidPacketIdError {id, state: self.state}), + } + } + } + + fn change_state(&mut self, state_index: i32) -> Result<()> { + println!("<- {}: Moving connection to state: {}", self.address, state_index); + match state_index { + 1 => self.state = ConnectionState::Status, + 2 => self.state = ConnectionState::Login, + 3 => self.state = ConnectionState::Play, + _ => bail!(Error::InvalidNextState {next_state: state_index}) + } + + Ok(()) + } +} + +#[derive(Error, Debug)] +enum Error { + #[error("Packet has invalid packet id {id} for current state {state}")] + InvalidPacketIdError {id: i32, state: ConnectionState}, + #[error("Requested to move to invalid next state {next_state}.")] + InvalidNextState {next_state: i32}, +} + +#[derive(Debug, Serialize)] +struct ServerStatusResponseData { + version: ServerStatusResponseDataVersion, + players: ServerStatusResponseDataPlayers, + description: ServerStatusResponseDataDescription, +} + +#[derive(Debug, Serialize)] +struct ServerStatusResponseDataVersion { + name: String, + protocol: i32, +} + +#[derive(Debug, Serialize)] +struct ServerStatusResponseDataPlayers { + max: i32, + online: i32, +} + +#[derive(Debug, Serialize)] +struct ServerStatusResponseDataDescription { + text: String, +} + diff --git a/src/packets/clientbound/login.rs b/src/packets/clientbound/login.rs new file mode 100644 index 0000000..969f8be --- /dev/null +++ b/src/packets/clientbound/login.rs @@ -0,0 +1,39 @@ +use uuid::Uuid; + +use crate::{datatypes::{lenght_prefixed_string::LenghtPrefixedString, varint::VarInt}, packets::{Decode, Encode, Packet}}; + + +pub struct LoginSuccessPacket { + pub uuid: Uuid, + pub username: LenghtPrefixedString, + pub property_length: VarInt, + // TODO: properties... +} + +impl Decode for LoginSuccessPacket { + fn decode(cursor: &mut std::io::Cursor<&[u8]>) -> anyhow::Result { + let uuid = Uuid::decode(cursor)?; + let username = LenghtPrefixedString::decode(cursor)?; + let property_length = VarInt::decode(cursor)?; + + Ok(LoginSuccessPacket { + uuid, + username, + property_length + }) + } +} + +impl Encode for LoginSuccessPacket { + fn encode(&self, buffer: &mut Vec) -> anyhow::Result<()> { + self.uuid.encode(buffer)?; + self.username.encode(buffer)?; + self.property_length.encode(buffer)?; + + Ok(()) + } +} + +impl Packet for LoginSuccessPacket { + fn get_id(&self) -> i32 {0x02} +} diff --git a/src/packets/clientbound/mod.rs b/src/packets/clientbound/mod.rs new file mode 100644 index 0000000..9206a11 --- /dev/null +++ b/src/packets/clientbound/mod.rs @@ -0,0 +1,3 @@ +pub mod status; +pub mod login; + diff --git a/src/packets/clientbound/status.rs b/src/packets/clientbound/status.rs new file mode 100644 index 0000000..82672c2 --- /dev/null +++ b/src/packets/clientbound/status.rs @@ -0,0 +1,51 @@ +use std::io::Cursor; + +use anyhow::Result; + +use crate::{datatypes::lenght_prefixed_string::LenghtPrefixedString, packets::{Decode, Encode, Packet}}; + + +pub struct StatusResponsePacket { + pub json: LenghtPrefixedString, +} + +impl Decode for StatusResponsePacket { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let json = LenghtPrefixedString::decode(cursor)?; + + Ok(StatusResponsePacket { json }) + } +} + +impl Encode for StatusResponsePacket { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + self.json.encode(buffer) + } +} + +impl Packet for StatusResponsePacket { + fn get_id(&self) -> i32 {0x00} +} + + +pub struct StatusPongResponsePacket { + pub payload: i64, +} + +impl Decode for StatusPongResponsePacket { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let payload = i64::decode(cursor)?; + + Ok(StatusPongResponsePacket { payload }) + } +} + +impl Encode for StatusPongResponsePacket { + fn encode(&self, buffer: &mut Vec) -> anyhow::Result<()> { + self.payload.encode(buffer) + } +} + +impl Packet for StatusPongResponsePacket { + fn get_id(&self) -> i32 {0x01} +} diff --git a/src/packets/mod.rs b/src/packets/mod.rs new file mode 100644 index 0000000..c40b5b7 --- /dev/null +++ b/src/packets/mod.rs @@ -0,0 +1,95 @@ +use std::io::Cursor; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use anyhow::Result; +use uuid::Uuid; + +pub mod clientbound; +pub mod serverbound; + +pub trait Decode: Sized { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result; +} + +pub trait Encode: Sized { + fn encode(&self, buffer: &mut Vec) -> Result<()>; +} + +pub trait Packet: Decode + Encode { + fn get_id(&self) -> i32; +} + +impl Encode for Option +where T: Encode { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + match self { + Some(it) => it.encode(buffer), + None => Ok(()) + } + } +} + + +macro_rules! number_impl { + ($($type:ty, $read_fn:tt, $write_fn:tt),* $(,)?) => { + $( + impl Decode for $type { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + cursor.$read_fn::().map_err(anyhow::Error::from) + } + } + impl Encode for $type { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + buffer.$write_fn::(*self).map_err(anyhow::Error::from) + } + } + )* + } +} + +number_impl! { + u16, read_u16, write_u16, + u32, read_u32, write_u32, + u64, read_u64, write_u64, + + i16, read_i16, write_i16, + i32, read_i32, write_i32, + i64, read_i64, write_i64, + + f32, read_f32, write_f32, + f64, read_f64, write_f64, +} + + +impl Decode for bool { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let data = cursor.read_u8()?; + Ok(data != 0x00) + } +} + +impl Encode for bool { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + if *self { + buffer.push(0x01); + } else { + buffer.push(0x00); + } + + Ok(()) + } +} + +impl Decode for Uuid { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let data = cursor.read_u128::()?; + Ok(Uuid::from_u128(data)) + } +} + +impl Encode for Uuid { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + buffer.write_u128::(self.as_u128())?; + Ok(()) + } +} diff --git a/src/packets/serverbound/handshake.rs b/src/packets/serverbound/handshake.rs new file mode 100644 index 0000000..13e8c71 --- /dev/null +++ b/src/packets/serverbound/handshake.rs @@ -0,0 +1,40 @@ +use std::io::Cursor; + +use anyhow::Result; + +use crate::{datatypes::{varint::VarInt, lenght_prefixed_string::LenghtPrefixedString}, packets::{Decode, Encode, Packet}}; + +#[derive(Debug)] +pub struct HandshakePacket { + pub protocal_version: VarInt, + pub server_address: LenghtPrefixedString, + pub server_port: u16, + pub next_state: VarInt, +} + + +impl Decode for HandshakePacket { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let protocal_version = VarInt::decode(cursor)?; + let server_address = LenghtPrefixedString::decode(cursor)?; + let server_port = u16::decode(cursor)?; + let next_state = VarInt::decode(cursor)?; + + Ok(HandshakePacket { + protocal_version, + server_address, + server_port, + next_state, + }) + } +} + +impl Encode for HandshakePacket { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + todo!() + } +} + +impl Packet for HandshakePacket { + fn get_id(&self) -> i32 {0x00} +} diff --git a/src/packets/serverbound/login.rs b/src/packets/serverbound/login.rs new file mode 100644 index 0000000..81618e4 --- /dev/null +++ b/src/packets/serverbound/login.rs @@ -0,0 +1,42 @@ +use uuid::Uuid; + +use crate::{datatypes::lenght_prefixed_string::LenghtPrefixedString, packets::{Decode, Encode, Packet}}; + + +pub struct LoginStartPacket { + pub username: LenghtPrefixedString, + pub has_uuid: bool, + pub uuid: Option, +} + +impl Decode for LoginStartPacket { + fn decode(cursor: &mut std::io::Cursor<&[u8]>) -> anyhow::Result { + let username = LenghtPrefixedString::decode(cursor)?; + let has_uuid = bool::decode(cursor)?; + let uuid = if has_uuid { + Some(Uuid::decode(cursor)?) + } else { + None + }; + + Ok(LoginStartPacket { + username, + has_uuid, + uuid, + }) + } +} + +impl Encode for LoginStartPacket { + fn encode(&self, buffer: &mut Vec) -> anyhow::Result<()> { + self.username.encode(buffer)?; + self.has_uuid.encode(buffer)?; + self.uuid.encode(buffer)?; + + Ok(()) + } +} + +impl Packet for LoginStartPacket { + fn get_id(&self) -> i32 {0x00} +} diff --git a/src/packets/serverbound/mod.rs b/src/packets/serverbound/mod.rs new file mode 100644 index 0000000..c70debf --- /dev/null +++ b/src/packets/serverbound/mod.rs @@ -0,0 +1,4 @@ +pub mod handshake; +pub mod status; +pub mod login; + diff --git a/src/packets/serverbound/status.rs b/src/packets/serverbound/status.rs new file mode 100644 index 0000000..7d3820c --- /dev/null +++ b/src/packets/serverbound/status.rs @@ -0,0 +1,28 @@ +use std::io::Cursor; + +use anyhow::Result; + +use crate::packets::{Encode, Packet, Decode}; + + +pub struct StatusPingRequestPacket { + pub payload: i64, +} + +impl Decode for StatusPingRequestPacket { + fn decode(cursor: &mut Cursor<&[u8]>) -> Result { + let payload = i64::decode(cursor)?; + + Ok(StatusPingRequestPacket { payload }) + } +} + +impl Encode for StatusPingRequestPacket { + fn encode(&self, buffer: &mut Vec) -> Result<()> { + self.payload.encode(buffer) + } +} + +impl Packet for StatusPingRequestPacket { + fn get_id(&self) -> i32 {0x01} +}