From 86e519ed63a81ba9c60d0148631931822a150c41 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 1 May 2025 16:42:12 +0300 Subject: [PATCH] rewrite on rust mc proto --- Cargo.lock | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 +- src/d.rs | 45 ---------------- src/data.rs | 89 +++---------------------------- src/main.rs | 132 ++++++++++++++++++++++++++++++++++++---------- src/pohuy.rs | 5 ++ 6 files changed, 265 insertions(+), 157 deletions(-) delete mode 100644 src/d.rs create mode 100644 src/pohuy.rs diff --git a/Cargo.lock b/Cargo.lock index fc8e05c..58003be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,152 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust_mc_proto" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734168f5b9aef1991db4b11c0bcd71c0336b0a5ba98269f0df39b41b8463ac8c" +dependencies = [ + "flate2", + "uuid", +] + [[package]] name = "rust_minecraft_server" version = "0.1.0" +dependencies = [ + "rust_mc_proto", + "serde", + "serde_json", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" diff --git a/Cargo.toml b/Cargo.toml index 5f154ec..570035b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,7 @@ name = "rust_minecraft_server" version = "0.1.0" edition = "2024" -[dependencies] \ No newline at end of file +[dependencies] +rust_mc_proto = "0.1.19" +serde = "1.0.219" # used in text component +serde_json = "1.0.140" diff --git a/src/d.rs b/src/d.rs deleted file mode 100644 index e50c356..0000000 --- a/src/d.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::ops::Index; - - - - - - - - -pub enum BufferError { - EndOfBuffer -} - -pub struct Buffer { - bytes: Vec, - index: usize -} - -impl Buffer { - pub fn new(bytes: Vec, index: usize) -> Self { - Buffer { bytes, index } - } - - pub fn read(&self, size: usize) -> Result, BufferError> { - if self.index + size >= self.bytes.len() {return Err(BufferError::EndOfBuffer);} - // self.index += size; - Ok(self.bytes[self.index..self.index+size-1].to_vec()) - } - - pub fn read2(&mut self, size: usize) -> Result, BufferError> { - if self.index + size >= self.bytes.len() {return Err(BufferError::EndOfBuffer);} - self.index += size; - Ok(self.bytes[self.index..self.index+size-1].to_vec()) - } -} - -pub trait Sas { - fn ts(&mut self); -} - -impl Sas for Buffer { - fn ts(&mut self) { - self.index += 1; - } -} \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index e3367aa..f6a8c6f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,7 +1,6 @@ -use std::{io::Read, net::{SocketAddr, TcpListener, TcpStream}}; - - +use std::{error::Error, fmt::Display}; +#[derive(Debug)] pub enum ServerError { ReadPacketError, ConnectionClosedError, @@ -11,84 +10,10 @@ pub enum ServerError { PacketIsEnd } - - -pub struct Packet { - size: i32, - data: Vec +impl Display for ServerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{:?}", self)) + } } -impl Packet { - pub fn read_from(socket: &Socket) -> Result { - let (size, n) = socket.read_varint_size()?; - let data = socket.read((size - n as i32) as usize)?; - Ok(Packet { size, data }) - } -} - - - -pub struct Socket { - pub stream: TcpStream, - pub addr: SocketAddr -} - -impl Socket { - pub fn read(&self, size: usize) -> Result, ServerError>{ - let mut buf: Vec = vec![0; size]; - match (&self.stream).read(&mut buf) { - Ok(n) => if n == size { - Ok(buf) - } else if n == 0 { - Err(ServerError::ConnectionClosedError) - } else { - buf.truncate(n); - buf.append(&mut self.read(size-n)?); - Ok(buf) - }, - Err(_) => Err(ServerError::ReadError) - } - } - - pub fn read_varint_size(&self) -> Result<(i32, u8), ServerError>{ - let mut result = 0i32; - let mut offset = 0; - let mut byte: u8; - loop { - byte = self.read(1)?[0]; - result |= ((byte & 0x7F) << offset) as i32; - if (byte & 0x80) == 0 {break;}; - offset += 7; - if offset >= 32 {return Err(ServerError::VarIntIsTooBig)} - } - Ok((result, offset / 7)) - } - - pub fn read_varint(&self) -> Result{ - Ok(self.read_varint_size()?.0) - } -} - - - -pub struct Server { - listener: TcpListener -} - -impl Server { - pub fn new(addr: &str) -> Result { - match TcpListener::bind(addr) { - Ok(listener) => Ok(Server { listener }), - Err(_) => Err(ServerError::BindError) - } - } - - pub fn accept(&self) -> Socket { - loop { - match self.listener.accept() { - Ok((stream, addr)) => return Socket {stream, addr}, - Err(_) => continue - } - } - } -} \ No newline at end of file +impl Error for ServerError {} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 40c5fed..6aa4f1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,114 @@ -mod data; -use data::{Packet, Server, Socket}; +use std::{error::Error, io::{Read, Write}, net::TcpListener, thread, time::Duration}; -mod d; -use d::*; +use pohuy::Pohuy; +use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; -use std::thread; +pub mod pohuy; +pub mod data; -fn get_byte_size(i: i32) -> u8 { - for j in 1..4 { - if (i & -1 << (j * 7)) == 0 { - return j; - } - }; return 5; -} +// Сделать настройку хоста через конфиг +pub const HOST: &str = "127.0.0.1:25565"; fn main() { - println!("{}", get_byte_size(-2147483648)); + let Ok(server) = TcpListener::bind(HOST) else { + println!("Не удалось забиндить сервер на {}", HOST); + return; + }; - // let Ok(server) = Server::new("127.0.0.1:25565") else { - // println!("Не удалось забиндить сервер"); return; - // }; + println!("Сервер запущен на {}", HOST); - // loop { - // let socket = server.accept(); - // thread::spawn(move || { handle_connection(socket); }); - // } + while let Ok((stream, addr)) = server.accept() { + thread::spawn(move || { + println!("Подключение: {}", addr); + + // Установка таймаутов на чтение и запись + // По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг + stream.set_read_timeout(Some(Duration::from_secs(5))).pohuy(); + stream.set_write_timeout(Some(Duration::from_secs(5))).pohuy(); + + // Обработка подключения + // Если ошибка -> похуй + handle_connection(MinecraftConnection::new(&stream)).pohuy(); + + println!("Отключение: {}", addr); + }); + } } -fn handle_connection(socket: Socket) { - let Ok(packet) = Packet::read_from(&socket) else {return;}; - // пакет уже имеет свой размер (size) и данные (data) - // надо поместить пакет в очередь, обработать по шаблону и отдать обработчику +fn handle_connection( + mut conn: MinecraftConnection // Подключение +) -> Result<(), Box> { + // Чтение рукопожатия + let mut packet = conn.read_packet()?; - // fn on_keep_alive(socket: Socket, time: u64) { - // if time != self.time { - // socket.close() - // } - // } + if packet.id() != 0x00 { return Ok(()); } // Айди пакета не рукопожатное - выходим из функции + + 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 для обычного подключения + + match next_state { + 1 => { // Тип подключения - статус + loop { + // Чтение запроса + let packet = conn.read_packet()?; + + match packet.id() { + 0x00 => { // Запрос статуса + conn.write_packet(&Packet::build(0x00, |packet| { + // Отправка статуса + // В будущем это надо будет переделать чтобы это отправлялось через Listener'ы а не самим ядром сервера + // Хотя можно сделать и дефолтное значение через конфиг + packet.write_string(&format!( + // Пример статуса с дебаг-инфой + "{{ + \"version\": {{ + \"name\": \"1.21.5\", + \"protocol\": {protocol_version} + }}, + \"players\": {{ + \"max\": 100, + \"online\": 5, + \"sample\": [ + {{ + \"name\": \"thinkofdeath\", + \"id\": \"4566e69f-c907-48ee-8d71-d7ba5aa00d20\" + }} + ] + }}, + \"description\": {{ + \"text\": \"pv: {protocol_version}, sp: {server_port}\nsa: {server_address}\" + }}, + \"favicon\": \"data:image/png;base64,\", + \"enforcesSecureChat\": false + }}" + )) + })?)?; + }, + 0x01 => { // Пинг + conn.write_packet(&packet)?; + // Просто отправляем этот же пакет обратно + // ID такой-же, содержание тоже, так почему бы и нет? + }, + _ => { + break; + } + } + } + }, + 2 | 3 => { // Тип подключения - игра + // Отключение игрока с сообщением + // Заглушка так сказать + conn.write_packet(&Packet::build(0x00, |packet| { + packet.write_string("{\"text\": \"This server is in developement!!\", \"color\": \"red\", \"bold\": true}") + })?)?; + + // TODO: Чтение Configuration (возможно с примешиванием Listener'ов) + // TODO: Обработчик пакетов Play (тоже трейт), который уже будет дергать Listener'ы + }, + _ => {} // Тип подключения не рукопожатный + } + + Ok(()) } diff --git a/src/pohuy.rs b/src/pohuy.rs new file mode 100644 index 0000000..d1a3902 --- /dev/null +++ b/src/pohuy.rs @@ -0,0 +1,5 @@ +pub trait Pohuy: Sized { + fn pohuy(self) -> () {} +} + +impl Pohuy for Result {} \ No newline at end of file