diff --git a/src/lib.rs b/src/lib.rs index 5c2ed6c..82b45e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,9 +10,9 @@ pub mod server; /// The data types for blocks, chunks, dimensions, and world files. pub mod world; -pub use mctypes::*; -use serde::{Serialize, Deserialize}; use log::warn; +pub use mctypes::*; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct Config { @@ -32,7 +32,10 @@ lazy_static! { if let Ok(c) = toml::from_str::(&data) { Ok(c) } else { - Err(std::io::Error::new(std::io::ErrorKind::Other, "Could not parse toml")) + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Could not parse toml", + )) } }; if let Ok(c) = config_from_file() { @@ -81,9 +84,6 @@ pub fn init() { } /// Start the server. -pub async fn start_server() -> server::GameServer { - // Start the network. - let network = server::net::NetworkServer::new(format!("0.0.0.0:{}", CONFIG.port)); - let server = server::GameServer { network: network }; - server +pub async fn start_server() -> server::Server { + server::Server::new(format!("0.0.0.0:{}", CONFIG.port)) } diff --git a/src/server/mod.rs b/src/server/mod.rs index f6ae366..0003064 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,15 +1,196 @@ -/// Deals with all the network stuff. -pub mod net; +/// Definitions for all the packets in the Minecraft protocol. +pub mod packets; + +use crate::{mctypes::*, CONFIG, FAVICON}; +use log::{debug, info}; +use packets::*; +use serde_json::json; +use std::sync::mpsc::{self, Receiver, TryRecvError}; +use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; /// The struct containing all the data and running all the updates. -pub struct GameServer { - pub network: net::NetworkServer, +pub struct Server { + pub network_clients: Vec, + network_receiver: Receiver, } -impl GameServer { +impl Server { + pub fn new(addr: A) -> Server { + let (tx, rx) = mpsc::channel(); + tokio::task::spawn(async move { + let listener = TcpListener::bind(addr) + .await + .expect("Could not bind to TCP socket"); + let mut id = 0; + loop { + let (stream, _) = listener + .accept() + .await + .expect("Network receiver disconnected"); + tx.send(NetworkClient { + id: id as u128, + connected: true, + stream, + state: NetworkClientState::Handshake, + }) + .expect("Network receiver disconnected"); + id += 1; + } + }); + info!("Network server started!"); + Server { + network_receiver: rx, + network_clients: vec![], + } + } + + /// Update the network server. + /// + /// Update each client in `self.network_clients`. + async fn update_network(&mut self) { + loop { + match self.network_receiver.try_recv() { + Ok(client) => { + info!( + "Got client at {}", + client.stream.peer_addr().expect("Could not get peer addr") + ); + self.network_clients.push(client) + } + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => panic!("Network sender disconnected"), + } + } + let num_players = self.network_clients.iter().fold(0, |acc, nc| { + if nc.state == NetworkClientState::Play { + acc + 1 + } else { + acc + } + }); + for client in self.network_clients.iter_mut() { + client.update(num_players).await; + } + // Remove disconnected clients. + self.network_clients + .retain(|nc| nc.state != NetworkClientState::Disconnected); + } + /// Update the game server. /// /// Start by updating the network. pub async fn update(&mut self) { - self.network.update().await; + self.update_network().await; + } +} + +/// The network client can only be in a few states, +/// this enum keeps track of that. +#[derive(PartialEq)] +pub enum NetworkClientState { + Handshake, + Status, + Login, + Play, + Disconnected, +} + +/// A wrapper to contain everything related +/// to networking for the client. +pub struct NetworkClient { + pub id: u128, + pub connected: bool, + pub stream: TcpStream, + pub state: NetworkClientState, +} +impl NetworkClient { + /// Update the client. + /// + /// Updating could mean connecting new clients, reading packets, + /// writing packets, or disconnecting clients. + pub async fn update(&mut self, num_players: usize) { + match self.state { + NetworkClientState::Handshake => { + let (_packet_length, _packet_id) = + read_packet_header(&mut self.stream).await.unwrap(); + let handshake = Handshake::read(&mut self.stream).await.unwrap(); + // Minecraft versions 1.8 - 1.8.9 use protocol version 47. + let compatible_versions = handshake.protocol_version == 47; + let next_state = match handshake.next_state.into() { + 1 => NetworkClientState::Status, + 2 => NetworkClientState::Login, + _ => NetworkClientState::Disconnected, + }; + self.state = next_state; + // If incompatible versions or wrong next state + if !compatible_versions { + let mut logindisconnect = LoginDisconnect::new(); + logindisconnect.reason = MCChat { + text: MCString::from("Incompatible client! Server is on 1.8.9"), + }; + logindisconnect.write(&mut self.stream).await.unwrap(); + self.state = NetworkClientState::Disconnected; + } + debug!("Got handshake: {:?}", handshake); + } + NetworkClientState::Status => { + let (_packet_length, _packet_id) = + read_packet_header(&mut self.stream).await.unwrap(); + let statusrequest = StatusRequest::read(&mut self.stream).await.unwrap(); + debug!("Got status request: {:?}", statusrequest); + let mut statusresponse = StatusResponse::new(); + statusresponse.json_response = json!({ + "version": { + "name": "1.8.9", + "protocol": 47, + }, + "players": { + "max": CONFIG.max_players, + "online": num_players, + "sample": [ + { + "name": "shvr", + "id": "e3f58380-60bb-4714-91f2-151d525e64aa" + } + ] + }, + "description": { + "text": CONFIG.motd + }, + // TODO: Dynamically send the icon instead of linking statically. + "favicon": format!("data:image/png;base64,{}", if FAVICON.is_ok() { radix64::STD.encode(FAVICON.as_ref().unwrap().as_slice()) } else { "".to_owned() }) + }) + .to_string() + .into(); + statusresponse.write(&mut self.stream).await.unwrap(); + debug!("Sending status response: StatusResponse"); + let (_packet_length, _packet_id) = + read_packet_header(&mut self.stream).await.unwrap(); + let statusping = StatusPing::read(&mut self.stream).await.unwrap(); + debug!("Got status ping: {:?}", statusping); + let mut statuspong = StatusPong::new(); + statuspong.payload = statusping.payload; + statuspong.write(&mut self.stream).await.unwrap(); + debug!("Sending status pong: {:?}", statuspong); + self.state = NetworkClientState::Disconnected; + } + NetworkClientState::Login => { + let (_packet_length, _packet_id) = + read_packet_header(&mut self.stream).await.unwrap(); + let loginstart = LoginStart::read(&mut self.stream).await.unwrap(); + debug!("{:?}", loginstart); + // Offline mode skips encryption and compression. + let mut loginsuccess = LoginSuccess::new(); + // We're in offline mode, so this is a temporary uuid. + loginsuccess.uuid = "00000000-0000-3000-0000-000000000000".into(); + loginsuccess.username = loginstart.player_name; + loginsuccess.write(&mut self.stream).await.unwrap(); + debug!("{:?}", loginsuccess); + self.state = NetworkClientState::Play; + } + NetworkClientState::Play => {} + NetworkClientState::Disconnected => { + self.connected = false; + } + } } } diff --git a/src/server/net/mod.rs b/src/server/net/mod.rs deleted file mode 100644 index 88b9a08..0000000 --- a/src/server/net/mod.rs +++ /dev/null @@ -1,179 +0,0 @@ -/// Definitions for all the packets in the Minecraft protocol. -pub mod packets; - -use crate::{mctypes::*, CONFIG, FAVICON}; -use log::{debug, info}; -use packets::*; -use serde_json::json; -use std::sync::mpsc::{self, Receiver, TryRecvError}; -use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; - -/// The part of the server that handles -/// connecting clients and receiving/sending packets. -pub struct NetworkServer { - pub clients: Vec, - receiver: Receiver, -} -impl NetworkServer { - /// Create a thread for listening to new clients. - /// Use `std::sync::mpsc::channel()` to send the new clients across threads, - /// then hold that in a queue for processing on an update. - pub fn new(addr: A) -> NetworkServer { - let (tx, rx) = mpsc::channel(); - tokio::task::spawn(async move { - let listener = TcpListener::bind(addr) - .await - .expect("Could not bind to TCP socket"); - let mut id = 0; - loop { - let (stream, _) = listener - .accept() - .await - .expect("Network receiver disconnected"); - tx.send(NetworkClient { - id: id as u128, - connected: true, - stream, - state: NetworkClientState::Handshake, - }) - .expect("Network receiver disconnected"); - id += 1; - } - }); - info!("Network server started!"); - NetworkServer { - clients: vec![], - receiver: rx, - } - } - /// Update each client in `self.clients`. - pub async fn update(&mut self) { - loop { - match self.receiver.try_recv() { - Ok(client) => { - info!( - "Got client at {}", - client.stream.peer_addr().expect("Could not get peer addr") - ); - self.clients.push(client) - } - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => panic!("Network sender disconnected"), - } - } - for client in self.clients.iter_mut() { - client.update().await; - } - } -} - -/// The network client can only be in a few states, -/// this enum keeps track of that. -pub enum NetworkClientState { - Handshake, - Status, - Login, - Play, - Disconnected, -} - -/// A wrapper to contain everything related -/// to networking for the client. -pub struct NetworkClient { - pub id: u128, - pub connected: bool, - pub stream: TcpStream, - pub state: NetworkClientState, -} -impl NetworkClient { - /// Update the client. - /// - /// Updating could mean connecting new clients, reading packets, - /// writing packets, or disconnecting clients. - pub async fn update(&mut self) { - match self.state { - NetworkClientState::Handshake => { - let (_packet_length, _packet_id) = - read_packet_header(&mut self.stream).await.unwrap(); - let handshake = Handshake::read(&mut self.stream).await.unwrap(); - // Minecraft versions 1.8 - 1.8.9 use protocol version 47. - let compatible_versions = handshake.protocol_version == 47; - let next_state = match handshake.next_state.into() { - 1 => NetworkClientState::Status, - 2 => NetworkClientState::Login, - _ => NetworkClientState::Disconnected, - }; - self.state = next_state; - // If incompatible versions or wrong next state - if !compatible_versions { - let mut logindisconnect = LoginDisconnect::new(); - logindisconnect.reason = MCChat { - text: MCString::from("Incompatible client! Server is on 1.8.9"), - }; - logindisconnect.write(&mut self.stream).await.unwrap(); - self.state = NetworkClientState::Disconnected; - } - debug!("Got handshake: {:?}", handshake); - } - NetworkClientState::Status => { - let (_packet_length, _packet_id) = - read_packet_header(&mut self.stream).await.unwrap(); - let statusrequest = StatusRequest::read(&mut self.stream).await.unwrap(); - debug!("Got status request: {:?}", statusrequest); - let mut statusresponse = StatusResponse::new(); - statusresponse.json_response = json!({ - "version": { - "name": "1.8.9", - "protocol": 47, - }, - "players": { - "max": CONFIG.max_players, - "online": 5, - "sample": [ - { - "name": "shvr", - "id": "e3f58380-60bb-4714-91f2-151d525e64aa" - } - ] - }, - "description": { - "text": CONFIG.motd - }, - // TODO: Dynamically send the icon instead of linking statically. - "favicon": format!("data:image/png;base64,{}", if FAVICON.is_ok() { radix64::STD.encode(FAVICON.as_ref().unwrap().as_slice()) } else { "".to_owned() }) - }) - .to_string() - .into(); - statusresponse.write(&mut self.stream).await.unwrap(); - debug!("Sending status response: StatusResponse"); - let (_packet_length, _packet_id) = - read_packet_header(&mut self.stream).await.unwrap(); - let statusping = StatusPing::read(&mut self.stream).await.unwrap(); - debug!("Got status ping: {:?}", statusping); - let mut statuspong = StatusPong::new(); - statuspong.payload = statusping.payload; - statuspong.write(&mut self.stream).await.unwrap(); - debug!("Sending status pong: {:?}", statuspong); - self.state = NetworkClientState::Disconnected; - } - NetworkClientState::Login => { - let (_packet_length, _packet_id) = - read_packet_header(&mut self.stream).await.unwrap(); - let loginstart = LoginStart::read(&mut self.stream).await.unwrap(); - debug!("{:?}", loginstart); - // Offline mode skips encryption and compression. - let mut loginsuccess = LoginSuccess::new(); - // We're in offline mode, so this is a temporary uuid. - loginsuccess.uuid = "00000000-0000-3000-0000-000000000000".into(); - loginsuccess.username = loginstart.player_name; - loginsuccess.write(&mut self.stream).await.unwrap(); - debug!("{:?}", loginsuccess); - self.state = NetworkClientState::Play; - } - NetworkClientState::Play => {} - NetworkClientState::Disconnected => { - self.connected = false; - } - } - } -} diff --git a/src/server/net/packets/clientbound.rs b/src/server/packets/clientbound.rs similarity index 100% rename from src/server/net/packets/clientbound.rs rename to src/server/packets/clientbound.rs diff --git a/src/server/net/packets/mod.rs b/src/server/packets/mod.rs similarity index 100% rename from src/server/net/packets/mod.rs rename to src/server/packets/mod.rs diff --git a/src/server/net/packets/serverbound.rs b/src/server/packets/serverbound.rs similarity index 100% rename from src/server/net/packets/serverbound.rs rename to src/server/packets/serverbound.rs