219 lines
9.5 KiB
Rust
219 lines
9.5 KiB
Rust
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(())
|
||
}
|