From b12f612c62aaf3206abeeaaa70b98cc3945ac93b Mon Sep 17 00:00:00 2001 From: Garen Tyler Date: Tue, 19 Apr 2022 18:07:10 -0600 Subject: [PATCH] Start implementing Play state up to chunk data --- Cargo.lock | 90 +++++++++++++++++++++++++++--------- Cargo.toml | 3 +- composition.toml | 4 +- src/lib.rs | 2 +- src/main.rs | 6 ++- src/net/mctypes/numbers.rs | 58 ++++++++++++++++------- src/net/mctypes/other.rs | 44 ++++++++++++++++++ src/net/mod.rs | 14 ++---- src/net/packets.rs | 95 ++++++++++++++++++++++++++++++++++++-- src/server/mod.rs | 35 +++++++++++++- 10 files changed, 293 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 167621e..ae1f64e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" + [[package]] name = "arrayref" version = "0.3.6" @@ -109,11 +121,11 @@ dependencies = [ "async-trait", "chrono", "ctrlc", - "fastnbt", "fern", "futures", "lazy_static", "log", + "quartz_nbt", "radix64", "serde", "serde_json", @@ -123,6 +135,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "ctrlc" version = "3.2.1" @@ -133,18 +154,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "fastnbt" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbc536576ecb712740a53d5bf4c64f38ee0ceb221f3f90980919e7506e38cb7" -dependencies = [ - "byteorder", - "cesu8", - "serde", - "serde_bytes", -] - [[package]] name = "fern" version = "0.6.1" @@ -155,6 +164,18 @@ dependencies = [ "log", ] +[[package]] +name = "flate2" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "futures" version = "0.3.21" @@ -305,6 +326,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.2" @@ -429,6 +459,31 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "quartz_nbt" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348031720b71761481d77969dcb3c89ab06f04132ee1503aca1bd9313eef5e67" +dependencies = [ + "anyhow", + "byteorder", + "cesu8", + "flate2", + "quartz_nbt_macros", + "serde", +] + +[[package]] +name = "quartz_nbt_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "289baa0c8a4d1f840d2de528a7f8c29e0e9af48b3018172b3edad4f716e8daed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.18" @@ -478,15 +533,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.136" diff --git a/Cargo.toml b/Cargo.toml index 376e41a..a5378ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ build = "build.rs" async-trait = "0.1.48" chrono = "0.4.13" ctrlc = "3.1.8" -fastnbt = "*" +# fastnbt = "*" fern = {version = "0.6", features = ["colored"]} futures = "0.3.13" lazy_static = "1.4.0" @@ -23,6 +23,7 @@ tokio = {version = "1", features = ["full"]} toml = "0.5" uuid = "0.8.2" substring = "1.4.5" +quartz_nbt = {version = "0.2.6", features = ["serde"]} # colorful = "0.2.1" # ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2) diff --git a/composition.toml b/composition.toml index 36290ef..c16a524 100644 --- a/composition.toml +++ b/composition.toml @@ -1,5 +1,5 @@ favicon = "server-icon.png" -max_players = 20 -motd = "Hello world!" +max_players = 4294967295 +motd = "Hell world!" port = 25565 log_level = "debug" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6dc5b09..c7e571e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ pub mod prelude { pub use serde_json::json; pub use uuid::Uuid; pub type JSON = serde_json::Value; - pub type NBT = fastnbt::Value; + pub type NBT = quartz_nbt::NbtCompound; pub use std::collections::VecDeque; pub use substring::Substring; pub use tokio::io::{AsyncReadExt, AsyncWriteExt}; diff --git a/src/main.rs b/src/main.rs index b65af79..cbeaad7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,11 @@ use std::time::Duration; #[tokio::main] pub async fn main() { let ctrlc_rx = composition::init(); - info!("Starting {}", composition::CONFIG.server_version); + info!( + "Starting {} on port {}", + composition::CONFIG.server_version, + composition::CONFIG.port + ); let mut server = composition::start_server().await; info!("Done! Start took {:?}", composition::START_TIME.elapsed()); diff --git a/src/net/mctypes/numbers.rs b/src/net/mctypes/numbers.rs index 330f164..c5dada9 100644 --- a/src/net/mctypes/numbers.rs +++ b/src/net/mctypes/numbers.rs @@ -8,8 +8,8 @@ pub fn parse_byte(data: &[u8]) -> ParseResult { Ok((value, 1)) } } -pub fn serialize_byte(num: i8) -> [u8; 1] { - num.to_be_bytes() +pub fn serialize_byte(value: i8) -> [u8; 1] { + value.to_be_bytes() } pub fn parse_short(data: &[u8]) -> ParseResult { @@ -20,8 +20,8 @@ pub fn parse_short(data: &[u8]) -> ParseResult { Ok((value, 2)) } } -pub fn serialize_short(num: i16) -> [u8; 2] { - num.to_be_bytes() +pub fn serialize_short(value: i16) -> [u8; 2] { + value.to_be_bytes() } pub fn parse_int(data: &[u8]) -> ParseResult { @@ -32,8 +32,8 @@ pub fn parse_int(data: &[u8]) -> ParseResult { Ok((value, 4)) } } -pub fn serialize_int(num: i32) -> [u8; 4] { - num.to_be_bytes() +pub fn serialize_int(value: i32) -> [u8; 4] { + value.to_be_bytes() } pub fn parse_long(data: &[u8]) -> ParseResult { @@ -46,8 +46,8 @@ pub fn parse_long(data: &[u8]) -> ParseResult { Ok((value, 8)) } } -pub fn serialize_long(num: i64) -> [u8; 8] { - num.to_be_bytes() +pub fn serialize_long(value: i64) -> [u8; 8] { + value.to_be_bytes() } pub fn parse_varint(data: &[u8]) -> ParseResult { @@ -90,6 +90,32 @@ pub fn serialize_varint(value: i32) -> Vec { output } +pub fn parse_float(data: &[u8]) -> ParseResult { + if data.len() < 4 { + Err(ParseError::NotEnoughData) + } else { + let value = f32::from_be_bytes([data[0], data[1], data[2], data[3]]); + Ok((value, 4)) + } +} +pub fn serialize_float(value: f32) -> [u8; 4] { + value.to_be_bytes() +} + +pub fn parse_double(data: &[u8]) -> ParseResult { + if data.len() < 8 { + Err(ParseError::NotEnoughData) + } else { + let value = f64::from_be_bytes([ + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + ]); + Ok((value, 4)) + } +} +pub fn serialize_double(value: f64) -> [u8; 8] { + value.to_be_bytes() +} + pub fn parse_unsigned_byte(data: &[u8]) -> ParseResult { if data.is_empty() { Err(ParseError::NotEnoughData) @@ -98,8 +124,8 @@ pub fn parse_unsigned_byte(data: &[u8]) -> ParseResult { Ok((value, 1)) } } -pub fn serialize_unsigned_byte(num: u8) -> [u8; 1] { - num.to_be_bytes() +pub fn serialize_unsigned_byte(value: u8) -> [u8; 1] { + value.to_be_bytes() } pub fn parse_unsigned_short(data: &[u8]) -> ParseResult { @@ -110,8 +136,8 @@ pub fn parse_unsigned_short(data: &[u8]) -> ParseResult { Ok((value, 2)) } } -pub fn serialize_unsigned_short(num: u16) -> [u8; 2] { - num.to_be_bytes() +pub fn serialize_unsigned_short(value: u16) -> [u8; 2] { + value.to_be_bytes() } pub fn parse_unsigned_int(data: &[u8]) -> ParseResult { @@ -122,8 +148,8 @@ pub fn parse_unsigned_int(data: &[u8]) -> ParseResult { Ok((value, 4)) } } -pub fn serialize_unsigned_int(num: u32) -> [u8; 4] { - num.to_be_bytes() +pub fn serialize_unsigned_int(value: u32) -> [u8; 4] { + value.to_be_bytes() } pub fn parse_unsigned_long(data: &[u8]) -> ParseResult { @@ -136,8 +162,8 @@ pub fn parse_unsigned_long(data: &[u8]) -> ParseResult { Ok((value, 8)) } } -pub fn serialize_unsigned_long(num: u64) -> [u8; 8] { - num.to_be_bytes() +pub fn serialize_unsigned_long(value: u64) -> [u8; 8] { + value.to_be_bytes() } #[cfg(test)] diff --git a/src/net/mctypes/other.rs b/src/net/mctypes/other.rs index 64ae9c1..fedae02 100644 --- a/src/net/mctypes/other.rs +++ b/src/net/mctypes/other.rs @@ -46,3 +46,47 @@ pub fn parse_json(data: &[u8]) -> ParseResult { pub fn serialize_json(value: JSON) -> Vec { serialize_string(&serde_json::to_string(&value).expect("Could not serialize JSON")) } + +pub fn parse_nbt(data: &[u8]) -> ParseResult { + use quartz_nbt::io::{read_nbt, Flavor}; + use std::io::Cursor; + let mut data = Cursor::new(data); + // let (value_string, offset) = parse_string(data)?; + if let Ok(value) = read_nbt(&mut data, Flavor::Uncompressed) { + Ok((value.0, data.position() as usize)) + } else { + Err(ParseError::InvalidData) + } +} +pub fn serialize_nbt(value: NBT) -> Vec { + use quartz_nbt::io::{write_nbt, Flavor}; + // serialize_string(&fastnbt::to_string(&value).expect("Could not serialize JSON")) + let mut out = vec![]; + write_nbt(&mut out, None, &value, Flavor::Uncompressed).expect("Could not serialize NBT"); + out +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Position { + pub x: i32, + pub y: i16, + pub z: i32, +} +impl Position { + pub fn new(x: i32, y: i16, z: i32) -> Position { + Position { x, y, z } + } + pub fn parse(data: &[u8]) -> ParseResult { + let (value, offset) = parse_unsigned_long(data)?; + let x = (value >> 38) as i32; + let y = (value & 0xFFF) as i16; + let z = ((value >> 12) & 0x3FFFFFF) as i32; + Ok((Position::new(x, y, z), offset)) + } + pub fn serialize(&self) -> [u8; 8] { + (((self.x as u64 & 0x3FFFFFF) << 38) + | ((self.z as u64 & 0x3FFFFFF) << 12) + | (self.y as u64 & 0xFFF)) + .to_be_bytes() + } +} diff --git a/src/net/mod.rs b/src/net/mod.rs index 66fba37..6148c75 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -36,7 +36,7 @@ impl NetworkClient { } } pub async fn read_data(&mut self) -> Result<(), tokio::io::Error> { - trace!("NetworkClient.read_data()"); + trace!("NetworkClient.read_data() id {}", self.id); // Try to read 4kb at a time until there is no more data. loop { let mut buf = [0; 4096]; @@ -45,6 +45,7 @@ impl NetworkClient { Ok(0) => break, // Data was read. Ok(n) => { + trace!("Setting last_data_time for client {}", self.id); self.last_data_time = Instant::now(); self.buffer.extend(&buf[0..n]); debug!("Read {} bytes from client {}", n, self.id); @@ -58,7 +59,7 @@ impl NetworkClient { Ok(()) } pub fn read_packet(&mut self) -> Result<(), ParseError> { - trace!("NetworkClient.read_packet()"); + trace!("NetworkClient.read_packet() id {}", self.id); self.buffer.make_contiguous(); if let (data, &[]) = self.buffer.as_slices() { let mut offset = 0; @@ -85,15 +86,6 @@ impl NetworkClient { self.stream.write(&bytes).await?; Ok(()) } - pub async fn update(&mut self) { - // if self.state == NetworkClientState::Disconnected { - // return Err(tokio::io::Error::from(tokio::io::ErrorKind::BrokenPipe)); - // } else if self.last_data_time.elapsed() > Duration::from_secs(10) { - // return self.disconnect(tokio::io::ErrorKind::TimedOut); - // } - let _ = self.read_data().await; - let _ = self.read_packet(); - } pub async fn disconnect(&mut self, reason: Option) { if let Some(reason) = reason { if self.state == NetworkClientState::Login { diff --git a/src/net/packets.rs b/src/net/packets.rs index d6351e1..90084db 100644 --- a/src/net/packets.rs +++ b/src/net/packets.rs @@ -60,7 +60,42 @@ pub enum Packet { successful: bool, data: Option>, }, + // Play + CP14WindowItems { + window_id: u8, + state_id: i32, + slots: Vec, + carried_item: NBT, + }, + CP26JoinGame, + CP48HeldItemChange, + CP66DeclareRecipes, + CP67Tags, + CP1BEntityStatus, + CP12DeclareCommands, + CP39UnlockRecipes, + CP22ChunkDataAndUpdateLight, + CP38PlayerPositionAndLook { + x: (f64, bool), + y: (f64, bool), + z: (f64, bool), + yaw: (f32, bool), + pitch: (f32, bool), + teleport_id: i32, + dismount_vehicle: bool, + }, + CP36PlayerInfo, + CP49UpdateViewPosition, + CP25UpdateLight, + CP4BSpawnPosition { + location: Position, + angle: f32, + }, + CP00TeleportConfirm, + + SP05ClientSettings, + SP04ClientStatus, } impl Packet { pub fn parse_body( @@ -193,15 +228,69 @@ impl Packet { ), CS01Pong { payload } => (0x01, serialize_long(*payload).to_vec()), CL00Disconnect { reason } => (0x00, serialize_json(reason.clone())), - // CL01EncryptionRequest CL02LoginSuccess { uuid, username } => (0x02, { let mut out = vec![]; out.extend(uuid.to_be_bytes()); out.extend(serialize_string(username)); out }), - // CL03SetCompression - // CL04LoginPluginRequest + CP14WindowItems { + window_id, + state_id, + slots, + carried_item, + } => (0x14, { + let mut out = vec![*window_id]; + out.extend(serialize_varint(*state_id)); + out.extend(serialize_varint(slots.len() as i32)); + for slot in slots { + out.extend(serialize_nbt(slot.clone())); + } + out.extend(serialize_nbt(carried_item.clone())); + out + }), + CP38PlayerPositionAndLook { + x, + y, + z, + yaw, + pitch, + teleport_id, + dismount_vehicle, + } => (0x38, { + let mut out = vec![]; + out.extend(serialize_double(x.0)); + out.extend(serialize_double(y.0)); + out.extend(serialize_double(z.0)); + out.extend(serialize_float(yaw.0)); + out.extend(serialize_float(pitch.0)); + let mut flags = 0x00; + if x.1 { + flags |= 0x01; + } + if y.1 { + flags |= 0x02; + } + if z.1 { + flags |= 0x04; + } + if yaw.1 { + flags |= 0x10; + } + if pitch.1 { + flags |= 0x08; + } + out.push(flags); + out.extend(serialize_varint(*teleport_id)); + out.extend(serialize_bool(*dismount_vehicle)); + out + }), + CP4BSpawnPosition { location, angle } => (0x4b, { + let mut out = vec![]; + out.extend(location.serialize()); + out.extend(serialize_float(*angle)); + out + }), _ => unimplemented!("Serializing unknown packet"), }; let mut id_and_body = serialize_varint(id as i32); diff --git a/src/server/mod.rs b/src/server/mod.rs index 0138d05..e9df164 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,4 +1,8 @@ -use crate::{net::*, prelude::*}; +use crate::{ + net::{mctypes::Position, *}, + prelude::*, +}; +use std::time::Duration; use tokio::{ net::{TcpListener, ToSocketAddrs}, sync::mpsc::{self, error::TryRecvError, UnboundedReceiver}, @@ -52,6 +56,9 @@ impl Server { if self.clients[i].state == NetworkClientState::Disconnected { debug!("Removed client {}", self.clients[i].id); self.clients.remove(i); + } else if self.clients[i].last_data_time.elapsed() > Duration::from_secs(10) { + debug!("Client {} timed out", self.clients[i].id); + self.clients[i].disconnect(None).await; } else { i += 1; } @@ -149,6 +156,32 @@ impl Server { }) .await; client.state = NetworkClientState::Play; + // Log them in. + let _ = client + .send_packet(CP4BSpawnPosition { + location: Position::new(0, 0, 0), + angle: 0.0, + }) + .await; + let _ = client + .send_packet(CP14WindowItems { + window_id: 0, + state_id: 0, + slots: vec![quartz_nbt::compound! {}; 44], + carried_item: quartz_nbt::compound! {}, + }) + .await; + let _ = client + .send_packet(CP38PlayerPositionAndLook { + x: (0.0, false), + y: (0.0, false), + z: (0.0, false), + yaw: (0.0, false), + pitch: (0.0, false), + teleport_id: 0, + dismount_vehicle: false, + }) + .await; } _ => unimplemented!("Handling unknown packet"), }