diff --git a/Cargo.lock b/Cargo.lock index 56d17b0..e0815f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -114,6 +135,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -147,6 +188,27 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "humantime" version = "2.1.0" @@ -165,12 +227,33 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "md2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4f0f3ed25ff4f8d8d102288d92f900efc202661c884cf67dfe4f0d07c43d1f" +dependencies = [ + "digest", +] + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.7.4" @@ -181,10 +264,16 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" name = "minecraft_server_impl" version = "0.1.0" dependencies = [ + "base16ct", "byteorder", "colog", "flate2", "log", + "md2", + "md5", + "num-derive", + "num-traits", + "rand", ] [[package]] @@ -196,6 +285,83 @@ dependencies = [ "adler2", ] +[[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 = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +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 = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" version = "1.11.0" @@ -225,12 +391,47 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "windows-sys" version = "0.48.0" @@ -369,3 +570,24 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 477698d..b9509eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,13 @@ version = "0.1.0" edition = "2021" [dependencies] +base16ct = { version = "0.2.0", features = ["std"] } byteorder = "1.5.0" colog = "1.3.0" flate2 = "1.0.34" log = "0.4.22" +md2 = "0.10.2" +md5 = "0.7.0" +num-derive = "0.4.2" +num-traits = "0.2.19" +rand = "0.8.5" diff --git a/src/to_bytes.rs b/src/byte_ops.rs similarity index 100% rename from src/to_bytes.rs rename to src/byte_ops.rs diff --git a/src/chunk.rs b/src/chunk.rs index bdaaf1b..8a51d70 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -3,10 +3,10 @@ use flate2::write::ZlibEncoder; use flate2::Compression; use std::io::prelude::*; -use crate::to_bytes::ToBytes; +use crate::byte_ops::ToBytes; #[derive(Debug, Clone)] -struct MapChunk { +pub struct MapChunk { chunk_x: i32, chunk_y: i16, chunk_z: i32, @@ -17,7 +17,7 @@ struct MapChunk { } impl MapChunk { - fn new(chunk_x: i32, chunk_z: i32, compressed_data: BlockArray) -> Self { + pub fn new(chunk_x: i32, chunk_z: i32, compressed_data: BlockArray) -> Self { Self { chunk_x, chunk_y: 0, @@ -31,34 +31,35 @@ impl MapChunk { } #[derive(Debug, Clone)] -struct BlockArray { +pub struct BlockArray { compressed_size: i32, compressed_data: Vec, } impl BlockArray { - fn new_air() -> Self { - let mut output_vec = Vec::new(); + pub fn new_air() -> Self { + let mut block_vec = Vec::new(); for _ in 0..(16 * 127 * 15) { - output_vec.push(0); + block_vec.push(0); } for _ in 0..(16 * 127 * 15) / 2 { - output_vec.push(0); + block_vec.push(0); } for _ in 0..(16 * 127 * 15) / 2 { - output_vec.push(0); + block_vec.push(0); } for _ in 0..(16 * 127 * 15) / 2 { - output_vec.push(0); + block_vec.push(0); } let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - encoder.write(&output_vec).unwrap(); + encoder.write(&block_vec).unwrap(); + let output_buf = encoder.finish().unwrap(); Self { - compressed_size: 1, - compressed_data: encoder.finish().unwrap(), + compressed_size: output_buf.len() as i32, + compressed_data: output_buf, } } } @@ -87,6 +88,9 @@ impl ToBytes for MapChunk { buffer.write_u8(self.size_y).unwrap(); buffer.write_u8(self.size_z).unwrap(); + buffer.write_i32::(self.compressed_data.compressed_size).unwrap(); + buffer.write_all(&self.compressed_data.compressed_data).unwrap(); + buffer } } @@ -98,6 +102,24 @@ pub struct PreChunk { pub mode: bool, // True to load, False to unload } +impl PreChunk { + pub fn new_load(x_coord: i32, z_coord: i32) -> Self { + Self { + x_coord, + z_coord, + mode: true + } + } + + pub fn new_unload(x_coord: i32, z_coord: i32) -> Self { + Self { + x_coord, + z_coord, + mode: true + } + } +} + impl ToBytes for PreChunk { type Bytes = [u8; 9]; diff --git a/src/main.rs b/src/main.rs index 2734846..3d902af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,18 @@ mod utils; -mod to_bytes; +mod byte_ops; mod chunk; -use std::{io::{self, Write}, net::{TcpListener, TcpStream}}; +use std::{io::{self, Read, Write}, net::{TcpListener, TcpStream}}; -use chunk::PreChunk; +use base16ct::lower::encode_string; +use chunk::{BlockArray, MapChunk, PreChunk}; use log::{info, warn}; use byteorder::{ReadBytesExt, WriteBytesExt, BE}; -use to_bytes::ToBytes; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use byte_ops::ToBytes; use utils::{MCString, ReadMCString, WriteMCString}; +use rand::random; fn main() { @@ -21,71 +25,113 @@ fn main() { for mut connection in listener.incoming().filter_map(|c| c.ok()) { info!("Connected to client @ {}", connection.peer_addr().unwrap()); while let Some(cmd) = connection.read_u8().ok() { - let command = Command::try_from(cmd); - info!("COMMAND: {command:?}"); + let command = Command::from_u8(cmd); + info!("COMMAND: {command:?} (0x{cmd:02X?})"); handle_command(&mut connection, command.unwrap()).unwrap(); - } warn!("Lost connection to client"); } } -fn handle_command(connection: &mut TcpStream, command: Command) -> Result<(), io::Error> { +fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<(), io::Error> { match command { Command::KeepAlive => todo!(), Command::Handshake => { let username = connection.read_mcstring()?; - info!("Handshake: {username}"); + let random_number = random::(); + let random_hash = encode_string(md5::compute(random_number.to_le_bytes()).as_slice()); + + info!("Handshake with {username} successful. Providing hash: {random_hash:?}"); connection.write_u8(0x02)?; - connection.write_mcstring(&MCString::try_from("-").unwrap())?; + connection.write_mcstring(&MCString::try_from(random_hash).unwrap())?; }, Command::Login => { - info!("---"); info!("Initiating login"); let protocol_version = connection.read_u32::()?; let username = connection.read_mcstring()?; - let _password = connection.read_mcstring()?; - let _map_seed = connection.read_i64::()?; - let _dimension = connection.read_i8()?; + let password = connection.read_mcstring()?; + let map_seed = connection.read_i64::()?; + let dimension = connection.read_i8()?; info!("Protocol Version: {protocol_version}"); info!("Username: {username}"); + info!("Password: {password}"); + info!("Map Seed: {map_seed}"); + info!("Dimension: {dimension}"); let login_packet = ServerLoginPacket { - entity_id: 1, + entity_id: 1200, unknown1: MCString::default(), unknown2: MCString::default(), map_seed: 1715505462032542147, dimension: 0, }; - login_packet.write_into(connection)?; + connection.write_u8(Command::Login as u8).unwrap(); + connection.write(&login_packet.to_bytes())?; info!("Responded to auth request"); - let prechunk = PreChunk { - x_coord: 0, - z_coord: 0, - mode: true, - }; + for i in 0..7 { + for o in 0..7 { + let x = (-4 + i) * 16; + let z = (-5 + o) * 16; - connection.write_u8(Command::PreChunk as u8)?; - connection.write_all(&prechunk.to_bytes())?; + connection.write_u8(Command::PreChunk as u8).unwrap(); + connection.write_all(&PreChunk::new_load(x, z).to_bytes()).unwrap(); + + connection.write_u8(Command::MapChunk as u8)?; + connection.write_all(&MapChunk::new(x, z, BlockArray::new_air()).to_bytes())?; + } + } + + connection.write_u8(Command::SpawnPosition as u8)?; + connection.write_u32::(0)?; + connection.write_u32::(70)?; + connection.write_u32::(0)?; + + let playerpos = PlayerPositionAndLook { + position_x: 0.0, + stance: 0.0, + position_y: 70.0, + position_z: 0.0, + yaw: 0.0, + pitch: 0.0, + on_ground: true, + }; + connection.write_u8(Command::PlayerPositionAndLook as u8)?; + connection.write_all(&playerpos.to_bytes())?; }, - _ => unimplemented!("This command is probably `Server -> Client` only") + Command::PlayerPositionAndLook => { + let _poslook = PlayerPositionAndLook::from_bytes(&mut connection); + } + Command::PlayerPosition => { + let _pos = PlayerPosition::from_bytes(&mut connection); + } + c => unimplemented!("This command ({c:?}) is probably `Server -> Client` only") } Ok(()) } +#[repr(u8)] #[derive(Debug, Clone, Copy)] +#[derive(FromPrimitive)] enum Command { KeepAlive = 0x00, Login = 0x01, Handshake = 0x02, + ChatMessage = 0x03, + TimeUpdate = 0x04, + PlayerInventory = 0x05, + SpawnPosition = 0x06, + UpdateHealth = 0x08, + Respawn = 0x09, + PlayerPositionAndLook = 0x0D, + PlayerPosition = 0x0B, PreChunk = 0x32, - ChunkData = 0x33, - Kick = 0xFF, + MapChunk = 0x33, + Disconnect = 0xFF, } #[derive(Debug, Clone, Copy)] @@ -93,24 +139,6 @@ struct CommandError { _id: u8, } -impl TryFrom for Command { - type Error = CommandError; - - fn try_from(value: u8) -> Result { - Ok(match value { - 0x00 => Self::KeepAlive, - 0x01 => Self::Login, - 0x02 => Self::Handshake, - 0x32 => Self::PreChunk, - 0x33 => Self::ChunkData, - 0xFF => Self::Kick, - v => return Err(CommandError{ - _id: v, - }), - }) - } -} - struct ServerLoginPacket { entity_id: i32, unknown1: MCString, @@ -119,14 +147,111 @@ struct ServerLoginPacket { dimension: i8, } -impl ServerLoginPacket { - fn write_into(&self, stream: &mut W) -> Result<(), io::Error> { - stream.write_i32::(self.entity_id)?; - stream.write_mcstring(&self.unknown1)?; - stream.write_mcstring(&self.unknown2)?; - stream.write_i64::(self.map_seed)?; - stream.write_i8(self.dimension)?; +impl ToBytes for ServerLoginPacket { + type Bytes = Vec; - Ok(()) + fn to_bytes(self) -> Self::Bytes { + let mut out_buf = Vec::new(); + out_buf.write_i32::(self.entity_id).unwrap(); + out_buf.write_mcstring(&self.unknown1).unwrap(); + out_buf.write_mcstring(&self.unknown2).unwrap(); + out_buf.write_i64::(self.map_seed).unwrap(); + out_buf.write_i8(self.dimension).unwrap(); + + out_buf + } +} + +#[derive(Debug, Clone, Copy)] +struct PlayerPositionAndLook { + position_x: f64, + stance: f64, + position_y: f64, + position_z: f64, + yaw: f32, + pitch: f32, + on_ground: bool, +} + +impl ToBytes for PlayerPositionAndLook { + type Bytes = Vec; + + fn to_bytes(self) -> Self::Bytes { + let mut out_buf = Vec::new(); + out_buf.write_f64::(self.position_x).unwrap(); + out_buf.write_f64::(self.stance).unwrap(); + out_buf.write_f64::(self.position_y).unwrap(); + out_buf.write_f64::(self.position_z).unwrap(); + out_buf.write_f32::(self.yaw).unwrap(); + out_buf.write_f32::(self.pitch).unwrap(); + out_buf.write_u8(self.on_ground as u8).unwrap(); + + out_buf + } +} + +impl PlayerPositionAndLook { + fn from_bytes(stream: &mut R) -> Self { + let position_x = stream.read_f64::().unwrap(); + let position_y = stream.read_f64::().unwrap(); + let stance = stream.read_f64::().unwrap(); + let position_z = stream.read_f64::().unwrap(); + + let yaw = stream.read_f32::().unwrap(); + let pitch = stream.read_f32::().unwrap(); + let on_ground = stream.read_u8().unwrap() != 0; + + Self { + position_x, + stance, + position_y, + position_z, + yaw, + pitch, + on_ground + } + } +} + +#[derive(Debug, Clone, Copy)] +struct PlayerPosition { + position_x: f64, + position_y: f64, + stance: f64, + position_z: f64, + on_ground: bool, +} + +impl ToBytes for PlayerPosition { + type Bytes = Vec; + + fn to_bytes(self) -> Self::Bytes { + let mut out_buf = Vec::new(); + out_buf.write_f64::(self.position_x).unwrap(); + out_buf.write_f64::(self.position_y).unwrap(); + out_buf.write_f64::(self.stance).unwrap(); + out_buf.write_f64::(self.position_z).unwrap(); + out_buf.write_u8(self.on_ground as u8).unwrap(); + + out_buf + } +} + +impl PlayerPosition { + fn from_bytes(stream: &mut R) -> Self { + let position_x = stream.read_f64::().unwrap(); + let position_y = stream.read_f64::().unwrap(); + let stance = stream.read_f64::().unwrap(); + let position_z = stream.read_f64::().unwrap(); + + let on_ground = stream.read_u8().unwrap() != 0; + + Self { + position_x, + stance, + position_y, + position_z, + on_ground + } } } diff --git a/src/utils.rs b/src/utils.rs index d6f01ab..19bad30 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -23,6 +23,21 @@ impl TryFrom<&str> for MCString { } } +impl TryFrom for MCString { + type Error = (); + + fn try_from(value: String) -> Result { + if value.len() > u16::MAX as usize { + return Err(()) + } + + Ok(Self { + len: value.len() as u16, + chars: value.as_bytes().to_vec(), + }) + } +} + impl Display for MCString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", String::from_utf8(self.chars.clone()).unwrap())