rust_minecraft_server/src/server/protocol.rs
2025-05-02 19:26:43 +03:00

219 lines
9.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(())
}