From 1c3c3e0f636a9d39229b4bfacce7d747b612b3c5 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 18:45:25 +0300 Subject: [PATCH] added state field to client context created protocol helper read+write nbt trait moved event behaviour to new module added event on state change to packet handler --- Cargo.lock | 67 ++++++++++++++++++------------- Cargo.toml | 3 +- src/context.rs | 88 +++++++++++++---------------------------- src/data.rs | 45 +++++++++++++++------ src/event.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 38 ++++++++++-------- src/player.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 328 insertions(+), 119 deletions(-) create mode 100644 src/event.rs diff --git a/Cargo.lock b/Cargo.lock index 3ba9702..8e7e2eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,12 +121,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "cc" version = "1.2.20" @@ -194,6 +188,17 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "craftflow-nbt" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2d5312462b00f8420ace884a696f243be136ada9f50bf5f3d9858ff0c8e8e" +dependencies = [ + "cesu8", + "serde", + "thiserror", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -309,18 +314,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" -[[package]] -name = "fastnbt" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d4a73a95dc65551ccd98e1ecd1adb5d1ba5361146963b31f481ca42fc0520a3" -dependencies = [ - "byteorder", - "cesu8", - "serde", - "serde_bytes", -] - [[package]] name = "flate2" version = "1.1.1" @@ -569,6 +562,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "phf" version = "0.11.3" @@ -718,11 +717,12 @@ name = "rust_minecraft_server" version = "0.1.0" dependencies = [ "colog", + "craftflow-nbt", "dashmap", - "fastnbt", "itertools", "log", "palette", + "paste", "rust_mc_proto", "serde", "serde_default", @@ -759,15 +759,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" -dependencies = [ - "serde", -] - [[package]] name = "serde_default" version = "0.2.0" @@ -877,6 +868,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.41" diff --git a/Cargo.toml b/Cargo.toml index 73391a4..94b4ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,9 @@ serde_default = "0.2.0" toml = "0.8.22" itertools = "0.14.0" palette = "0.7.6" -fastnbt = "2.5.0" +craftflow-nbt = "2.1.0" colog = "1.3.0" log = "0.4.27" uuid = "1.16.0" dashmap = "6.1.0" +paste = "1.0.15" diff --git a/src/context.rs b/src/context.rs index fb4276f..9519783 100644 --- a/src/context.rs +++ b/src/context.rs @@ -2,10 +2,10 @@ use std::{hash::Hash, net::{SocketAddr, TcpStream}, sync::{Arc, RwLock, RwLockWr use dashmap::DashMap; use itertools::Itertools; -use rust_mc_proto::{MinecraftConnection, Packet}; +use rust_mc_proto::MinecraftConnection; use uuid::Uuid; -use crate::{config::Config, data::ServerError, player::{ClientInfo, Handshake, PlayerInfo}}; +use crate::{config::Config, data::ServerError, event::{ConnectionState, Listener, PacketHandler}, player::{ClientInfo, Handshake, PlayerInfo, ProtocolHelper}}; pub struct ServerContext { pub config: Arc, @@ -90,11 +90,12 @@ impl ServerContext { pub struct ClientContext { pub server: Arc, - pub conn: RwLock>, pub addr: SocketAddr, - pub handshake: RwLock>, - pub client_info: RwLock>, - pub player_info: RwLock> + conn: RwLock>, + handshake: RwLock>, + client_info: RwLock>, + player_info: RwLock>, + state: RwLock } impl PartialEq for ClientContext { @@ -111,7 +112,6 @@ impl Hash for ClientContext { impl Eq for ClientContext {} - impl ClientContext { pub fn new( server: Arc, @@ -123,7 +123,8 @@ impl ClientContext { conn: RwLock::new(conn), handshake: RwLock::new(None), client_info: RwLock::new(None), - player_info: RwLock::new(None) + player_info: RwLock::new(None), + state: RwLock::new(ConnectionState::Handshake) } } @@ -139,6 +140,18 @@ impl ClientContext { *self.player_info.write().unwrap() = Some(player_info); } + pub fn set_state(self: &Arc, state: ConnectionState) -> Result<(), ServerError> { + *self.state.write().unwrap() = state.clone(); + + for handler in self.server.packet_handlers( + |o| o.on_state_priority() + ).iter() { + handler.on_state(self.clone(), state.clone())?; + } + + Ok(()) + } + pub fn handshake(self: &Arc) -> Option { self.handshake.read().unwrap().clone() } @@ -151,60 +164,15 @@ impl ClientContext { self.player_info.read().unwrap().clone() } + pub fn state(self: &Arc) -> ConnectionState { + self.state.read().unwrap().clone() + } + pub fn conn(self: &Arc) -> RwLockWriteGuard<'_, MinecraftConnection> { self.conn.write().unwrap() } -} -pub trait Listener: Sync + Send { - fn on_status_priority(&self) -> i8 { 0 } - fn on_status(&self, _: Arc, _: &mut String) -> Result<(), ServerError> { Ok(()) } -} - -pub trait PacketHandler: Sync + Send { - fn on_incoming_packet_priority(&self) -> i8 { 0 } - fn on_incoming_packet(&self, _: Arc, _: &mut Packet, _: ConnectionState) -> Result<(), ServerError> { Ok(()) } - - fn on_outcoming_packet_priority(&self) -> i8 { 0 } - fn on_outcoming_packet(&self, _: Arc, _: &mut Packet, _: ConnectionState) -> Result<(), ServerError> { Ok(()) } -} - -#[derive(Debug)] -pub enum ConnectionState { - Handshake, - Status, - Login, - Configuration, - Play -} - - -#[macro_export] -macro_rules! call_handlers { - ($packet:expr, $client:ident, $state:ident, incoming) => { - { - use crate::context::ConnectionState; - let mut packet = $packet; - for handler in $client.server.packet_handlers( - |o| o.on_incoming_packet_priority() - ).iter() { - handler.on_incoming_packet($client.clone(), &mut packet, ConnectionState::$state)?; - } - packet.get_mut().set_position(0); - packet - } - }; - ($packet:expr, $client:ident, $state:ident, outcoming) => { - { - use crate::context::ConnectionState; - let mut packet = $packet; - for handler in $client.server.packet_handlers( - |o| o.on_outcoming_packet_priority() - ).iter() { - handler.on_outcoming_packet($client.clone(), &mut packet, ConnectionState::$state)?; - } - packet.get_mut().set_position(0); - packet - } - }; + pub fn protocol_helper(self: &Arc) -> ProtocolHelper { + ProtocolHelper::new(self.clone()) + } } \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index 347aa2f..ac21304 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,8 +1,8 @@ -use std::{error::Error, fmt::Display}; +use std::{error::Error, fmt::Display, io::Read}; use palette::{Hsl, IntoColor, Srgb}; use serde::{Deserialize, Serialize}; -use rust_mc_proto::ProtocolError; +use rust_mc_proto::{DataReader, Packet, ProtocolError}; use serde_with::skip_serializing_none; // Ошибки сервера @@ -12,7 +12,8 @@ pub enum ServerError { Protocol(ProtocolError), ConnectionClosed, SerTextComponent, - DeTextComponent + DeTextComponent, + UnexpectedState } impl Display for ServerError { @@ -94,16 +95,6 @@ impl TextComponent { TextComponentBuilder::new() } - pub fn as_nbt(self) -> Result, ServerError> { - fastnbt::to_bytes(&self) - .map_err(|_| ServerError::SerTextComponent) - } - - pub fn from_nbt(bytes: &[u8]) -> Result { - fastnbt::from_bytes(bytes) - .map_err(|_| ServerError::DeTextComponent) - } - pub fn as_json(self) -> Result { serde_json::to_string(&self) .map_err(|_| ServerError::SerTextComponent) @@ -115,6 +106,34 @@ impl TextComponent { } } +impl Default for TextComponent { + fn default() -> Self { + Self::new(String::new()) + } +} + +pub trait ReadWriteNBT: DataReader { + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; +} + +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + let mut data = Vec::new(); + let pos = self.get_ref().position(); + self.get_mut().read_to_end(&mut data).map_err(|_| ServerError::DeTextComponent)?; + let (remaining, value) = craftflow_nbt::from_slice(&data).map_err(|_| ServerError::DeTextComponent)?; + self.get_mut().set_position(pos + (data.len() - remaining.len()) as u64); + Ok(value) + } + + fn write_nbt(&mut self, val: &TextComponent) -> Result<(), ServerError> { + craftflow_nbt::to_writer(self.get_mut(), val) + .map_err(|_| ServerError::SerTextComponent)?; + Ok(()) + } +} + pub struct TextComponentBuilder { text: String, color: Option, diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..1682f05 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,101 @@ +use rust_mc_proto::Packet; + +use crate::{context::ClientContext, data::ServerError}; + +#[macro_export] +macro_rules! generate_handlers { + ($name:ident $(, $arg_ty:ty)* $(,)?) => { + paste::paste! { + fn [](&self) -> i8 { + 0 + } + + fn [](&self, _: std::sync::Arc $(, _: $arg_ty)*) -> Result<(), ServerError> { + Ok(()) + } + } + }; +} + +#[macro_export] +macro_rules! trigger_packet { + ($packet:expr, $client:ident, $state:ident, $bound:ident) => { + { + paste::paste! { + let mut packet = $packet; + for handler in $client.server.packet_handlers( + |o| o.[]() + ).iter() { + handler.[]($client.clone(), &mut packet, crate::event::ConnectionState::$state)?; + } + packet.get_mut().set_position(0); + packet + } + } + }; +} + +#[macro_export] +macro_rules! trigger_event { + ($client:ident, $event:ident, $($arg:expr),* $(,)?) => {{ + paste::paste! { + trigger_event!(@declare_mut_vars 0, $($arg),*); + + for handler in $client.server.listeners( + |o| o.[]() + ).iter() { + handler.[]( + $client.clone(), + $(trigger_event!(@expand_arg 0, $arg)),* + )?; + } + } + }}; + + (@declare_mut_vars $i:tt, &mut $head:expr, $($tail:tt)*) => { + paste::paste! { + let mut [<__arg $i>] = $head; + } + trigger_event!(@declare_mut_vars trigger_event!(@inc $i), $($tail)*); + }; + (@declare_mut_vars $i:tt, $head:expr, $($tail:tt)*) => { + trigger_event!(@declare_mut_vars trigger_event!(@inc $i), $($tail)*); + }; + (@declare_mut_vars $_i:tt,) => {}; + + (@expand_arg $i:tt, &mut $head:expr) => { + paste::paste! { &mut [<__arg $i>] } + }; + (@expand_arg $_i:tt, $head:expr) => { + $head + }; + + (@inc 0) => { 1 }; + (@inc 1) => { 2 }; + (@inc 2) => { 3 }; + (@inc 3) => { 4 }; + (@inc 4) => { 5 }; + (@inc 5) => { 6 }; + (@inc 6) => { 7 }; + (@inc 7) => { 8 }; +} + +pub trait Listener: Sync + Send { + generate_handlers!(status, &mut String); + generate_handlers!(plugin_message, &mut String); +} + +pub trait PacketHandler: Sync + Send { + generate_handlers!(incoming_packet, &mut Packet, ConnectionState); + generate_handlers!(outcoming_packet, &mut Packet, ConnectionState); + generate_handlers!(state, ConnectionState); +} + +#[derive(Debug, Clone)] +pub enum ConnectionState { + Handshake, + Status, + Login, + Configuration, + Play +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5a3a900..7586620 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use std::{env::args, io::Read, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration}; use config::Config; -use context::{ClientContext, ConnectionState, Listener, PacketHandler, ServerContext}; +use event::{ConnectionState, Listener, PacketHandler}; +use context::{ClientContext, ServerContext}; use log::{debug, error, info}; use player::{ClientInfo, Handshake, PlayerInfo}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; @@ -11,6 +12,7 @@ use pohuy::Pohuy; pub mod config; pub mod data; +pub mod event; pub mod context; pub mod player; pub mod pohuy; @@ -194,7 +196,7 @@ fn handle_connection( // Получение пакетов производится через client.conn(), // ВАЖНО: не помещать сам client.conn() в переменные, // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = call_handlers!(client.conn().read_packet()?, client, Handshake, incoming); + let mut packet = trigger_packet!(client.conn().read_packet()?, client, Handshake, incoming); if packet.id() != 0x00 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); @@ -214,9 +216,11 @@ fn handle_connection( match next_state { 1 => { // Тип подключения - статус + client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status + loop { // Чтение запроса - let packet = call_handlers!(client.conn().read_packet()?, client, Status, incoming); + let packet = trigger_packet!(client.conn().read_packet()?, client, Status, incoming); match packet.id() { 0x00 => { // Запрос статуса @@ -241,10 +245,10 @@ fn handle_connection( // Отправка статуса packet.write_string(&status)?; - client.conn().write_packet(&call_handlers!(packet, client, Status, outcoming))?; + client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?; }, 0x01 => { // Пинг - client.conn().write_packet(&call_handlers!(packet, client, Status, outcoming))?; + client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?; // Просто отправляем этот же пакет обратно // ID такой-же, содержание тоже, так почему бы и нет? }, @@ -255,10 +259,10 @@ fn handle_connection( } }, 2 => { // Тип подключения - игра - // Мы находимся в режиме Login + client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login // Читаем пакет Login Start - let mut packet = call_handlers!(client.conn().read_packet()?, client, Login, incoming); + let mut packet = trigger_packet!(client.conn().read_packet()?, client, Login, incoming); let name = packet.read_string()?; let uuid = packet.read_uuid()?; @@ -274,29 +278,29 @@ fn handle_connection( // Отправляем пакет Set Compression если сжатие указано if let Some(threshold) = client.server.config.server.compression_threshold { - client.conn().write_packet(&call_handlers!(Packet::build(0x03, |p| p.write_usize_varint(threshold))?, client, Login, outcoming))?; + client.conn().write_packet(&trigger_packet!(Packet::build(0x03, |p| p.write_usize_varint(threshold))?, client, Login, outcoming))?; client.conn().set_compression(Some(threshold)); // Устанавливаем сжатие на соединении } // Отправка пакета Login Success - client.conn().write_packet(&call_handlers!(Packet::build(0x02, |p| { + client.conn().write_packet(&trigger_packet!(Packet::build(0x02, |p| { p.write_uuid(&uuid)?; p.write_string(&name)?; p.write_varint(0) })?, client, Login, outcoming))?; - let packet = call_handlers!(client.conn().read_packet()?, client, Login, incoming); + let packet = trigger_packet!(client.conn().read_packet()?, client, Login, incoming); if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged"))); } - // Мы перешли в режим Configuration + client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration // Получение бренда клиента из Serverbound Plugin Message // Identifier канала откуда берется бренд: minecraft:brand let brand = loop { - let mut packet = call_handlers!(client.conn().read_packet()?, client, Configuration, incoming); + let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming); if packet.id() == 0x02 { // Пакет Serverbound Plugin Message let identifier = packet.read_string()?; @@ -316,7 +320,7 @@ fn handle_connection( // debug!("brand: {brand}"); - let mut packet = call_handlers!(client.conn().read_packet()?, client, Configuration, incoming); + let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming); // Пакет Client Information if packet.id() != 0x00 { @@ -358,19 +362,19 @@ fn handle_connection( // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото - client.conn().write_packet(&call_handlers!(Packet::empty(0x03), client, Configuration, outcoming))?; + client.conn().write_packet(&trigger_packet!(Packet::empty(0x03), client, Configuration, outcoming))?; - let packet = call_handlers!(client.conn().read_packet()?, client, Configuration, incoming); + let packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming); if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration"))); } - // Мы перешли в режим Play + client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play // Отключение игрока с сообщением // Отправляет в формате NBT TAG_String (https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/NBT#Specification:string_tag) - client.conn().write_packet(&call_handlers!(Packet::build(0x1C, |p| { + client.conn().write_packet(&trigger_packet!(Packet::build(0x1C, |p| { let message = "server is in developmenet lol".to_string(); p.write_byte(0x08)?; // NBT Type Name (TAG_String) p.write_unsigned_short(message.len() as u16)?; // String length in unsigned short diff --git a/src/player.rs b/src/player.rs index b0a3c21..91c09cd 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,5 +1,10 @@ +use std::{io::Read, sync::Arc}; + +use rust_mc_proto::{DataReader, DataWriter, Packet}; use uuid::Uuid; +use crate::{context::ClientContext, data::{ServerError, TextComponent, ReadWriteNBT}, event::ConnectionState}; + #[derive(Clone)] pub struct Handshake { pub protocol_version: i32, @@ -25,4 +30,104 @@ pub struct ClientInfo { pub struct PlayerInfo { pub name: String, pub uuid: Uuid +} + +pub struct ProtocolHelper { + client: Arc, + state: ConnectionState +} + +impl ProtocolHelper { + pub fn new(client: Arc) -> Self { + Self { + state: client.state(), + client + } + } + + pub fn disconnect(&self, reason: TextComponent) -> Result<(), ServerError> { + let packet = match self.state { + ConnectionState::Login => { + let text = reason.as_json()?; + Packet::build(0x00, |p| p.write_string(&text))? + }, + ConnectionState::Configuration => { + let mut packet = Packet::empty(0x02); + packet.write_nbt(&reason)?; + packet + }, + ConnectionState::Play => { + let mut packet = Packet::empty(0x1C); + packet.write_nbt(&reason)?; + packet + }, + _ => { + self.client.conn().close(); + return Ok(()) + }, + }; + self.client.conn().write_packet(&packet)?; + Ok(()) + } + + /// Returns cookie content + pub fn request_cookie(&self, id: &str) -> Result>, ServerError> { + match self.state { + ConnectionState::Configuration => { + let mut packet = Packet::empty(0x00); + packet.write_string(id)?; + self.client.conn().write_packet(&packet)?; + + let mut packet = self.client.conn().read_packet()?; + packet.read_string()?; + let data = if packet.read_boolean()? { + let n = packet.read_usize_varint()?; + Some(packet.read_bytes(n)?) + } else { + None + }; + + Ok(data) + }, + _ => Err(ServerError::UnexpectedState) + } + } + + /// Returns login plugin response - (message_id, payload) + pub fn send_login_plugin_request(&self, id: i32, channel: &str, data: &[u8]) -> Result<(i32, Option>), ServerError> { + match self.state { + ConnectionState::Login => { + let mut packet = Packet::empty(0x04); + packet.write_varint(id)?; + packet.write_string(channel)?; + packet.write_bytes(data)?; + self.client.conn().write_packet(&packet)?; + + let mut packet = self.client.conn().read_packet()?; + let identifier = packet.read_varint()?; + let data = if packet.read_boolean()? { + let mut data = Vec::new(); + packet.get_mut().read_to_end(&mut data).unwrap(); + Some(data) + } else { + None + }; + + Ok((identifier, data)) + }, + _ => Err(ServerError::UnexpectedState) + } + } + + pub fn send_plugin_message(&self, channel: &str, data: &[u8]) -> Result<(), ServerError> { + let mut packet = match self.state { + ConnectionState::Configuration => Packet::empty(0x01), + ConnectionState::Play => Packet::empty(0x18), + _ => return Err(ServerError::UnexpectedState) + }; + packet.write_string(channel)?; + packet.write_bytes(data)?; + self.client.conn().write_packet(&packet)?; + Ok(()) + } } \ No newline at end of file