reorganize
This commit is contained in:
parent
1c3c3e0f63
commit
8b537f8339
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -384,6 +384,12 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ignore-result"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "665ff4dce8edd10d490641ccb78949832f1ddbff02c584fb1f85ab888fe0e50c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@ -719,6 +725,7 @@ dependencies = [
|
|||||||
"colog",
|
"colog",
|
||||||
"craftflow-nbt",
|
"craftflow-nbt",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
"ignore-result",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"palette",
|
"palette",
|
||||||
|
@ -18,3 +18,4 @@ log = "0.4.27"
|
|||||||
uuid = "1.16.0"
|
uuid = "1.16.0"
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
|
ignore-result = "0.2.0"
|
||||||
|
268
src/main.rs
268
src/main.rs
@ -1,21 +1,12 @@
|
|||||||
use std::{env::args, io::Read, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration};
|
use std::{env::args, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use config::Config;
|
|
||||||
use event::{ConnectionState, Listener, PacketHandler};
|
|
||||||
use context::{ClientContext, ServerContext};
|
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use player::{ClientInfo, Handshake, PlayerInfo};
|
use rust_mc_proto::Packet;
|
||||||
use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet};
|
use server::{
|
||||||
|
config::Config, context::ServerContext, data::text_component::TextComponent, event::{Listener, PacketHandler}, player::context::ClientContext, protocol::ConnectionState, start_server, ServerError
|
||||||
|
};
|
||||||
|
|
||||||
use data::{ServerError, TextComponent};
|
pub mod server;
|
||||||
use pohuy::Pohuy;
|
|
||||||
|
|
||||||
pub mod config;
|
|
||||||
pub mod data;
|
|
||||||
pub mod event;
|
|
||||||
pub mod context;
|
|
||||||
pub mod player;
|
|
||||||
pub mod pohuy;
|
|
||||||
|
|
||||||
|
|
||||||
struct ExampleListener;
|
struct ExampleListener;
|
||||||
@ -144,249 +135,6 @@ fn main() {
|
|||||||
// Бетонируем сервер контекст от изменений
|
// Бетонируем сервер контекст от изменений
|
||||||
let server = Arc::new(server);
|
let server = Arc::new(server);
|
||||||
|
|
||||||
// Биндим сервер где надо
|
// Запускаем сервер из специально отведенной под это дело функцией
|
||||||
let Ok(listener) = TcpListener::bind(&server.config.bind.host) else {
|
start_server(server);
|
||||||
error!("Не удалось забиндить сервер на {}", &server.config.bind.host);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("Сервер запущен на {}", &server.config.bind.host);
|
|
||||||
|
|
||||||
while let Ok((stream, addr)) = listener.accept() {
|
|
||||||
let server = server.clone();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
info!("Подключение: {}", addr);
|
|
||||||
|
|
||||||
// Установка таймаутов на чтение и запись
|
|
||||||
// По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг
|
|
||||||
stream.set_read_timeout(Some(Duration::from_secs(server.config.bind.timeout))).pohuy();
|
|
||||||
stream.set_write_timeout(Some(Duration::from_secs(server.config.bind.timeout))).pohuy();
|
|
||||||
|
|
||||||
// Оборачиваем стрим в майнкрафт конекшн лично для нашего удовольствия
|
|
||||||
let conn = MinecraftConnection::new(stream);
|
|
||||||
|
|
||||||
// Создаем контекст клиента
|
|
||||||
// Передавется во все листенеры и хандлеры чтобы определять именно этот клиент
|
|
||||||
let client = Arc::new(ClientContext::new(server.clone(), conn));
|
|
||||||
|
|
||||||
server.clients.insert(client.addr, client.clone());
|
|
||||||
|
|
||||||
// Обработка подключения
|
|
||||||
// Если ошибка -> выводим
|
|
||||||
match handle_connection(client.clone()) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(ServerError::ConnectionClosed) => {},
|
|
||||||
Err(error) => {
|
|
||||||
error!("Ошибка подключения: {error:?}");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
server.clients.remove(&client.addr);
|
|
||||||
|
|
||||||
info!("Отключение: {}", addr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_connection(
|
|
||||||
client: Arc<ClientContext>, // Контекст клиента
|
|
||||||
) -> Result<(), ServerError> {
|
|
||||||
// Чтение рукопожатия
|
|
||||||
// Получение пакетов производится через client.conn(),
|
|
||||||
// ВАЖНО: не помещать сам client.conn() в переменные,
|
|
||||||
// он должен сразу убиваться иначе соединение гдето задедлочится
|
|
||||||
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Handshake, incoming);
|
|
||||||
|
|
||||||
if packet.id() != 0x00 {
|
|
||||||
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия")));
|
|
||||||
} // Айди пакета не рукопожатное - выходим из функции
|
|
||||||
|
|
||||||
let protocol_version = packet.read_varint()?; // Получаем версия протокола, может быть отрицательным если наш клиент дэбил
|
|
||||||
let server_address = packet.read_string()?; // Получаем домен/адрес сервера к которому пытается подключиться клиент, например "play.example.com", а не айпи
|
|
||||||
let server_port = packet.read_unsigned_short()?; // Все тоже самое что и с адресом сервера и все потому же и за тем же
|
|
||||||
let next_state = packet.read_varint()?; // Тип подключения: 1 для получения статуса и пинга, 2 и 3 для обычного подключения
|
|
||||||
|
|
||||||
// debug!("protocol_version: {protocol_version}");
|
|
||||||
// debug!("server_address: {server_address}");
|
|
||||||
// debug!("server_port: {server_port}");
|
|
||||||
// debug!("next_state: {next_state}");
|
|
||||||
|
|
||||||
client.set_handshake(Handshake { protocol_version, server_address, server_port });
|
|
||||||
|
|
||||||
match next_state {
|
|
||||||
1 => { // Тип подключения - статус
|
|
||||||
client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Чтение запроса
|
|
||||||
let packet = trigger_packet!(client.conn().read_packet()?, client, Status, incoming);
|
|
||||||
|
|
||||||
match packet.id() {
|
|
||||||
0x00 => { // Запрос статуса
|
|
||||||
let mut packet = Packet::empty(0x00);
|
|
||||||
|
|
||||||
// Дефолтный статус
|
|
||||||
let mut status = "{
|
|
||||||
\"version\": {
|
|
||||||
\"name\": \"Error\",
|
|
||||||
\"protocol\": 0
|
|
||||||
},
|
|
||||||
\"description\": {\"text\": \"Internal server error\"}
|
|
||||||
}".to_string();
|
|
||||||
|
|
||||||
// Опрос всех листенеров
|
|
||||||
for listener in client.server.listeners( // Цикл по листенерам
|
|
||||||
|o| o.on_status_priority() // Сортировка по приоритетности
|
|
||||||
).iter() {
|
|
||||||
listener.on_status(client.clone(), &mut status)?; // Вызов метода листенера
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка статуса
|
|
||||||
packet.write_string(&status)?;
|
|
||||||
|
|
||||||
client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?;
|
|
||||||
},
|
|
||||||
0x01 => { // Пинг
|
|
||||||
client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?;
|
|
||||||
// Просто отправляем этот же пакет обратно
|
|
||||||
// ID такой-же, содержание тоже, так почему бы и нет?
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при чтении запросов статуса")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
2 => { // Тип подключения - игра
|
|
||||||
client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login
|
|
||||||
|
|
||||||
// Читаем пакет Login Start
|
|
||||||
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Login, incoming);
|
|
||||||
|
|
||||||
let name = packet.read_string()?;
|
|
||||||
let uuid = packet.read_uuid()?;
|
|
||||||
|
|
||||||
// debug!("name: {name}");
|
|
||||||
// debug!("uuid: {uuid}");
|
|
||||||
|
|
||||||
client.set_player_info(PlayerInfo { name: name.clone(), uuid: uuid.clone() });
|
|
||||||
|
|
||||||
if client.server.config.server.online_mode {
|
|
||||||
// TODO: encryption packets
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправляем пакет Set Compression если сжатие указано
|
|
||||||
if let Some(threshold) = client.server.config.server.compression_threshold {
|
|
||||||
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(&trigger_packet!(Packet::build(0x02, |p| {
|
|
||||||
p.write_uuid(&uuid)?;
|
|
||||||
p.write_string(&name)?;
|
|
||||||
p.write_varint(0)
|
|
||||||
})?, client, Login, outcoming))?;
|
|
||||||
|
|
||||||
let packet = trigger_packet!(client.conn().read_packet()?, client, Login, incoming);
|
|
||||||
|
|
||||||
if packet.id() != 0x03 {
|
|
||||||
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged")));
|
|
||||||
}
|
|
||||||
|
|
||||||
client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration
|
|
||||||
|
|
||||||
// Получение бренда клиента из Serverbound Plugin Message
|
|
||||||
// Identifier канала откуда берется бренд: minecraft:brand
|
|
||||||
let brand = loop {
|
|
||||||
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming);
|
|
||||||
|
|
||||||
if packet.id() == 0x02 { // Пакет Serverbound Plugin Message
|
|
||||||
let identifier = packet.read_string()?;
|
|
||||||
|
|
||||||
let mut data = Vec::new();
|
|
||||||
packet.get_mut().read_to_end(&mut data).unwrap();
|
|
||||||
|
|
||||||
if identifier == "minecraft:brand" {
|
|
||||||
break String::from_utf8_lossy(&data).to_string();
|
|
||||||
} else {
|
|
||||||
error!("unknown plugin message channel: {}", identifier);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Serverbound Plugin Message")));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// debug!("brand: {brand}");
|
|
||||||
|
|
||||||
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming);
|
|
||||||
|
|
||||||
// Пакет Client Information
|
|
||||||
if packet.id() != 0x00 {
|
|
||||||
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Client Information")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let locale = packet.read_string()?; // for example: ru_RU
|
|
||||||
let view_distance = packet.read_signed_byte()?; // client-side render distance in chunks
|
|
||||||
let chat_mode = packet.read_varint()?; // 0: enabled, 1: commands only, 2: hidden. See Chat#Client chat mode for more information.
|
|
||||||
let chat_colors = packet.read_boolean()?; // this settings does nothing on client but can be used on serverside
|
|
||||||
let displayed_skin_parts = packet.read_byte()?; // bit mask https://minecraft.wiki/w/Java_Edition_protocol#Client_Information_(configuration)
|
|
||||||
let main_hand = packet.read_varint()?; // 0 for left and 1 for right
|
|
||||||
let enable_text_filtering = packet.read_boolean()?; // filtering text for profanity, always false for offline mode
|
|
||||||
let allow_server_listings = packet.read_boolean()?; // allows showing player in server listings in status
|
|
||||||
let particle_status = packet.read_varint()?; // 0 for all, 1 for decreased, 2 for minimal
|
|
||||||
|
|
||||||
// debug!("locale: {locale}");
|
|
||||||
// debug!("view_distance: {view_distance}");
|
|
||||||
// debug!("chat_mode: {chat_mode}");
|
|
||||||
// debug!("chat_colors: {chat_colors}");
|
|
||||||
// debug!("displayed_skin_parts: {displayed_skin_parts}");
|
|
||||||
// debug!("main_hand: {main_hand}");
|
|
||||||
// debug!("enable_text_filtering: {enable_text_filtering}");
|
|
||||||
// debug!("allow_server_listings: {allow_server_listings}");
|
|
||||||
// debug!("particle_status: {particle_status}");
|
|
||||||
|
|
||||||
client.set_client_info(ClientInfo {
|
|
||||||
brand,
|
|
||||||
locale,
|
|
||||||
view_distance,
|
|
||||||
chat_mode,
|
|
||||||
chat_colors,
|
|
||||||
displayed_skin_parts,
|
|
||||||
main_hand,
|
|
||||||
enable_text_filtering,
|
|
||||||
allow_server_listings,
|
|
||||||
particle_status
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото
|
|
||||||
|
|
||||||
client.conn().write_packet(&trigger_packet!(Packet::empty(0x03), client, Configuration, outcoming))?;
|
|
||||||
|
|
||||||
let packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming);
|
|
||||||
|
|
||||||
if packet.id() != 0x03 {
|
|
||||||
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration")));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(&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
|
|
||||||
p.write_bytes(message.as_bytes())
|
|
||||||
})?, client, Play, outcoming))?;
|
|
||||||
|
|
||||||
// TODO: Сделать отправку пакетов Play
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(ServerError::UnknownPacket(format!("Неизвестный NextState при рукопожатии")));
|
|
||||||
} // Тип подключения не рукопожатный
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
pub trait Pohuy: Sized {
|
|
||||||
fn pohuy(self) -> () {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E> Pohuy for Result<T, E> {}
|
|
88
src/server/context.rs
Normal file
88
src/server/context.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::{config::Config, event::{Listener, PacketHandler}, player::context::ClientContext};
|
||||||
|
|
||||||
|
pub struct ServerContext {
|
||||||
|
pub config: Arc<Config>,
|
||||||
|
pub clients: DashMap<SocketAddr, Arc<ClientContext>>,
|
||||||
|
listeners: Vec<Box<dyn Listener>>,
|
||||||
|
handlers: Vec<Box<dyn PacketHandler>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerContext {
|
||||||
|
pub fn new(config: Arc<Config>) -> ServerContext {
|
||||||
|
ServerContext {
|
||||||
|
config,
|
||||||
|
listeners: Vec::new(),
|
||||||
|
handlers: Vec::new(),
|
||||||
|
clients: DashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_player_by_uuid(self: &Arc<Self>, uuid: Uuid) -> Option<Arc<ClientContext>> {
|
||||||
|
self.clients.iter()
|
||||||
|
.find(|o| {
|
||||||
|
let info = o.player_info();
|
||||||
|
if let Some(info) = info {
|
||||||
|
info.uuid == uuid
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|o| o.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_player_by_name(self: &Arc<Self>, name: &str) -> Option<Arc<ClientContext>> {
|
||||||
|
self.clients.iter()
|
||||||
|
.find(|o| {
|
||||||
|
let info = o.player_info();
|
||||||
|
if let Some(info) = info {
|
||||||
|
info.name == name
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|o| o.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn players(self: &Arc<Self>) -> Vec<Arc<ClientContext>> {
|
||||||
|
self.clients.iter()
|
||||||
|
.filter(|o| o.player_info().is_some())
|
||||||
|
.map(|o| o.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_packet_handler(&mut self, handler: Box<dyn PacketHandler>) {
|
||||||
|
self.handlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_listener(&mut self, listener: Box<dyn Listener>) {
|
||||||
|
self.listeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn packet_handlers<F, K>(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
sort_by: F
|
||||||
|
) -> Vec<&Box<dyn PacketHandler>>
|
||||||
|
where
|
||||||
|
K: Ord,
|
||||||
|
F: FnMut(&&Box<dyn PacketHandler>) -> K
|
||||||
|
{
|
||||||
|
self.handlers.iter().sorted_by_key(sort_by).collect_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listeners<F, K>(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
sort_by: F
|
||||||
|
) -> Vec<&Box<dyn Listener>>
|
||||||
|
where
|
||||||
|
K: Ord,
|
||||||
|
F: FnMut(&&Box<dyn Listener>) -> K
|
||||||
|
{
|
||||||
|
self.listeners.iter().sorted_by_key(sort_by).collect_vec()
|
||||||
|
}
|
||||||
|
}
|
2
src/server/data/mod.rs
Normal file
2
src/server/data/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod text_component;
|
||||||
|
|
@ -1,44 +1,14 @@
|
|||||||
use std::{error::Error, fmt::Display, io::Read};
|
use std::io::Read;
|
||||||
use palette::{Hsl, IntoColor, Srgb};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use rust_mc_proto::{DataReader, Packet, ProtocolError};
|
use palette::{Hsl, IntoColor, Srgb};
|
||||||
|
use rust_mc_proto::{DataReader, Packet};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
|
|
||||||
// Ошибки сервера
|
use crate::server::ServerError;
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ServerError {
|
|
||||||
UnknownPacket(String),
|
|
||||||
Protocol(ProtocolError),
|
|
||||||
ConnectionClosed,
|
|
||||||
SerTextComponent,
|
|
||||||
DeTextComponent,
|
|
||||||
UnexpectedState
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ServerError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(&format!("{:?}", self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ServerError {}
|
|
||||||
|
|
||||||
// Делаем чтобы ProtocolError мог переделываться в наш ServerError
|
|
||||||
impl From<ProtocolError> for ServerError {
|
|
||||||
fn from(error: ProtocolError) -> ServerError {
|
|
||||||
match error {
|
|
||||||
// Если просто закрыто соединение, переделываем в нашу ошибку этого
|
|
||||||
ProtocolError::ConnectionClosedError => {
|
|
||||||
ServerError::ConnectionClosed
|
|
||||||
},
|
|
||||||
// Все остальное просто засовываем в обертку
|
|
||||||
error => {
|
|
||||||
ServerError::Protocol(error)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
@ -112,28 +82,6 @@ impl Default for TextComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
pub struct TextComponentBuilder {
|
||||||
text: String,
|
text: String,
|
||||||
color: Option<String>,
|
color: Option<String>,
|
||||||
@ -212,3 +160,25 @@ impl TextComponentBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use rust_mc_proto::Packet;
|
use rust_mc_proto::Packet;
|
||||||
|
|
||||||
use crate::{context::ClientContext, data::ServerError};
|
use super::protocol::ConnectionState;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! generate_handlers {
|
macro_rules! generate_handlers {
|
||||||
@ -10,7 +10,7 @@ macro_rules! generate_handlers {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn [<on_ $name>](&self, _: std::sync::Arc<ClientContext> $(, _: $arg_ty)*) -> Result<(), ServerError> {
|
fn [<on_ $name>](&self, _: std::sync::Arc<crate::server::player::context::ClientContext> $(, _: $arg_ty)*) -> Result<(), crate::server::ServerError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ macro_rules! trigger_packet {
|
|||||||
for handler in $client.server.packet_handlers(
|
for handler in $client.server.packet_handlers(
|
||||||
|o| o.[<on_ $bound _packet_priority>]()
|
|o| o.[<on_ $bound _packet_priority>]()
|
||||||
).iter() {
|
).iter() {
|
||||||
handler.[<on_ $bound _packet>]($client.clone(), &mut packet, crate::event::ConnectionState::$state)?;
|
handler.[<on_ $bound _packet>]($client.clone(), &mut packet, crate::server::protocol::ConnectionState::$state)?;
|
||||||
}
|
}
|
||||||
packet.get_mut().set_position(0);
|
packet.get_mut().set_position(0);
|
||||||
packet
|
packet
|
||||||
@ -90,12 +90,3 @@ pub trait PacketHandler: Sync + Send {
|
|||||||
generate_handlers!(outcoming_packet, &mut Packet, ConnectionState);
|
generate_handlers!(outcoming_packet, &mut Packet, ConnectionState);
|
||||||
generate_handlers!(state, ConnectionState);
|
generate_handlers!(state, ConnectionState);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ConnectionState {
|
|
||||||
Handshake,
|
|
||||||
Status,
|
|
||||||
Login,
|
|
||||||
Configuration,
|
|
||||||
Play
|
|
||||||
}
|
|
96
src/server/mod.rs
Normal file
96
src/server/mod.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use std::{error::Error, fmt::Display, net::TcpListener, sync::Arc, thread, time::Duration};
|
||||||
|
|
||||||
|
use context::ServerContext;
|
||||||
|
use ignore_result::Ignore;
|
||||||
|
use log::{error, info};
|
||||||
|
use player::context::ClientContext;
|
||||||
|
use protocol::handle_connection;
|
||||||
|
use rust_mc_proto::{MinecraftConnection, ProtocolError};
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod data;
|
||||||
|
pub mod event;
|
||||||
|
pub mod player;
|
||||||
|
pub mod context;
|
||||||
|
pub mod protocol;
|
||||||
|
|
||||||
|
// Ошибки сервера
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ServerError {
|
||||||
|
UnknownPacket(String),
|
||||||
|
Protocol(ProtocolError),
|
||||||
|
ConnectionClosed,
|
||||||
|
SerTextComponent,
|
||||||
|
DeTextComponent,
|
||||||
|
UnexpectedState
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ServerError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&format!("{:?}", self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ServerError {}
|
||||||
|
|
||||||
|
// Делаем чтобы ProtocolError мог переделываться в наш ServerError
|
||||||
|
impl From<ProtocolError> for ServerError {
|
||||||
|
fn from(error: ProtocolError) -> ServerError {
|
||||||
|
match error {
|
||||||
|
// Если просто закрыто соединение, переделываем в нашу ошибку этого
|
||||||
|
ProtocolError::ConnectionClosedError => {
|
||||||
|
ServerError::ConnectionClosed
|
||||||
|
},
|
||||||
|
// Все остальное просто засовываем в обертку
|
||||||
|
error => {
|
||||||
|
ServerError::Protocol(error)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_server(server: Arc<ServerContext>) {
|
||||||
|
// Биндим сервер где надо
|
||||||
|
let Ok(listener) = TcpListener::bind(&server.config.bind.host) else {
|
||||||
|
error!("Не удалось забиндить сервер на {}", &server.config.bind.host);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Сервер запущен на {}", &server.config.bind.host);
|
||||||
|
|
||||||
|
while let Ok((stream, addr)) = listener.accept() {
|
||||||
|
let server = server.clone();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
info!("Подключение: {}", addr);
|
||||||
|
|
||||||
|
// Установка таймаутов на чтение и запись
|
||||||
|
// По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг
|
||||||
|
stream.set_read_timeout(Some(Duration::from_secs(server.config.bind.timeout))).ignore();
|
||||||
|
stream.set_write_timeout(Some(Duration::from_secs(server.config.bind.timeout))).ignore();
|
||||||
|
|
||||||
|
// Оборачиваем стрим в майнкрафт конекшн лично для нашего удовольствия
|
||||||
|
let conn = MinecraftConnection::new(stream);
|
||||||
|
|
||||||
|
// Создаем контекст клиента
|
||||||
|
// Передавется во все листенеры и хандлеры чтобы определять именно этот клиент
|
||||||
|
let client = Arc::new(ClientContext::new(server.clone(), conn));
|
||||||
|
|
||||||
|
server.clients.insert(client.addr, client.clone());
|
||||||
|
|
||||||
|
// Обработка подключения
|
||||||
|
// Если ошибка -> выводим
|
||||||
|
match handle_connection(client.clone()) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(ServerError::ConnectionClosed) => {},
|
||||||
|
Err(error) => {
|
||||||
|
error!("Ошибка подключения: {error:?}");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
server.clients.remove(&client.addr);
|
||||||
|
|
||||||
|
info!("Отключение: {}", addr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,92 +1,12 @@
|
|||||||
use std::{hash::Hash, net::{SocketAddr, TcpStream}, sync::{Arc, RwLock, RwLockWriteGuard}};
|
use std::{hash::Hash, net::{SocketAddr, TcpStream}, sync::{Arc, RwLock, RwLockWriteGuard}};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use rust_mc_proto::MinecraftConnection;
|
use rust_mc_proto::MinecraftConnection;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{config::Config, data::ServerError, event::{ConnectionState, Listener, PacketHandler}, player::{ClientInfo, Handshake, PlayerInfo, ProtocolHelper}};
|
use crate::server::{context::ServerContext, protocol::ConnectionState, ServerError};
|
||||||
|
|
||||||
pub struct ServerContext {
|
use super::protocol::ProtocolHelper;
|
||||||
pub config: Arc<Config>,
|
|
||||||
pub clients: DashMap<SocketAddr, Arc<ClientContext>>,
|
|
||||||
listeners: Vec<Box<dyn Listener>>,
|
|
||||||
handlers: Vec<Box<dyn PacketHandler>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServerContext {
|
|
||||||
pub fn new(config: Arc<Config>) -> ServerContext {
|
|
||||||
ServerContext {
|
|
||||||
config,
|
|
||||||
listeners: Vec::new(),
|
|
||||||
handlers: Vec::new(),
|
|
||||||
clients: DashMap::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_player_by_uuid(self: &Arc<Self>, uuid: Uuid) -> Option<Arc<ClientContext>> {
|
|
||||||
self.clients.iter()
|
|
||||||
.find(|o| {
|
|
||||||
let info = o.player_info();
|
|
||||||
if let Some(info) = info {
|
|
||||||
info.uuid == uuid
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|o| o.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_player_by_name(self: &Arc<Self>, name: &str) -> Option<Arc<ClientContext>> {
|
|
||||||
self.clients.iter()
|
|
||||||
.find(|o| {
|
|
||||||
let info = o.player_info();
|
|
||||||
if let Some(info) = info {
|
|
||||||
info.name == name
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|o| o.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn players(self: &Arc<Self>) -> Vec<Arc<ClientContext>> {
|
|
||||||
self.clients.iter()
|
|
||||||
.filter(|o| o.player_info().is_some())
|
|
||||||
.map(|o| o.clone())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_packet_handler(&mut self, handler: Box<dyn PacketHandler>) {
|
|
||||||
self.handlers.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_listener(&mut self, listener: Box<dyn Listener>) {
|
|
||||||
self.listeners.push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn packet_handlers<F, K>(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
sort_by: F
|
|
||||||
) -> Vec<&Box<dyn PacketHandler>>
|
|
||||||
where
|
|
||||||
K: Ord,
|
|
||||||
F: FnMut(&&Box<dyn PacketHandler>) -> K
|
|
||||||
{
|
|
||||||
self.handlers.iter().sorted_by_key(sort_by).collect_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn listeners<F, K>(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
sort_by: F
|
|
||||||
) -> Vec<&Box<dyn Listener>>
|
|
||||||
where
|
|
||||||
K: Ord,
|
|
||||||
F: FnMut(&&Box<dyn Listener>) -> K
|
|
||||||
{
|
|
||||||
self.listeners.iter().sorted_by_key(sort_by).collect_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ClientContext {
|
pub struct ClientContext {
|
||||||
pub server: Arc<ServerContext>,
|
pub server: Arc<ServerContext>,
|
||||||
@ -176,3 +96,30 @@ impl ClientContext {
|
|||||||
ProtocolHelper::new(self.clone())
|
ProtocolHelper::new(self.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Handshake {
|
||||||
|
pub protocol_version: i32,
|
||||||
|
pub server_address: String,
|
||||||
|
pub server_port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ClientInfo {
|
||||||
|
pub brand: String,
|
||||||
|
pub locale: String,
|
||||||
|
pub view_distance: i8,
|
||||||
|
pub chat_mode: i32,
|
||||||
|
pub chat_colors: bool,
|
||||||
|
pub displayed_skin_parts: u8,
|
||||||
|
pub main_hand: i32,
|
||||||
|
pub enable_text_filtering: bool,
|
||||||
|
pub allow_server_listings: bool,
|
||||||
|
pub particle_status: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PlayerInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub uuid: Uuid
|
||||||
|
}
|
2
src/server/player/mod.rs
Normal file
2
src/server/player/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod context;
|
||||||
|
pub mod protocol;
|
@ -1,36 +1,11 @@
|
|||||||
use std::{io::Read, sync::Arc};
|
use std::{io::Read, sync::Arc};
|
||||||
|
|
||||||
use rust_mc_proto::{DataReader, DataWriter, Packet};
|
use rust_mc_proto::{DataReader, DataWriter, Packet};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{context::ClientContext, data::{ServerError, TextComponent, ReadWriteNBT}, event::ConnectionState};
|
use crate::server::{data::text_component::{ReadWriteNBT, TextComponent}, protocol::ConnectionState, ServerError};
|
||||||
|
|
||||||
#[derive(Clone)]
|
use super::context::ClientContext;
|
||||||
pub struct Handshake {
|
|
||||||
pub protocol_version: i32,
|
|
||||||
pub server_address: String,
|
|
||||||
pub server_port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ClientInfo {
|
|
||||||
pub brand: String,
|
|
||||||
pub locale: String,
|
|
||||||
pub view_distance: i8,
|
|
||||||
pub chat_mode: i32,
|
|
||||||
pub chat_colors: bool,
|
|
||||||
pub displayed_skin_parts: u8,
|
|
||||||
pub main_hand: i32,
|
|
||||||
pub enable_text_filtering: bool,
|
|
||||||
pub allow_server_listings: bool,
|
|
||||||
pub particle_status: i32
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PlayerInfo {
|
|
||||||
pub name: String,
|
|
||||||
pub uuid: Uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProtocolHelper {
|
pub struct ProtocolHelper {
|
||||||
client: Arc<ClientContext>,
|
client: Arc<ClientContext>,
|
218
src/server/protocol.rs
Normal file
218
src/server/protocol.rs
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
use std::{io::Read, sync::Arc};
|
||||||
|
|
||||||
|
use super::{player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, ServerError};
|
||||||
|
use log::error;
|
||||||
|
use rust_mc_proto::{DataReader, DataWriter, Packet};
|
||||||
|
|
||||||
|
use crate::trigger_packet;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ConnectionState {
|
||||||
|
Handshake,
|
||||||
|
Status,
|
||||||
|
Login,
|
||||||
|
Configuration,
|
||||||
|
Play
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_connection(
|
||||||
|
client: Arc<ClientContext>, // Контекст клиента
|
||||||
|
) -> Result<(), ServerError> {
|
||||||
|
// Чтение рукопожатия
|
||||||
|
// Получение пакетов производится через client.conn(),
|
||||||
|
// ВАЖНО: не помещать сам client.conn() в переменные,
|
||||||
|
// он должен сразу убиваться иначе соединение гдето задедлочится
|
||||||
|
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Handshake, incoming);
|
||||||
|
|
||||||
|
if packet.id() != 0x00 {
|
||||||
|
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия")));
|
||||||
|
} // Айди пакета не рукопожатное - выходим из функции
|
||||||
|
|
||||||
|
let protocol_version = packet.read_varint()?; // Получаем версия протокола, может быть отрицательным если наш клиент дэбил
|
||||||
|
let server_address = packet.read_string()?; // Получаем домен/адрес сервера к которому пытается подключиться клиент, например "play.example.com", а не айпи
|
||||||
|
let server_port = packet.read_unsigned_short()?; // Все тоже самое что и с адресом сервера и все потому же и за тем же
|
||||||
|
let next_state = packet.read_varint()?; // Тип подключения: 1 для получения статуса и пинга, 2 и 3 для обычного подключения
|
||||||
|
|
||||||
|
// debug!("protocol_version: {protocol_version}");
|
||||||
|
// debug!("server_address: {server_address}");
|
||||||
|
// debug!("server_port: {server_port}");
|
||||||
|
// debug!("next_state: {next_state}");
|
||||||
|
|
||||||
|
client.set_handshake(Handshake { protocol_version, server_address, server_port });
|
||||||
|
|
||||||
|
match next_state {
|
||||||
|
1 => { // Тип подключения - статус
|
||||||
|
client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Чтение запроса
|
||||||
|
let packet = trigger_packet!(client.conn().read_packet()?, client, Status, incoming);
|
||||||
|
|
||||||
|
match packet.id() {
|
||||||
|
0x00 => { // Запрос статуса
|
||||||
|
let mut packet = Packet::empty(0x00);
|
||||||
|
|
||||||
|
// Дефолтный статус
|
||||||
|
let mut status = "{
|
||||||
|
\"version\": {
|
||||||
|
\"name\": \"Error\",
|
||||||
|
\"protocol\": 0
|
||||||
|
},
|
||||||
|
\"description\": {\"text\": \"Internal server error\"}
|
||||||
|
}".to_string();
|
||||||
|
|
||||||
|
// Опрос всех листенеров
|
||||||
|
for listener in client.server.listeners( // Цикл по листенерам
|
||||||
|
|o| o.on_status_priority() // Сортировка по приоритетности
|
||||||
|
).iter() {
|
||||||
|
listener.on_status(client.clone(), &mut status)?; // Вызов метода листенера
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправка статуса
|
||||||
|
packet.write_string(&status)?;
|
||||||
|
|
||||||
|
client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?;
|
||||||
|
},
|
||||||
|
0x01 => { // Пинг
|
||||||
|
client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?;
|
||||||
|
// Просто отправляем этот же пакет обратно
|
||||||
|
// ID такой-же, содержание тоже, так почему бы и нет?
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при чтении запросов статуса")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
2 => { // Тип подключения - игра
|
||||||
|
client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login
|
||||||
|
|
||||||
|
// Читаем пакет Login Start
|
||||||
|
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Login, incoming);
|
||||||
|
|
||||||
|
let name = packet.read_string()?;
|
||||||
|
let uuid = packet.read_uuid()?;
|
||||||
|
|
||||||
|
// debug!("name: {name}");
|
||||||
|
// debug!("uuid: {uuid}");
|
||||||
|
|
||||||
|
client.set_player_info(PlayerInfo { name: name.clone(), uuid: uuid.clone() });
|
||||||
|
|
||||||
|
if client.server.config.server.online_mode {
|
||||||
|
// TODO: encryption packets
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправляем пакет Set Compression если сжатие указано
|
||||||
|
if let Some(threshold) = client.server.config.server.compression_threshold {
|
||||||
|
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(&trigger_packet!(Packet::build(0x02, |p| {
|
||||||
|
p.write_uuid(&uuid)?;
|
||||||
|
p.write_string(&name)?;
|
||||||
|
p.write_varint(0)
|
||||||
|
})?, client, Login, outcoming))?;
|
||||||
|
|
||||||
|
let packet = trigger_packet!(client.conn().read_packet()?, client, Login, incoming);
|
||||||
|
|
||||||
|
if packet.id() != 0x03 {
|
||||||
|
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged")));
|
||||||
|
}
|
||||||
|
|
||||||
|
client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration
|
||||||
|
|
||||||
|
// Получение бренда клиента из Serverbound Plugin Message
|
||||||
|
// Identifier канала откуда берется бренд: minecraft:brand
|
||||||
|
let brand = loop {
|
||||||
|
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming);
|
||||||
|
|
||||||
|
if packet.id() == 0x02 { // Пакет Serverbound Plugin Message
|
||||||
|
let identifier = packet.read_string()?;
|
||||||
|
|
||||||
|
let mut data = Vec::new();
|
||||||
|
packet.get_mut().read_to_end(&mut data).unwrap();
|
||||||
|
|
||||||
|
if identifier == "minecraft:brand" {
|
||||||
|
break String::from_utf8_lossy(&data).to_string();
|
||||||
|
} else {
|
||||||
|
error!("unknown plugin message channel: {}", identifier);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Serverbound Plugin Message")));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// debug!("brand: {brand}");
|
||||||
|
|
||||||
|
let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming);
|
||||||
|
|
||||||
|
// Пакет Client Information
|
||||||
|
if packet.id() != 0x00 {
|
||||||
|
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Client Information")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let locale = packet.read_string()?; // for example: ru_RU
|
||||||
|
let view_distance = packet.read_signed_byte()?; // client-side render distance in chunks
|
||||||
|
let chat_mode = packet.read_varint()?; // 0: enabled, 1: commands only, 2: hidden. See Chat#Client chat mode for more information.
|
||||||
|
let chat_colors = packet.read_boolean()?; // this settings does nothing on client but can be used on serverside
|
||||||
|
let displayed_skin_parts = packet.read_byte()?; // bit mask https://minecraft.wiki/w/Java_Edition_protocol#Client_Information_(configuration)
|
||||||
|
let main_hand = packet.read_varint()?; // 0 for left and 1 for right
|
||||||
|
let enable_text_filtering = packet.read_boolean()?; // filtering text for profanity, always false for offline mode
|
||||||
|
let allow_server_listings = packet.read_boolean()?; // allows showing player in server listings in status
|
||||||
|
let particle_status = packet.read_varint()?; // 0 for all, 1 for decreased, 2 for minimal
|
||||||
|
|
||||||
|
// debug!("locale: {locale}");
|
||||||
|
// debug!("view_distance: {view_distance}");
|
||||||
|
// debug!("chat_mode: {chat_mode}");
|
||||||
|
// debug!("chat_colors: {chat_colors}");
|
||||||
|
// debug!("displayed_skin_parts: {displayed_skin_parts}");
|
||||||
|
// debug!("main_hand: {main_hand}");
|
||||||
|
// debug!("enable_text_filtering: {enable_text_filtering}");
|
||||||
|
// debug!("allow_server_listings: {allow_server_listings}");
|
||||||
|
// debug!("particle_status: {particle_status}");
|
||||||
|
|
||||||
|
client.set_client_info(ClientInfo {
|
||||||
|
brand,
|
||||||
|
locale,
|
||||||
|
view_distance,
|
||||||
|
chat_mode,
|
||||||
|
chat_colors,
|
||||||
|
displayed_skin_parts,
|
||||||
|
main_hand,
|
||||||
|
enable_text_filtering,
|
||||||
|
allow_server_listings,
|
||||||
|
particle_status
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото
|
||||||
|
|
||||||
|
client.conn().write_packet(&trigger_packet!(Packet::empty(0x03), client, Configuration, outcoming))?;
|
||||||
|
|
||||||
|
let packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming);
|
||||||
|
|
||||||
|
if packet.id() != 0x03 {
|
||||||
|
return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration")));
|
||||||
|
}
|
||||||
|
|
||||||
|
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(&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
|
||||||
|
p.write_bytes(message.as_bytes())
|
||||||
|
})?, client, Play, outcoming))?;
|
||||||
|
|
||||||
|
// TODO: Сделать отправку пакетов Play
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(ServerError::UnknownPacket(format!("Неизвестный NextState при рукопожатии")));
|
||||||
|
} // Тип подключения не рукопожатный
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user