diff --git a/Cargo.lock b/Cargo.lock index 84ca590..986f985 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,6 +188,7 @@ dependencies = [ "sha2", "thiserror", "tokio", + "tokio-util", "tracing", "tracing-subscriber", "uuid", @@ -234,6 +235,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "generic-array" version = "0.14.7" @@ -658,6 +671,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tracing" version = "0.1.40" diff --git a/Cargo.toml b/Cargo.toml index 30b968e..5a52ed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ serde_json = "1.0.132" sha2 = "0.10.8" thiserror = "1.0.65" tokio = { version = "1.41.0", features = ["full"] } +tokio-util = "0.7.12" tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_warn"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } uuid = "1.11.0" diff --git a/src/main.rs b/src/main.rs index 3813f39..38e70aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,10 +17,14 @@ * . */ -use std::sync::Arc; +use std::{ + sync::{Arc, LazyLock}, + time::Duration, +}; use color_eyre::eyre::Result; use server::Server; +use tokio_util::sync::CancellationToken; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; #[macro_use] @@ -80,6 +84,7 @@ async fn main() -> Result<()> { // TODO: more graceful shutdown? tokio::signal::ctrl_c().await?; + state.shutdown_token.cancel(); Ok(()) } diff --git a/src/net/player.rs b/src/net/player.rs index a26d499..de88736 100644 --- a/src/net/player.rs +++ b/src/net/player.rs @@ -29,15 +29,19 @@ use tokio::{ sync::{Mutex, OwnedSemaphorePermit, RwLock}, time::{self, timeout}, }; +use uuid::Uuid; use crate::{ protocol::{ datatypes::{Bounded, VarInt}, packets::{ login::*, - play::{ConfirmTeleportS, Gamemode, LoginPlayC, SynchronisePositionC}, + play::{ + ConfirmTeleportS, Gamemode, LoginPlayC, PlayerInfoUpdateC, PlayerStatus, + SynchronisePositionC, + }, }, - PacketState, + PacketState, Property, }, CrawlState, }; @@ -56,6 +60,7 @@ pub struct Player { crawlstate: CrawlState, packet_state: RwLock, + uuid: RwLock>, tp_state: Mutex, } @@ -80,6 +85,7 @@ impl SharedPlayer { id, io: Mutex::new(NetIo::new(connection)), _permit: permit, + uuid: RwLock::new(None), crawlstate, packet_state: RwLock::new(PacketState::Handshaking), @@ -142,11 +148,16 @@ impl SharedPlayer { let next_state = p.next_state; drop(io); + let mut s = self.0.packet_state.write().await; match next_state { PacketState::Status => { + *s = PacketState::Status; + drop(s); self.handle_status().await?; } PacketState::Login => { + *s = PacketState::Login; + drop(s); self.login().await?; } s => unimplemented!("state {:#?} unimplemented after handshake", s), @@ -208,6 +219,11 @@ impl SharedPlayer { strict_error_handling: false, }; + { + let mut own_uuid = self.0.uuid.write().await; + *own_uuid = Some(uuid); + } + io.tx(&success).await?; io.rx::().await?; @@ -303,17 +319,29 @@ impl SharedPlayer { Err(why)?; } Err(why) => { - warn!("Spawning player {} failed: {why}", self.0.id); + warn!("Spawning player {} timed out: {why}", self.0.id); Err(why)?; } } + let player_add = PlayerInfoUpdateC { + players: &[PlayerStatus::for_player(self.uuid().await).add_player("AFK", &[])], + }; + io.tx(&player_add).await?; + // FIXME: GROSS LOL?????? this should(?) change ownership of the player to the server // thread but realistically who knows burhhhh state.player_send.send(self.clone()).await?; loop { - self.handle_packets().await?; + tokio::select! { + _ = self.handle_packets() => { + time::sleep(Duration::from_millis(50)).await; + } + _ = state.shutdown_token.cancelled() => { + return Ok(()); + } + } } } @@ -331,6 +359,11 @@ impl SharedPlayer { }, } } + + async fn uuid(&self) -> Uuid { + let uuid = self.0.uuid.read().await; + uuid.expect("uuid() called on uninitialized player - only call this after login!") + } } #[derive(Debug, Error)] diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index ff03a79..1e52158 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -47,9 +47,11 @@ pub mod packets { pub mod play { mod login; + mod status; mod teleport; pub use login::*; + pub use status::*; pub use teleport::*; } } @@ -60,7 +62,7 @@ mod encoder; use std::{fmt::Debug, io::Write}; use color_eyre::eyre::{Context, Result}; -use datatypes::VarInt; +use datatypes::{Bounded, VarInt}; pub use decoder::*; pub use encoder::*; use thiserror::Error; @@ -127,3 +129,23 @@ pub trait ClientboundPacket: Packet + Encode + Debug { } } impl

