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
This commit is contained in:
MeexReay 2025-05-02 18:45:25 +03:00
parent ca7eb4e350
commit 1c3c3e0f63
7 changed files with 328 additions and 119 deletions

67
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<Config>,
@ -90,11 +90,12 @@ impl ServerContext {
pub struct ClientContext {
pub server: Arc<ServerContext>,
pub conn: RwLock<MinecraftConnection<TcpStream>>,
pub addr: SocketAddr,
pub handshake: RwLock<Option<Handshake>>,
pub client_info: RwLock<Option<ClientInfo>>,
pub player_info: RwLock<Option<PlayerInfo>>
conn: RwLock<MinecraftConnection<TcpStream>>,
handshake: RwLock<Option<Handshake>>,
client_info: RwLock<Option<ClientInfo>>,
player_info: RwLock<Option<PlayerInfo>>,
state: RwLock<ConnectionState>
}
impl PartialEq for ClientContext {
@ -111,7 +112,6 @@ impl Hash for ClientContext {
impl Eq for ClientContext {}
impl ClientContext {
pub fn new(
server: Arc<ServerContext>,
@ -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<Self>, 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<Self>) -> Option<Handshake> {
self.handshake.read().unwrap().clone()
}
@ -151,60 +164,15 @@ impl ClientContext {
self.player_info.read().unwrap().clone()
}
pub fn state(self: &Arc<Self>) -> ConnectionState {
self.state.read().unwrap().clone()
}
pub fn conn(self: &Arc<Self>) -> RwLockWriteGuard<'_, MinecraftConnection<TcpStream>> {
self.conn.write().unwrap()
}
}
pub trait Listener: Sync + Send {
fn on_status_priority(&self) -> i8 { 0 }
fn on_status(&self, _: Arc<ClientContext>, _: &mut String) -> Result<(), ServerError> { Ok(()) }
}
pub trait PacketHandler: Sync + Send {
fn on_incoming_packet_priority(&self) -> i8 { 0 }
fn on_incoming_packet(&self, _: Arc<ClientContext>, _: &mut Packet, _: ConnectionState) -> Result<(), ServerError> { Ok(()) }
fn on_outcoming_packet_priority(&self) -> i8 { 0 }
fn on_outcoming_packet(&self, _: Arc<ClientContext>, _: &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<Self>) -> ProtocolHelper {
ProtocolHelper::new(self.clone())
}
}

View File

@ -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<Vec<u8>, ServerError> {
fastnbt::to_bytes(&self)
.map_err(|_| ServerError::SerTextComponent)
}
pub fn from_nbt(bytes: &[u8]) -> Result<TextComponent, ServerError> {
fastnbt::from_bytes(bytes)
.map_err(|_| ServerError::DeTextComponent)
}
pub fn as_json(self) -> Result<String, ServerError> {
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<T>: DataReader {
fn read_nbt(&mut self) -> Result<T, ServerError>;
fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>;
}
impl ReadWriteNBT<TextComponent> for Packet {
fn read_nbt(&mut self) -> Result<TextComponent, ServerError> {
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<String>,

101
src/event.rs Normal file
View File

@ -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 [<on_ $name _priority>](&self) -> i8 {
0
}
fn [<on_ $name>](&self, _: std::sync::Arc<ClientContext> $(, _: $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.[<on_ $bound _packet_priority>]()
).iter() {
handler.[<on_ $bound _packet>]($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.[<on_ $event _priority>]()
).iter() {
handler.[<on_ $event>](
$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
}

View File

@ -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

View File

@ -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,
@ -26,3 +31,103 @@ pub struct PlayerInfo {
pub name: String,
pub uuid: Uuid
}
pub struct ProtocolHelper {
client: Arc<ClientContext>,
state: ConnectionState
}
impl ProtocolHelper {
pub fn new(client: Arc<ClientContext>) -> 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<Option<Vec<u8>>, 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<Vec<u8>>), 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(())
}
}