diff --git a/src/network/client.rs b/src/network/client.rs index 48d3a38..bb9098a 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -43,8 +43,8 @@ impl NetworkClient { } } - pub fn send_packet(&mut self, packet: ClientBoundPacket) -> std::io::Result<()> { - self.stream.write_all(&packet.encode())?; + pub fn send_packet(&mut self, packet: impl ClientBoundPacket) -> std::io::Result<()> { + self.stream.write_all(&encode_packet(packet))?; Ok(()) } diff --git a/src/network/server.rs b/src/network/server.rs index 9d40b7b..7947241 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -68,7 +68,7 @@ impl <'lua> NetworkServer<'lua> { let mut closed = Vec::new(); for client in self.clients.iter_mut() { if client.player.is_some() { - let result = client.send_packet(ClientBoundPacket::KeepAlive(0)); + let result = client.send_packet(KeepAlive { data: 0 }); if result.is_err() { client.close(); self.plugins.player_leave(client.player.as_ref().unwrap()); @@ -114,7 +114,7 @@ impl <'lua> NetworkServer<'lua> { for client in self.clients.iter_mut() { if let Some(p) = &client.player { if p.name == player || p.uuid.to_string() == player { - client.send_packet(ClientBoundPacket::SystemChatMessage(message, false))?; + client.send_packet(SystemChatMessage { message, overlay: false })?; break } } @@ -123,7 +123,7 @@ impl <'lua> NetworkServer<'lua> { Response::Broadcast { message } => { for client in self.clients.iter_mut() { if client.player.is_some() { - client.send_packet(ClientBoundPacket::SystemChatMessage(message.clone(), false))?; + client.send_packet(SystemChatMessage { message: message.clone(), overlay: false })?; } } }, @@ -131,7 +131,7 @@ impl <'lua> NetworkServer<'lua> { for client in self.clients.iter_mut() { if let Some(pl) = &client.player { if pl.name == player || pl.uuid.to_string() == player { - client.send_packet(ClientBoundPacket::Disconnect(reason.clone()))?; + client.send_packet(Disconnect { reason: reason.clone() })?; } } } @@ -147,88 +147,21 @@ impl <'lua> NetworkServer<'lua> { ServerBoundPacket::Unknown(id) => warn!("Unknown packet: {}", id), ServerBoundPacket::Handshake(_) => (), ServerBoundPacket::StatusRequest() - => client.send_packet(ClientBoundPacket::StatusResponse( - r#"{"version":{"name":"1.19.3","protocol":761}}"#.to_owned() - ))?, + => client.send_packet(StatusResponse { + data: r#"{"version":{"name":"1.19.3","protocol":761}}"#.to_owned() + })?, ServerBoundPacket::PingRequest(n) => { - client.send_packet(ClientBoundPacket::PingResponse(n))?; + client.send_packet(PingResponse { data: n })?; client.close(); } - ServerBoundPacket::LoginStart(login_start) => { - if self.clients.iter().filter_map(|x| x.player.as_ref()).any(|x| x.uuid == login_start.uuid) { - client.send_packet(ClientBoundPacket::LoginDisconnect(json!({ - "translate": "multiplayer.disconnect.duplicate_login" - })))?; - client.close(); - return Ok(()) - } - client.player = Some(Player { - name: login_start.name.clone(), - uuid: login_start.uuid, - }); - match self.config.login { - LoginMode::Offline => { - client.verified = true; - client.send_packet(ClientBoundPacket::LoginPluginRequest{ - id: -1, - channel: "qc:init".to_owned(), - data: Vec::new() - })?; - }, - LoginMode::Velocity => { - client.send_packet(ClientBoundPacket::LoginPluginRequest{ - id: 10, - channel: "velocity:player_info".to_owned(), - data: vec![1], - })? - } - } - } - // Finalize login - ServerBoundPacket::LoginPluginResponse { id: -1, .. } => { - if !client.verified { - client.send_packet(ClientBoundPacket::Disconnect(json!({ - "text": "Failed to verify your connection", - "color": "red", - })))?; - client.close(); - } - client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess { - name: client.player.as_ref().unwrap().name.to_owned(), - uuid: client.player.as_ref().unwrap().uuid, - }))?; - self.plugins.player_join(client.player.as_ref().unwrap()); - self.post_login(client)?; - } - // Velocity login - ServerBoundPacket::LoginPluginResponse { id: 10, data } => { - let Some(data) = data else { - client.send_packet(ClientBoundPacket::LoginDisconnect(json!({ - "text": "This server can only be connected to via a Velocity proxy", - "color": "red" - })))?; - client.close(); - return Ok(()); - }; - let (sig, data) = data.split_at(32); - let mut mac = Hmac::::new_from_slice(self.config.velocity_secret.clone().unwrap().as_bytes())?; - mac.update(data); - if let Err(_) = mac.verify_slice(sig) { - client.send_packet(ClientBoundPacket::Disconnect(json!({ - "text": "Could not verify secret. Ensure that the secrets configured for Velocity and Quectocraft match." - })))?; - client.close(); - return Ok(()) - } - client.verified = true; - client.send_packet(ClientBoundPacket::LoginPluginRequest{ - id: -1, - channel: "qc:init".to_owned(), - data: Vec::new() - })?; - } + ServerBoundPacket::LoginStart(login_start) + => self.start_login(client, login_start)?, + ServerBoundPacket::LoginPluginResponse(LoginPluginResponse { id: 10, data }) + => self.velocity_login(client, data)?, + ServerBoundPacket::LoginPluginResponse(LoginPluginResponse{ id: -1, .. }) + => self.login(client)?, ServerBoundPacket::LoginPluginResponse { .. } => { - client.send_packet(ClientBoundPacket::LoginDisconnect(json!({"text": "bad plugin response"})))?; + client.send_packet(LoginDisconnect { reason: json!({"text": "bad plugin response"}) })?; client.close(); } ServerBoundPacket::ChatMessage(msg) => { @@ -238,10 +171,10 @@ impl <'lua> NetworkServer<'lua> { let mut parts = msg.message.splitn(1, " "); if let Some(cmd) = parts.next() { if cmd == "qc" { - client.send_packet(ClientBoundPacket::SystemChatMessage(json!({ + client.send_packet(SystemChatMessage { message: json!({ "text": format!("QuectoCraft version {}", VERSION), "color": "green" - }), false))?; + }), overlay: false })?; } else { let args = parts.next().unwrap_or_default(); self.plugins.command(client.player.as_ref().unwrap(), cmd, args); @@ -252,12 +185,85 @@ impl <'lua> NetworkServer<'lua> { Ok(()) } - fn post_login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> { - client.send_packet(ClientBoundPacket::LoginPlay(LoginPlay { + fn start_login(&mut self, client: &mut NetworkClient, login_start: LoginStart) -> Result<(), Box> { + if self.clients.iter().filter_map(|x| x.player.as_ref()).any(|x| x.uuid == login_start.uuid) { + client.send_packet(LoginDisconnect { reason: json!({ + "translate": "multiplayer.disconnect.duplicate_login" + })})?; + client.close(); + return Ok(()) + } + client.player = Some(Player { + name: login_start.name.clone(), + uuid: login_start.uuid, + }); + match self.config.login { + LoginMode::Offline => { + client.verified = true; + client.send_packet(LoginPluginRequest{ + id: -1, + channel: "qc:init".to_owned(), + data: Vec::new() + })?; + }, + LoginMode::Velocity => { + client.send_packet(LoginPluginRequest{ + id: 10, + channel: "velocity:player_info".to_owned(), + data: vec![1], + })? + } + } + Ok(()) + } + + fn velocity_login(&mut self, client: &mut NetworkClient, data: Option>) -> Result<(), Box> { + let Some(data) = data else { + client.send_packet(LoginDisconnect { reason: json!({ + "text": "This server can only be connected to via a Velocity proxy", + "color": "red" + })})?; + client.close(); + return Ok(()); + }; + let (sig, data) = data.split_at(32); + let mut mac = Hmac::::new_from_slice(self.config.velocity_secret.clone().unwrap().as_bytes())?; + mac.update(data); + if let Err(_) = mac.verify_slice(sig) { + client.send_packet(Disconnect { reason: json!({ + "text": "Could not verify secret. Ensure that the secrets configured for Velocity and Quectocraft match." + })})?; + client.close(); + return Ok(()) + } + client.verified = true; + client.send_packet(LoginPluginRequest{ + id: -1, + channel: "qc:init".to_owned(), + data: Vec::new() + })?; + Ok(()) + } + + fn login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> { + if !client.verified { + client.send_packet(Disconnect { reason: json!({ + "text": "Failed to verify your connection", + "color": "red", + })})?; + client.close(); + return Ok(()) + } + client.send_packet(LoginSuccess { + name: client.player.as_ref().unwrap().name.to_owned(), + uuid: client.player.as_ref().unwrap().uuid, + })?; + self.plugins.player_join(client.player.as_ref().unwrap()); + client.send_packet(LoginPlay { eid: client.id, is_hardcore: false, - gamemode: 1, - prev_gamemode: 1, + gamemode: 3, + prev_gamemode: 3, dimensions: vec![ "qc:world".to_owned(), ], @@ -273,16 +279,16 @@ impl <'lua> NetworkServer<'lua> { is_debug: false, is_flat: false, death_location: None, - }))?; - client.send_packet(ClientBoundPacket::PluginMessage(PluginMessage { + })?; + client.send_packet(PluginMessage { channel: "minecraft:brand".to_owned(), data: { let mut data = Vec::new(); data.write_string(32767, &format!("Quectocraft {}", VERSION)); data } - }))?; - client.send_packet(ClientBoundPacket::Commands(self.commands.clone()))?; + })?; + client.send_packet(self.commands.clone())?; let mut chunk_data: Vec = Vec::new(); for _ in 0..(384 / 16) { // number of non-air blocks @@ -299,16 +305,16 @@ impl <'lua> NetworkServer<'lua> { let hmdata = vec![0i64; 37]; let mut heightmap = nbt::Blob::new(); heightmap.insert("MOTION_BLOCKING", hmdata).unwrap(); - client.send_packet(ClientBoundPacket::ChunkData(ChunkData { + client.send_packet(ChunkData { x: 0, z: 0, heightmap, chunk_data, - }))?; - client.send_packet(ClientBoundPacket::SetDefaultSpawnPosition( - Position { x: 0, y: 0, z: 0 }, 0.0 - ))?; - client.send_packet(ClientBoundPacket::SyncPlayerPosition(SyncPlayerPosition { + })?; + client.send_packet(SetDefaultSpawnPosition { + pos: Position { x: 0, y: 0, z: 0 }, angle: 0.0 + })?; + client.send_packet(SyncPlayerPosition { x: 0.0, y: 64.0, z: 0.0, @@ -317,7 +323,7 @@ impl <'lua> NetworkServer<'lua> { flags: 0, teleport_id: 0, dismount: false - }))?; + })?; // TODO why doesn't this work with quilt? // client.send_packet(ClientBoundPacket::PlayerAbilities(0x0f, 0.05, 0.1))?; Ok(()) diff --git a/src/protocol/clientbound.rs b/src/protocol/clientbound.rs index ca02404..60db1b2 100644 --- a/src/protocol/clientbound.rs +++ b/src/protocol/clientbound.rs @@ -1,6 +1,50 @@ use uuid::Uuid; -use super::{data::{PacketEncoder, finalize_packet}, Position, command::Commands}; +use super::{data::{PacketEncoder, finalize_packet}, Position}; + +pub trait ClientBoundPacket: std::fmt::Debug { + fn encode(&self, encoder: &mut impl PacketEncoder); + fn packet_id(&self) -> i32; +} + +////////////////// +// // +// Status // +// // +////////////////// + +#[derive(Debug)] +pub struct PingResponse { + pub data: i64 +} + +impl ClientBoundPacket for PingResponse { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_long(self.data) + } + + fn packet_id(&self) -> i32 { 0x01 } +} + +#[derive(Debug)] +pub struct StatusResponse { + pub data: String +} + +impl ClientBoundPacket for StatusResponse { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_string(32767, &self.data) + } + + fn packet_id(&self) -> i32 { 0x00 } +} + + +///////////////// +// // +// Login // +// // +///////////////// #[derive(Debug)] pub struct LoginSuccess { @@ -8,14 +52,52 @@ pub struct LoginSuccess { pub name: String, } -impl LoginSuccess { - pub fn encode(self, encoder: &mut impl PacketEncoder) { +impl ClientBoundPacket for LoginSuccess { + fn encode(&self, encoder: &mut impl PacketEncoder) { encoder.write_uuid(self.uuid); encoder.write_string(16, &self.name); encoder.write_varint(0); } + fn packet_id(&self) -> i32 { 0x02 } } +#[derive(Debug)] +pub struct LoginPluginRequest { + pub id: i32, + pub channel: String, + pub data: Vec, +} + +impl ClientBoundPacket for LoginPluginRequest { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_varint(self.id); + encoder.write_string(32767, &self.channel); + encoder.write_bytes(&self.data); + } + + fn packet_id(&self) -> i32 { 0x04 } +} + +#[derive(Debug)] +pub struct LoginDisconnect { + pub reason: serde_json::Value +} + +impl ClientBoundPacket for LoginDisconnect { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_string(262144, &self.reason.to_string()) + } + + fn packet_id(&self) -> i32 { 0x00 } +} + + +//////////////// +// // +// Play // +// // +//////////////// + #[derive(Debug)] pub struct LoginPlay { pub eid: i32, @@ -37,14 +119,14 @@ pub struct LoginPlay { pub death_location: Option<(String, Position)> } -impl LoginPlay { - pub fn encode(self, encoder: &mut impl PacketEncoder) { +impl ClientBoundPacket for LoginPlay { + fn encode(&self, encoder: &mut impl PacketEncoder) { encoder.write_int(self.eid); encoder.write_bool(self.is_hardcore); encoder.write_ubyte(self.gamemode); encoder.write_ubyte(self.prev_gamemode); encoder.write_varint(self.dimensions.len() as i32); - for dim in self.dimensions { + for dim in &self.dimensions { encoder.write_string(32767, &dim); } encoder.write_bytes(&self.registry_codec); @@ -59,11 +141,13 @@ impl LoginPlay { encoder.write_bool(self.is_debug); encoder.write_bool(self.is_flat); encoder.write_bool(self.death_location.is_some()); - if let Some(dl) = self.death_location { + if let Some(dl) = &self.death_location { encoder.write_string(32767, &dl.0); encoder.write_position(dl.1); } } + + fn packet_id(&self) -> i32 { 0x24 } } #[derive(Debug)] @@ -72,11 +156,13 @@ pub struct PluginMessage { pub data: Vec, } -impl PluginMessage { - pub fn encode(self, encoder: &mut impl PacketEncoder) { +impl ClientBoundPacket for PluginMessage { + fn encode(&self, encoder: &mut impl PacketEncoder) { encoder.write_string(32767, &self.channel); encoder.write_bytes(&self.data); } + + fn packet_id(&self) -> i32 { 0x15 } } #[derive(Debug)] @@ -91,8 +177,8 @@ pub struct SyncPlayerPosition { pub dismount: bool, } -impl SyncPlayerPosition { - pub fn encode(self, encoder: &mut impl PacketEncoder) { +impl ClientBoundPacket for SyncPlayerPosition { + fn encode(&self, encoder: &mut impl PacketEncoder) { encoder.write_double(self.x); encoder.write_double(self.y); encoder.write_double(self.z); @@ -102,6 +188,8 @@ impl SyncPlayerPosition { encoder.write_varint(self.teleport_id); encoder.write_bool(self.dismount); } + + fn packet_id(&self) -> i32 { 0x38 } } #[derive(Debug)] @@ -112,8 +200,8 @@ pub struct ChunkData { pub chunk_data: Vec, } -impl ChunkData { - pub fn encode(self, encoder: &mut impl PacketEncoder) { +impl ClientBoundPacket for ChunkData { + fn encode(&self, encoder: &mut impl PacketEncoder) { encoder.write_int(self.x); encoder.write_int(self.z); self.heightmap.to_writer(encoder).unwrap(); @@ -133,104 +221,86 @@ impl ChunkData { // block light array encoder.write_varint(0); } + + fn packet_id(&self) -> i32 { 0x20 } } -#[allow(unused)] #[derive(Debug)] -pub enum ClientBoundPacket { - // status - StatusResponse(String), - PingResponse(i64), - // login - LoginPluginRequest { id: i32, channel: String, data: Vec }, - LoginSuccess(LoginSuccess), - LoginDisconnect(serde_json::Value), - // play - LoginPlay(LoginPlay), - PluginMessage(PluginMessage), - Commands(Commands), - ChunkData(ChunkData), - SyncPlayerPosition(SyncPlayerPosition), - KeepAlive(i64), - PlayerAbilities(i8, f32, f32), - Disconnect(serde_json::Value), - SetDefaultSpawnPosition(Position, f32), - SystemChatMessage(serde_json::Value, bool), +pub struct KeepAlive { + pub data: i64 } -impl ClientBoundPacket { - pub fn encode(self) -> Vec { - let mut packet = Vec::new(); - match self { - // Status - Self::StatusResponse(status) => { - packet.write_string(32767, &status); - finalize_packet(packet, 0) - }, - Self::PingResponse(n) => { - packet.write_long(n); - finalize_packet(packet, 1) - }, - // Login - Self::LoginDisconnect(message) => { - packet.write_string(262144, &message.to_string()); - finalize_packet(packet, 0) - } - Self::LoginPluginRequest { id, channel, data } => { - packet.write_varint(id); - packet.write_string(32767, &channel); - packet.write_bytes(&data); - finalize_packet(packet, 4) - } - Self::LoginSuccess(login_success) => { - login_success.encode(&mut packet); - finalize_packet(packet, 2) - } - // Play - Self::Disconnect(message) => { - packet.write_string(262144, &message.to_string()); - finalize_packet(packet, 23) - } - Self::LoginPlay(login_play) => { - login_play.encode(&mut packet); - finalize_packet(packet, 36) - } - Self::PluginMessage(plugin_message) => { - plugin_message.encode(&mut packet); - finalize_packet(packet, 21) - } - Self::Commands(commands) => { - commands.encode(&mut packet); - finalize_packet(packet, 14) - } - Self::ChunkData(chunk_data) => { - chunk_data.encode(&mut packet); - finalize_packet(packet, 32) - } - Self::SyncPlayerPosition(sync_player_position) => { - sync_player_position.encode(&mut packet); - finalize_packet(packet, 56) - } - Self::KeepAlive(n) => { - packet.write_long(n); - finalize_packet(packet, 31) - } - Self::SetDefaultSpawnPosition(pos, angle) => { - packet.write_position(pos); - packet.write_float(angle); - finalize_packet(packet, 76) - } - Self::PlayerAbilities(flags, speed, view) => { - packet.write_byte(flags); - packet.write_float(speed); - packet.write_float(view); - finalize_packet(packet, 48) - } - Self::SystemChatMessage(msg, overlay) => { - packet.write_string(262144, &msg.to_string()); - packet.write_bool(overlay); - finalize_packet(packet, 96) - } - } +impl ClientBoundPacket for KeepAlive { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_long(self.data) } + + fn packet_id(&self) -> i32 { 0x1f } +} + +#[derive(Debug)] +pub struct PlayerAbilities { + pub flags: i8, + pub fly_speed: f32, + pub fov_modifier: f32, +} + +impl ClientBoundPacket for PlayerAbilities { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_byte(self.flags); + encoder.write_float(self.fly_speed); + encoder.write_float(self.fov_modifier); + } + + fn packet_id(&self) -> i32 { 0x30 } +} + + +#[derive(Debug)] +pub struct Disconnect { + pub reason: serde_json::Value +} + +impl ClientBoundPacket for Disconnect { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_string(262144, &self.reason.to_string()) + } + + fn packet_id(&self) -> i32 { 0x17 } +} + +#[derive(Debug)] +pub struct SetDefaultSpawnPosition { + pub pos: Position, + pub angle: f32 +} + +impl ClientBoundPacket for SetDefaultSpawnPosition { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_position(self.pos); + encoder.write_float(self.angle); + } + + fn packet_id(&self) -> i32 { 0x4c } +} + +#[derive(Debug)] +pub struct SystemChatMessage { + pub message: serde_json::Value, + pub overlay: bool +} + +impl ClientBoundPacket for SystemChatMessage { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_string(262144, &self.message.to_string()); + encoder.write_bool(self.overlay); + } + + fn packet_id(&self) -> i32 { 0x60 } +} + +pub fn encode_packet(packet: impl ClientBoundPacket) -> Vec { + let mut buffer = Vec::new(); + packet.encode(&mut buffer); + finalize_packet(buffer, packet.packet_id()) } diff --git a/src/protocol/command.rs b/src/protocol/command.rs index 4a81a6a..7cbb38a 100644 --- a/src/protocol/command.rs +++ b/src/protocol/command.rs @@ -1,10 +1,23 @@ -use super::data::PacketEncoder; +use super::{data::PacketEncoder, clientbound::ClientBoundPacket}; #[derive(Debug, Clone)] pub struct Commands { nodes: Vec, } +impl ClientBoundPacket for Commands { + fn encode(&self, encoder: &mut impl PacketEncoder) { + encoder.write_varint(self.nodes.len() as i32); + for node in &self.nodes { + node.encode(encoder); + } + // root node + encoder.write_varint(0); + } + + fn packet_id(&self) -> i32 { 0x0e } +} + impl Commands { pub fn new() -> Self { let root = CommandNode { @@ -67,14 +80,6 @@ impl Commands { self.nodes[node as usize].children.push(child); } - pub fn encode(&self, encoder: &mut impl PacketEncoder) { - encoder.write_varint(self.nodes.len() as i32); - for node in &self.nodes { - node.encode(encoder); - } - // root node - encoder.write_varint(0); - } } pub enum NodeError { diff --git a/src/protocol/serverbound.rs b/src/protocol/serverbound.rs index 38fe448..07fc135 100644 --- a/src/protocol/serverbound.rs +++ b/src/protocol/serverbound.rs @@ -60,6 +60,25 @@ impl ChatMessage { } } +#[derive(Debug)] +pub struct LoginPluginResponse { + pub id: i32, + pub data: Option>, +} + +impl LoginPluginResponse { + pub fn decode(mut decoder: PacketDecoder) -> Self { + let id = decoder.read_varint(); + let success = decoder.read_bool(); + let data = if success { + Some(decoder.read_to_end().to_vec()) + } else { + None + }; + Self { id, data } + } +} + #[derive(Debug)] pub enum ServerBoundPacket { Unknown(i32), @@ -71,7 +90,7 @@ pub enum ServerBoundPacket { PingRequest(i64), // login LoginStart(LoginStart), - LoginPluginResponse { id: i32, data: Option> }, + LoginPluginResponse(LoginPluginResponse), // play ChatMessage(ChatMessage), ChatCommand(ChatMessage), @@ -98,17 +117,11 @@ impl ServerBoundPacket { ServerBoundPacket::LoginStart(LoginStart::decode(decoder)) }, (NS::Login, 2) => { - let id = decoder.read_varint(); - let success = decoder.read_bool(); - let data = if success { - Some(decoder.read_to_end().to_vec()) - } else { - None - }; - if id == -1 { + let lpr = LoginPluginResponse::decode(decoder); + if lpr.id == -1 { *state = NetworkState::Play; } - ServerBoundPacket::LoginPluginResponse { id, data } + ServerBoundPacket::LoginPluginResponse(lpr) }, (NS::Play, 4) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)), (NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),