From 621249d50de43316d7f3bee871b288fdeb362310 Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Fri, 8 Nov 2024 03:45:57 +0100 Subject: [PATCH] init --- .gitignore | 2 + Cargo.lock | 72 +++++++++++++++ Cargo.toml | 8 ++ src/main.rs | 200 ++++++++++++++++++++++++++++++++++++++++ src/protocols.rs | 23 +++++ src/protocols/status.rs | 48 ++++++++++ src/types.rs | 22 +++++ src/types/string.rs | 58 ++++++++++++ src/types/var_int.rs | 91 ++++++++++++++++++ src/utils.rs | 58 ++++++++++++ 10 files changed, 582 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/protocols.rs create mode 100644 src/protocols/status.rs create mode 100644 src/types.rs create mode 100644 src/types/string.rs create mode 100644 src/types/var_int.rs create mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b74481f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/.idea/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a47ecbd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,72 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "mc-rust-server" +version = "0.1.0" +dependencies = [ + "num-derive", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..082867a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "mc-rust-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +num-derive = "0.4.2" +num-traits = "0.2.19" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e33de33 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,200 @@ +pub mod protocols; +pub mod types; +pub mod utils; + +use crate::protocols::handle; +use crate::types::string::McString; +use crate::types::var_int::VarInt; +use crate::types::{McRead, McRustRepr}; +use crate::utils::RWStreamWithLimit; +use num_derive::FromPrimitive; +use num_traits::{FromPrimitive, ToPrimitive}; +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::thread; +use std::time::Duration; + +fn main() { + println!("Hello, world!"); + let listener = TcpListener::bind("127.0.0.1:25565").unwrap(); + println!("Listening started."); + for stream in listener.incoming() { + match stream { + Ok(stream) => { + thread::spawn(|| { + println!("===============START====================="); + + stream + .set_read_timeout(Some(Duration::from_secs(3))) + .unwrap(); + stream + .set_write_timeout(Some(Duration::from_secs(3))) + .unwrap(); + println!( + "Timeout for connection: {:?}/{:?}", + stream.read_timeout(), + stream.write_timeout() + ); + handle_connection(stream); + println!("===============DONE======================"); + }); + } + Err(err) => { + dbg!(err); + } + } + } +} +#[derive(FromPrimitive, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +enum ConnectionState { + NotConnected = 0, + Status = 1, + Login = 2, + Transfer = 3, + Closed = -1, +} +struct Connection { + connection_state: ConnectionState, + tcp_stream: TcpStream, + compression_active: bool, +} +impl Connection { + fn handle(&mut self) -> Result<(), String> { + while self.connection_state != ConnectionState::Closed { + let x = self.tcp_stream.peek(&mut [0]); //see if we have at least one byte available + match x { + Ok(size) => { + println!("we should have 1 here: {size}"); + if size == 0 { + println!("Reached end of stream."); + self.connection_state = ConnectionState::Closed; + continue; + } + } + Err(_) => { + println!("could not peek if we reached the end of the stream."); + } + } + + let length = VarInt::read_stream(&mut self.tcp_stream)?; + println!("packet length: {}", length.as_rs()); + let bytes_left_in_package = length.to_rs(); + + let mut package_stream = RWStreamWithLimit::new( + &mut self.tcp_stream, + bytes_left_in_package.to_usize().unwrap(), + ); + let result = Self::handle_package( + &mut package_stream, + self.connection_state, + self.compression_active, + ); + match result { + Ok(new_connection_state) => { + assert_eq!( + package_stream.get_read_left(), + 0, + "The not failed package did not use up all its bytes or used to much!" + ); + self.connection_state = new_connection_state; + } + Err(e) => { + //discard rest of package for failed ones + discard_read(&mut self.tcp_stream, bytes_left_in_package.to_u8().unwrap()) + .map_err(|x| x.to_string())?; + + println!("Got an error during package handling: {e}"); + } + } + } + + Ok(()) + } + fn handshake( + stream: &mut T, + _compression: bool, + // bytes_left_in_package: &mut i32, + ) -> Result { + // println!("bytes left:{}", bytes_left_in_package); + let protocol_version = VarInt::read_stream(stream)?; + // *bytes_left_in_package -= read as i32; + println!("protocol version: {}", protocol_version.as_rs()); + // println!("bytes left:{}", bytes_left_in_package); + let address = + McString::read_stream(stream).map_err(|_| "Could not read string".to_string())?; + // *bytes_left_in_package -= read as i32; + println!("address: '{}'", address.as_rs()); + stream.read_exact(&mut [0, 2]).unwrap(); //server port. Unused + // *bytes_left_in_package -= 2; + let next_state_id = VarInt::read_stream(stream)?; + // *bytes_left_in_package -= read as i32; + println!("next state: {}", next_state_id.as_rs()); + // println!("bytes left:{}", bytes_left_in_package); + let next_state = FromPrimitive::from_i32(next_state_id.to_rs()); + match next_state { + Some(next_state) => Ok(next_state), + None => Err(format!( + "Got an unknown next state: {}", + next_state_id.as_rs() + )), + } + } + fn handle_package( + stream: &mut RWStreamWithLimit, + connection_state: ConnectionState, + compression: bool, + // bytes_left_in_package: usize, + ) -> Result { + // let mut stream = RWStreamWithLimit::new(stream, bytes_left_in_package); + // let stream = &mut stream; + let packet_id = VarInt::read_stream(stream)?; + // *bytes_left_in_package = i32::max(*bytes_left_in_package - read as i32, 0); + + println!("id: {:0>2x}", packet_id.as_rs()); + if connection_state == ConnectionState::NotConnected && packet_id.to_rs() == 0x00 { + return Self::handshake(stream, compression); + } + match FromPrimitive::from_i32(packet_id.to_rs()) { + Some(protocol) => { + // println!("bytes left:{}", bytes_left_in_package); + let res = handle(protocol, stream); + // println!("bytes left:{}", bytes_left_in_package); + match res { + Ok(_) => { + // println!("bytes left:{}", bytes_left_in_package); + println!("Success!"); + } + Err(_) => { + stream.discard_unread().map_err(|x| x.to_string())?; + // println!("bytes left:{}", bytes_left_in_package); + // *bytes_left_in_package -= discard_read(stream, *bytes_left_in_package as u8) + // as i32; + println!("Failure :("); + } + } + } + None => { + stream.discard_unread().map_err(|x| x.to_string())?; + // *bytes_left_in_package -= discard_read(stream, *bytes_left_in_package as u8) + // .map_err(|x| x.to_string())? as i32; + println!("I don't know this protocol yet, so Im gonna ignore it..."); + } + } + Ok(connection_state) + } +} +fn handle_connection(stream: TcpStream) { + let mut connection = Connection { + connection_state: ConnectionState::NotConnected, + tcp_stream: stream, + compression_active: false, + }; + let result = connection.handle(); + if let Err(e) = result { + dbg!(e); + } +} +fn discard_read(stream: &mut T, bytes: u8) -> Result { + stream.read_exact(&mut [0, bytes])?; + Ok(bytes as usize) +} diff --git a/src/protocols.rs b/src/protocols.rs new file mode 100644 index 0000000..7a8a272 --- /dev/null +++ b/src/protocols.rs @@ -0,0 +1,23 @@ +use crate::protocols::status::StatusProtocol; +use crate::utils::RWStreamWithLimit; +use num_derive::FromPrimitive; +use std::io::{Read, Write}; +// use num_traits::FromPrimitive; + +#[derive(FromPrimitive)] +pub enum Protocols { + Status = 0x00, + Ping = 0x01, +} +pub fn handle( + protocol: Protocols, + stream: &mut RWStreamWithLimit, + // bytes_left_in_package: &mut i32, +) -> Result<(), ()> { + match protocol { + Protocols::Status => StatusProtocol::handle(stream)?, + Protocols::Ping => {} + }; + Ok(()) +} +mod status; diff --git a/src/protocols/status.rs b/src/protocols/status.rs new file mode 100644 index 0000000..39c2f72 --- /dev/null +++ b/src/protocols/status.rs @@ -0,0 +1,48 @@ +use crate::types::string::McString; +use crate::types::McWrite; +use crate::utils::RWStreamWithLimit; +use std::io::{Read, Write}; + +pub struct StatusProtocol {} + +impl StatusProtocol { + pub fn handle( + stream: &mut RWStreamWithLimit, + // bytes_left_in_package: &mut i32, + ) -> Result<(), ()> { + McString(Self::get_sample_result()) + .write_stream(stream) + .map_err(|x| { + dbg!(x); + })?; + stream.discard_unread().map_err(|x| { + dbg!(x); + })?; + // *bytes_left_in_package = 0; + Ok(()) + } + fn get_sample_result() -> String { + "{ + \"version\": { + \"name\": \"1.21.2\", + \"protocol\": 768 + }, + \"players\": { + \"max\": 100, + \"online\": 5, + \"sample\": [ + { + \"name\": \"thinkofdeath\", + \"id\": \"4566e69f-c907-48ee-8d71-d7ba5aa00d20\" + } + ] + }, + \"description\": { + \"text\": \"Hello, world!\" + }, + \"favicon\": \"data:image/png;base64,\", + \"enforcesSecureChat\": false + }" + .to_string() + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..35d42ff --- /dev/null +++ b/src/types.rs @@ -0,0 +1,22 @@ +use std::io::{Read, Write}; + +pub trait McRead { + type Error; + fn read_stream(stream: &mut T) -> Result + where + Self: Sized; +} +pub trait McWrite { + type Error; + fn write_stream(&self, stream: &mut T) -> Result + where + Self: Sized; +} +pub trait McRustRepr { + type RustRepresentation; + fn into_rs(self) -> Self::RustRepresentation; + fn to_rs(&self) -> Self::RustRepresentation; + fn as_rs(&self) -> &Self::RustRepresentation; +} +pub mod string; +pub mod var_int; diff --git a/src/types/string.rs b/src/types/string.rs new file mode 100644 index 0000000..7ffa644 --- /dev/null +++ b/src/types/string.rs @@ -0,0 +1,58 @@ +use crate::types::var_int::VarInt; +use crate::types::{McRead, McRustRepr, McWrite}; +use std::io::{Read, Write}; + +pub struct McString(pub String); +impl McRead for McString { + type Error = (); + + fn read_stream(b: &mut T) -> Result + where + Self: Sized, + { + let size = VarInt::read_stream(b).map_err(|x| { + dbg!(x); + })?; + let size = *size as usize; + + let mut bytes = vec![0u8; size]; + let actual_size = b.read(&mut bytes).map_err(|x| { + dbg!(x); + })?; + assert_eq!(size, actual_size); + let value = String::from_utf8(bytes).map_err(|x| { + dbg!(x); + })?; + Ok(Self(value)) + } +} +impl McWrite for McString { + type Error = std::io::Error; + + fn write_stream(&self, stream: &mut T) -> Result + where + Self: Sized, + { + let buf = self.0.as_bytes(); + let length = buf.len(); //This does not actually count right (see https://wiki.vg/Protocol#Type:String) + VarInt(length as i32).write_stream(stream)?; + + stream.write_all(buf)?; + Ok(length) + } +} +impl McRustRepr for McString { + type RustRepresentation = String; + + fn into_rs(self) -> Self::RustRepresentation { + self.0 + } + + fn to_rs(&self) -> Self::RustRepresentation { + self.0.to_owned() + } + + fn as_rs(&self) -> &Self::RustRepresentation { + &self.0 + } +} diff --git a/src/types/var_int.rs b/src/types/var_int.rs new file mode 100644 index 0000000..6a7ba61 --- /dev/null +++ b/src/types/var_int.rs @@ -0,0 +1,91 @@ +use crate::types::{McRead, McRustRepr, McWrite}; +use std::io::{Read, Write}; +use std::ops::Deref; + +#[derive(Debug, Copy, Clone)] +pub struct VarInt(pub i32); +impl Deref for VarInt { + type Target = i32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl McWrite for VarInt { + type Error = std::io::Error; + + fn write_stream(&self, stream: &mut T) -> Result + where + Self: Sized, + { + let mut value = self.0 as u32; + loop { + if (value & Self::SEGMENT_BITS as u32) == 0 { + let _ = stream.write(&[value.to_le_bytes()[0]])?; + return Ok(1); + } + + let x = value & Self::SEGMENT_BITS as u32 | Self::CONTINUE_BIT as u32; + let x = x.to_le_bytes()[0]; + let _ = stream.write(&[x])?; + value >>= 7; + } + } +} +impl McRead for VarInt { + type Error = String; + fn read_stream(b: &mut T) -> Result { + let mut value = 0i32; + let mut position = 0; + // println!("CONTINUE bit: {:0>32b}", Self::CONTINUE_BIT); + // println!("SEGMENT bit: {:0>32b}", Self::SEGMENT_BITS); + + loop { + let mut current_byte = 0u8; + b.read_exact(std::slice::from_mut(&mut current_byte)) + .map_err(|x| x.to_string())?; + // println!( + // "b: {:0>32b}\nm: {:0>32b}\nr: {:0>32b}\n>: {:0>32b} ({position})\nv: {:0>32b}\nr2:{:0>32b}", + // current_byte, + // Self::SEGMENT_BITS, + // current_byte & Self::SEGMENT_BITS, + // ((current_byte & Self::SEGMENT_BITS) as i32) << position, + // value, + // value | (((current_byte & Self::SEGMENT_BITS) as i32) << position) + // ); + value |= ((current_byte & Self::SEGMENT_BITS) as i32) << position; + // println!("{x}:\n{current_byte:>32b}:\n{value:>32b}:\n{value}"); + + if (current_byte & Self::CONTINUE_BIT) == 0 { + break; + } + + position += 7; + + if position >= 32 { + return Err("VarInt is too big".to_string()); + } + } + + Ok(Self(value)) + } +} +impl VarInt { + const SEGMENT_BITS: u8 = 0x7F; + const CONTINUE_BIT: u8 = 0x80; +} +impl McRustRepr for VarInt { + type RustRepresentation = i32; + + fn into_rs(self) -> Self::RustRepresentation { + self.0 + } + + fn to_rs(&self) -> Self::RustRepresentation { + self.0 + } + + fn as_rs(&self) -> &Self::RustRepresentation { + &self.0 + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..550ab23 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,58 @@ +use std::io::{ErrorKind, Read, Write}; + +pub struct RWStreamWithLimit<'a, T: Read + Write> { + stream: &'a mut T, + read_bytes_left: usize, +} +impl<'a, T: Read + Write> RWStreamWithLimit<'a, T> { + pub(crate) fn new(stream: &'a mut T, read_limit: usize) -> Self { + Self { + stream, + read_bytes_left: read_limit, + } + } + pub(crate) fn discard_unread(&mut self) -> std::io::Result { + let mut total_read = 0; + while self.read_bytes_left > 0 { + let read = self.stream.read(&mut vec![0; self.read_bytes_left])?; + total_read += read; + self.read_bytes_left -= read; + } + Ok(total_read) + } + pub fn get_read_left(&self) -> usize { + self.read_bytes_left + } +} +impl<'a, T: Read + Write> Read for RWStreamWithLimit<'a, T> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let bytes_read; + if self.read_bytes_left > 0 { + if self.read_bytes_left >= buf.len() { + bytes_read = self.stream.read(buf)?; + } else { + println!("wants to read more than in the readable part of the stream"); + bytes_read = self.stream.read(&mut buf[0..self.read_bytes_left])?; + //TODO: decide if we wanna throw an error here or nah + } + self.read_bytes_left -= bytes_read; //TODO: maybe check if we read to much? + } else { + return Err(std::io::Error::new( + ErrorKind::Other, + "There is nothing more to read in this package", + )); + //TODO: maybe throw an error since there is no way anything gets read anymore? + } + Ok(bytes_read) + } +} +impl<'a, T: Read + Write> Write for RWStreamWithLimit<'a, T> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.read_bytes_left = 0; + self.stream.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.stream.flush() + } +}