ClientboundPacket for P where P: Packet + Encode + Debug {} + +#[derive(Debug)] +pub struct Property<'a> { + name: Bounded<&'a str, 32767>, + value: Bounded<&'a str, 32767>, + signature: Option>, +} + +impl Encode for Property<'_> { + fn encode(&self, mut w: impl std::io::Write) -> Result<()> { + let signed = self.signature.is_some(); + + self.name.encode(&mut w)?; + self.value.encode(&mut w)?; + signed.encode(&mut w)?; + self.signature.encode(&mut w)?; + + Ok(()) + } +} diff --git a/src/protocol/packets/login/login.rs b/src/protocol/packets/login/login.rs index 12ae32e..02ef277 100644 --- a/src/protocol/packets/login/login.rs +++ b/src/protocol/packets/login/login.rs @@ -17,12 +17,12 @@ * . */ - use color_eyre::eyre::Result; use uuid::Uuid; use crate::protocol::{ - datatypes::{Bounded, Bytes, VarInt}, Decode, Encode, Packet, + datatypes::{Bounded, Bytes, VarInt}, + Decode, Encode, Packet, Property, }; #[derive(Debug)] @@ -52,26 +52,6 @@ pub struct LoginSuccessC<'a> { pub strict_error_handling: bool, } -#[derive(Debug)] -pub struct Property<'a> { - name: Bounded<&'a str, 32767>, - value: Bounded<&'a str, 32767>, - signature: Option>, -} - -impl Encode for Property<'_> { - fn encode(&self, mut w: impl std::io::Write) -> Result<()> { - let signed = self.signature.is_some(); - - self.name.encode(&mut w)?; - self.value.encode(&mut w)?; - signed.encode(&mut w)?; - self.signature.encode(&mut w)?; - - Ok(()) - } -} - impl Packet for LoginSuccessC<'_> { const ID: i32 = 0x02; } diff --git a/src/protocol/packets/play/status.rs b/src/protocol/packets/play/status.rs new file mode 100644 index 0000000..4d404c1 --- /dev/null +++ b/src/protocol/packets/play/status.rs @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + +use uuid::Uuid; + +use crate::protocol::{ + datatypes::{Bounded, VarInt}, + Encode, Packet, Property, +}; + +use super::Gamemode; + +#[derive(Debug)] +pub struct PlayerInfoUpdateC<'a> { + pub players: &'a [PlayerStatus<'a>], +} + +#[derive(Debug)] +pub struct PlayerStatus<'a> { + uuid: Uuid, + actions: Vec>, +} + +#[derive(Debug)] +enum PlayerAction<'a> { + AddPlayer { + name: Bounded<&'a str, 16>, + properties: &'a [Property<'a>], + }, + UpdateGamemode { + game_mode: VarInt, + }, + UpdateListed { + listed: bool, + }, + UpdateLatency { + latency: VarInt, + }, +} + +impl Packet for PlayerInfoUpdateC<'_> { + const ID: i32 = 0x3E; +} + +// "I'm a Never-Nester" +// The unwavering Minecraft Protocol: +impl Encode for PlayerInfoUpdateC<'_> { + fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { + let actions = self.players.iter().fold(0, |acc, p| { + acc | p.actions.iter().fold(0, |acc2, a| acc2 | a.mask()) + }); + + actions.encode(&mut w)?; + VarInt(self.players.len() as i32).encode(&mut w)?; + + for PlayerStatus { uuid, actions } in self.players { + uuid.encode(&mut w)?; + + for action in actions { + match action { + PlayerAction::AddPlayer { name, properties } => { + name.encode(&mut w)?; + VarInt(properties.len() as i32).encode(&mut w)?; + for p in *properties { + p.encode(&mut w)?; + } + } + PlayerAction::UpdateGamemode { game_mode } => { + game_mode.encode(&mut w)?; + } + PlayerAction::UpdateListed { listed } => { + listed.encode(&mut w)?; + } + PlayerAction::UpdateLatency { latency } => { + latency.encode(&mut w)?; + } + } + } + } + + Ok(()) + } +} + +impl PlayerAction<'_> { + const fn mask(&self) -> i8 { + match self { + PlayerAction::AddPlayer { .. } => 0x01, + PlayerAction::UpdateGamemode { .. } => 0x04, + PlayerAction::UpdateListed { .. } => 0x08, + PlayerAction::UpdateLatency { .. } => 0x10, + } + } +} + +impl<'a> PlayerStatus<'a> { + pub fn for_player(player: Uuid) -> Self { + Self { + uuid: player, + actions: Vec::new(), + } + } + + pub fn add_player(mut self, name: &'a str, props: &'a [Property]) -> Self { + self.actions.push(PlayerAction::AddPlayer { + name: Bounded::<&'a str, 16>(name), + properties: props, + }); + self + } + + pub fn update_gamemode(mut self, gamemode: Gamemode) -> Self { + self.actions.push(PlayerAction::UpdateGamemode { + game_mode: VarInt(u8::from(gamemode) as i32), + }); + self + } + + pub fn update_listed(mut self, listed: bool) -> Self { + self.actions.push(PlayerAction::UpdateListed { listed }); + self + } + + pub fn update_latency(mut self, latency: i32) -> Self { + self.actions.push(PlayerAction::UpdateLatency { + latency: VarInt(latency), + }); + self + } +} diff --git a/src/protocol/packets/play/teleport.rs b/src/protocol/packets/play/teleport.rs index 5193c8d..34e61cf 100644 --- a/src/protocol/packets/play/teleport.rs +++ b/src/protocol/packets/play/teleport.rs @@ -19,12 +19,7 @@ use std::sync::atomic::{AtomicI32, Ordering}; -use rand::Rng; - -use crate::protocol::{ - datatypes::{Bounded, Position, VarInt}, - Decode, Encode, Packet, -}; +use crate::protocol::{datatypes::VarInt, Decode, Encode, Packet}; static TP_ID: AtomicI32 = AtomicI32::new(0); diff --git a/src/state.rs b/src/state.rs index 0ffdd03..02788a6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -20,6 +20,7 @@ use std::sync::{atomic::AtomicUsize, Arc}; use tokio::sync::{mpsc, Mutex, Semaphore}; +use tokio_util::sync::CancellationToken; use crate::net::player::SharedPlayer; @@ -35,8 +36,7 @@ pub struct State { pub player_send: mpsc::Sender, pub player_recv: Mutex>, - pub shutdown_send: mpsc::UnboundedSender<()>, - pub shutdown_recv: Mutex>, + pub shutdown_token: CancellationToken, pub net_sema: Arc, } @@ -57,7 +57,7 @@ impl State { } let (player_send, player_recv) = mpsc::channel(16); - let (shutdown_send, shutdown_recv) = mpsc::unbounded_channel(); + let shutdown_token = CancellationToken::new(); Self { max_players: max, @@ -70,8 +70,7 @@ impl State { player_send, player_recv: Mutex::new(player_recv), - shutdown_send, - shutdown_recv: Mutex::new(shutdown_recv), + shutdown_token, net_sema: Arc::new(Semaphore::new(max)), }