diff --git a/.gitignore b/.gitignore index 710d3d3..9a1824f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target/ .direnv/ .devenv/ +/run/ diff --git a/Cargo.lock b/Cargo.lock index bbc2d8b..3d72f95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,72 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "darling" version = "0.20.10" @@ -66,6 +120,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "ident_case" version = "1.0.1" @@ -78,20 +138,96 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "potato" version = "0.1.0" dependencies = [ + "bytes", "potato-protocol", "serde", "serde_json", "thiserror", + "tokio", "uuid", ] @@ -100,11 +236,13 @@ name = "potato-protocol" version = "0.1.0" dependencies = [ "byteorder", + "bytes", "fastnbt", "potato-protocol-derive", "serde", "serde_json", "thiserror", + "tokio", "uuid", ] @@ -136,12 +274,33 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.218" @@ -183,6 +342,31 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "strsim" version = "0.11.1" @@ -220,6 +404,35 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -231,3 +444,82 @@ name = "uuid" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 52d7033..d011cee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,5 @@ serde = { version = "1.0.218", features = ["derive"] } serde_json = "1.0.140" thiserror = "2.0.11" uuid = "1.15.1" +bytes = "1" +tokio = { version = "1.43.0", features = ["full"] } diff --git a/potato-protocol-derive/src/lib.rs b/potato-protocol-derive/src/lib.rs index 4c34726..9b0170d 100644 --- a/potato-protocol-derive/src/lib.rs +++ b/potato-protocol-derive/src/lib.rs @@ -122,16 +122,16 @@ fn generate_packet_encodable_enum_impl( .collect(); quote! { impl crate::packet_encodable::PacketEncodable for #ident { - fn encode_packet(&self, cursor: &mut Vec) -> Result<(), crate::packet_encodable::PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl bytes::BufMut) -> Result<(), crate::packet_encodable::PacketEncodeError> { let value: crate::datatypes::var_int::VarInt = match self { #(#encode_arms)* }.into(); - value.encode_packet(cursor) + value.encode_packet(buf) } - fn decode_packet(cursor: &mut std::io::Cursor<&[u8]>) -> Result { - let value: i32 = crate::datatypes::var_int::VarInt::decode_packet(cursor)?.into(); + fn decode_packet(buf: &mut impl bytes::Buf) -> Result { + let value: i32 = crate::datatypes::var_int::VarInt::decode_packet(buf)?.into(); match value { #(#decode_arms)* _ => Err(crate::packet_encodable::PacketDecodeError::UnkownEnumVariant { @@ -160,12 +160,12 @@ fn generate_packet_encodable_struct_impl( quote! { impl crate::packet_encodable::PacketEncodable for #ident { - fn encode_packet(&self, cursor: &mut Vec) -> Result<(), crate::packet_encodable::PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl bytes::BufMut) -> Result<(), crate::packet_encodable::PacketEncodeError> { #(#fields_encode_packet_calls)* Ok(()) } - fn decode_packet(cursor: &mut std::io::Cursor<&[u8]>) -> Result { + fn decode_packet(buf: &mut impl bytes::Buf) -> Result { Ok(Self { #(#fields_decode_packet_calls)* }) @@ -179,7 +179,7 @@ fn generate_struct_field_decode_packet_calls(field: &syn::Field) -> proc_macro2: let field_ty = &field.ty; quote! { - #field_ident: <#field_ty>::decode_packet(cursor)?, + #field_ident: <#field_ty>::decode_packet(buf)?, } } @@ -187,7 +187,7 @@ fn generate_struct_field_encode_packet_calls(field: &syn::Field) -> proc_macro2: let field_ident = &field.ident; quote! { - self.#field_ident.encode_packet(cursor)?; + self.#field_ident.encode_packet(buf)?; } } diff --git a/potato-protocol/Cargo.toml b/potato-protocol/Cargo.toml index 72793e4..266e431 100644 --- a/potato-protocol/Cargo.toml +++ b/potato-protocol/Cargo.toml @@ -10,6 +10,8 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true uuid.workspace = true +bytes.workspace = true +tokio.workspace = true byteorder = "1.5.0" diff --git a/potato-protocol/src/datatypes/byte_array.rs b/potato-protocol/src/datatypes/byte_array.rs index 2b2fbaf..c84cc86 100644 --- a/potato-protocol/src/datatypes/byte_array.rs +++ b/potato-protocol/src/datatypes/byte_array.rs @@ -1,4 +1,6 @@ -use crate::packet_encodable::{PacketDecodeError, PacketEncodable}; +use bytes::{Buf, BufMut}; + +use crate::packet_encodable::PacketEncodable; #[derive(Debug)] pub struct ByteArray(Vec); @@ -6,25 +8,22 @@ pub struct ByteArray(Vec); impl PacketEncodable for ByteArray { fn encode_packet( &self, - buffer: &mut Vec, + buf: &mut impl BufMut, ) -> Result<(), crate::packet_encodable::PacketEncodeError> { // Push all data to the buffer, length is not included. - buffer.extend_from_slice(&self.0); + buf.put_slice(&self.0); Ok(()) } fn decode_packet( - cursor: &mut std::io::Cursor<&[u8]>, + buf: &mut impl Buf, ) -> Result { // Read all remaining data from the cursor. Length should be assumed to be the rest of the // message. - let data = cursor - .get_ref() - .get(cursor.position() as usize..) - .ok_or(PacketDecodeError::UnexpectedEndOfPacket)? - .to_vec(); + let mut dst = Vec::with_capacity(buf.remaining()); + buf.copy_to_slice(&mut dst); - Ok(ByteArray(data)) + Ok(ByteArray(dst)) } } diff --git a/potato-protocol/src/datatypes/identifier.rs b/potato-protocol/src/datatypes/identifier.rs index b17af97..afcba94 100644 --- a/potato-protocol/src/datatypes/identifier.rs +++ b/potato-protocol/src/datatypes/identifier.rs @@ -1,3 +1,5 @@ +use bytes::{Buf, BufMut}; + use crate::packet_encodable::{PacketDecodeError, PacketEncodable, PacketEncodeError}; // The _phantom is used to stop construction of the struct outside of the new() function. Not for @@ -42,13 +44,13 @@ impl Identifier { } impl PacketEncodable for Identifier { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { let val: String = self.into(); - val.encode_packet(buffer) + val.encode_packet(buf) } - fn decode_packet(cursor: &mut std::io::Cursor<&[u8]>) -> Result { - let str = String::decode_packet(cursor)?; + fn decode_packet(buf: &mut impl Buf) -> Result { + let str = String::decode_packet(buf)?; let mut parts = str.split(":"); Ok(Self::new( diff --git a/potato-protocol/src/datatypes/position.rs b/potato-protocol/src/datatypes/position.rs index 52ef149..81213bf 100644 --- a/potato-protocol/src/datatypes/position.rs +++ b/potato-protocol/src/datatypes/position.rs @@ -1,3 +1,5 @@ +use bytes::{Buf, BufMut}; + use crate::packet_encodable::PacketEncodable; /// Struct holding an integer position in the world. Values are bound as follows: @@ -30,18 +32,18 @@ impl Position { impl PacketEncodable for Position { fn encode_packet( &self, - buffer: &mut Vec, + buf: &mut impl BufMut, ) -> Result<(), crate::packet_encodable::PacketEncodeError> { let stuffed: i64 = ((self.x as i64 & 0x3FFFFFF) << 38) | ((self.z as i64 & 0x3FFFFFF) << 12) | (self.y as i64 & 0xFFF); - stuffed.encode_packet(buffer) + stuffed.encode_packet(buf) } fn decode_packet( - cursor: &mut std::io::Cursor<&[u8]>, + buf: &mut impl Buf, ) -> Result { - let stuffed = i64::decode_packet(cursor)?; + let stuffed = i64::decode_packet(buf)?; Ok(Position { x: (stuffed >> 38) as i32, y: (stuffed << 52 >> 52) as i16, diff --git a/potato-protocol/src/datatypes/var_int.rs b/potato-protocol/src/datatypes/var_int.rs index 7c5699f..32ce5ac 100644 --- a/potato-protocol/src/datatypes/var_int.rs +++ b/potato-protocol/src/datatypes/var_int.rs @@ -1,55 +1,86 @@ -use std::io::Read; +use bytes::{Buf, BufMut}; +use tokio::{io::AsyncWriteExt, net::tcp::OwnedWriteHalf}; use crate::packet_encodable::{PacketDecodeError, PacketEncodable}; const SEGMENT_BITS: u8 = 0x7F; const CONTINUE_BIT: u8 = 0x80; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct VarInt(i32); impl VarInt { - pub fn read(readable: &mut dyn Read) -> Result { - let mut value: i32 = 0; - let mut pos = 0; - let mut current_byte = vec![0u8; 1]; + pub fn encoded_len(&self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + pub async fn write_to_stream(&self, stream: &mut OwnedWriteHalf) -> Result<(), std::io::Error> { + let mut value = self.0; loop { - readable.read_exact(&mut current_byte)?; - - value |= ((current_byte[0] & SEGMENT_BITS) as i32) << pos; - if current_byte[0] & 0x80 == 0 { + if value & !(SEGMENT_BITS as i32) == 0 { + stream.write_u8(value as u8).await?; break; } - pos += 7; + + stream + .write_u8(((value & (SEGMENT_BITS as i32)) | (CONTINUE_BIT as i32)) as u8) + .await?; + value >>= 7; } - Ok(VarInt(value)) + Ok(()) } } impl PacketEncodable for VarInt { fn encode_packet( &self, - buffer: &mut Vec, + buf: &mut impl BufMut, ) -> Result<(), crate::packet_encodable::PacketEncodeError> { let mut value = self.0; loop { if value & !(SEGMENT_BITS as i32) == 0 { - buffer.push(value as u8); + buf.put_u8(value as u8); break; } - buffer.push(((value & (SEGMENT_BITS as i32)) | (CONTINUE_BIT as i32)) as u8); + buf.put_u8(((value & (SEGMENT_BITS as i32)) | (CONTINUE_BIT as i32)) as u8); value >>= 7; } Ok(()) } - fn decode_packet(cursor: &mut std::io::Cursor<&[u8]>) -> Result { - VarInt::read(cursor).map_err(PacketDecodeError::IOError) + fn decode_packet(buf: &mut impl Buf) -> Result { + let mut value: i32 = 0; + let mut pos = 0; + + loop { + if !buf.has_remaining() { + // The data is incomplete + return Err(PacketDecodeError::Incomplete); + } + + let current_byte = buf.get_u8(); + + value |= ((current_byte & SEGMENT_BITS) as i32) << pos; + if current_byte & 0x80 == 0 { + break; + } + pos += 7; + + if pos > 32 { + // The value is too large + return Err(PacketDecodeError::InvalidVarInt); + } + } + + Ok(VarInt(value)) } } diff --git a/potato-protocol/src/packet/clientbound/keep_alive.rs b/potato-protocol/src/packet/clientbound/keep_alive.rs new file mode 100644 index 0000000..1ec362b --- /dev/null +++ b/potato-protocol/src/packet/clientbound/keep_alive.rs @@ -0,0 +1,10 @@ +use potato_protocol_derive::Packet; + +#[derive(Debug, Packet)] +#[packet( + configuration_id = crate::ids::configuration::clientbound::KEEP_ALIVE, + play_id = crate::ids::play::clientbound::KEEP_ALIVE, +)] +pub struct KeepAlivePacket { + pub id: i64, +} diff --git a/potato-protocol/src/packet/clientbound/mod.rs b/potato-protocol/src/packet/clientbound/mod.rs index dcc712e..457caf2 100644 --- a/potato-protocol/src/packet/clientbound/mod.rs +++ b/potato-protocol/src/packet/clientbound/mod.rs @@ -1,5 +1,6 @@ pub mod finish_configuration; pub mod game_event; +pub mod keep_alive; pub mod login; pub mod login_disconnect; pub mod login_finished; @@ -11,6 +12,7 @@ pub mod status_response; pub use finish_configuration::FinishConfigurationPacket; pub use game_event::GameEventPacket; +pub use keep_alive::KeepAlivePacket; pub use login::LoginPacket; pub use login_disconnect::LoginDisconnectPacket; pub use login_finished::LoginFinishedPacket; diff --git a/potato-protocol/src/packet/mod.rs b/potato-protocol/src/packet/mod.rs index 6307416..5574411 100644 --- a/potato-protocol/src/packet/mod.rs +++ b/potato-protocol/src/packet/mod.rs @@ -1,7 +1,15 @@ pub mod clientbound; pub mod serverbound; -use crate::packet_encodable::PacketEncodable; +use std::fmt; + +use bytes::Bytes; +use tokio::{io::AsyncWriteExt, net::tcp::OwnedWriteHalf}; + +use crate::{ + datatypes::var_int::VarInt, + packet_encodable::{PacketDecodeError, PacketEncodable}, +}; pub trait Packet: PacketEncodable { const HANDSHAKE_ID: i32; @@ -9,4 +17,30 @@ pub trait Packet: PacketEncodable { const LOGIN_ID: i32; const CONFIGURATION_ID: i32; const PLAY_ID: i32; + + fn from_raw(mut raw: RawPacket) -> Result { + Self::decode_packet(&mut raw.data) + } +} + +pub struct RawPacket { + pub id: VarInt, + pub data: Bytes, +} + +impl RawPacket { + pub async fn write_to_stream(&self, stream: &mut OwnedWriteHalf) -> Result<(), std::io::Error> { + let length: VarInt = (self.id.encoded_len() + self.data.len()).into(); + length.write_to_stream(stream).await?; + self.id.write_to_stream(stream).await?; + stream.write_all(&self.data).await?; + Ok(()) + } +} + +impl fmt::Display for RawPacket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let id: i32 = self.id.into(); + write!(f, "{{ id: {}, data: {:X?} }}", id, self.data) + } } diff --git a/potato-protocol/src/packet/serverbound/client_information.rs b/potato-protocol/src/packet/serverbound/client_information.rs index ecabec5..c85f957 100644 --- a/potato-protocol/src/packet/serverbound/client_information.rs +++ b/potato-protocol/src/packet/serverbound/client_information.rs @@ -1,3 +1,4 @@ +use bytes::{Buf, BufMut}; use potato_protocol_derive::{Packet, PacketEncodable}; use crate::packet_encodable::{PacketDecodeError, PacketEncodable, PacketEncodeError}; @@ -56,7 +57,7 @@ pub struct DisplayedSkinParts { } impl PacketEncodable for DisplayedSkinParts { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { let value = (self.cape as u8) | (self.jacket as u8) << 1 | (self.left_sleeve as u8) << 2 @@ -65,11 +66,11 @@ impl PacketEncodable for DisplayedSkinParts { | (self.right_pants_leg as u8) << 5 | (self.hat as u8) << 6; - value.encode_packet(buffer) + value.encode_packet(buf) } - fn decode_packet(cursor: &mut std::io::Cursor<&[u8]>) -> Result { - let value = u8::decode_packet(cursor)?; + fn decode_packet(buf: &mut impl Buf) -> Result { + let value = u8::decode_packet(buf)?; Ok(Self { cape: value & 0x01 != 0, diff --git a/potato-protocol/src/packet/serverbound/keep_alive.rs b/potato-protocol/src/packet/serverbound/keep_alive.rs new file mode 100644 index 0000000..3baa854 --- /dev/null +++ b/potato-protocol/src/packet/serverbound/keep_alive.rs @@ -0,0 +1,10 @@ +use potato_protocol_derive::Packet; + +#[derive(Debug, Packet)] +#[packet( + configuration_id = crate::ids::configuration::serverbound::KEEP_ALIVE, + play_id = crate::ids::play::serverbound::KEEP_ALIVE, +)] +pub struct KeepAlivePacket { + pub id: i64, +} diff --git a/potato-protocol/src/packet/serverbound/mod.rs b/potato-protocol/src/packet/serverbound/mod.rs index 9719ab2..c8dd7fb 100644 --- a/potato-protocol/src/packet/serverbound/mod.rs +++ b/potato-protocol/src/packet/serverbound/mod.rs @@ -4,6 +4,7 @@ pub mod custom_payload; pub mod finish_configuration; pub mod hello; pub mod intention; +pub mod keep_alive; pub mod login_acknowledged; pub mod move_player_pos; pub mod move_player_pos_rot; @@ -18,6 +19,7 @@ pub use custom_payload::CustomPayloadPacket; pub use finish_configuration::FinishConfigurationPacket; pub use hello::HelloPacket; pub use intention::IntentionPacket; +pub use keep_alive::KeepAlivePacket; pub use login_acknowledged::LoginAcknowledgedPacket; pub use move_player_pos::MovePlayerPosPacket; pub use move_player_pos_rot::MovePlayerPosRotPacket; diff --git a/potato-protocol/src/packet_encodable/mod.rs b/potato-protocol/src/packet_encodable/mod.rs index 03cf4d9..7d3d981 100644 --- a/potato-protocol/src/packet_encodable/mod.rs +++ b/potato-protocol/src/packet_encodable/mod.rs @@ -1,10 +1,6 @@ -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use serde::{Deserialize, Serialize, de::DeserializeOwned}; -use std::{ - fmt, - io::{Cursor, Read}, - marker::PhantomData, -}; +use bytes::{Buf, BufMut}; +use serde::{Serialize, de::DeserializeOwned}; +use std::fmt; use thiserror::Error; use uuid::Uuid; @@ -12,8 +8,8 @@ use uuid::Uuid; use crate::datatypes::var_int::VarInt; pub trait PacketEncodable: Sized + fmt::Debug { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError>; - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result; + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError>; + fn decode_packet(buf: &mut impl Buf) -> Result; } #[derive(Error, Debug)] @@ -32,6 +28,10 @@ pub enum PacketDecodeError { InvalidIdentifier(String), #[error("Error while decoding NBT in packet {0}")] NbtError(#[from] fastnbt::error::Error), + #[error("Not enough data to decode the packet yet")] + Incomplete, + #[error("Invalid VarInt")] + InvalidVarInt, } #[derive(Error, Debug)] @@ -47,13 +47,13 @@ pub enum PacketEncodeError { } impl PacketEncodable for Uuid { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { - buffer.write_u128::(self.as_u128())?; + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { + buf.put_u128(self.as_u128()); Ok(()) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let data: u128 = cursor.read_u128::()?; + fn decode_packet(buf: &mut impl Buf) -> Result { + let data = buf.get_u128(); Ok(Uuid::from_u128(data)) } } @@ -65,13 +65,13 @@ impl PacketEncodable for Json where T: Serialize + DeserializeOwned + fmt::Debug, { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { let json = serde_json::to_string(&self.0)?; - json.encode_packet(buffer) + json.encode_packet(buf) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let json = String::decode_packet(cursor)?; + fn decode_packet(buf: &mut impl Buf) -> Result { + let json = String::decode_packet(buf)?; Ok(Self(serde_json::from_str(&json)?)) } } @@ -85,15 +85,16 @@ impl PacketEncodable for Nbt where T: Serialize + DeserializeOwned + fmt::Debug, { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { - fastnbt::to_writer_with_opts(buffer, &self.0, fastnbt::SerOpts::network_nbt())?; + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { + fastnbt::to_writer_with_opts(buf.writer(), &self.0, fastnbt::SerOpts::network_nbt())?; Ok(()) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let value = fastnbt::from_bytes(cursor.get_ref())?; - - Ok(Nbt(value)) + fn decode_packet(buf: &mut impl Buf) -> Result { + todo!("Decode NBT in packets") + // let value = fastnbt::from_bytes(buf)?; + // + // Ok(Nbt(value)) } } @@ -101,21 +102,21 @@ impl PacketEncodable for Option where T: PacketEncodable, { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { let present = self.is_some(); - present.encode_packet(buffer)?; + present.encode_packet(buf)?; if let Some(value) = self { - value.encode_packet(buffer)?; + value.encode_packet(buf)?; } Ok(()) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let present = bool::decode_packet(cursor)?; + fn decode_packet(buf: &mut impl Buf) -> Result { + let present = bool::decode_packet(buf)?; if present { - Ok(Some(T::decode_packet(cursor)?)) + Ok(Some(T::decode_packet(buf)?)) } else { Ok(None) } @@ -126,22 +127,22 @@ impl PacketEncodable for Vec where T: PacketEncodable, { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { let length: VarInt = self.len().into(); - length.encode_packet(buffer)?; + length.encode_packet(buf)?; for item in self { - item.encode_packet(buffer)?; + item.encode_packet(buf)?; } Ok(()) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let length: usize = VarInt::decode_packet(cursor)?.into(); + fn decode_packet(buf: &mut impl Buf) -> Result { + let length: usize = VarInt::decode_packet(buf)?.into(); let mut vec = Vec::with_capacity(length); for _ in 0..length { - vec.push(T::decode_packet(cursor)?); + vec.push(T::decode_packet(buf)?); } Ok(vec) @@ -149,18 +150,18 @@ where } impl PacketEncodable for String { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { let lenght: VarInt = self.len().into(); - lenght.encode_packet(buffer)?; + lenght.encode_packet(buf)?; - buffer.extend_from_slice(self.as_bytes()); + buf.put_slice(self.as_bytes()); Ok(()) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let lenght: usize = VarInt::decode_packet(cursor)?.into(); + fn decode_packet(buf: &mut impl Buf) -> Result { + let lenght: usize = VarInt::decode_packet(buf)?.into(); let mut buffer = vec![0; lenght]; - cursor.read_exact(&mut buffer)?; + buf.copy_to_slice(&mut buffer); let value = String::from_utf8(buffer)?; Ok(value) @@ -168,52 +169,28 @@ impl PacketEncodable for String { } impl PacketEncodable for bool { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { - buffer.push(if *self { 1 } else { 0 }); + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { + buf.put_u8(if *self { 1 } else { 0 }); Ok(()) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let v = cursor.read_u8()?; + fn decode_packet(buf: &mut impl Buf) -> Result { + let v = buf.get_u8(); Ok(v != 0) } } -impl PacketEncodable for i8 { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { - buffer.write_i8(*self)?; - Ok(()) - } - - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let value = cursor.read_i8()?; - Ok(value) - } -} - -impl PacketEncodable for u8 { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { - buffer.write_u8(*self)?; - Ok(()) - } - - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let value = cursor.read_u8()?; - Ok(value) - } -} - macro_rules! number_impl { ($($type:ty, $read_fn:tt, $write_fn:tt),* $(,)?) => { $( impl PacketEncodable for $type { - fn encode_packet(&self, buffer: &mut Vec) -> Result<(), PacketEncodeError> { - buffer.$write_fn::(*self)?; + fn encode_packet(&self, buf: &mut impl BufMut) -> Result<(), PacketEncodeError> { + buf.$write_fn(*self); Ok(()) } - fn decode_packet(cursor: &mut Cursor<&[u8]>) -> Result { - let value = cursor.$read_fn::()?; + fn decode_packet(buf: &mut impl Buf) -> Result { + let value = buf.$read_fn(); Ok(value) } } @@ -222,14 +199,16 @@ macro_rules! number_impl { } number_impl! { - u16, read_u16, write_u16, - u32, read_u32, write_u32, - u64, read_u64, write_u64, + u8, get_u8, put_u8, + u16, get_u16, put_u16, + u32, get_u32, put_u32, + u64, get_u64, put_u64, - i16, read_i16, write_i16, - i32, read_i32, write_i32, - i64, read_i64, write_i64, + i8, get_i8, put_i8, + i16, get_i16, put_i16, + i32, get_i32, put_i32, + i64, get_i64, put_i64, - f32, read_f32, write_f32, - f64, read_f64, write_f64, + f32, get_f32, put_f32, + f64, get_f64, put_f64, } diff --git a/potato/Cargo.toml b/potato/Cargo.toml index 68d0ca4..192f221 100644 --- a/potato/Cargo.toml +++ b/potato/Cargo.toml @@ -9,4 +9,6 @@ potato-protocol = { path = "../potato-protocol" } serde.workspace = true serde_json.workspace = true thiserror.workspace = true +tokio.workspace = true uuid.workspace = true +bytes.workspace = true diff --git a/potato/src/connection.rs b/potato/src/connection.rs new file mode 100644 index 0000000..de46de0 --- /dev/null +++ b/potato/src/connection.rs @@ -0,0 +1,528 @@ +use std::{collections::VecDeque, net::SocketAddr, sync::Arc}; + +use bytes::{Buf, BytesMut}; +use potato_protocol::{ + datatypes::{identifier::Identifier, pack::Pack, var_int::VarInt}, + packet::{ + Packet, RawPacket, + clientbound::{ + self, GameEventPacket, SetChunkCacheCenterPacket, + registry_data::{ + Biome, BiomeEffects, DimensionType, PaintingVariant, RegistryData, + RegistryDataEntry, WolfVariant, + }, + status_response, + }, + serverbound, + }, + packet_encodable::{Json, Nbt, PacketDecodeError, PacketEncodable, PacketEncodeError}, +}; +use thiserror::Error; +use tokio::{ + io::AsyncReadExt, + net::tcp::OwnedReadHalf, + sync::{Mutex, RwLock, mpsc::Sender}, +}; + +use crate::server::Registries; + +// Max packet size is 2MB in vanilla +const RECV_BUFFER_SIZE: usize = 1024 * 1024 * 2; + +pub struct Client { + address: SocketAddr, + packet_sender: Sender, + readable_stream: Mutex, + state: RwLock, + recv_buffer: Mutex, + packet_queue: Mutex>, +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum ConnectionState { + Handshaking, + Status, + Login, + Configuration, + Play, +} + +#[allow(clippy::enum_variant_names)] +#[derive(Error, Debug)] +pub enum ConnectionError { + #[error("IO error while reading packet: {0}")] + IoError(#[from] std::io::Error), + #[error("Error while decoding packet: {0}")] + DecodeError(#[from] PacketDecodeError), + #[error("Error while encoding packet: {0}")] + EncodeError(#[from] PacketEncodeError), + #[error("Client provided invalid next state")] + InvalidNextState, + #[error("Finished")] + Finished, +} + +impl Client { + pub fn new( + packet_sender: Sender, + readable_stream: OwnedReadHalf, + ) -> Result { + Ok(Client { + address: readable_stream.peer_addr()?, + packet_sender, + readable_stream: Mutex::new(readable_stream), + state: RwLock::new(ConnectionState::Handshaking), + recv_buffer: Mutex::new(BytesMut::new()), + packet_queue: Mutex::new(VecDeque::new()), + }) + } + + pub async fn needs_keep_alive(&self) -> bool { + let state = self.state.read().await; + *state == ConnectionState::Configuration || *state == ConnectionState::Play + } + + pub async fn read_packets(&self) -> Result<(), ConnectionError> { + { + // NOTE: Deadlock waiting to happen, maybe there is a way to only lock once both are + // available. + let mut stream = self.readable_stream.lock().await; + let mut recv_buffer = self.recv_buffer.lock().await; + recv_buffer.reserve(RECV_BUFFER_SIZE); + // TODO: Deal with errors + let _ = stream.read_buf(&mut *recv_buffer).await; + } + + // Try to read packets until we encounter an incomplete packet + loop { + match self.try_read_packet().await { + Ok(raw_packet) => { + let mut packet_queue = self.packet_queue.lock().await; + packet_queue.push_back(raw_packet); + } + Err(ConnectionError::DecodeError(PacketDecodeError::Incomplete)) => { + break; + } + Err(e) => return Err(e), + } + } + + Ok(()) + } + + async fn try_read_packet(&self) -> Result { + let mut recv_buffer = self.recv_buffer.lock().await; + let length: usize = VarInt::decode_packet(&mut *recv_buffer)?.into(); + + if recv_buffer.remaining() < length { + return Err(PacketDecodeError::Incomplete.into()); + } + + let id = VarInt::decode_packet(&mut *recv_buffer)?; + let data = recv_buffer.split_to(length - id.encoded_len()).freeze(); + + Ok(RawPacket { id, data }) + } + + pub async fn send_packet(&self, packet: &T) -> Result<(), ConnectionError> { + let state = { *self.state.read().await }; + println!("[{} ({:?})] -> {:?}", self.address, state, packet); + let mut buffer = BytesMut::new(); + // Encode ID + let packet_id: i32 = match state { + ConnectionState::Handshaking => T::HANDSHAKE_ID, + ConnectionState::Status => T::STATUS_ID, + ConnectionState::Login => T::LOGIN_ID, + ConnectionState::Configuration => T::CONFIGURATION_ID, + ConnectionState::Play => T::PLAY_ID, + }; + assert!(packet_id != -1, "Packet ID can not be -1"); + + let packet_id: VarInt = packet_id.into(); + + packet.encode_packet(&mut buffer)?; + + match self + .packet_sender + .send(RawPacket { + id: packet_id, + data: buffer.freeze(), + }) + .await + { + Ok(_) => (), + Err(e) => { + println!("[{}] Error while sending packet: {}", self.address, e); + return Err(ConnectionError::Finished); + } + } + + Ok(()) + } + + pub async fn handle_packets( + &self, + registries: &Arc, + ) -> Result<(), ConnectionError> { + while let Some(raw_packet) = { + let mut packet_queue = self.packet_queue.lock().await; + packet_queue.pop_front() + } { + // TODO: Need to check that this gets dropped immediately + let state = { *self.state.read().await }; + match state { + ConnectionState::Handshaking => { + self.handle_handshaking(raw_packet).await?; + } + ConnectionState::Status => { + self.handle_status(raw_packet).await?; + } + ConnectionState::Login => { + self.handle_login(raw_packet).await?; + } + ConnectionState::Configuration => { + self.handle_configuration(raw_packet, registries).await?; + } + ConnectionState::Play => { + // TODO: Might not be able to handle this in the client, might need a full + // player for that. + // println!("Play packet on client. Ignoring for now") + // self.handle_play(raw_packet)?; + } + } + } + + Ok(()) + } + + async fn handle_handshaking(&self, raw_packet: RawPacket) -> Result<(), ConnectionError> { + match raw_packet.id.into() { + serverbound::IntentionPacket::HANDSHAKE_ID => { + let packet = serverbound::IntentionPacket::from_raw(raw_packet)?; + println!("[{} (Handshaking)] <- {:?}", self.address, packet,); + self.change_state_num(packet.next_state.into()).await? + } + // TODO: Legacy server list ping + _ => { + println!( + "[{} (Handshaking)] <- Unknown packet: {}", + self.address, raw_packet + ); + } + } + Ok(()) + } + + async fn handle_status(&self, raw_packet: RawPacket) -> Result<(), ConnectionError> { + match raw_packet.id.into() { + serverbound::StatusRequestPacket::STATUS_ID => { + let packet = serverbound::StatusRequestPacket; + println!("[{} (Status)] <- {:?}", self.address, packet); + self.send_packet(&clientbound::StatusResponsePacket { + status: Json(status_response::StatusResponseData { + version: status_response::Version { + name: "Rust 1.21.4".to_owned(), + protocol: 769, + }, + players: status_response::Players { + max: 500, + online: 0, + sample: vec![], + }, + description: status_response::Description { + text: "A rust server!".to_owned(), + }, + favicon: None, + enforce_secure_chat: false, + }), + }) + .await?; + } + serverbound::PingRequestPacket::STATUS_ID => { + let packet = serverbound::PingRequestPacket::from_raw(raw_packet)?; + println!("[{} (Status)] <- {:?}", self.address, packet); + self.send_packet(&clientbound::PongResponsePacket { + timestamp: packet.timestamp, + }) + .await?; + // TODO: Signal to close connection + // self.stream.shutdown(Shutdown::Both)?; + return Err(ConnectionError::Finished); + } + + _ => { + println!( + "[{} (Status)] <- Unknown packet: {}", + self.address, raw_packet + ); + } + } + Ok(()) + } + + async fn handle_login(&self, raw_packet: RawPacket) -> Result<(), ConnectionError> { + match raw_packet.id.into() { + serverbound::HelloPacket::LOGIN_ID => { + let packet = serverbound::HelloPacket::from_raw(raw_packet)?; + println!("[{} (Login)] <- {:?}", self.address, packet); + self.send_packet(&clientbound::LoginFinishedPacket { + uuid: packet.uuid, + username: packet.name, + properties: vec![], + }) + .await?; + } + serverbound::LoginAcknowledgedPacket::LOGIN_ID => { + let packet = serverbound::LoginAcknowledgedPacket::from_raw(raw_packet)?; + println!("[{} (Login)] <- {:?}", self.address, packet); + self.change_state(ConnectionState::Configuration).await?; + self.send_packet(&clientbound::SelectKnownPacksPacket { + packs: vec![Pack { + namespace: "minecraft".to_owned(), + id: "core".to_owned(), + version: "1.21.4".to_owned(), + }], + }) + .await?; + } + _ => { + println!( + "[{} (Login)] <- Unknown packet: {}", + self.address, raw_packet + ); + } + } + Ok(()) + } + + async fn handle_configuration( + &self, + raw_packet: RawPacket, + registries: &Arc, + ) -> Result<(), ConnectionError> { + match raw_packet.id.into() { + serverbound::ClientInformationPacket::CONFIGURATION_ID => { + let packet = serverbound::ClientInformationPacket::from_raw(raw_packet)?; + println!("[{} (Configuration)] <- {:?}", self.address, packet); + } + serverbound::CustomPayloadPacket::CONFIGURATION_ID => { + let packet = serverbound::CustomPayloadPacket::from_raw(raw_packet)?; + println!("[{} (Configuration)] <- {:?}", self.address, packet); + } + serverbound::SelectKnownPacksPacket::CONFIGURATION_ID => { + let packet = serverbound::SelectKnownPacksPacket::from_raw(raw_packet)?; + println!("[{} (Configuration)] <- {:?}", self.address, packet); + + self.sync_registries(registries).await?; + + self.send_packet(&clientbound::FinishConfigurationPacket) + .await?; + } + serverbound::FinishConfigurationPacket::CONFIGURATION_ID => { + let packet = serverbound::finish_configuration::FinishConfigurationPacket; + println!("[{} (Configuration)] <- {:?}", self.address, packet); + self.change_state(ConnectionState::Play).await?; + self.send_packet(&clientbound::LoginPacket { + entity_id: 0, + is_hardcore: false, + dimension_names: vec![], + max_players: 500.into(), + view_distance: 10.into(), + simulation_distance: 10.into(), + reduced_debug_info: false, + enable_respawn_screen: true, + do_limited_crafting: false, + dimension_type: 0.into(), + dimension_name: Identifier::minecraft("overworld".to_owned()), + hashed_seed: 0, + game_mode: 1, + previous_game_mode: -1, + is_debug: false, + is_flat: false, + death_info: None, + portal_cooldown: 0.into(), + sea_level: 63.into(), + enforces_secure_chat: false, + }) + .await?; + + self.send_packet(&GameEventPacket { + event: 13, + data: 0., + }) + .await?; + + self.send_packet(&SetChunkCacheCenterPacket { + x: 0.into(), + z: 0.into(), + }) + .await?; + } + serverbound::KeepAlivePacket::CONFIGURATION_ID => { + let _ = serverbound::KeepAlivePacket::from_raw(raw_packet)?; + } + _ => { + println!( + "[{} (Configuration)] <- Unknown packet: {}", + self.address, raw_packet + ); + } + } + Ok(()) + } + + // fn handle_play(&mut self, length: usize) -> Result<(), ConnectionError> { + // let cursor = &mut Cursor::new(&self.recv_buffer[..length]); + // let id: i32 = VarInt::decode_packet(cursor)?.into(); + // + // match id { + // serverbound::ClientTickEndPacket::PLAY_ID => { + // serverbound::ClientTickEndPacket::decode_packet(cursor)?; + // } + // serverbound::MovePlayerPosPacket::PLAY_ID => { + // let _packet = serverbound::MovePlayerPosPacket::decode_packet(cursor)?; + // // println!("[{} (Play)] <- {:?}", self.address, packet); + // } + // serverbound::MovePlayerPosRotPacket::PLAY_ID => { + // let _packet = serverbound::MovePlayerPosRotPacket::decode_packet(cursor)?; + // // println!("[{} (Play)] <- {:?}", self.address, packet); + // } + // serverbound::MovePlayerRotPacket::PLAY_ID => { + // let _packet = serverbound::MovePlayerRotPacket::decode_packet(cursor)?; + // // println!("[{} (Play)] <- {:?}", self.address, packet); + // } + // _ => { + // println!( + // "[{} (Play)] <- Unknown packet id: {} with length {} data: {:X?}", + // self.address, + // id, + // length, + // &self.recv_buffer[..length] + // ); + // } + // } + // Ok(()) + // } + + async fn sync_registries(&self, registries: &Arc) -> Result<(), ConnectionError> { + self.send_packet(&clientbound::RegistryDataPacket { + registry_id: Identifier::minecraft_str("dimension_type"), + entries: vec![RegistryDataEntry { + id: Identifier::minecraft_str("overworld"), + data: Some(Nbt(RegistryData::DimensionType(DimensionType { + fixed_time: None, + has_skylight: 1, + has_ceiling: 0, + ultrawarm: 0, + natural: 1, + coordinate_scale: 1., + bed_works: 1, + respawn_anchor_works: 0, + min_y: 0, + height: 256, + logical_height: 256, + infiniburn: "#minecraft:infiniburn_overworld".to_owned(), + effects: "minecraft:overworld".to_owned(), + ambient_light: 0., + piglin_safe: 1, + has_raids: 1, + monster_spawn_light_level: 0, + monster_spawn_block_light_limit: 0, + }))), + }], + }) + .await?; + + self.send_packet(&clientbound::RegistryDataPacket { + registry_id: Identifier::minecraft_str("painting_variant"), + entries: vec![RegistryDataEntry { + id: Identifier::minecraft_str("backyard"), + data: Some(Nbt(RegistryData::PaintingVariant(PaintingVariant { + asset_id: "minecraft:backyard".to_owned(), + height: 2, + width: 2, + }))), + }], + }) + .await?; + + self.send_packet(&clientbound::RegistryDataPacket { + registry_id: Identifier::minecraft_str("wolf_variant"), + entries: vec![RegistryDataEntry { + id: Identifier::minecraft_str("ashen"), + data: Some(Nbt(RegistryData::WolfVariant(WolfVariant { + wild_texture: "minecraft:entity/wolf/wolf_ashen".to_owned(), + tame_texture: "minecraft:entity/wolf/wolf_ashen_tame".to_owned(), + angry_texture: "minecraft:entity/wolf/wolf_ashen_angry".to_owned(), + biomes: vec![], + }))), + }], + }) + .await?; + + let damage_type_registry_entries = registries + .damage_types + .iter() + .map(|(key, value)| RegistryDataEntry { + id: key.clone(), + data: Some(Nbt(RegistryData::DamegeType(value.clone().into()))), + }) + .collect(); + + self.send_packet(&clientbound::RegistryDataPacket { + registry_id: Identifier::minecraft_str("damage_type"), + entries: damage_type_registry_entries, + }) + .await?; + + self.send_packet(&clientbound::RegistryDataPacket { + registry_id: Identifier::minecraft_str("worldgen/biome"), + entries: vec![RegistryDataEntry { + id: Identifier::minecraft_str("plains"), + data: Some(Nbt(RegistryData::Biome(Biome { + has_precipitation: 0, + temperature: 0., + temperature_modifier: None, + downfall: 0., + effects: BiomeEffects { + fog_color: 0, + water_color: 0, + water_fog_color: 0, + sky_color: 0, + foliage_color: None, + grass_color: None, + grass_color_modifier: None, + particle: None, + ambient_sound: None, + mood_sound: None, + additions_sound: None, + music: None, + }, + }))), + }], + }) + .await?; + + Ok(()) + } + + async fn change_state_num(&self, state: i32) -> Result<(), ConnectionError> { + let new_state = match state { + 1 => ConnectionState::Status, + 2 => ConnectionState::Login, + _ => return Err(ConnectionError::InvalidNextState), + }; + + self.change_state(new_state).await + } + + async fn change_state(&self, new_state: ConnectionState) -> Result<(), ConnectionError> { + { + let state = self.state.read().await; + println!("[{}] {:?} -> {:?}", self.address, state, new_state); + } + *self.state.write().await = new_state; + + Ok(()) + } +} diff --git a/potato/src/datapack/damage_type.rs b/potato/src/datapack/damage_type.rs new file mode 100644 index 0000000..ce728eb --- /dev/null +++ b/potato/src/datapack/damage_type.rs @@ -0,0 +1,86 @@ +use potato_protocol::packet::clientbound::registry_data; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DamageType { + message_id: String, + scaling: DamageTypeScaling, + exhaustion: f32, + effects: Option, + death_message_type: Option, +} + +// FIXME: It is stupid to have two identical types +impl From for registry_data::DamageType { + fn from(value: DamageType) -> Self { + registry_data::DamageType { + message_id: value.message_id, + scaling: value.scaling.into(), + exhaustion: value.exhaustion, + effects: value.effects.map(Into::::into), + death_message_type: value.death_message_type.map(Into::::into), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum DamageTypeScaling { + Never, + WhenCausedByLivingNonPlayer, + Always, +} + +impl From for String { + fn from(value: DamageTypeScaling) -> Self { + match value { + DamageTypeScaling::Never => "never".into(), + DamageTypeScaling::WhenCausedByLivingNonPlayer => { + "when_caused_by_living_non_player".into() + } + DamageTypeScaling::Always => "always".into(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum DamageTypeEffect { + Hurt, + Thorns, + Drowning, + Burning, + Poking, + Freezing, +} + +impl From for String { + fn from(value: DamageTypeEffect) -> Self { + match value { + DamageTypeEffect::Hurt => "hurt".into(), + DamageTypeEffect::Thorns => "thorns".into(), + DamageTypeEffect::Drowning => "drowning".into(), + DamageTypeEffect::Burning => "burning".into(), + DamageTypeEffect::Poking => "poking".into(), + DamageTypeEffect::Freezing => "freezing".into(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum DamageTypeDeathMessageType { + Default, + FallVariants, + IntentionalGameDesign, +} + +impl From for String { + fn from(value: DamageTypeDeathMessageType) -> Self { + match value { + DamageTypeDeathMessageType::Default => "default".into(), + DamageTypeDeathMessageType::FallVariants => "fall_variants".into(), + DamageTypeDeathMessageType::IntentionalGameDesign => "intentional_game_design".into(), + } + } +} diff --git a/potato/src/datapack/mod.rs b/potato/src/datapack/mod.rs new file mode 100644 index 0000000..14d73d2 --- /dev/null +++ b/potato/src/datapack/mod.rs @@ -0,0 +1,56 @@ +pub mod damage_type; + +use std::{fs::read_to_string, path::PathBuf}; + +use damage_type::DamageType; +use potato_protocol::datatypes::identifier::Identifier; + +use crate::server::{Registries, Registry}; + +pub struct Datapack { + pub name: String, + path: PathBuf, +} + +impl Datapack { + pub fn new(path: PathBuf) -> Self { + // TODO: Load name (and other info) from pack.mcmeta + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + + Self { name, path } + } + + pub fn load(&self, registries: &mut Registries) { + println!("Loading datapack: {}", self.name); + self.load_damage_types(&mut registries.damage_types); + } + + // TODO: Remove the unwraps and deal with errors gracefully + fn load_damage_types(&self, registry: &mut Registry) { + let namespaces: Vec<_> = self + .path + .join("data") + .read_dir() + .unwrap() + .map(|f| f.unwrap().path()) + .filter(|p| p.is_dir()) + .collect(); + + for namespace in namespaces { + let namespace_str = namespace.file_name().unwrap().to_str().unwrap(); + let path = namespace.join("damage_type"); + if let Ok(files) = std::fs::read_dir(path) { + for file in files { + let file = file.unwrap().path(); + let name = file.file_stem().unwrap().to_str().unwrap(); + let identfiier = Identifier::new_str(namespace_str, name); + + let data: DamageType = serde_json::from_str(&read_to_string(file).unwrap()) + .expect("Failed to parse damage type"); + + registry.insert(identfiier, data); + } + } + } + } +} diff --git a/potato/src/main.rs b/potato/src/main.rs index 839472f..d2f4635 100644 --- a/potato/src/main.rs +++ b/potato/src/main.rs @@ -1,544 +1,102 @@ -use std::{ - collections::HashMap, - fmt::Debug, - fs::read_to_string, - io::{Cursor, Read, Write}, - net::{Shutdown, SocketAddr, TcpListener, TcpStream}, - path::PathBuf, - sync::Arc, - thread, -}; +pub mod connection; +pub mod datapack; +pub mod player; +pub mod server; -use potato_protocol::{ - datatypes::{identifier::Identifier, pack::Pack, var_int::VarInt}, - packet::{ - Packet, - clientbound::{ - self, GameEventPacket, SetChunkCacheCenterPacket, - registry_data::{ - Biome, BiomeEffects, DamageType, DimensionType, PaintingVariant, RegistryData, - RegistryDataEntry, WolfVariant, - }, - status_response, - }, - serverbound, - }, - packet_encodable::{Json, Nbt, PacketDecodeError, PacketEncodable, PacketEncodeError}, -}; -use thiserror::Error; +use std::{sync::Arc, time::Duration}; -fn main() { - println!("Looking for datapacks..."); - let datapacks: Vec = std::fs::read_dir("run/datapacks") - .expect("Failed to read datapacks directory") - .map(|f| f.unwrap().path()) - .filter(|p| p.is_dir()) - .collect(); - println!("Found {} datapacks", datapacks.len()); - println!("Loading datapacks..."); - let mut damage_types = HashMap::new(); - for datapack in datapacks { - let datapack_name = datapack.file_name().unwrap().to_str().unwrap(); - println!("Loading datapack: {}", datapack_name); - let path = datapack.join("data/minecraft/damage_type"); - if let Ok(files) = std::fs::read_dir(path) { - for file in files { - let file = file.unwrap().path(); - let name = file.file_stem().unwrap().to_str().unwrap().to_owned(); - let data: DamageType = serde_json::from_str(&read_to_string(file).unwrap()) - .expect("Failed to parse damage type"); +use connection::Client; +use potato_protocol::packet::clientbound; +use server::Server; +use tokio::{net::TcpListener, time::interval}; - damage_types.insert(Identifier::minecraft(name), data); - } - } - } +#[tokio::main] +async fn main() { + let server = PotatoServer::new(); - println!("Finished loading datapacks"); - println!("Loaded {} damage types", damage_types.len()); - - let registries = Arc::new(Registries { damage_types }); - - let listener = TcpListener::bind("127.0.0.1:25565").unwrap(); - println!("listening started, ready to accept"); - for stream in listener.incoming() { - let registries = registries.clone(); - match stream { - Err(e) => println!("Accept ERROR: {}", e), - // Explicit drop because of the diverging branches - Ok(stream) => drop(thread::spawn(|| handle_client(stream, registries))), - } - } + server.listen_for_connections().await; } -fn handle_client(stream: TcpStream, registries: Arc) { - if let Ok(mut client) = ClientConnection::new(stream, registries) { +pub struct PotatoServer { + server: Server, +} + +impl PotatoServer { + pub fn new() -> Self { + PotatoServer { + server: Server::new(), + } + } + + pub async fn listen_for_connections(&self) { + let listener = TcpListener::bind("0.0.0.0:25565") + .await + .expect("Failed to start listening on port 25565"); + println!("listening started, ready to accept"); + loop { - match client.read_packet() { - Ok(_) => (), - Err(e) => { - println!("[{}] Error while handling packets: {}", client.address, e); - // Make sure the connection is closed. - let _ = client.stream.shutdown(Shutdown::Both); - break; + if let Ok((stream, _)) = listener.accept().await { + let (stream_read, mut stream_send) = stream.into_split(); + let (tx, mut rx) = tokio::sync::mpsc::channel(64); + let Ok(client) = Client::new(tx, stream_read) else { + println!("Failed to create client"); + continue; + }; + let client = Arc::new(client); + + tokio::spawn(async move { + while let Some(packet) = rx.recv().await { + if let Err(e) = packet.write_to_stream(&mut stream_send).await { + println!("Error while sending packet: {}", e); + // TODO: Close connection with client here + break; + } + } + }); + + // Keep alive task + { + let client = client.clone(); + tokio::spawn(async move { + let mut interval = interval(Duration::from_secs(10)); + // TODO: Terminate this when the client connection is closed. + loop { + interval.tick().await; + if client.needs_keep_alive().await { + // TODO: Deal with this error by closing the connection. + // TODO: Use a unique id for each keep alive packet (or at least a + // timestamp) + match client + .send_packet(&clientbound::KeepAlivePacket { id: 0 }) + .await + { + Ok(_) => {} + Err(e) => { + println!("Error while sending keep alive packet: {}", e) + } + }; + } + } + }); + } + + { + let registries = self.server.registries.clone(); + tokio::spawn(async move { + // TODO: Deal with errors + loop { + client + .read_packets() + .await + .expect("Error while reading packets"); + client + .handle_packets(®istries) + .await + .expect("Error while handling packets"); + } + }); } } } } } - -#[derive(Debug)] -enum ConnectionState { - Handshaking, - Status, - Login, - Configuration, - Play, -} - -struct ClientConnection { - stream: TcpStream, - address: SocketAddr, - state: ConnectionState, - recv_buffer: Vec, - - registries: Arc, -} - -struct Registries { - damage_types: HashMap, -} - -// Max packet size is 2MB in vanilla -const RECV_BUFFER_SIZE: usize = 1024 * 1024 * 2; - -#[allow(clippy::enum_variant_names)] -#[derive(Error, Debug)] -enum ConnectionError { - #[error("IO error while reading packet: {0}")] - IoError(#[from] std::io::Error), - #[error("Error while decoding packet: {0}")] - DecodeError(#[from] PacketDecodeError), - #[error("Error while encoding packet: {0}")] - EncodeError(#[from] PacketEncodeError), - #[error("Client provided invalid next state")] - InvalidNextState, - #[error("Finished")] - Finished, -} - -// TODO: Need to start sending keep alive packets once we enter configuration phase -impl ClientConnection { - fn new( - stream: TcpStream, - registries: Arc, - ) -> Result { - let address = stream.peer_addr()?; - - Ok(ClientConnection { - state: ConnectionState::Handshaking, - address, - stream, - recv_buffer: vec![0; RECV_BUFFER_SIZE], - registries, - }) - } - - fn read_packet(&mut self) -> Result<(), ConnectionError> { - let packet_size: usize = VarInt::read(&mut self.stream)?.into(); - - self.stream - .read_exact(&mut self.recv_buffer[..packet_size])?; - - self.handle_packet(packet_size) - } - - fn send_packet(&mut self, packet: &T) -> Result<(), ConnectionError> { - println!("[{} ({:?})] -> {:?}", self.address, self.state, packet); - let buffer = &mut Vec::new(); - // Encode ID - let packet_id: i32 = match self.state { - ConnectionState::Handshaking => T::HANDSHAKE_ID, - ConnectionState::Status => T::STATUS_ID, - ConnectionState::Login => T::LOGIN_ID, - ConnectionState::Configuration => T::CONFIGURATION_ID, - ConnectionState::Play => T::PLAY_ID, - }; - if packet_id == -1 { - return Err(ConnectionError::EncodeError( - PacketEncodeError::InvalidPacketId(packet_id), - )); - } - let packet_id: VarInt = packet_id.into(); - - packet_id.encode_packet(buffer)?; - // Encode packet - packet.encode_packet(buffer)?; - // Encode length - let length_buffer = &mut Vec::new(); - let length: VarInt = buffer.len().into(); - length.encode_packet(length_buffer)?; - - // Print raw packet bytes - // println!( - // "{:X?} ({}) {:X?} ({})", - // length_buffer, - // length_buffer.len(), - // buffer, - // buffer.len() - // ); - - // Send packet - self.stream.write_all(length_buffer)?; - self.stream.write_all(buffer)?; - - Ok(()) - } - - fn handle_packet(&mut self, length: usize) -> Result<(), ConnectionError> { - match self.state { - ConnectionState::Handshaking => { - self.handle_handshaking(length)?; - } - ConnectionState::Status => { - self.handle_status(length)?; - } - ConnectionState::Login => { - self.handle_login(length)?; - } - ConnectionState::Configuration => { - self.handle_configuration(length)?; - } - ConnectionState::Play => { - self.handle_play(length)?; - } - } - - Ok(()) - } - - fn handle_handshaking(&mut self, length: usize) -> Result<(), ConnectionError> { - let cursor = &mut Cursor::new(&self.recv_buffer[..length]); - let id = VarInt::decode_packet(cursor)?.into(); - - match id { - serverbound::IntentionPacket::HANDSHAKE_ID => { - let packet = serverbound::IntentionPacket::decode_packet(cursor)?; - println!("[{} (Handshaking)] <- {:?}", self.address, packet,); - self.change_state_num(packet.next_state.into())? - } - // TODO: Legacy server list ping - _ => { - println!( - "[{} (Handshaking)] <- Unknown packet id: {} with length {} data: {:X?}", - self.address, - id, - length, - &self.recv_buffer[..length] - ); - } - } - Ok(()) - } - - fn handle_status(&mut self, length: usize) -> Result<(), ConnectionError> { - let cursor = &mut Cursor::new(&self.recv_buffer[..length]); - let id: i32 = VarInt::decode_packet(cursor)?.into(); - - match id { - serverbound::StatusRequestPacket::STATUS_ID => { - let packet = serverbound::StatusRequestPacket; - println!("[{} (Status)] <- {:?}", self.address, packet); - self.send_packet(&clientbound::StatusResponsePacket { - status: Json(status_response::StatusResponseData { - version: status_response::Version { - name: "Rust 1.21.4".to_owned(), - protocol: 769, - }, - players: status_response::Players { - max: 500, - online: 0, - sample: vec![], - }, - description: status_response::Description { - text: "A rust server!".to_owned(), - }, - favicon: None, - enforce_secure_chat: false, - }), - })?; - } - serverbound::PingRequestPacket::STATUS_ID => { - let packet = serverbound::PingRequestPacket::decode_packet(cursor)?; - println!("[{} (Status)] <- {:?}", self.address, packet); - self.send_packet(&clientbound::PongResponsePacket { - timestamp: packet.timestamp, - })?; - self.stream.shutdown(Shutdown::Both)?; - return Err(ConnectionError::Finished); - } - - _ => { - println!( - "[{} (Status)] <- Unknown packet id: {} with length {} data: {:X?}", - self.address, - id, - length, - &self.recv_buffer[..length] - ); - } - } - Ok(()) - } - - fn handle_login(&mut self, length: usize) -> Result<(), ConnectionError> { - let cursor = &mut Cursor::new(&self.recv_buffer[..length]); - let id: i32 = VarInt::decode_packet(cursor)?.into(); - - match id { - serverbound::HelloPacket::LOGIN_ID => { - let packet = serverbound::HelloPacket::decode_packet(cursor)?; - println!("[{} (Login)] <- {:?}", self.address, packet); - self.send_packet(&clientbound::LoginFinishedPacket { - uuid: packet.uuid, - username: packet.name, - properties: vec![], - })?; - } - serverbound::LoginAcknowledgedPacket::LOGIN_ID => { - let packet = serverbound::LoginAcknowledgedPacket::decode_packet(cursor)?; - println!("[{} (Login)] <- {:?}", self.address, packet); - self.change_state(ConnectionState::Configuration)?; - self.send_packet(&clientbound::SelectKnownPacksPacket { - packs: vec![Pack { - namespace: "minecraft".to_owned(), - id: "core".to_owned(), - version: "1.21.4".to_owned(), - }], - })?; - } - _ => { - println!( - "[{} (Login)] <- Unknown packet id: {} with length {} data: {:X?}", - self.address, - id, - length, - &self.recv_buffer[..length] - ); - } - } - Ok(()) - } - - fn handle_configuration(&mut self, length: usize) -> Result<(), ConnectionError> { - let cursor = &mut Cursor::new(&self.recv_buffer[..length]); - let id: i32 = VarInt::decode_packet(cursor)?.into(); - - match id { - serverbound::ClientInformationPacket::CONFIGURATION_ID => { - let packet = serverbound::ClientInformationPacket::decode_packet(cursor)?; - println!("[{} (Configuration)] <- {:?}", self.address, packet); - } - serverbound::CustomPayloadPacket::CONFIGURATION_ID => { - let packet = serverbound::CustomPayloadPacket::decode_packet(cursor)?; - println!("[{} (Configuration)] <- {:?}", self.address, packet); - } - serverbound::SelectKnownPacksPacket::CONFIGURATION_ID => { - let packet = serverbound::SelectKnownPacksPacket::decode_packet(cursor)?; - println!("[{} (Configuration)] <- {:?}", self.address, packet); - - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("dimension_type"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("overworld"), - data: Some(Nbt(RegistryData::DimensionType(DimensionType { - fixed_time: None, - has_skylight: 1, - has_ceiling: 0, - ultrawarm: 0, - natural: 1, - coordinate_scale: 1., - bed_works: 1, - respawn_anchor_works: 0, - min_y: 0, - height: 256, - logical_height: 256, - infiniburn: "#minecraft:infiniburn_overworld".to_owned(), - effects: "minecraft:overworld".to_owned(), - ambient_light: 0., - piglin_safe: 1, - has_raids: 1, - monster_spawn_light_level: 0, - monster_spawn_block_light_limit: 0, - }))), - }], - })?; - - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("painting_variant"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("backyard"), - data: Some(Nbt(RegistryData::PaintingVariant(PaintingVariant { - asset_id: "minecraft:backyard".to_owned(), - height: 2, - width: 2, - }))), - }], - })?; - - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("wolf_variant"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("ashen"), - data: Some(Nbt(RegistryData::WolfVariant(WolfVariant { - wild_texture: "minecraft:entity/wolf/wolf_ashen".to_owned(), - tame_texture: "minecraft:entity/wolf/wolf_ashen_tame".to_owned(), - angry_texture: "minecraft:entity/wolf/wolf_ashen_angry".to_owned(), - biomes: vec![], - }))), - }], - })?; - - let damage_type_registry_entries = self - .registries - .damage_types - .iter() - .map(|(key, value)| RegistryDataEntry { - id: key.clone(), - data: Some(Nbt(RegistryData::DamegeType(value.clone()))), - }) - .collect(); - - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("damage_type"), - entries: damage_type_registry_entries, - })?; - - self.send_packet(&clientbound::RegistryDataPacket { - registry_id: Identifier::minecraft_str("worldgen/biome"), - entries: vec![RegistryDataEntry { - id: Identifier::minecraft_str("plains"), - data: Some(Nbt(RegistryData::Biome(Biome { - has_precipitation: 0, - temperature: 0., - temperature_modifier: None, - downfall: 0., - effects: BiomeEffects { - fog_color: 0, - water_color: 0, - water_fog_color: 0, - sky_color: 0, - foliage_color: None, - grass_color: None, - grass_color_modifier: None, - particle: None, - ambient_sound: None, - mood_sound: None, - additions_sound: None, - music: None, - }, - }))), - }], - })?; - - self.send_packet(&clientbound::FinishConfigurationPacket)?; - } - serverbound::FinishConfigurationPacket::CONFIGURATION_ID => { - let packet = serverbound::finish_configuration::FinishConfigurationPacket; - println!("[{} (Configuration)] <- {:?}", self.address, packet); - self.change_state(ConnectionState::Play)?; - self.send_packet(&clientbound::LoginPacket { - entity_id: 0, - is_hardcore: false, - dimension_names: vec![], - max_players: 500.into(), - view_distance: 10.into(), - simulation_distance: 10.into(), - reduced_debug_info: false, - enable_respawn_screen: true, - do_limited_crafting: false, - dimension_type: 0.into(), - dimension_name: Identifier::minecraft("overworld".to_owned()), - hashed_seed: 0, - game_mode: 1, - previous_game_mode: -1, - is_debug: false, - is_flat: false, - death_info: None, - portal_cooldown: 0.into(), - sea_level: 63.into(), - enforces_secure_chat: false, - })?; - - self.send_packet(&GameEventPacket { - event: 13, - data: 0., - })?; - - self.send_packet(&SetChunkCacheCenterPacket { - x: 0.into(), - z: 0.into(), - })?; - } - _ => { - println!( - "[{} (Configuration)] <- Unknown packet id: {} with length {} data: {:X?}", - self.address, - id, - length, - &self.recv_buffer[..length] - ); - } - } - Ok(()) - } - - fn handle_play(&mut self, length: usize) -> Result<(), ConnectionError> { - let cursor = &mut Cursor::new(&self.recv_buffer[..length]); - let id: i32 = VarInt::decode_packet(cursor)?.into(); - - match id { - serverbound::ClientTickEndPacket::PLAY_ID => { - serverbound::ClientTickEndPacket::decode_packet(cursor)?; - } - serverbound::MovePlayerPosPacket::PLAY_ID => { - let _packet = serverbound::MovePlayerPosPacket::decode_packet(cursor)?; - // println!("[{} (Play)] <- {:?}", self.address, packet); - } - serverbound::MovePlayerPosRotPacket::PLAY_ID => { - let _packet = serverbound::MovePlayerPosRotPacket::decode_packet(cursor)?; - // println!("[{} (Play)] <- {:?}", self.address, packet); - } - serverbound::MovePlayerRotPacket::PLAY_ID => { - let _packet = serverbound::MovePlayerRotPacket::decode_packet(cursor)?; - // println!("[{} (Play)] <- {:?}", self.address, packet); - } - _ => { - println!( - "[{} (Play)] <- Unknown packet id: {} with length {} data: {:X?}", - self.address, - id, - length, - &self.recv_buffer[..length] - ); - } - } - Ok(()) - } - - fn change_state_num(&mut self, state: i32) -> Result<(), ConnectionError> { - let new_state = match state { - 1 => ConnectionState::Status, - 2 => ConnectionState::Login, - _ => return Err(ConnectionError::InvalidNextState), - }; - - self.change_state(new_state) - } - - fn change_state(&mut self, new_state: ConnectionState) -> Result<(), ConnectionError> { - println!("[{}] {:?} -> {:?}", self.address, self.state, new_state); - self.state = new_state; - - Ok(()) - } -} diff --git a/potato/src/player.rs b/potato/src/player.rs new file mode 100644 index 0000000..139597f --- /dev/null +++ b/potato/src/player.rs @@ -0,0 +1,2 @@ + + diff --git a/potato/src/server.rs b/potato/src/server.rs new file mode 100644 index 0000000..94ef26f --- /dev/null +++ b/potato/src/server.rs @@ -0,0 +1,47 @@ +use std::{collections::HashMap, sync::Arc}; + +use potato_protocol::datatypes::identifier::Identifier; + +use crate::datapack::{Datapack, damage_type::DamageType}; + +pub struct Server { + pub registries: Arc, +} + +impl Server { + #[allow(clippy::new_without_default)] + pub fn new() -> Server { + println!("Looking for datapacks..."); + let datapacks: Vec<_> = std::fs::read_dir("datapacks") + .expect("Failed to read datapacks directory") + .map(|f| f.unwrap().path()) + .filter(|p| p.is_dir()) + .map(Datapack::new) + .collect(); + println!("Found {} datapacks", datapacks.len()); + println!("Loading datapacks..."); + let mut registries = Registries::empty(); + datapacks.iter().for_each(|pack| pack.load(&mut registries)); + + println!("Finished loading datapacks"); + println!("Loaded {} damage types", registries.damage_types.len()); + + Server { + registries: Arc::new(registries), + } + } +} + +pub type Registry = HashMap; + +pub struct Registries { + pub damage_types: Registry, +} + +impl Registries { + fn empty() -> Registries { + Registries { + damage_types: HashMap::new(), + } + } +}