diff --git a/Cargo.lock b/Cargo.lock index da06f2e..a90fad3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -684,7 +684,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "minecraft-protocol" version = "0.1.0" -source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#a4fc2bcf7bced11fa255c371d8d5c780157ecbb4" +source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#bef4fa8c009857e3753d85d8e2df1f71c3ae08f6" dependencies = [ "byteorder", "minecraft-protocol-derive", @@ -697,7 +697,7 @@ dependencies = [ [[package]] name = "minecraft-protocol-derive" version = "0.0.0" -source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#a4fc2bcf7bced11fa255c371d8d5c780157ecbb4" +source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#bef4fa8c009857e3753d85d8e2df1f71c3ae08f6" dependencies = [ "proc-macro2", "quote", diff --git a/src/lobby.rs b/src/lobby.rs index de84e1b..b9ada6d 100644 --- a/src/lobby.rs +++ b/src/lobby.rs @@ -17,8 +17,8 @@ use minecraft_protocol::version::v1_14_4::login::{LoginDisconnect, LoginStart, L use minecraft_protocol::version::v1_14_4::status::StatusResponse; use minecraft_protocol::version::v1_17_1::game::{ ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, GameDisconnect, JoinGame, - PlayerPositionAndLook, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, SpawnPosition, - TimeUpdate, + PlayerPositionAndLook, PluginMessage, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, + SpawnPosition, TimeUpdate, }; use nbt::CompoundTag; use tokio::io::{self, AsyncWriteExt}; @@ -29,7 +29,7 @@ use tokio::time; use uuid::Uuid; use crate::config::*; -use crate::proto::{self, Client, ClientState, RawPacket}; +use crate::proto::{self, Client, ClientInfo, ClientState, RawPacket}; use crate::proxy; use crate::server::{self, Server, State}; use crate::service; @@ -53,6 +53,7 @@ const TICKS_PER_SECOND: u32 = 20; // TODO: on error, nicely kick client with message pub async fn serve( client: Client, + client_info: ClientInfo, mut inbound: TcpStream, config: Arc, server: Arc, @@ -63,6 +64,12 @@ pub async fn serve( // TODO: note this assumes the first receiving packet (over queue) is login start // TODO: assert client is in login mode! + // We must have useful client info + if client_info.username.is_none() { + error!(target: "lazymc::lobby", "Client username is unknown, closing connection"); + return Err(()); + } + // Incoming buffer and packet holding queue let mut inbound_buf = queue; let mut server_queue = BytesMut::new(); @@ -99,7 +106,8 @@ pub async fn serve( // Wait for server to come online, then set up new connection to it stage_wait(config.clone(), server.clone(), &mut writer).await?; - let (mut outbound, mut server_buf) = connect_to_server(client, &config, server).await?; + let (mut outbound, mut server_buf) = + connect_to_server(client, client_info, &config, server).await?; // Grab join game packet from server let join_game = wait_for_server_join_game(&mut outbound, &mut server_buf).await?; @@ -137,36 +145,8 @@ pub async fn serve( return Ok(()); } - // TODO: is this ever called? - if client_state == ClientState::Play - && packet.id == proto::packets::play::SERVER_CLIENT_SETTINGS - { - debug!(target: "lazymc::lobby", "Ignoring client settings packet"); - continue; - } - - // TODO: is this ever called? - if client_state == ClientState::Play - && packet.id == proto::packets::play::SERVER_PLUGIN_MESSAGE - { - debug!(target: "lazymc::lobby", "Ignoring plugin message packet"); - continue; - } - - // TODO: is this ever called? - if client_state == ClientState::Play - && packet.id == proto::packets::play::SERVER_PLAYER_POS_ROT - { - debug!(target: "lazymc::lobby", "Ignoring player pos rot packet"); - continue; - } - - // TODO: is this ever called? - if client_state == ClientState::Play && packet.id == proto::packets::play::SERVER_PLAYER_POS - { - debug!(target: "lazymc::lobby", "Ignoring player pos packet"); - continue; - } + // TODO: when receiving Login Plugin Request, respond with empty payload + // See: https://wiki.vg/Protocol#Login_Plugin_Request // Show unhandled packet warning debug!(target: "lazymc", "Received unhandled packet:"); @@ -232,7 +212,9 @@ async fn send_lobby_play_packets(writer: &mut WriteHalf<'_>) -> Result<(), ()> { // Send initial game join send_lobby_join_game(writer).await?; - // TODO: send plugin message for brand + // Send server brand + // TODO: does client ever receive real brand after this? + send_lobby_brand(writer).await?; // Send spawn and player position, disables 'download terrain' screen // TODO: is sending spawn this required? @@ -286,6 +268,24 @@ async fn send_lobby_join_game(writer: &mut WriteHalf<'_>) -> Result<(), ()> { Ok(()) } +/// Send lobby brand to client. +async fn send_lobby_brand(writer: &mut WriteHalf<'_>) -> Result<(), ()> { + let brand = b"lazymc".to_vec(); + + let packet = PluginMessage { + channel: "minecraft:brand".into(), + data: brand, + }; + + let mut data = Vec::new(); + packet.encode(&mut data).map_err(|_| ())?; + + let response = RawPacket::new(proto::packets::play::CLIENT_PLUGIN_MESSAGE, data).encode()?; + writer.write_all(&response).await.map_err(|_| ())?; + + Ok(()) +} + /// Send lobby spawn position to client. async fn send_lobby_spawn_pos(writer: &mut WriteHalf<'_>) -> Result<(), ()> { let packet = SpawnPosition { @@ -537,6 +537,7 @@ async fn wait_for_server<'a>(config: Arc, server: Arc) -> Result // TODO: clean this up async fn connect_to_server( real_client: Client, + client_info: ClientInfo, config: &Config, server: Arc, ) -> Result<(TcpStream, BytesMut), ()> { @@ -551,9 +552,9 @@ async fn connect_to_server( let tmp_client = Client::default(); tmp_client.set_state(ClientState::Login); - // TODO: use client version + // Handshake packet let packet = Handshake { - protocol_version: 755, + protocol_version: client_info.protocol_version.unwrap(), server_addr: config.server.address.ip().to_string(), server_port: config.server.address.port(), next_state: ClientState::Login.to_id(), @@ -566,9 +567,8 @@ async fn connect_to_server( writer.write_all(&request).await.map_err(|_| ())?; // Request login start - // TODO: use client username let packet = LoginStart { - name: "timvisee".into(), + name: client_info.username.ok_or(())?, }; let mut data = Vec::new(); diff --git a/src/proto.rs b/src/proto.rs index 002cc4b..8f2b479 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -53,6 +53,7 @@ pub mod packets { pub const CLIENT_CHAT_MSG: i32 = 0x0F; pub const CLIENT_SPAWN_POS: i32 = 0x4B; pub const CLIENT_DISCONNECT: i32 = 0x1A; + pub const CLIENT_PLUGIN_MESSAGE: i32 = 0x18; } } @@ -125,6 +126,22 @@ impl Default for ClientState { } } +/// Client info, useful during connection handling. +#[derive(Debug, Default)] +pub struct ClientInfo { + /// Client protocol version. + pub protocol_version: Option, + + /// Client username. + pub username: Option, +} + +impl ClientInfo { + pub fn empty() -> Self { + Self::default() + } +} + /// Raw Minecraft packet. /// /// Having a packet ID and a raw data byte array. diff --git a/src/status.rs b/src/status.rs index a708b53..fcb702a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -17,7 +17,7 @@ use tokio::time; use crate::config::*; use crate::lobby; -use crate::proto::{self, Client, ClientState, RawPacket}; +use crate::proto::{self, Client, ClientInfo, ClientState, RawPacket}; use crate::server::{self, Server, State}; use crate::service; @@ -39,6 +39,8 @@ pub async fn serve( || config.join.methods.contains(&Method::Forward); let mut inbound_history = BytesMut::new(); + let mut client_info = ClientInfo::empty(); + loop { // Read packet from stream let (packet, raw) = match proto::read_packet(&mut buf, &mut reader).await { @@ -55,22 +57,28 @@ pub async fn serve( // Hijack handshake if client_state == ClientState::Handshake && packet.id == proto::STATUS_PACKET_ID_STATUS { - // Parse handshake, grab new state - let new_state = match Handshake::decode(&mut packet.data.as_slice()) { - Ok(handshake) => match ClientState::from_id(handshake.next_state) { - Some(state) => state, - None => { - error!(target: "lazymc", "Client tried to switch into unknown protcol state ({}), disconnecting", handshake.next_state); - break; - } - }, + // Parse handshake + let handshake = match Handshake::decode(&mut packet.data.as_slice()) { + Ok(handshake) => handshake, Err(_) => { debug!(target: "lazymc", "Got malformed handshake from client, disconnecting"); break; } }; - // Update client state + // Parse new state + let new_state = match ClientState::from_id(handshake.next_state) { + Some(state) => state, + None => { + error!(target: "lazymc", "Client tried to switch into unknown protcol state ({}), disconnecting", handshake.next_state); + break; + } + }; + + // Update client info and client state + client_info + .protocol_version + .replace(handshake.protocol_version); client.set_state(new_state); // If login handshake and holding is enabled, hold packets @@ -103,10 +111,12 @@ pub async fn serve( // Hijack login start if client_state == ClientState::Login && packet.id == proto::LOGIN_PACKET_ID_LOGIN_START { - // Try to get login username + // Try to get login username, update client info + // TODO: we should always parse this packet successfully let username = LoginStart::decode(&mut packet.data.as_slice()) .ok() .map(|p| p.name); + client_info.username = username.clone(); // Kick if lockout is enabled if config.lockout.enabled { @@ -137,7 +147,7 @@ pub async fn serve( queue.extend(buf.split_off(0)); // Start lobby - lobby::serve(client, inbound, config, server, queue).await?; + lobby::serve(client, client_info, inbound, config, server, queue).await?; return Ok(()); }