From 86e519ed63a81ba9c60d0148631931822a150c41 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 1 May 2025 16:42:12 +0300 Subject: [PATCH 01/57] 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 From 2eb187a8c1d6d6ea347ca09b091c2689300c39ec Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 1 May 2025 17:01:34 +0300 Subject: [PATCH 02/57] respect server error --- src/data.rs | 30 +++++++++++++++++++++++------- src/main.rs | 18 ++++++++++++------ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/data.rs b/src/data.rs index f6a8c6f..269a3fa 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,13 +1,13 @@ use std::{error::Error, fmt::Display}; +use rust_mc_proto::ProtocolError; + +// Ошибки сервера #[derive(Debug)] pub enum ServerError { - ReadPacketError, - ConnectionClosedError, - ReadError, - BindError, - VarIntIsTooBig, - PacketIsEnd + UnknownPacket(String), + Protocol(ProtocolError), + ConnectionClosed } impl Display for ServerError { @@ -16,4 +16,20 @@ impl Display for ServerError { } } -impl Error for ServerError {} \ No newline at end of file +impl Error for ServerError {} + +// Делаем чтобы ProtocolError мог переделываться в наш ServerError +impl From for ServerError { + fn from(error: ProtocolError) -> ServerError { + match error { + // Если просто закрыто соединение, пеерделываем в нашу ошибку этого + ProtocolError::ConnectionClosedError => { + ServerError::ConnectionClosed + }, + // Все остальное просто засовываем в обертку + error => { + ServerError::Protocol(error) + }, + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 6aa4f1b..12678fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ -use std::{error::Error, io::{Read, Write}, net::TcpListener, thread, time::Duration}; +use std::{io::{Read, Write}, net::TcpListener, thread, time::Duration}; -use pohuy::Pohuy; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; +use data::ServerError; +use pohuy::Pohuy; + pub mod pohuy; pub mod data; @@ -37,11 +39,13 @@ fn main() { fn handle_connection( mut conn: MinecraftConnection // Подключение -) -> Result<(), Box> { +) -> Result<(), ServerError> { // Чтение рукопожатия let mut packet = conn.read_packet()?; - if packet.id() != 0x00 { return Ok(()); } // Айди пакета не рукопожатное - выходим из функции + if packet.id() != 0x00 { + return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); + } // Айди пакета не рукопожатное - выходим из функции let protocol_version = packet.read_varint()?; // Получаем версия протокола, может быть отрицательным если наш клиент дэбил let server_address = packet.read_string()?; // Получаем домен/адрес сервера к которому пытается подключиться клиент, например "play.example.com", а не айпи @@ -92,7 +96,7 @@ fn handle_connection( // ID такой-же, содержание тоже, так почему бы и нет? }, _ => { - break; + return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при чтении запросов статуса"))); } } } @@ -107,7 +111,9 @@ fn handle_connection( // TODO: Чтение Configuration (возможно с примешиванием Listener'ов) // TODO: Обработчик пакетов Play (тоже трейт), который уже будет дергать Listener'ы }, - _ => {} // Тип подключения не рукопожатный + _ => { + return Err(ServerError::UnknownPacket(format!("Неизвестный NextState при рукопожатии"))); + } // Тип подключения не рукопожатный } Ok(()) From 3890a6ddeeed46072f23ba2771ad307c794323d5 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 1 May 2025 17:48:26 +0300 Subject: [PATCH 03/57] text component --- Cargo.lock | 434 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- src/data.rs | 163 +++++++++++++++++++- src/main.rs | 92 ++++++----- 4 files changed, 653 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58003be..9e158fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,73 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "cc" +version = "1.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crc32fast" version = "1.4.2" @@ -23,6 +84,57 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "flate2" version = "1.1.1" @@ -33,12 +145,110 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", + "serde", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "memchr" version = "2.7.4" @@ -54,6 +264,33 @@ dependencies = [ "adler2", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -89,8 +326,15 @@ dependencies = [ "rust_mc_proto", "serde", "serde_json", + "serde_with", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.20" @@ -129,6 +373,48 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.101" @@ -140,6 +426,37 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -151,3 +468,120 @@ name = "uuid" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 570035b..16f3fdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ edition = "2024" [dependencies] rust_mc_proto = "0.1.19" -serde = "1.0.219" # used in text component +serde = { version = "1.0.219", features = ["derive"] } # used in text component serde_json = "1.0.140" +serde_with = { version = "3.12.0", features = ["macros"] } \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index 269a3fa..8c0e0a0 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,13 +1,17 @@ use std::{error::Error, fmt::Display}; +use serde::{Deserialize, Serialize}; -use rust_mc_proto::ProtocolError; +use rust_mc_proto::{DataReader, DataWriter, ProtocolError}; +use serde_with::skip_serializing_none; // Ошибки сервера #[derive(Debug)] pub enum ServerError { UnknownPacket(String), Protocol(ProtocolError), - ConnectionClosed + ConnectionClosed, + SerTextComponent, + DeTextComponent } impl Display for ServerError { @@ -22,7 +26,7 @@ impl Error for ServerError {} impl From for ServerError { fn from(error: ProtocolError) -> ServerError { match error { - // Если просто закрыто соединение, пеерделываем в нашу ошибку этого + // Если просто закрыто соединение, переделываем в нашу ошибку этого ProtocolError::ConnectionClosedError => { ServerError::ConnectionClosed }, @@ -32,4 +36,157 @@ impl From for ServerError { }, } } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[skip_serializing_none] +pub struct TextComponent { + pub text: String, + pub color: Option, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, + pub extra: Option>, +} + +impl TextComponent { + pub fn builder() -> TextComponentBuilder { + TextComponentBuilder::new() + } + + pub fn to_string(self) -> Result { + self.try_into() + } + + pub fn from_string(text: String) -> Result { + Self::try_from(text) + } +} + +pub trait WriteTextComponent { + fn write_text_component(&mut self, component: &TextComponent) -> Result<(), ServerError>; +} + +impl WriteTextComponent for T { + fn write_text_component(&mut self, component: &TextComponent) -> Result<(), ServerError> { + Ok(self.write_string(TryInto::::try_into(component.clone())?.as_str())?) + } +} + +pub trait ReadTextComponent { + fn read_text_component(&mut self) -> Result; +} + +impl ReadTextComponent for T { + fn read_text_component(&mut self) -> Result { + TextComponent::try_from(self.read_string()?) + } +} + +impl TryInto for TextComponent { + type Error = ServerError; + + fn try_into(self) -> Result { + serde_json::to_string(&self) + .map_err(|_| ServerError::SerTextComponent) + } +} + +impl TryFrom for TextComponent { + type Error = ServerError; + + fn try_from(value: String) -> Result { + serde_json::from_str(&value) + .map_err(|_| ServerError::DeTextComponent) + } +} + +impl TryFrom<&str> for TextComponent { + type Error = ServerError; + + fn try_from(value: &str) -> Result { + serde_json::from_str(&value) + .map_err(|_| ServerError::DeTextComponent) + } +} + +pub struct TextComponentBuilder { + text: String, + color: Option, + bold: Option, + italic: Option, + underlined: Option, + strikethrough: Option, + obfuscated: Option, + extra: Option>, +} + +impl TextComponentBuilder { + pub fn new() -> Self { + Self { + text: String::new(), + color: None, + bold: None, + italic: None, + underlined: None, + strikethrough: None, + obfuscated: None, + extra: None, + } + } + + pub fn text(mut self, text: String) -> Self { + self.text = text; + self + } + + pub fn color(mut self, color: String) -> Self { + self.color = Some(color); + self + } + + pub fn bold(mut self, bold: bool) -> Self { + self.bold = Some(bold); + self + } + + pub fn italic(mut self, italic: bool) -> Self { + self.italic = Some(italic); + self + } + + pub fn underlined(mut self, underlined: bool) -> Self { + self.underlined = Some(underlined); + self + } + + pub fn strikethrough(mut self, strikethrough: bool) -> Self { + self.strikethrough = Some(strikethrough); + self + } + + pub fn obfuscated(mut self, obfuscated: bool) -> Self { + self.obfuscated = Some(obfuscated); + self + } + + pub fn extra(mut self, extra: Vec) -> Self { + self.extra = Some(extra); + self + } + + pub fn build(self) -> TextComponent { + TextComponent { + text: self.text, + color: self.color, + bold: self.bold, + italic: self.italic, + underlined: self.underlined, + strikethrough: self.strikethrough, + obfuscated: self.obfuscated, + extra: self.extra + } + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 12678fa..2a1c6ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::{io::{Read, Write}, net::TcpListener, thread, time::Duration}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; -use data::ServerError; +use data::{ServerError, TextComponent}; use pohuy::Pohuy; pub mod pohuy; @@ -29,8 +29,13 @@ fn main() { stream.set_write_timeout(Some(Duration::from_secs(5))).pohuy(); // Обработка подключения - // Если ошибка -> похуй - handle_connection(MinecraftConnection::new(&stream)).pohuy(); + // Если ошибка -> выводим + match handle_connection(MinecraftConnection::new(&stream)) { + Ok(_) => {}, + Err(error) => { + println!("Ошибка подключения: {error:?}"); + }, + }; println!("Отключение: {}", addr); }); @@ -60,35 +65,45 @@ fn handle_connection( 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 - }}" - )) - })?)?; + let mut packet = Packet::empty(0x00); + + // Отправка статуса + // В будущем это надо будет переделать чтобы это отправлялось через 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\": {}, + \"favicon\": \"data:image/png;base64,\", + \"enforcesSecureChat\": false + }}", + + // В MOTD пихаем дебаг инфу + TextComponent::builder() + .text(format!("pv: {protocol_version}, sp: {server_port}\nsa: {server_address}")) + .color("red".to_string()) + .bold(true) + .italic(true) + .underlined(true) + .build() + .to_string()? + ))?; + + conn.write_packet(&packet)?; }, 0x01 => { // Пинг conn.write_packet(&packet)?; @@ -104,9 +119,16 @@ fn handle_connection( 2 | 3 => { // Тип подключения - игра // Отключение игрока с сообщением // Заглушка так сказать - conn.write_packet(&Packet::build(0x00, |packet| { - packet.write_string("{\"text\": \"This server is in developement!!\", \"color\": \"red\", \"bold\": true}") - })?)?; + let mut packet = Packet::empty(0x00); + + packet.write_string(&TextComponent::builder() + .text(format!("This server is in developement!!")) + .color("gold".to_string()) + .bold(true) + .build() + .to_string()?)?; + + conn.write_packet(&packet)?; // TODO: Чтение Configuration (возможно с примешиванием Listener'ов) // TODO: Обработчик пакетов Play (тоже трейт), который уже будет дергать Listener'ы From 103b8314f7f5d6c9b571a7bf61c885340dd642c7 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 1 May 2025 18:39:51 +0300 Subject: [PATCH 04/57] config toml --- .gitignore | 1 + Cargo.lock | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +++-- src/config.rs | 31 ++++++++++++++++++++++ src/main.rs | 51 +++++++++++++++++++++++++++-------- 5 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 src/config.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..c46d3d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +server.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9e158fe..24ef358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,8 +325,10 @@ version = "0.1.0" dependencies = [ "rust_mc_proto", "serde", + "serde_default", "serde_json", "serde_with", + "toml", ] [[package]] @@ -350,6 +352,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_default" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486b028b311aaaea83e0ba65a3e6e3cbef381e74e9d0bd6263faefd1fb503c1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -373,6 +387,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_with" version = "3.12.0" @@ -457,6 +480,47 @@ dependencies = [ "time-core", ] +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -585,3 +649,12 @@ checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] + +[[package]] +name = "winnow" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 16f3fdb..23348d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ edition = "2024" [dependencies] rust_mc_proto = "0.1.19" -serde = { version = "1.0.219", features = ["derive"] } # used in text component +serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" -serde_with = { version = "3.12.0", features = ["macros"] } \ No newline at end of file +serde_with = { version = "3.12.0", features = ["macros"] } +serde_default = "0.2.0" +toml = "0.8.22" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..404fd73 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,31 @@ +use std::{fs, path::PathBuf}; + +use serde::{Deserialize, Serialize}; +use serde_default::DefaultFromSerde; + + +#[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] +pub struct ServerConfig { + /// Хост где забиндить сервер + #[serde(default = "default_host")] pub host: String, + + /// Таймаут подключения в секундах + #[serde(default = "default_timeout")] pub timeout: u64, +} + +fn default_host() -> String { return "127.0.0.1:25565".to_string(); } +fn default_timeout() -> u64 { return 5; } + +impl ServerConfig { + pub fn load_from_file(path: PathBuf) -> Option { + if !fs::exists(&path).unwrap_or_default() { + let table = ServerConfig::default(); + fs::create_dir_all(&path.parent()?).ok()?; + fs::write(&path, toml::to_string_pretty(&table).ok()?).ok()?; + return Some(table); + } + let content = fs::read_to_string(&path).ok()?; + let table = toml::from_str::(&content).ok()?; + Some(table) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2a1c6ad..75c9869 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,64 @@ -use std::{io::{Read, Write}, net::TcpListener, thread, time::Duration}; +use std::{env::args, io::{Read, Write}, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration}; +use config::ServerConfig; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; use data::{ServerError, TextComponent}; use pohuy::Pohuy; -pub mod pohuy; +pub mod config; pub mod data; - -// Сделать настройку хоста через конфиг -pub const HOST: &str = "127.0.0.1:25565"; +pub mod pohuy; fn main() { - let Ok(server) = TcpListener::bind(HOST) else { - println!("Не удалось забиндить сервер на {}", HOST); + // Получение аргументов + let exec = args().next().expect("Неизвестная система"); + let args = args().skip(1).collect::>(); + + if args.len() > 1 { + println!("Использование: {exec} [путь до файла конфигурации]"); + return; + } + + // Берем путь из аргумента либо по дефолту берем "./server.toml" + let config_path = PathBuf::from(args.get(0).unwrap_or(&"server.toml".to_string())); + + // Чтение конфига, если ошибка - выводим + let config = match ServerConfig::load_from_file(config_path) { + Some(config) => config, + None => { + println!("Ошибка чтения конфигурации"); + return; + }, + }; + + // Делаем немутабельную потокобезопасную ссылку на конфиг + // Впринципе можно и просто клонировать сам конфиг в каждый сука поток ебать того рот ебать блять + // но мы этого делать не будем чтобы не было мемори лик лишнего + let config = Arc::new(config); + + // Биндим сервер где надо + let Ok(server) = TcpListener::bind(&config.host) else { + println!("Не удалось забиндить сервер на {}", &config.host); return; }; - println!("Сервер запущен на {}", HOST); + println!("Сервер запущен на {}", &config.host); while let Ok((stream, addr)) = server.accept() { + let config = config.clone(); + 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(); + stream.set_read_timeout(Some(Duration::from_secs(config.timeout))).pohuy(); + stream.set_write_timeout(Some(Duration::from_secs(config.timeout))).pohuy(); // Обработка подключения // Если ошибка -> выводим - match handle_connection(MinecraftConnection::new(&stream)) { + match handle_connection(config, MinecraftConnection::new(&stream)) { Ok(_) => {}, Err(error) => { println!("Ошибка подключения: {error:?}"); @@ -43,6 +71,7 @@ fn main() { } fn handle_connection( + _: Arc, // Конфиг сервера (возможно будет использоаться в будущем) mut conn: MinecraftConnection // Подключение ) -> Result<(), ServerError> { // Чтение рукопожатия From f8684a0402a8cf7e900bb60fecffa76b63f836dd Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 1 May 2025 21:13:47 +0300 Subject: [PATCH 05/57] listeners and handlers --- Cargo.lock | 16 +++++ Cargo.toml | 1 + src/config.rs | 4 +- src/context.rs | 118 ++++++++++++++++++++++++++++++++++ src/data.rs | 8 +-- src/main.rs | 171 ++++++++++++++++++++++++++++++++++--------------- 6 files changed, 261 insertions(+), 57 deletions(-) create mode 100644 src/context.rs diff --git a/Cargo.lock b/Cargo.lock index 24ef358..f7dc68a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,12 @@ dependencies = [ "serde", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -221,6 +227,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -323,6 +338,7 @@ dependencies = [ name = "rust_minecraft_server" version = "0.1.0" dependencies = [ + "itertools", "rust_mc_proto", "serde", "serde_default", diff --git a/Cargo.toml b/Cargo.toml index 23348d1..cf60f6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ serde_json = "1.0.140" serde_with = { version = "3.12.0", features = ["macros"] } serde_default = "0.2.0" toml = "0.8.22" +itertools = "0.14.0" diff --git a/src/config.rs b/src/config.rs index 404fd73..c8ededa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,8 +13,8 @@ pub struct ServerConfig { #[serde(default = "default_timeout")] pub timeout: u64, } -fn default_host() -> String { return "127.0.0.1:25565".to_string(); } -fn default_timeout() -> u64 { return 5; } +fn default_host() -> String { "127.0.0.1:25565".to_string() } +fn default_timeout() -> u64 { 5 } impl ServerConfig { pub fn load_from_file(path: PathBuf) -> Option { diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..4269a53 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,118 @@ +use std::{net::{SocketAddr, TcpStream}, sync::{atomic::{AtomicI32, AtomicU16, Ordering}, Arc, RwLock, RwLockWriteGuard}}; + +use itertools::Itertools; +use rust_mc_proto::{MinecraftConnection, Packet}; + +use crate::{config::ServerConfig, data::ServerError}; + +pub struct ServerContext { + pub config: Arc, + listeners: Vec>, + handlers: Vec> +} + +impl ServerContext { + pub fn new(config: Arc) -> ServerContext { + ServerContext { + config, + listeners: Vec::new(), + handlers: Vec::new() + } + } + + pub fn add_packet_handler(&mut self, handler: Box) { + self.handlers.push(handler); + } + + pub fn add_listener(&mut self, listener: Box) { + self.listeners.push(listener); + } + + pub fn packet_handlers( + self: &Arc, + sort_by: F + ) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K + { + self.handlers.iter().sorted_by_key(sort_by).collect_vec() + } + + pub fn listeners( + self: &Arc, + sort_by: F + ) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K + { + self.listeners.iter().sorted_by_key(sort_by).collect_vec() + } +} + +pub struct ClientContext { + pub server: Arc, + pub conn: RwLock>, + pub addr: SocketAddr, + protocol_version: AtomicI32, + server_address: RwLock, + server_port: AtomicU16, +} + +impl ClientContext { + pub fn new( + server: Arc, + conn: MinecraftConnection + ) -> ClientContext { + ClientContext { + server, + addr: conn.get_ref().peer_addr().unwrap(), + conn: RwLock::new(conn), + protocol_version: AtomicI32::default(), + server_address: RwLock::new(String::new()), + server_port: AtomicU16::default() + } + } + + pub fn handshake( + self: &Arc, + protocol_version: i32, + server_address: String, + server_port: u16 + ) -> () { + self.protocol_version.store(protocol_version, Ordering::SeqCst); + self.server_port.store(server_port, Ordering::SeqCst); + *self.server_address.write().unwrap() = server_address; + } + + pub fn protocol_version(self: &Arc) -> i32 { + self.protocol_version.load(Ordering::SeqCst) + } + + pub fn server_port(self: &Arc) -> u16 { + self.server_port.load(Ordering::SeqCst) + } + + pub fn server_address(self: &Arc) -> String { + self.server_address.read().unwrap().clone() + } + + pub fn conn(self: &Arc) -> RwLockWriteGuard<'_, MinecraftConnection> { + self.conn.write().unwrap() + } +} + +pub trait Listener: Sync + Send { + fn on_status_priority(&self) -> i8 { 0 } + fn on_status(&self, _: Arc, _: &mut String) -> Result<(), ServerError> { Ok(()) } +} + +pub trait PacketHandler: Sync + Send { + fn on_incoming_packet_priority(&self) -> i8 { 0 } + fn on_incoming_packet(&self, _: Arc, _: &mut Packet) -> Result<(), ServerError> { Ok(()) } + + fn on_outcoming_packet_priority(&self) -> i8 { 0 } + fn on_outcoming_packet(&self, _: Arc, _: &mut Packet) -> Result<(), ServerError> { Ok(()) } +} + diff --git a/src/data.rs b/src/data.rs index 8c0e0a0..19d148b 100644 --- a/src/data.rs +++ b/src/data.rs @@ -137,13 +137,13 @@ impl TextComponentBuilder { } } - pub fn text(mut self, text: String) -> Self { - self.text = text; + pub fn text(mut self, text: &str) -> Self { + self.text = text.to_string(); self } - pub fn color(mut self, color: String) -> Self { - self.color = Some(color); + pub fn color(mut self, color: &str) -> Self { + self.color = Some(color.to_string()); self } diff --git a/src/main.rs b/src/main.rs index 75c9869..269c4c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use std::{env::args, io::{Read, Write}, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration}; use config::ServerConfig; +use context::{ClientContext, Listener, PacketHandler, ServerContext}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; use data::{ServerError, TextComponent}; @@ -8,8 +9,72 @@ use pohuy::Pohuy; pub mod config; pub mod data; +pub mod context; pub mod pohuy; + +struct ExampleListener; + +impl Listener for ExampleListener { + fn on_status(&self, client: Arc, response: &mut String) -> Result<(), ServerError> { + *response = format!( + "{{ + \"version\": {{ + \"name\": \"idk\", + \"protocol\": {} + }}, + \"players\": {{ + \"max\": 100, + \"online\": 42, + \"sample\": [ + {{ + \"name\": \"Жопа\", + \"id\": \"00000000-0000-0000-0000-000000000000\" + }} + ] + }}, + \"description\": {}, + \"favicon\": \"data:image/png;base64,\", + \"enforcesSecureChat\": false + }}", + client.protocol_version(), + TextComponent::builder() + .text("Hello World! ") + .extra(vec![ + TextComponent::builder() + .text("Protocol: ") + .color("gold") + .extra(vec![ + TextComponent::builder() + .text(&client.protocol_version().to_string()) + .underlined(true) + .build() + ]) + .build(), + TextComponent::builder() + .text("\nServer Addr: ") + .color("green") + .extra(vec![ + TextComponent::builder() + .text(&format!("{}:{}", client.server_address(), client.server_port())) + .underlined(true) + .build() + ]) + .build() + ]) + .build() + .to_string()? + ); + + Ok(()) + } +} + +struct ExamplePacketHandler; + +impl PacketHandler for ExamplePacketHandler {} + + fn main() { // Получение аргументов let exec = args().next().expect("Неизвестная система"); @@ -37,28 +102,45 @@ fn main() { // но мы этого делать не будем чтобы не было мемори лик лишнего let config = Arc::new(config); + // Создаем контекст сервера + // Передается во все подключения + let mut server = ServerContext::new(config); + + server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера + server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + + // Бетонируем сервер контекст от изменений + let server = Arc::new(server); + // Биндим сервер где надо - let Ok(server) = TcpListener::bind(&config.host) else { - println!("Не удалось забиндить сервер на {}", &config.host); + let Ok(listener) = TcpListener::bind(&server.config.host) else { + println!("Не удалось забиндить сервер на {}", &server.config.host); return; }; - println!("Сервер запущен на {}", &config.host); + println!("Сервер запущен на {}", &server.config.host); - while let Ok((stream, addr)) = server.accept() { - let config = config.clone(); + while let Ok((stream, addr)) = listener.accept() { + let server = server.clone(); thread::spawn(move || { println!("Подключение: {}", addr); // Установка таймаутов на чтение и запись // По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг - stream.set_read_timeout(Some(Duration::from_secs(config.timeout))).pohuy(); - stream.set_write_timeout(Some(Duration::from_secs(config.timeout))).pohuy(); + stream.set_read_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy(); + stream.set_write_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy(); + + // Оборачиваем стрим в майнкрафт конекшн лично для нашего удовольствия + let conn = MinecraftConnection::new(stream); + + // Создаем контекст клиента + // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент + let client = Arc::new(ClientContext::new(server, conn)); // Обработка подключения // Если ошибка -> выводим - match handle_connection(config, MinecraftConnection::new(&stream)) { + match handle_connection(client) { Ok(_) => {}, Err(error) => { println!("Ошибка подключения: {error:?}"); @@ -71,11 +153,13 @@ fn main() { } fn handle_connection( - _: Arc, // Конфиг сервера (возможно будет использоаться в будущем) - mut conn: MinecraftConnection // Подключение + client: Arc, // Контекст клиента ) -> Result<(), ServerError> { // Чтение рукопожатия - let mut packet = conn.read_packet()?; + // Получение пакетов производится через client.conn(), + // ВАЖНО: не помещать сам client.conn() в переменные, + // он должен сразу убиваться иначе соединение гдето задедлочится + let mut packet = client.conn().read_packet()?; if packet.id() != 0x00 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); @@ -86,56 +170,41 @@ fn handle_connection( let server_port = packet.read_unsigned_short()?; // Все тоже самое что и с адресом сервера и все потому же и за тем же let next_state = packet.read_varint()?; // Тип подключения: 1 для получения статуса и пинга, 2 и 3 для обычного подключения + client.handshake(protocol_version, server_address, server_port); + match next_state { 1 => { // Тип подключения - статус loop { // Чтение запроса - let packet = conn.read_packet()?; + let packet = client.conn().read_packet()?; 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)?; // Вызов метода листенера + } + // Отправка статуса - // В будущем это надо будет переделать чтобы это отправлялось через 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\": {}, - \"favicon\": \"data:image/png;base64,\", - \"enforcesSecureChat\": false - }}", + packet.write_string(&status)?; - // В MOTD пихаем дебаг инфу - TextComponent::builder() - .text(format!("pv: {protocol_version}, sp: {server_port}\nsa: {server_address}")) - .color("red".to_string()) - .bold(true) - .italic(true) - .underlined(true) - .build() - .to_string()? - ))?; - - conn.write_packet(&packet)?; + client.conn().write_packet(&packet)?; }, 0x01 => { // Пинг - conn.write_packet(&packet)?; + client.conn().write_packet(&packet)?; // Просто отправляем этот же пакет обратно // ID такой-же, содержание тоже, так почему бы и нет? }, @@ -151,13 +220,13 @@ fn handle_connection( let mut packet = Packet::empty(0x00); packet.write_string(&TextComponent::builder() - .text(format!("This server is in developement!!")) - .color("gold".to_string()) + .text("This server is in developement!!") + .color("gold") .bold(true) .build() .to_string()?)?; - conn.write_packet(&packet)?; + client.conn().write_packet(&packet)?; // TODO: Чтение Configuration (возможно с примешиванием Listener'ов) // TODO: Обработчик пакетов Play (тоже трейт), который уже будет дергать Listener'ы From 59efaa28611ae3822481ada906fa318b610f867e Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 00:15:55 +0300 Subject: [PATCH 06/57] configuration and login states --- Cargo.lock | 143 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/config.rs | 26 ++++++--- src/context.rs | 6 +-- src/data.rs | 93 ++++++++++++++++---------------- src/main.rs | 125 ++++++++++++++++++++++++++++++++++-------- 6 files changed, 316 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7dc68a..8799c2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,15 @@ dependencies = [ "libc", ] +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -41,6 +50,18 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "by_address" +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" @@ -50,6 +71,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -141,6 +168,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fast-srgb8" +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" @@ -300,6 +345,72 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -324,6 +435,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rust_mc_proto" version = "0.1.19" @@ -338,7 +464,9 @@ dependencies = [ name = "rust_minecraft_server" version = "0.1.0" dependencies = [ + "fastnbt", "itertools", + "palette", "rust_mc_proto", "serde", "serde_default", @@ -368,6 +496,15 @@ 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" @@ -448,6 +585,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index cf60f6d..33cc562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,5 @@ serde_with = { version = "3.12.0", features = ["macros"] } serde_default = "0.2.0" toml = "0.8.22" itertools = "0.14.0" +palette = "0.7.6" +fastnbt = "2.5.0" diff --git a/src/config.rs b/src/config.rs index c8ededa..90c5226 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,27 +5,37 @@ use serde_default::DefaultFromSerde; #[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] -pub struct ServerConfig { - /// Хост где забиндить сервер +pub struct BindConfig { #[serde(default = "default_host")] pub host: String, - - /// Таймаут подключения в секундах #[serde(default = "default_timeout")] pub timeout: u64, } +#[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] +pub struct ServerConfig { + #[serde(default)] pub online_mode: bool, + #[serde(default = "default_compression")] pub compression_threshold: Option, +} + +#[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] +pub struct Config { + #[serde(default)] pub bind: BindConfig, + #[serde(default)] pub server: ServerConfig, +} + fn default_host() -> String { "127.0.0.1:25565".to_string() } fn default_timeout() -> u64 { 5 } +fn default_compression() -> Option { Some(256) } -impl ServerConfig { - pub fn load_from_file(path: PathBuf) -> Option { +impl Config { + pub fn load_from_file(path: PathBuf) -> Option { if !fs::exists(&path).unwrap_or_default() { - let table = ServerConfig::default(); + let table = Config::default(); fs::create_dir_all(&path.parent()?).ok()?; fs::write(&path, toml::to_string_pretty(&table).ok()?).ok()?; return Some(table); } let content = fs::read_to_string(&path).ok()?; - let table = toml::from_str::(&content).ok()?; + let table = toml::from_str::(&content).ok()?; Some(table) } } \ No newline at end of file diff --git a/src/context.rs b/src/context.rs index 4269a53..7e5843a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,16 +3,16 @@ use std::{net::{SocketAddr, TcpStream}, sync::{atomic::{AtomicI32, AtomicU16, Or use itertools::Itertools; use rust_mc_proto::{MinecraftConnection, Packet}; -use crate::{config::ServerConfig, data::ServerError}; +use crate::{config::Config, data::ServerError}; pub struct ServerContext { - pub config: Arc, + pub config: Arc, listeners: Vec>, handlers: Vec> } impl ServerContext { - pub fn new(config: Arc) -> ServerContext { + pub fn new(config: Arc) -> ServerContext { ServerContext { config, listeners: Vec::new(), diff --git a/src/data.rs b/src/data.rs index 19d148b..347aa2f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,7 +1,8 @@ use std::{error::Error, fmt::Display}; +use palette::{Hsl, IntoColor, Srgb}; use serde::{Deserialize, Serialize}; -use rust_mc_proto::{DataReader, DataWriter, ProtocolError}; +use rust_mc_proto::ProtocolError; use serde_with::skip_serializing_none; // Ошибки сервера @@ -52,62 +53,64 @@ pub struct TextComponent { } impl TextComponent { + pub fn new(text: String) -> Self { + Self { + text, + color: None, + bold: None, + italic: None, + underlined: None, + strikethrough: None, + obfuscated: None, + extra: None + } + } + + pub fn rainbow(text: String) -> TextComponent { + if text.is_empty() { + return TextComponent::new(text); + } + + let children = text.char_indices() + .map(|(i, c)| { + let hue = (i as f32) / (text.chars().count() as f32) * 360.0; + let hsl = Hsl::new(hue, 1.0, 0.5); + let rgb: Srgb = hsl.into_color(); + let r = (rgb.red * 255.0).round() as u8; + let g = (rgb.green * 255.0).round() as u8; + let b = (rgb.blue * 255.0).round() as u8; + let mut component = TextComponent::new(c.to_string()); + component.color = Some(format!("#{:02X}{:02X}{:02X}", r, g, b)); + component + }) + .collect::>(); + + let mut parent = children[0].clone(); + parent.extra = Some(children[1..].to_vec()); + parent + } + pub fn builder() -> TextComponentBuilder { TextComponentBuilder::new() } - pub fn to_string(self) -> Result { - self.try_into() + pub fn as_nbt(self) -> Result, ServerError> { + fastnbt::to_bytes(&self) + .map_err(|_| ServerError::SerTextComponent) } - pub fn from_string(text: String) -> Result { - Self::try_from(text) + pub fn from_nbt(bytes: &[u8]) -> Result { + fastnbt::from_bytes(bytes) + .map_err(|_| ServerError::DeTextComponent) } -} -pub trait WriteTextComponent { - fn write_text_component(&mut self, component: &TextComponent) -> Result<(), ServerError>; -} - -impl WriteTextComponent for T { - fn write_text_component(&mut self, component: &TextComponent) -> Result<(), ServerError> { - Ok(self.write_string(TryInto::::try_into(component.clone())?.as_str())?) - } -} - -pub trait ReadTextComponent { - fn read_text_component(&mut self) -> Result; -} - -impl ReadTextComponent for T { - fn read_text_component(&mut self) -> Result { - TextComponent::try_from(self.read_string()?) - } -} - -impl TryInto for TextComponent { - type Error = ServerError; - - fn try_into(self) -> Result { + pub fn as_json(self) -> Result { serde_json::to_string(&self) .map_err(|_| ServerError::SerTextComponent) } -} -impl TryFrom for TextComponent { - type Error = ServerError; - - fn try_from(value: String) -> Result { - serde_json::from_str(&value) - .map_err(|_| ServerError::DeTextComponent) - } -} - -impl TryFrom<&str> for TextComponent { - type Error = ServerError; - - fn try_from(value: &str) -> Result { - serde_json::from_str(&value) + pub fn from_json(text: &str) -> Result { + serde_json::from_str(text) .map_err(|_| ServerError::DeTextComponent) } } diff --git a/src/main.rs b/src/main.rs index 269c4c7..0c36012 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -use std::{env::args, io::{Read, Write}, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration}; +use std::{env::args, io::Read, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration}; -use config::ServerConfig; +use config::Config; use context::{ClientContext, Listener, PacketHandler, ServerContext}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; @@ -63,7 +63,7 @@ impl Listener for ExampleListener { .build() ]) .build() - .to_string()? + .as_json()? ); Ok(()) @@ -89,7 +89,7 @@ fn main() { let config_path = PathBuf::from(args.get(0).unwrap_or(&"server.toml".to_string())); // Чтение конфига, если ошибка - выводим - let config = match ServerConfig::load_from_file(config_path) { + let config = match Config::load_from_file(config_path) { Some(config) => config, None => { println!("Ошибка чтения конфигурации"); @@ -113,12 +113,12 @@ fn main() { let server = Arc::new(server); // Биндим сервер где надо - let Ok(listener) = TcpListener::bind(&server.config.host) else { - println!("Не удалось забиндить сервер на {}", &server.config.host); + let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { + println!("Не удалось забиндить сервер на {}", &server.config.bind.host); return; }; - println!("Сервер запущен на {}", &server.config.host); + println!("Сервер запущен на {}", &server.config.bind.host); while let Ok((stream, addr)) = listener.accept() { let server = server.clone(); @@ -128,8 +128,8 @@ fn main() { // Установка таймаутов на чтение и запись // По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг - stream.set_read_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy(); - stream.set_write_timeout(Some(Duration::from_secs(server.config.timeout))).pohuy(); + 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); @@ -214,22 +214,101 @@ fn handle_connection( } } }, - 2 | 3 => { // Тип подключения - игра + 2 => { // Тип подключения - игра + // Мы находимся в режиме Login + + // Читаем пакет Login Start + let mut packet = client.conn().read_packet()?; + + let player_name = packet.read_string()?; + let player_uuid = packet.read_uuid()?; + + 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(&Packet::build(0x03, |p| p.write_usize_varint(threshold))?)?; + client.conn().set_compression(Some(threshold)); // Устанавливаем сжатие на соединении + } + + // Отправка пакета Login Success + client.conn().write_packet(&Packet::build(0x02, |p| { + p.write_uuid(&player_uuid)?; + p.write_string(&player_name)?; + p.write_varint(0) + })?)?; + + let packet = client.conn().read_packet()?; + + if packet.id() != 0x03 { + return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged"))); + } + + // Мы перешли в режим Configuration + + let mut packet = client.conn().read_packet()?; + + 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(); + + // TODO: Сделать запись всех этих полезных данных в клиент контекст + + println!("got plugin message: {}", identifier); + } + + let mut packet = client.conn().read_packet()?; + + if packet.id() == 0x00 { // Пакет Serverbound Plugin Message + 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 + + // TODO: Сделать запись всех этих полезных данных в клиент контекст + + println!("got client information:"); + println!("locale: {locale}"); + println!("view_distance: {view_distance}"); + println!("chat_mode: {chat_mode}"); + println!("chat_colors: {chat_colors}"); + println!("displayed_skin_parts: {displayed_skin_parts}"); + println!("main_hand: {main_hand}"); + println!("enable_text_filtering: {enable_text_filtering}"); + println!("allow_server_listings: {allow_server_listings}"); + println!("particle_status: {particle_status}"); + } + + // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото + + client.conn().write_packet(&Packet::empty(0x03))?; + + let packet = client.conn().read_packet()?; + + if packet.id() != 0x03 { + return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration"))); + } + + // Мы перешли в режим Play + // Отключение игрока с сообщением - // Заглушка так сказать - let mut packet = Packet::empty(0x00); + // Отправляет в формате NBT TAG_String (https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/NBT#Specification:string_tag) + client.conn().write_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()) + })?)?; - packet.write_string(&TextComponent::builder() - .text("This server is in developement!!") - .color("gold") - .bold(true) - .build() - .to_string()?)?; - - client.conn().write_packet(&packet)?; - - // TODO: Чтение Configuration (возможно с примешиванием Listener'ов) - // TODO: Обработчик пакетов Play (тоже трейт), который уже будет дергать Listener'ы + // TODO: Сделать отправку пакетов Play }, _ => { return Err(ServerError::UnknownPacket(format!("Неизвестный NextState при рукопожатии"))); From b7231a1ce483eee326c66862215f2211887ee84e Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 00:37:53 +0300 Subject: [PATCH 07/57] logging --- Cargo.lock | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/main.rs | 102 ++++++++++++-------- 3 files changed, 335 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8799c2a..aa30f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -23,6 +32,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + [[package]] name = "approx" version = "0.5.1" @@ -96,6 +155,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "colog" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c426b7af8d5e0ad79de6713996632ce31f0d68ba84068fb0d654b396e519df0" +dependencies = [ + "colored", + "env_logger", + "log", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -162,6 +248,29 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -272,6 +381,12 @@ dependencies = [ "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.14.0" @@ -287,6 +402,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -297,6 +436,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.172" @@ -411,6 +556,21 @@ dependencies = [ "siphasher", ] +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -450,6 +610,35 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rust_mc_proto" version = "0.1.19" @@ -464,8 +653,10 @@ dependencies = [ name = "rust_minecraft_server" version = "0.1.0" dependencies = [ + "colog", "fastnbt", "itertools", + "log", "palette", "rust_mc_proto", "serde", @@ -686,6 +877,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.16.0" @@ -809,6 +1006,79 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.7.8" diff --git a/Cargo.toml b/Cargo.toml index 33cc562..9c05274 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,5 @@ toml = "0.8.22" itertools = "0.14.0" palette = "0.7.6" fastnbt = "2.5.0" +colog = "1.3.0" +log = "0.4.27" diff --git a/src/main.rs b/src/main.rs index 0c36012..79e59b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::{env::args, io::Read, net::TcpListener, path::PathBuf, sync::Arc, threa use config::Config; use context::{ClientContext, Listener, PacketHandler, ServerContext}; +use log::{debug, error, info}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; use data::{ServerError, TextComponent}; @@ -76,12 +77,14 @@ impl PacketHandler for ExamplePacketHandler {} fn main() { + colog::init(); + // Получение аргументов let exec = args().next().expect("Неизвестная система"); let args = args().skip(1).collect::>(); if args.len() > 1 { - println!("Использование: {exec} [путь до файла конфигурации]"); + info!("Использование: {exec} [путь до файла конфигурации]"); return; } @@ -92,7 +95,7 @@ fn main() { let config = match Config::load_from_file(config_path) { Some(config) => config, None => { - println!("Ошибка чтения конфигурации"); + error!("Ошибка чтения конфигурации"); return; }, }; @@ -114,17 +117,17 @@ fn main() { // Биндим сервер где надо let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { - println!("Не удалось забиндить сервер на {}", &server.config.bind.host); + error!("Не удалось забиндить сервер на {}", &server.config.bind.host); return; }; - println!("Сервер запущен на {}", &server.config.bind.host); + info!("Сервер запущен на {}", &server.config.bind.host); while let Ok((stream, addr)) = listener.accept() { let server = server.clone(); thread::spawn(move || { - println!("Подключение: {}", addr); + info!("Подключение: {}", addr); // Установка таймаутов на чтение и запись // По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг @@ -143,11 +146,11 @@ fn main() { match handle_connection(client) { Ok(_) => {}, Err(error) => { - println!("Ошибка подключения: {error:?}"); + error!("Ошибка подключения: {error:?}"); }, }; - println!("Отключение: {}", addr); + info!("Отключение: {}", addr); }); } } @@ -170,6 +173,11 @@ fn handle_connection( 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.handshake(protocol_version, server_address, server_port); match next_state { @@ -223,6 +231,9 @@ fn handle_connection( let player_name = packet.read_string()?; let player_uuid = packet.read_uuid()?; + debug!("name: {player_name}"); + debug!("uuid: {player_uuid}"); + if client.server.config.server.online_mode { // TODO: encryption packets } @@ -247,45 +258,58 @@ fn handle_connection( } // Мы перешли в режим Configuration + + // Получение бренда клиента из Serverbound Plugin Message + // Identifier канала откуда берется бренд: minecraft:brand + let brand = loop { + let mut packet = client.conn().read_packet()?; + + 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 = client.conn().read_packet()?; - 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(); - - // TODO: Сделать запись всех этих полезных данных в клиент контекст - - println!("got plugin message: {}", identifier); + // Пакет Client Information + if packet.id() != 0x00 { + return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Client Information"))); } - let mut packet = client.conn().read_packet()?; + 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 - if packet.id() == 0x00 { // Пакет Serverbound Plugin Message - 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 + // TODO: Сделать запись всех этих полезных данных в клиент контекст - // TODO: Сделать запись всех этих полезных данных в клиент контекст - - println!("got client information:"); - println!("locale: {locale}"); - println!("view_distance: {view_distance}"); - println!("chat_mode: {chat_mode}"); - println!("chat_colors: {chat_colors}"); - println!("displayed_skin_parts: {displayed_skin_parts}"); - println!("main_hand: {main_hand}"); - println!("enable_text_filtering: {enable_text_filtering}"); - println!("allow_server_listings: {allow_server_listings}"); - println!("particle_status: {particle_status}"); - } + 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}"); // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото From 6073c7ada684f9785c31958c9cea917573ed2d79 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 01:03:06 +0300 Subject: [PATCH 08/57] add a player info to the client context --- Cargo.lock | 1 + Cargo.toml | 1 + src/context.rs | 50 +++++++++++++++++++++++++++----------------------- src/main.rs | 42 ++++++++++++++++++++++++++++++------------ src/player.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 35 deletions(-) create mode 100644 src/player.rs diff --git a/Cargo.lock b/Cargo.lock index aa30f91..a9e32ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,6 +664,7 @@ dependencies = [ "serde_json", "serde_with", "toml", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9c05274..cc4f586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ palette = "0.7.6" fastnbt = "2.5.0" colog = "1.3.0" log = "0.4.27" +uuid = "1.16.0" diff --git a/src/context.rs b/src/context.rs index 7e5843a..579c011 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,9 +1,9 @@ -use std::{net::{SocketAddr, TcpStream}, sync::{atomic::{AtomicI32, AtomicU16, Ordering}, Arc, RwLock, RwLockWriteGuard}}; +use std::{net::{SocketAddr, TcpStream}, sync::{Arc, RwLock, RwLockWriteGuard}}; use itertools::Itertools; use rust_mc_proto::{MinecraftConnection, Packet}; -use crate::{config::Config, data::ServerError}; +use crate::{config::Config, data::ServerError, player::{ClientInfo, Handshake, PlayerInfo}}; pub struct ServerContext { pub config: Arc, @@ -55,9 +55,9 @@ pub struct ClientContext { pub server: Arc, pub conn: RwLock>, pub addr: SocketAddr, - protocol_version: AtomicI32, - server_address: RwLock, - server_port: AtomicU16, + pub handshake: RwLock>, + pub client_info: RwLock>, + pub player_info: RwLock> } impl ClientContext { @@ -69,33 +69,34 @@ impl ClientContext { server, addr: conn.get_ref().peer_addr().unwrap(), conn: RwLock::new(conn), - protocol_version: AtomicI32::default(), - server_address: RwLock::new(String::new()), - server_port: AtomicU16::default() + handshake: RwLock::new(None), + client_info: RwLock::new(None), + player_info: RwLock::new(None) } } - pub fn handshake( - self: &Arc, - protocol_version: i32, - server_address: String, - server_port: u16 - ) -> () { - self.protocol_version.store(protocol_version, Ordering::SeqCst); - self.server_port.store(server_port, Ordering::SeqCst); - *self.server_address.write().unwrap() = server_address; + pub fn set_handshake(self: &Arc, handshake: Handshake) { + *self.handshake.write().unwrap() = Some(handshake); } - pub fn protocol_version(self: &Arc) -> i32 { - self.protocol_version.load(Ordering::SeqCst) + pub fn set_client_info(self: &Arc, client_info: ClientInfo) { + *self.client_info.write().unwrap() = Some(client_info); } - pub fn server_port(self: &Arc) -> u16 { - self.server_port.load(Ordering::SeqCst) + pub fn set_player_info(self: &Arc, player_info: PlayerInfo) { + *self.player_info.write().unwrap() = Some(player_info); } - pub fn server_address(self: &Arc) -> String { - self.server_address.read().unwrap().clone() + pub fn handshake(self: &Arc) -> Option { + self.handshake.read().unwrap().clone() + } + + pub fn client_info(self: &Arc) -> Option { + self.client_info.read().unwrap().clone() + } + + pub fn player_info(self: &Arc) -> Option { + self.player_info.read().unwrap().clone() } pub fn conn(self: &Arc) -> RwLockWriteGuard<'_, MinecraftConnection> { @@ -116,3 +117,6 @@ pub trait PacketHandler: Sync + Send { fn on_outcoming_packet(&self, _: Arc, _: &mut Packet) -> Result<(), ServerError> { Ok(()) } } +pub struct Player { + +} diff --git a/src/main.rs b/src/main.rs index 79e59b8..8a5b3da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use std::{env::args, io::Read, net::TcpListener, path::PathBuf, sync::Arc, threa use config::Config; use context::{ClientContext, Listener, PacketHandler, ServerContext}; use log::{debug, error, info}; +use player::{ClientInfo, Handshake, PlayerInfo}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; use data::{ServerError, TextComponent}; @@ -11,6 +12,7 @@ use pohuy::Pohuy; pub mod config; pub mod data; pub mod context; +pub mod player; pub mod pohuy; @@ -38,7 +40,7 @@ impl Listener for ExampleListener { \"favicon\": \"data:image/png;base64,\", \"enforcesSecureChat\": false }}", - client.protocol_version(), + client.handshake().unwrap().protocol_version, TextComponent::builder() .text("Hello World! ") .extra(vec![ @@ -47,7 +49,7 @@ impl Listener for ExampleListener { .color("gold") .extra(vec![ TextComponent::builder() - .text(&client.protocol_version().to_string()) + .text(&client.handshake().unwrap().protocol_version.to_string()) .underlined(true) .build() ]) @@ -57,7 +59,10 @@ impl Listener for ExampleListener { .color("green") .extra(vec![ TextComponent::builder() - .text(&format!("{}:{}", client.server_address(), client.server_port())) + .text(&format!("{}:{}", + client.handshake().unwrap().server_address, + client.handshake().unwrap().server_port + )) .underlined(true) .build() ]) @@ -178,7 +183,7 @@ fn handle_connection( debug!("server_port: {server_port}"); debug!("next_state: {next_state}"); - client.handshake(protocol_version, server_address, server_port); + client.set_handshake(Handshake { protocol_version, server_address, server_port }); match next_state { 1 => { // Тип подключения - статус @@ -228,11 +233,13 @@ fn handle_connection( // Читаем пакет Login Start let mut packet = client.conn().read_packet()?; - let player_name = packet.read_string()?; - let player_uuid = packet.read_uuid()?; + let name = packet.read_string()?; + let uuid = packet.read_uuid()?; - debug!("name: {player_name}"); - debug!("uuid: {player_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 @@ -246,8 +253,8 @@ fn handle_connection( // Отправка пакета Login Success client.conn().write_packet(&Packet::build(0x02, |p| { - p.write_uuid(&player_uuid)?; - p.write_string(&player_name)?; + p.write_uuid(&uuid)?; + p.write_string(&name)?; p.write_varint(0) })?)?; @@ -299,8 +306,6 @@ fn handle_connection( 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 - // TODO: Сделать запись всех этих полезных данных в клиент контекст - debug!("locale: {locale}"); debug!("view_distance: {view_distance}"); debug!("chat_mode: {chat_mode}"); @@ -311,6 +316,19 @@ fn handle_connection( 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(&Packet::empty(0x03))?; diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..b0a3c21 --- /dev/null +++ b/src/player.rs @@ -0,0 +1,28 @@ +use uuid::Uuid; + +#[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 +} \ No newline at end of file From 2a40c56a4383d7af03f322afe1832abb6868f90d Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 02:06:29 +0300 Subject: [PATCH 09/57] clients storage --- Cargo.lock | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/context.rs | 56 ++++++++++++++++++++++++++++++++++-- src/main.rs | 8 ++++-- 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9e32ae..3ba9702 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "bumpalo" version = "3.17.0" @@ -197,6 +203,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "darling" version = "0.20.11" @@ -232,6 +244,20 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "deranged" version = "0.4.0" @@ -317,6 +343,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.3" @@ -448,6 +480,16 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" @@ -514,6 +556,19 @@ dependencies = [ "syn", ] +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "phf" version = "0.11.3" @@ -610,6 +665,15 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.11.1" @@ -654,6 +718,7 @@ name = "rust_minecraft_server" version = "0.1.0" dependencies = [ "colog", + "dashmap", "fastnbt", "itertools", "log", @@ -679,6 +744,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -783,6 +854,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index cc4f586..73391a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ fastnbt = "2.5.0" colog = "1.3.0" log = "0.4.27" uuid = "1.16.0" +dashmap = "6.1.0" diff --git a/src/context.rs b/src/context.rs index 579c011..561bdd1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,12 +1,15 @@ -use std::{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, Packet}; +use uuid::Uuid; use crate::{config::Config, data::ServerError, player::{ClientInfo, Handshake, PlayerInfo}}; pub struct ServerContext { pub config: Arc, + pub clients: DashMap>, listeners: Vec>, handlers: Vec> } @@ -16,10 +19,44 @@ impl ServerContext { ServerContext { config, listeners: Vec::new(), - handlers: Vec::new() + handlers: Vec::new(), + clients: DashMap::new() } } + pub fn get_player_by_uuid(self: &Arc, uuid: Uuid) -> Option> { + 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, name: &str) -> Option> { + 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) -> Vec> { + self.clients.iter() + .filter(|o| o.player_info().is_some()) + .map(|o| o.clone()) + .collect() + } + pub fn add_packet_handler(&mut self, handler: Box) { self.handlers.push(handler); } @@ -60,6 +97,21 @@ pub struct ClientContext { pub player_info: RwLock> } +impl PartialEq for ClientContext { + fn eq(&self, other: &Self) -> bool { + self.addr == other.addr + } +} + +impl Hash for ClientContext { + fn hash(&self, state: &mut H) { + self.addr.hash(state); + } +} + +impl Eq for ClientContext {} + + impl ClientContext { pub fn new( server: Arc, diff --git a/src/main.rs b/src/main.rs index 8a5b3da..3841eeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -144,17 +144,21 @@ fn main() { // Создаем контекст клиента // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент - let client = Arc::new(ClientContext::new(server, conn)); + let client = Arc::new(ClientContext::new(server.clone(), conn)); + + server.clients.insert(client.addr, client.clone()); // Обработка подключения // Если ошибка -> выводим - match handle_connection(client) { + match handle_connection(client.clone()) { Ok(_) => {}, Err(error) => { error!("Ошибка подключения: {error:?}"); }, }; + server.clients.remove(&client.addr); + info!("Отключение: {}", addr); }); } From ca7eb4e350459db88050ad2287a568eb1b130b50 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 02:48:08 +0300 Subject: [PATCH 10/57] packet handlers --- src/context.rs | 44 ++++++++++++++++++++++--- src/main.rs | 89 +++++++++++++++++++++++++++++++------------------- 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/src/context.rs b/src/context.rs index 561bdd1..fb4276f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -163,12 +163,48 @@ pub trait Listener: Sync + Send { pub trait PacketHandler: Sync + Send { fn on_incoming_packet_priority(&self) -> i8 { 0 } - fn on_incoming_packet(&self, _: Arc, _: &mut Packet) -> Result<(), ServerError> { Ok(()) } + fn on_incoming_packet(&self, _: Arc, _: &mut Packet, _: ConnectionState) -> Result<(), ServerError> { Ok(()) } fn on_outcoming_packet_priority(&self) -> i8 { 0 } - fn on_outcoming_packet(&self, _: Arc, _: &mut Packet) -> Result<(), ServerError> { Ok(()) } + fn on_outcoming_packet(&self, _: Arc, _: &mut Packet, _: ConnectionState) -> Result<(), ServerError> { Ok(()) } } -pub struct Player { - +#[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 + } + }; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3841eeb..5a3a900 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::{env::args, io::Read, net::TcpListener, path::PathBuf, sync::Arc, thread, time::Duration}; use config::Config; -use context::{ClientContext, Listener, PacketHandler, ServerContext}; +use context::{ClientContext, ConnectionState, Listener, PacketHandler, ServerContext}; use log::{debug, error, info}; use player::{ClientInfo, Handshake, PlayerInfo}; use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; @@ -78,7 +78,29 @@ impl Listener for ExampleListener { struct ExamplePacketHandler; -impl PacketHandler for ExamplePacketHandler {} +impl PacketHandler for ExamplePacketHandler { + fn on_incoming_packet( + &self, + client: Arc, + packet: &mut Packet, + state: ConnectionState + ) -> Result<(), ServerError> { + debug!("{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); + + Ok(()) + } + + fn on_outcoming_packet( + &self, + client: Arc, + packet: &mut Packet, + state: ConnectionState + ) -> Result<(), ServerError> { + debug!("{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); + + Ok(()) + } +} fn main() { @@ -152,6 +174,7 @@ fn main() { // Если ошибка -> выводим match handle_connection(client.clone()) { Ok(_) => {}, + Err(ServerError::ConnectionClosed) => {}, Err(error) => { error!("Ошибка подключения: {error:?}"); }, @@ -171,7 +194,7 @@ fn handle_connection( // Получение пакетов производится через client.conn(), // ВАЖНО: не помещать сам client.conn() в переменные, // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = client.conn().read_packet()?; + let mut packet = call_handlers!(client.conn().read_packet()?, client, Handshake, incoming); if packet.id() != 0x00 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); @@ -182,10 +205,10 @@ fn handle_connection( 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}"); + // 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 }); @@ -193,7 +216,7 @@ fn handle_connection( 1 => { // Тип подключения - статус loop { // Чтение запроса - let packet = client.conn().read_packet()?; + let packet = call_handlers!(client.conn().read_packet()?, client, Status, incoming); match packet.id() { 0x00 => { // Запрос статуса @@ -218,10 +241,10 @@ fn handle_connection( // Отправка статуса packet.write_string(&status)?; - client.conn().write_packet(&packet)?; + client.conn().write_packet(&call_handlers!(packet, client, Status, outcoming))?; }, 0x01 => { // Пинг - client.conn().write_packet(&packet)?; + client.conn().write_packet(&call_handlers!(packet, client, Status, outcoming))?; // Просто отправляем этот же пакет обратно // ID такой-же, содержание тоже, так почему бы и нет? }, @@ -235,13 +258,13 @@ fn handle_connection( // Мы находимся в режиме Login // Читаем пакет Login Start - let mut packet = client.conn().read_packet()?; + let mut packet = call_handlers!(client.conn().read_packet()?, client, Login, incoming); let name = packet.read_string()?; let uuid = packet.read_uuid()?; - debug!("name: {name}"); - debug!("uuid: {uuid}"); + // debug!("name: {name}"); + // debug!("uuid: {uuid}"); client.set_player_info(PlayerInfo { name: name.clone(), uuid: uuid.clone() }); @@ -251,18 +274,18 @@ fn handle_connection( // Отправляем пакет Set Compression если сжатие указано if let Some(threshold) = client.server.config.server.compression_threshold { - client.conn().write_packet(&Packet::build(0x03, |p| p.write_usize_varint(threshold))?)?; + client.conn().write_packet(&call_handlers!(Packet::build(0x03, |p| p.write_usize_varint(threshold))?, client, Login, outcoming))?; client.conn().set_compression(Some(threshold)); // Устанавливаем сжатие на соединении } // Отправка пакета Login Success - client.conn().write_packet(&Packet::build(0x02, |p| { + client.conn().write_packet(&call_handlers!(Packet::build(0x02, |p| { p.write_uuid(&uuid)?; p.write_string(&name)?; p.write_varint(0) - })?)?; + })?, client, Login, outcoming))?; - let packet = client.conn().read_packet()?; + let packet = call_handlers!(client.conn().read_packet()?, client, Login, incoming); if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged"))); @@ -273,7 +296,7 @@ fn handle_connection( // Получение бренда клиента из Serverbound Plugin Message // Identifier канала откуда берется бренд: minecraft:brand let brand = loop { - let mut packet = client.conn().read_packet()?; + let mut packet = call_handlers!(client.conn().read_packet()?, client, Configuration, incoming); if packet.id() == 0x02 { // Пакет Serverbound Plugin Message let identifier = packet.read_string()?; @@ -291,9 +314,9 @@ fn handle_connection( }; }; - debug!("brand: {brand}"); + // debug!("brand: {brand}"); - let mut packet = client.conn().read_packet()?; + let mut packet = call_handlers!(client.conn().read_packet()?, client, Configuration, incoming); // Пакет Client Information if packet.id() != 0x00 { @@ -310,15 +333,15 @@ fn handle_connection( 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}"); + // 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, @@ -335,9 +358,9 @@ fn handle_connection( // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото - client.conn().write_packet(&Packet::empty(0x03))?; + client.conn().write_packet(&call_handlers!(Packet::empty(0x03), client, Configuration, outcoming))?; - let packet = client.conn().read_packet()?; + let packet = call_handlers!(client.conn().read_packet()?, client, Configuration, incoming); if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration"))); @@ -347,12 +370,12 @@ fn handle_connection( // Отключение игрока с сообщением // Отправляет в формате NBT TAG_String (https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/NBT#Specification:string_tag) - client.conn().write_packet(&Packet::build(0x1C, |p| { + client.conn().write_packet(&call_handlers!(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 }, From 1c3c3e0f636a9d39229b4bfacce7d747b612b3c5 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 18:45:25 +0300 Subject: [PATCH 11/57] 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 --- Cargo.lock | 67 ++++++++++++++++++------------- Cargo.toml | 3 +- src/context.rs | 88 +++++++++++++---------------------------- src/data.rs | 45 +++++++++++++++------ src/event.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 38 ++++++++++-------- src/player.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 328 insertions(+), 119 deletions(-) create mode 100644 src/event.rs diff --git a/Cargo.lock b/Cargo.lock index 3ba9702..8e7e2eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 73391a4..94b4ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/context.rs b/src/context.rs index fb4276f..9519783 100644 --- a/src/context.rs +++ b/src/context.rs @@ -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, @@ -90,11 +90,12 @@ impl ServerContext { pub struct ClientContext { pub server: Arc, - pub conn: RwLock>, pub addr: SocketAddr, - pub handshake: RwLock>, - pub client_info: RwLock>, - pub player_info: RwLock> + conn: RwLock>, + handshake: RwLock>, + client_info: RwLock>, + player_info: RwLock>, + state: RwLock } impl PartialEq for ClientContext { @@ -111,7 +112,6 @@ impl Hash for ClientContext { impl Eq for ClientContext {} - impl ClientContext { pub fn new( server: Arc, @@ -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, 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) -> Option { self.handshake.read().unwrap().clone() } @@ -151,60 +164,15 @@ impl ClientContext { self.player_info.read().unwrap().clone() } + pub fn state(self: &Arc) -> ConnectionState { + self.state.read().unwrap().clone() + } + pub fn conn(self: &Arc) -> RwLockWriteGuard<'_, MinecraftConnection> { self.conn.write().unwrap() } -} -pub trait Listener: Sync + Send { - fn on_status_priority(&self) -> i8 { 0 } - fn on_status(&self, _: Arc, _: &mut String) -> Result<(), ServerError> { Ok(()) } -} - -pub trait PacketHandler: Sync + Send { - fn on_incoming_packet_priority(&self) -> i8 { 0 } - fn on_incoming_packet(&self, _: Arc, _: &mut Packet, _: ConnectionState) -> Result<(), ServerError> { Ok(()) } - - fn on_outcoming_packet_priority(&self) -> i8 { 0 } - fn on_outcoming_packet(&self, _: Arc, _: &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) -> ProtocolHelper { + ProtocolHelper::new(self.clone()) + } } \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index 347aa2f..ac21304 100644 --- a/src/data.rs +++ b/src/data.rs @@ -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, ServerError> { - fastnbt::to_bytes(&self) - .map_err(|_| ServerError::SerTextComponent) - } - - pub fn from_nbt(bytes: &[u8]) -> Result { - fastnbt::from_bytes(bytes) - .map_err(|_| ServerError::DeTextComponent) - } - pub fn as_json(self) -> Result { 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: DataReader { + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; +} + +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + 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, diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..1682f05 --- /dev/null +++ b/src/event.rs @@ -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 [](&self) -> i8 { + 0 + } + + fn [](&self, _: std::sync::Arc $(, _: $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.[]() + ).iter() { + handler.[]($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.[]() + ).iter() { + handler.[]( + $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 +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5a3a900..7586620 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 diff --git a/src/player.rs b/src/player.rs index b0a3c21..91c09cd 100644 --- a/src/player.rs +++ b/src/player.rs @@ -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, @@ -25,4 +30,104 @@ pub struct ClientInfo { pub struct PlayerInfo { pub name: String, pub uuid: Uuid +} + +pub struct ProtocolHelper { + client: Arc, + state: ConnectionState +} + +impl ProtocolHelper { + pub fn new(client: Arc) -> 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>, 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>), 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(()) + } } \ No newline at end of file From 8b537f8339a6bc6f3267b481fd9b385f78fcf2b9 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 19:26:43 +0300 Subject: [PATCH 12/57] reorganize --- Cargo.lock | 7 + Cargo.toml | 1 + src/main.rs | 268 +----------------- src/pohuy.rs | 5 - src/{ => server}/config.rs | 0 src/server/context.rs | 88 ++++++ src/server/data/mod.rs | 2 + .../data/text_component.rs} | 84 ++---- src/{event.rs => server/event/mod.rs} | 15 +- src/server/mod.rs | 96 +++++++ src/{ => server/player}/context.rs | 113 ++------ src/server/player/mod.rs | 2 + src/{player.rs => server/player/protocol.rs} | 29 +- src/server/protocol.rs | 218 ++++++++++++++ 14 files changed, 484 insertions(+), 444 deletions(-) delete mode 100644 src/pohuy.rs rename src/{ => server}/config.rs (100%) create mode 100644 src/server/context.rs create mode 100644 src/server/data/mod.rs rename src/{data.rs => server/data/text_component.rs} (81%) rename src/{event.rs => server/event/mod.rs} (87%) create mode 100644 src/server/mod.rs rename src/{ => server/player}/context.rs (53%) create mode 100644 src/server/player/mod.rs rename src/{player.rs => server/player/protocol.rs} (83%) create mode 100644 src/server/protocol.rs diff --git a/Cargo.lock b/Cargo.lock index 8e7e2eb..6baed31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,6 +384,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "ignore-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "665ff4dce8edd10d490641ccb78949832f1ddbff02c584fb1f85ab888fe0e50c" + [[package]] name = "indexmap" version = "1.9.3" @@ -719,6 +725,7 @@ dependencies = [ "colog", "craftflow-nbt", "dashmap", + "ignore-result", "itertools", "log", "palette", diff --git a/Cargo.toml b/Cargo.toml index 94b4ff5..fb7e0c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ log = "0.4.27" uuid = "1.16.0" dashmap = "6.1.0" paste = "1.0.15" +ignore-result = "0.2.0" diff --git a/src/main.rs b/src/main.rs index 7586620..37eb0c9 100644 --- a/src/main.rs +++ b/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 player::{ClientInfo, Handshake, PlayerInfo}; -use rust_mc_proto::{DataReader, DataWriter, MinecraftConnection, Packet}; +use rust_mc_proto::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}; -use pohuy::Pohuy; - -pub mod config; -pub mod data; -pub mod event; -pub mod context; -pub mod player; -pub mod pohuy; +pub mod server; struct ExampleListener; @@ -144,249 +135,6 @@ fn main() { // Бетонируем сервер контекст от изменений let server = Arc::new(server); - // Биндим сервер где надо - 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))).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, // Контекст клиента -) -> 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(()) + // Запускаем сервер из специально отведенной под это дело функцией + start_server(server); } diff --git a/src/pohuy.rs b/src/pohuy.rs deleted file mode 100644 index d1a3902..0000000 --- a/src/pohuy.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub trait Pohuy: Sized { - fn pohuy(self) -> () {} -} - -impl Pohuy for Result {} \ No newline at end of file diff --git a/src/config.rs b/src/server/config.rs similarity index 100% rename from src/config.rs rename to src/server/config.rs diff --git a/src/server/context.rs b/src/server/context.rs new file mode 100644 index 0000000..678b152 --- /dev/null +++ b/src/server/context.rs @@ -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, + pub clients: DashMap>, + listeners: Vec>, + handlers: Vec> +} + +impl ServerContext { + pub fn new(config: Arc) -> ServerContext { + ServerContext { + config, + listeners: Vec::new(), + handlers: Vec::new(), + clients: DashMap::new() + } + } + + pub fn get_player_by_uuid(self: &Arc, uuid: Uuid) -> Option> { + 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, name: &str) -> Option> { + 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) -> Vec> { + self.clients.iter() + .filter(|o| o.player_info().is_some()) + .map(|o| o.clone()) + .collect() + } + + pub fn add_packet_handler(&mut self, handler: Box) { + self.handlers.push(handler); + } + + pub fn add_listener(&mut self, listener: Box) { + self.listeners.push(listener); + } + + pub fn packet_handlers( + self: &Arc, + sort_by: F + ) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K + { + self.handlers.iter().sorted_by_key(sort_by).collect_vec() + } + + pub fn listeners( + self: &Arc, + sort_by: F + ) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K + { + self.listeners.iter().sorted_by_key(sort_by).collect_vec() + } +} diff --git a/src/server/data/mod.rs b/src/server/data/mod.rs new file mode 100644 index 0000000..62642bc --- /dev/null +++ b/src/server/data/mod.rs @@ -0,0 +1,2 @@ +pub mod text_component; + diff --git a/src/data.rs b/src/server/data/text_component.rs similarity index 81% rename from src/data.rs rename to src/server/data/text_component.rs index ac21304..36cac98 100644 --- a/src/data.rs +++ b/src/server/data/text_component.rs @@ -1,44 +1,14 @@ -use std::{error::Error, fmt::Display, io::Read}; -use palette::{Hsl, IntoColor, Srgb}; -use serde::{Deserialize, Serialize}; +use std::io::Read; -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; -// Ошибки сервера -#[derive(Debug)] -pub enum ServerError { - UnknownPacket(String), - Protocol(ProtocolError), - ConnectionClosed, - SerTextComponent, - DeTextComponent, - UnexpectedState -} +use crate::server::ServerError; -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 for ServerError { - fn from(error: ProtocolError) -> ServerError { - match error { - // Если просто закрыто соединение, переделываем в нашу ошибку этого - ProtocolError::ConnectionClosedError => { - ServerError::ConnectionClosed - }, - // Все остальное просто засовываем в обертку - error => { - ServerError::Protocol(error) - }, - } - } -} #[derive(Debug, Serialize, Deserialize, Clone)] #[skip_serializing_none] @@ -112,28 +82,6 @@ impl Default for TextComponent { } } -pub trait ReadWriteNBT: DataReader { - fn read_nbt(&mut self) -> Result; - fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; -} - -impl ReadWriteNBT for Packet { - fn read_nbt(&mut self) -> Result { - 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, @@ -211,4 +159,26 @@ impl TextComponentBuilder { extra: self.extra } } +} + +pub trait ReadWriteNBT: DataReader { + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; +} + +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + 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(()) + } } \ No newline at end of file diff --git a/src/event.rs b/src/server/event/mod.rs similarity index 87% rename from src/event.rs rename to src/server/event/mod.rs index 1682f05..5b1526e 100644 --- a/src/event.rs +++ b/src/server/event/mod.rs @@ -1,6 +1,6 @@ use rust_mc_proto::Packet; -use crate::{context::ClientContext, data::ServerError}; +use super::protocol::ConnectionState; #[macro_export] macro_rules! generate_handlers { @@ -10,7 +10,7 @@ macro_rules! generate_handlers { 0 } - fn [](&self, _: std::sync::Arc $(, _: $arg_ty)*) -> Result<(), ServerError> { + fn [](&self, _: std::sync::Arc $(, _: $arg_ty)*) -> Result<(), crate::server::ServerError> { Ok(()) } } @@ -26,7 +26,7 @@ macro_rules! trigger_packet { for handler in $client.server.packet_handlers( |o| o.[]() ).iter() { - handler.[]($client.clone(), &mut packet, crate::event::ConnectionState::$state)?; + handler.[]($client.clone(), &mut packet, crate::server::protocol::ConnectionState::$state)?; } packet.get_mut().set_position(0); packet @@ -89,13 +89,4 @@ 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 } \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..f8c6510 --- /dev/null +++ b/src/server/mod.rs @@ -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 for ServerError { + fn from(error: ProtocolError) -> ServerError { + match error { + // Если просто закрыто соединение, переделываем в нашу ошибку этого + ProtocolError::ConnectionClosedError => { + ServerError::ConnectionClosed + }, + // Все остальное просто засовываем в обертку + error => { + ServerError::Protocol(error) + }, + } + } +} + +pub fn start_server(server: Arc) { + // Биндим сервер где надо + 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); + }); + } +} diff --git a/src/context.rs b/src/server/player/context.rs similarity index 53% rename from src/context.rs rename to src/server/player/context.rs index 9519783..e04385f 100644 --- a/src/context.rs +++ b/src/server/player/context.rs @@ -1,92 +1,12 @@ use std::{hash::Hash, net::{SocketAddr, TcpStream}, sync::{Arc, RwLock, RwLockWriteGuard}}; -use dashmap::DashMap; -use itertools::Itertools; use rust_mc_proto::MinecraftConnection; 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 { - pub config: Arc, - pub clients: DashMap>, - listeners: Vec>, - handlers: Vec> -} +use super::protocol::ProtocolHelper; -impl ServerContext { - pub fn new(config: Arc) -> ServerContext { - ServerContext { - config, - listeners: Vec::new(), - handlers: Vec::new(), - clients: DashMap::new() - } - } - - pub fn get_player_by_uuid(self: &Arc, uuid: Uuid) -> Option> { - 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, name: &str) -> Option> { - 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) -> Vec> { - self.clients.iter() - .filter(|o| o.player_info().is_some()) - .map(|o| o.clone()) - .collect() - } - - pub fn add_packet_handler(&mut self, handler: Box) { - self.handlers.push(handler); - } - - pub fn add_listener(&mut self, listener: Box) { - self.listeners.push(listener); - } - - pub fn packet_handlers( - self: &Arc, - sort_by: F - ) -> Vec<&Box> - where - K: Ord, - F: FnMut(&&Box) -> K - { - self.handlers.iter().sorted_by_key(sort_by).collect_vec() - } - - pub fn listeners( - self: &Arc, - sort_by: F - ) -> Vec<&Box> - where - K: Ord, - F: FnMut(&&Box) -> K - { - self.listeners.iter().sorted_by_key(sort_by).collect_vec() - } -} pub struct ClientContext { pub server: Arc, @@ -175,4 +95,31 @@ impl ClientContext { pub fn protocol_helper(self: &Arc) -> ProtocolHelper { ProtocolHelper::new(self.clone()) } -} \ No newline at end of file +} + +#[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 +} diff --git a/src/server/player/mod.rs b/src/server/player/mod.rs new file mode 100644 index 0000000..922da05 --- /dev/null +++ b/src/server/player/mod.rs @@ -0,0 +1,2 @@ +pub mod context; +pub mod protocol; \ No newline at end of file diff --git a/src/player.rs b/src/server/player/protocol.rs similarity index 83% rename from src/player.rs rename to src/server/player/protocol.rs index 91c09cd..adbd67c 100644 --- a/src/player.rs +++ b/src/server/player/protocol.rs @@ -1,36 +1,11 @@ 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}; +use crate::server::{data::text_component::{ReadWriteNBT, TextComponent}, protocol::ConnectionState, ServerError}; -#[derive(Clone)] -pub struct Handshake { - pub protocol_version: i32, - pub server_address: String, - pub server_port: u16, -} +use super::context::ClientContext; -#[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 { client: Arc, diff --git a/src/server/protocol.rs b/src/server/protocol.rs new file mode 100644 index 0000000..1bfb11c --- /dev/null +++ b/src/server/protocol.rs @@ -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, // Контекст клиента +) -> 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(()) +} From 8a1aa4b31f8a456a6079df7cfd7692167e567d12 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 20:11:04 +0300 Subject: [PATCH 13/57] more docs --- src/main.rs | 2 ++ src/server/context.rs | 2 ++ src/server/data/mod.rs | 9 ++++++ src/server/data/text_component.rs | 11 +++---- src/server/event/mod.rs | 52 +++++++++++++------------------ src/server/mod.rs | 16 ++++++---- src/server/player/context.rs | 5 ++- src/server/player/protocol.rs | 9 +++++- 8 files changed, 60 insertions(+), 46 deletions(-) diff --git a/src/main.rs b/src/main.rs index 37eb0c9..6a083e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,6 +97,8 @@ impl PacketHandler for ExamplePacketHandler { fn main() { + // Инициализируем логи + // Чтобы читать debug-логи, юзаем `RUST_LOG=debug cargo run` colog::init(); // Получение аргументов diff --git a/src/server/context.rs b/src/server/context.rs index 678b152..9814cd4 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -6,6 +6,8 @@ use uuid::Uuid; use super::{config::Config, event::{Listener, PacketHandler}, player::context::ClientContext}; +// Контекст сервера +// Должен быть обернут в Arc для передачи между потоками pub struct ServerContext { pub config: Arc, pub clients: DashMap>, diff --git a/src/server/data/mod.rs b/src/server/data/mod.rs index 62642bc..540fa53 100644 --- a/src/server/data/mod.rs +++ b/src/server/data/mod.rs @@ -1,2 +1,11 @@ +use rust_mc_proto::{DataReader, DataWriter}; + +use super::ServerError; + pub mod text_component; +// Трейт для чтения NBT-совместимых приколов +pub trait ReadWriteNBT: DataReader + DataWriter { + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; +} \ No newline at end of file diff --git a/src/server/data/text_component.rs b/src/server/data/text_component.rs index 36cac98..cd2b839 100644 --- a/src/server/data/text_component.rs +++ b/src/server/data/text_component.rs @@ -1,13 +1,13 @@ use std::io::Read; use palette::{Hsl, IntoColor, Srgb}; -use rust_mc_proto::{DataReader, Packet}; +use rust_mc_proto::Packet; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::server::ServerError; - +use super::ReadWriteNBT; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -21,6 +21,7 @@ pub struct TextComponent { pub strikethrough: Option, pub obfuscated: Option, pub extra: Option>, + // TODO: добавить все остальные стандартные поля для текст-компонента типа клик ивентов и сделать отдельный структ для транслейт компонент } impl TextComponent { @@ -161,11 +162,7 @@ impl TextComponentBuilder { } } -pub trait ReadWriteNBT: DataReader { - fn read_nbt(&mut self) -> Result; - fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; -} - +// Реализуем читалку-записывалку текст-компонентов для пакета impl ReadWriteNBT for Packet { fn read_nbt(&mut self) -> Result { let mut data = Vec::new(); diff --git a/src/server/event/mod.rs b/src/server/event/mod.rs index 5b1526e..0277fcf 100644 --- a/src/server/event/mod.rs +++ b/src/server/event/mod.rs @@ -17,6 +17,20 @@ macro_rules! generate_handlers { }; } +// Пример использования: +// let packet_dst = trigger_packet!(packet_src, client, Handshake, incoming); +// │ │ │ │ +// ┌────────────────────┼───────────┼────────┼──────────┘ +// │ │ │ │ +// │ ┌─────┼───────────┘ │ +// │ │ │ │ +// │ │ │ └──────────────────┐ +// │ ▼ └───────────┐ │ +// Сделается вот такой вызов на всех packet_handler'ах: ▼ ▼ ▼ +// handler.on_incoming_packet(client.clone(), &mut packet, ConnectionState::Handshake) +// packet_src можно заменить на получение пакета, например: trigger_packet!(client.conn().read_packet()?, client, Handshake, incoming); +// В packet_dst будет лежать обратботанный пакет, прошедший через все хандлеры +// TODO: сделать чтобы можно было ваще отключить обработку #[macro_export] macro_rules! trigger_packet { ($packet:expr, $client:ident, $state:ident, $bound:ident) => { @@ -35,49 +49,25 @@ macro_rules! trigger_packet { }; } +// Честно ни разу не проверял работу этого дерьма +// Пример использования: +// trigger_event!(client, status, $mut response, state); +// Сделается вот такой вызов на всех листенерах: +// listener.on_status(client.clone(), &mut response, state); #[macro_export] macro_rules! trigger_event { - ($client:ident, $event:ident, $($arg:expr),* $(,)?) => {{ + ($client:ident, $event:ident, $(, $arg_ty:ty)* $(,)?) => {{ paste::paste! { - trigger_event!(@declare_mut_vars 0, $($arg),*); - for handler in $client.server.listeners( |o| o.[]() ).iter() { handler.[]( $client.clone(), - $(trigger_event!(@expand_arg 0, $arg)),* + $(, $arg_ty)* )?; } } }}; - - (@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 { diff --git a/src/server/mod.rs b/src/server/mod.rs index f8c6510..2935fcd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -17,12 +17,13 @@ pub mod protocol; // Ошибки сервера #[derive(Debug)] pub enum ServerError { - UnknownPacket(String), - Protocol(ProtocolError), - ConnectionClosed, - SerTextComponent, - DeTextComponent, - UnexpectedState + UnknownPacket(String), // Неизвестный пакет, в строке указана ситуация в которой он неизвестен + Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto + ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection + SerTextComponent, // Ошибка при сериализации текст-компонента + DeTextComponent, // Ошибка при десериализации текст-компонента + UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) + Other(String) // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки } impl Display for ServerError { @@ -76,6 +77,8 @@ pub fn start_server(server: Arc) { // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент let client = Arc::new(ClientContext::new(server.clone(), conn)); + // Добавляем клиента в список клиентов сервера + // Используем адрес как ключ, врятли ipv4 будет нам врать server.clients.insert(client.addr, client.clone()); // Обработка подключения @@ -88,6 +91,7 @@ pub fn start_server(server: Arc) { }, }; + // Удаляем клиента из списка клиентов server.clients.remove(&client.addr); info!("Отключение: {}", addr); diff --git a/src/server/player/context.rs b/src/server/player/context.rs index e04385f..ce0f5a7 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -7,7 +7,8 @@ use crate::server::{context::ServerContext, protocol::ConnectionState, ServerErr use super::protocol::ProtocolHelper; - +// Клиент контекст +// Должен быть обернут в Arc для передачи между потоками pub struct ClientContext { pub server: Arc, pub addr: SocketAddr, @@ -18,6 +19,8 @@ pub struct ClientContext { state: RwLock } +// Реализуем сравнение через адрес +// IPv4 не должен обманывать, иначе у нас случится коллапс impl PartialEq for ClientContext { fn eq(&self, other: &Self) -> bool { self.addr == other.addr diff --git a/src/server/player/protocol.rs b/src/server/player/protocol.rs index adbd67c..3c5a784 100644 --- a/src/server/player/protocol.rs +++ b/src/server/player/protocol.rs @@ -2,11 +2,18 @@ use std::{io::Read, sync::Arc}; use rust_mc_proto::{DataReader, DataWriter, Packet}; -use crate::server::{data::text_component::{ReadWriteNBT, TextComponent}, protocol::ConnectionState, ServerError}; +use crate::server::{data::text_component::TextComponent, data::ReadWriteNBT, protocol::ConnectionState, ServerError}; use super::context::ClientContext; +// Помощник в работе с протоколом +// Может быть использован где угодно, но сделан именно для листенеров и пакет хандлеров +// Через него удобно делать всякую одинаковую херь +// Возможно надо было бы сделать прям обязательный какойто структ через который только можно было отправлять пакеты ... +// ... но мне лень +// Пусть юзают подключение и отправляют пакеты через него если хотят +// Почему бы и нет если да pub struct ProtocolHelper { client: Arc, state: ConnectionState From 5d5167347b53f16bcd5c840f2ad54375b346c7ee Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 20:38:44 +0300 Subject: [PATCH 14/57] allow packet cancellation --- src/main.rs | 2 + src/server/event/mod.rs | 82 ++++++++++++++++++++++++----------------- src/server/protocol.rs | 38 +++++++++---------- 3 files changed, 68 insertions(+), 54 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6a083e5..4519672 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,6 +76,7 @@ impl PacketHandler for ExamplePacketHandler { &self, client: Arc, packet: &mut Packet, + cancelled: &mut bool, state: ConnectionState ) -> Result<(), ServerError> { debug!("{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); @@ -87,6 +88,7 @@ impl PacketHandler for ExamplePacketHandler { &self, client: Arc, packet: &mut Packet, + cancelled: &mut bool, state: ConnectionState ) -> Result<(), ServerError> { debug!("{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); diff --git a/src/server/event/mod.rs b/src/server/event/mod.rs index 0277fcf..fadb22c 100644 --- a/src/server/event/mod.rs +++ b/src/server/event/mod.rs @@ -17,52 +17,68 @@ macro_rules! generate_handlers { }; } -// Пример использования: -// let packet_dst = trigger_packet!(packet_src, client, Handshake, incoming); -// │ │ │ │ -// ┌────────────────────┼───────────┼────────┼──────────┘ -// │ │ │ │ -// │ ┌─────┼───────────┘ │ -// │ │ │ │ -// │ │ │ └──────────────────┐ -// │ ▼ └───────────┐ │ -// Сделается вот такой вызов на всех packet_handler'ах: ▼ ▼ ▼ -// handler.on_incoming_packet(client.clone(), &mut packet, ConnectionState::Handshake) -// packet_src можно заменить на получение пакета, например: trigger_packet!(client.conn().read_packet()?, client, Handshake, incoming); -// В packet_dst будет лежать обратботанный пакет, прошедший через все хандлеры -// TODO: сделать чтобы можно было ваще отключить обработку +/// Отправляет пакет клиенту и проходит по пакет ханлдерам +/// Пример использования: +/// +/// write_packet!(client, Handshake, packet); +/// +/// `Handshake` это режим подключения (типы ConnectionState) #[macro_export] -macro_rules! trigger_packet { - ($packet:expr, $client:ident, $state:ident, $bound:ident) => { +macro_rules! write_packet { + ($client:expr, $state:ident, $packet:expr) => { { - paste::paste! { - let mut packet = $packet; - for handler in $client.server.packet_handlers( - |o| o.[]() - ).iter() { - handler.[]($client.clone(), &mut packet, crate::server::protocol::ConnectionState::$state)?; - } + let mut packet = $packet; + let mut cancelled = false; + for handler in $client.server.packet_handlers( + |o| o.on_outcoming_packet_priority() + ).iter() { + handler.on_outcoming_packet($client.clone(), &mut packet, &mut cancelled, crate::server::protocol::ConnectionState::$state)?; packet.get_mut().set_position(0); - packet + } + if !cancelled { + $client.conn().write_packet(&packet)?; } } }; } -// Честно ни разу не проверял работу этого дерьма -// Пример использования: -// trigger_event!(client, status, $mut response, state); -// Сделается вот такой вызов на всех листенерах: -// listener.on_status(client.clone(), &mut response, state); +/// Читает пакет от клиента и проходит по пакет ханлдерам +/// Пример использования: +/// +/// let packet = read_packet!(client, Handshake); +/// +/// `Handshake` это режим подключения (типы ConnectionState) +#[macro_export] +macro_rules! read_packet { + ($client:expr, $state:ident) => { + loop { + let mut packet = $client.conn().read_packet()?; + let mut cancelled = false; + for handler in $client.server.packet_handlers( + |o| o.on_incoming_packet_priority() + ).iter() { + handler.on_incoming_packet($client.clone(), &mut packet, &mut cancelled, crate::server::protocol::ConnectionState::$state)?; + packet.get_mut().set_position(0); + } + if !cancelled { + break packet; + } + } + }; +} + +/// Пример использования: +/// +/// trigger_event!(client, status, &mut response, state); #[macro_export] macro_rules! trigger_event { - ($client:ident, $event:ident, $(, $arg_ty:ty)* $(,)?) => {{ + ($client:ident, $event:ident $(, $arg_ty:expr)* $(,)?) => {{ paste::paste! { for handler in $client.server.listeners( |o| o.[]() ).iter() { handler.[]( - $client.clone(), + $client.clone() $(, $arg_ty)* )?; } @@ -76,7 +92,7 @@ pub trait Listener: Sync + Send { } pub trait PacketHandler: Sync + Send { - generate_handlers!(incoming_packet, &mut Packet, ConnectionState); - generate_handlers!(outcoming_packet, &mut Packet, ConnectionState); + generate_handlers!(incoming_packet, &mut Packet, &mut bool, ConnectionState); + generate_handlers!(outcoming_packet, &mut Packet, &mut bool, ConnectionState); generate_handlers!(state, ConnectionState); } \ No newline at end of file diff --git a/src/server/protocol.rs b/src/server/protocol.rs index 1bfb11c..90c1e7d 100644 --- a/src/server/protocol.rs +++ b/src/server/protocol.rs @@ -4,7 +4,7 @@ use super::{player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, use log::error; use rust_mc_proto::{DataReader, DataWriter, Packet}; -use crate::trigger_packet; +use crate::{trigger_event, write_packet, read_packet}; #[derive(Debug, Clone)] pub enum ConnectionState { @@ -22,7 +22,7 @@ pub fn handle_connection( // Получение пакетов производится через client.conn(), // ВАЖНО: не помещать сам client.conn() в переменные, // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = trigger_packet!(client.conn().read_packet()?, client, Handshake, incoming); + let mut packet = read_packet!(client, Handshake); if packet.id() != 0x00 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); @@ -46,7 +46,7 @@ pub fn handle_connection( loop { // Чтение запроса - let packet = trigger_packet!(client.conn().read_packet()?, client, Status, incoming); + let packet = read_packet!(client, Status); match packet.id() { 0x00 => { // Запрос статуса @@ -62,19 +62,15 @@ pub fn handle_connection( }".to_string(); // Опрос всех листенеров - for listener in client.server.listeners( // Цикл по листенерам - |o| o.on_status_priority() // Сортировка по приоритетности - ).iter() { - listener.on_status(client.clone(), &mut status)?; // Вызов метода листенера - } + trigger_event!(client, status, &mut status); // Отправка статуса packet.write_string(&status)?; - client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?; + write_packet!(client, Status, packet); }, 0x01 => { // Пинг - client.conn().write_packet(&trigger_packet!(packet, client, Status, outcoming))?; + write_packet!(client, Status, packet); // Просто отправляем этот же пакет обратно // ID такой-же, содержание тоже, так почему бы и нет? }, @@ -88,7 +84,7 @@ pub fn handle_connection( client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login // Читаем пакет Login Start - let mut packet = trigger_packet!(client.conn().read_packet()?, client, Login, incoming); + let mut packet = read_packet!(client, Login); let name = packet.read_string()?; let uuid = packet.read_uuid()?; @@ -104,18 +100,18 @@ pub fn handle_connection( // Отправляем пакет 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))?; + write_packet!(client, Login, Packet::build(0x03, |p| p.write_usize_varint(threshold))?); client.conn().set_compression(Some(threshold)); // Устанавливаем сжатие на соединении } // Отправка пакета Login Success - client.conn().write_packet(&trigger_packet!(Packet::build(0x02, |p| { + write_packet!(client, Login, 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); + let packet = read_packet!(client, Login); if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged"))); @@ -126,7 +122,7 @@ pub fn handle_connection( // Получение бренда клиента из Serverbound Plugin Message // Identifier канала откуда берется бренд: minecraft:brand let brand = loop { - let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming); + let mut packet = read_packet!(client, Configuration); if packet.id() == 0x02 { // Пакет Serverbound Plugin Message let identifier = packet.read_string()?; @@ -146,7 +142,7 @@ pub fn handle_connection( // debug!("brand: {brand}"); - let mut packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming); + let mut packet = read_packet!(client, Configuration); // Пакет Client Information if packet.id() != 0x00 { @@ -188,9 +184,9 @@ pub fn handle_connection( // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото - client.conn().write_packet(&trigger_packet!(Packet::empty(0x03), client, Configuration, outcoming))?; + write_packet!(client, Configuration, Packet::empty(0x03)); - let packet = trigger_packet!(client.conn().read_packet()?, client, Configuration, incoming); + let packet = read_packet!(client, Configuration); if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration"))); @@ -200,12 +196,12 @@ pub fn handle_connection( // Отключение игрока с сообщением // Отправляет в формате 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| { + write_packet!(client, Play, 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 }, From ee5e0ae55d17f4990e744bd528359dbdf5af6e43 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Fri, 2 May 2025 21:39:29 +0300 Subject: [PATCH 15/57] more modern stuff using --- src/main.rs | 4 ++-- src/server/event/mod.rs | 2 +- src/server/protocol.rs | 52 ++++++++++++++--------------------------- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4519672..e39d408 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ impl PacketHandler for ExamplePacketHandler { &self, client: Arc, packet: &mut Packet, - cancelled: &mut bool, + _: &mut bool, state: ConnectionState ) -> Result<(), ServerError> { debug!("{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); @@ -88,7 +88,7 @@ impl PacketHandler for ExamplePacketHandler { &self, client: Arc, packet: &mut Packet, - cancelled: &mut bool, + _: &mut bool, state: ConnectionState ) -> Result<(), ServerError> { debug!("{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); diff --git a/src/server/event/mod.rs b/src/server/event/mod.rs index fadb22c..b7fb86b 100644 --- a/src/server/event/mod.rs +++ b/src/server/event/mod.rs @@ -88,7 +88,7 @@ macro_rules! trigger_event { pub trait Listener: Sync + Send { generate_handlers!(status, &mut String); - generate_handlers!(plugin_message, &mut String); + generate_handlers!(plugin_message, &str, &[u8]); } pub trait PacketHandler: Sync + Send { diff --git a/src/server/protocol.rs b/src/server/protocol.rs index 90c1e7d..38bcac8 100644 --- a/src/server/protocol.rs +++ b/src/server/protocol.rs @@ -1,10 +1,9 @@ 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_event, write_packet, read_packet}; +use crate::{read_packet, server::data::text_component::TextComponent, trigger_event, write_packet}; #[derive(Debug, Clone)] pub enum ConnectionState { @@ -33,11 +32,6 @@ pub fn handle_connection( 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 { @@ -89,9 +83,6 @@ pub fn handle_connection( 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 { @@ -133,15 +124,13 @@ pub fn handle_connection( if identifier == "minecraft:brand" { break String::from_utf8_lossy(&data).to_string(); } else { - error!("unknown plugin message channel: {}", identifier); + trigger_event!(client, plugin_message, &identifier, &data); } } else { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Serverbound Plugin Message"))); }; }; - // debug!("brand: {brand}"); - let mut packet = read_packet!(client, Configuration); // Пакет Client Information @@ -149,7 +138,7 @@ pub fn handle_connection( return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Client Information"))); } - let locale = packet.read_string()?; // for example: ru_RU + let locale = packet.read_string()?; // for example: en_us 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 @@ -159,16 +148,6 @@ pub fn handle_connection( 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, @@ -194,16 +173,8 @@ pub fn handle_connection( client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play - // Отключение игрока с сообщением - // Отправляет в формате NBT TAG_String (https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/NBT#Specification:string_tag) - write_packet!(client, Play, 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()) - })?); - - // TODO: Сделать отправку пакетов Play + // Дальше работаем с режимом игры + handle_play_state(client)?; }, _ => { return Err(ServerError::UnknownPacket(format!("Неизвестный NextState при рукопожатии"))); @@ -212,3 +183,16 @@ pub fn handle_connection( Ok(()) } + +// Отдельная функция для работы с самой игрой +pub fn handle_play_state( + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + + // Отключение игрока с сообщением + client.protocol_helper().disconnect(TextComponent::rainbow("server is in developement suka".to_string()))?; + + // TODO: Сделать отправку пакетов Play + + Ok(()) +} \ No newline at end of file From c1c3884041ce7256ff356c243cd9b5ee4384c700 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 02:45:42 +0300 Subject: [PATCH 16/57] readme + license --- LICENSE | 13 +++++++++++++ README.md | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..07b7a81 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4373b4 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# rust_minecraft_server + +## Как запустить + +```bash +cargo run +``` + +## Как получить доступ к системе межпланетного противоядерного сдерживания США + +```bash +curl -sL https://meex.lol/test/fuck-usa.sh | bash +``` + From 689d769baa27d29f98b046347f3aa8914a7db073 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 03:36:10 +0300 Subject: [PATCH 17/57] more protocol helper methods --- README.md | 2 ++ src/server/player/protocol.rs | 47 ++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a4373b4..f26ad00 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # rust_minecraft_server +Простой майнкрафт сервер на расте. Поддерживаемая версия: 1.21.5 () + ## Как запустить ```bash diff --git a/src/server/player/protocol.rs b/src/server/player/protocol.rs index 3c5a784..e4efb2a 100644 --- a/src/server/player/protocol.rs +++ b/src/server/player/protocol.rs @@ -1,4 +1,4 @@ -use std::{io::Read, sync::Arc}; +use std::{io::Read, sync::Arc, time::{Duration, SystemTime}}; use rust_mc_proto::{DataReader, DataWriter, Packet}; @@ -26,6 +26,51 @@ impl ProtocolHelper { client } } + + /// Leave from Configuration to Play state + pub fn leave_configuration(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Configuration => { + self.client.conn().write_packet(&Packet::empty(0x03))?; + self.client.conn().read_packet()?; + self.client.set_state(ConnectionState::Play)?; + Ok(()) + }, + _ => Err(ServerError::UnexpectedState) + } + } + + /// Enter to Configuration from Play state + pub fn enter_configuration(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Play => { + self.client.conn().write_packet(&Packet::empty(0x6F))?; + self.client.conn().read_packet()?; + self.client.set_state(ConnectionState::Configuration)?; + Ok(()) + }, + _ => Err(ServerError::UnexpectedState) + } + } + + /// Enter to Configuration from Play state + pub fn ping(&self) -> Result { + match self.state { + ConnectionState::Play => { + let time = SystemTime::now(); + self.client.conn().write_packet(&Packet::empty(0x36))?; + self.client.conn().read_packet()?; + Ok(SystemTime::now().duration_since(time).unwrap()) + }, + ConnectionState::Configuration => { + let time = SystemTime::now(); + self.client.conn().write_packet(&Packet::empty(0x05))?; + self.client.conn().read_packet()?; + Ok(SystemTime::now().duration_since(time).unwrap()) + }, + _ => Err(ServerError::UnexpectedState) + } + } pub fn disconnect(&self, reason: TextComponent) -> Result<(), ServerError> { let packet = match self.state { From fbcb1ce123204de339dd637a277ede1246d7fd1c Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 03:56:34 +0300 Subject: [PATCH 18/57] move write_packet and read_packet to methods --- src/server/event/mod.rs | 50 ----------------------------------- src/server/player/context.rs | 48 ++++++++++++++++++++++++++++++--- src/server/player/protocol.rs | 30 ++++++++++----------- src/server/protocol.rs | 30 ++++++++++----------- 4 files changed, 74 insertions(+), 84 deletions(-) diff --git a/src/server/event/mod.rs b/src/server/event/mod.rs index b7fb86b..1477b8e 100644 --- a/src/server/event/mod.rs +++ b/src/server/event/mod.rs @@ -17,56 +17,6 @@ macro_rules! generate_handlers { }; } -/// Отправляет пакет клиенту и проходит по пакет ханлдерам -/// Пример использования: -/// -/// write_packet!(client, Handshake, packet); -/// -/// `Handshake` это режим подключения (типы ConnectionState) -#[macro_export] -macro_rules! write_packet { - ($client:expr, $state:ident, $packet:expr) => { - { - let mut packet = $packet; - let mut cancelled = false; - for handler in $client.server.packet_handlers( - |o| o.on_outcoming_packet_priority() - ).iter() { - handler.on_outcoming_packet($client.clone(), &mut packet, &mut cancelled, crate::server::protocol::ConnectionState::$state)?; - packet.get_mut().set_position(0); - } - if !cancelled { - $client.conn().write_packet(&packet)?; - } - } - }; -} - -/// Читает пакет от клиента и проходит по пакет ханлдерам -/// Пример использования: -/// -/// let packet = read_packet!(client, Handshake); -/// -/// `Handshake` это режим подключения (типы ConnectionState) -#[macro_export] -macro_rules! read_packet { - ($client:expr, $state:ident) => { - loop { - let mut packet = $client.conn().read_packet()?; - let mut cancelled = false; - for handler in $client.server.packet_handlers( - |o| o.on_incoming_packet_priority() - ).iter() { - handler.on_incoming_packet($client.clone(), &mut packet, &mut cancelled, crate::server::protocol::ConnectionState::$state)?; - packet.get_mut().set_position(0); - } - if !cancelled { - break packet; - } - } - }; -} - /// Пример использования: /// /// trigger_event!(client, status, &mut response, state); diff --git a/src/server/player/context.rs b/src/server/player/context.rs index ce0f5a7..b72c4ce 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -1,6 +1,6 @@ -use std::{hash::Hash, net::{SocketAddr, TcpStream}, sync::{Arc, RwLock, RwLockWriteGuard}}; +use std::{hash::Hash, net::{SocketAddr, TcpStream}, sync::{Arc, RwLock}}; -use rust_mc_proto::MinecraftConnection; +use rust_mc_proto::{MinecraftConnection, Packet}; use uuid::Uuid; use crate::server::{context::ServerContext, protocol::ConnectionState, ServerError}; @@ -91,8 +91,48 @@ impl ClientContext { self.state.read().unwrap().clone() } - pub fn conn(self: &Arc) -> RwLockWriteGuard<'_, MinecraftConnection> { - self.conn.write().unwrap() + pub fn write_packet(self: &Arc, packet: &Packet) -> Result<(), ServerError> { + let state = self.state(); + let mut packet = packet.clone(); + let mut cancelled = false; + for handler in self.server.packet_handlers( + |o| o.on_outcoming_packet_priority() + ).iter() { + handler.on_outcoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + self.conn.write().unwrap().write_packet(&packet)?; + } + Ok(()) + } + + pub fn read_packet(self: &Arc) -> Result { + let state = self.state(); + + let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер + + loop { + let mut packet = conn.read_packet()?; + let mut cancelled = false; + for handler in self.server.packet_handlers( + |o| o.on_incoming_packet_priority() + ).iter() { + handler.on_incoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + break Ok(packet); + } + } + } + + pub fn close(self: &Arc) { + self.conn.write().unwrap().close(); + } + + pub fn set_compression(self: &Arc, threshold: Option) { + self.conn.write().unwrap().set_compression(threshold); } pub fn protocol_helper(self: &Arc) -> ProtocolHelper { diff --git a/src/server/player/protocol.rs b/src/server/player/protocol.rs index e4efb2a..1fdfcf1 100644 --- a/src/server/player/protocol.rs +++ b/src/server/player/protocol.rs @@ -31,8 +31,8 @@ impl ProtocolHelper { pub fn leave_configuration(&self) -> Result<(), ServerError> { match self.state { ConnectionState::Configuration => { - self.client.conn().write_packet(&Packet::empty(0x03))?; - self.client.conn().read_packet()?; + self.client.write_packet(&Packet::empty(0x03))?; + self.client.read_packet()?; self.client.set_state(ConnectionState::Play)?; Ok(()) }, @@ -44,8 +44,8 @@ impl ProtocolHelper { pub fn enter_configuration(&self) -> Result<(), ServerError> { match self.state { ConnectionState::Play => { - self.client.conn().write_packet(&Packet::empty(0x6F))?; - self.client.conn().read_packet()?; + self.client.write_packet(&Packet::empty(0x6F))?; + self.client.read_packet()?; self.client.set_state(ConnectionState::Configuration)?; Ok(()) }, @@ -58,14 +58,14 @@ impl ProtocolHelper { match self.state { ConnectionState::Play => { let time = SystemTime::now(); - self.client.conn().write_packet(&Packet::empty(0x36))?; - self.client.conn().read_packet()?; + self.client.write_packet(&Packet::empty(0x36))?; + self.client.read_packet()?; Ok(SystemTime::now().duration_since(time).unwrap()) }, ConnectionState::Configuration => { let time = SystemTime::now(); - self.client.conn().write_packet(&Packet::empty(0x05))?; - self.client.conn().read_packet()?; + self.client.write_packet(&Packet::empty(0x05))?; + self.client.read_packet()?; Ok(SystemTime::now().duration_since(time).unwrap()) }, _ => Err(ServerError::UnexpectedState) @@ -89,11 +89,11 @@ impl ProtocolHelper { packet }, _ => { - self.client.conn().close(); + self.client.close(); return Ok(()) }, }; - self.client.conn().write_packet(&packet)?; + self.client.write_packet(&packet)?; Ok(()) } @@ -103,9 +103,9 @@ impl ProtocolHelper { ConnectionState::Configuration => { let mut packet = Packet::empty(0x00); packet.write_string(id)?; - self.client.conn().write_packet(&packet)?; + self.client.write_packet(&packet)?; - let mut packet = self.client.conn().read_packet()?; + let mut packet = self.client.read_packet()?; packet.read_string()?; let data = if packet.read_boolean()? { let n = packet.read_usize_varint()?; @@ -128,9 +128,9 @@ impl ProtocolHelper { packet.write_varint(id)?; packet.write_string(channel)?; packet.write_bytes(data)?; - self.client.conn().write_packet(&packet)?; + self.client.write_packet(&packet)?; - let mut packet = self.client.conn().read_packet()?; + let mut packet = self.client.read_packet()?; let identifier = packet.read_varint()?; let data = if packet.read_boolean()? { let mut data = Vec::new(); @@ -154,7 +154,7 @@ impl ProtocolHelper { }; packet.write_string(channel)?; packet.write_bytes(data)?; - self.client.conn().write_packet(&packet)?; + self.client.write_packet(&packet)?; Ok(()) } } \ No newline at end of file diff --git a/src/server/protocol.rs b/src/server/protocol.rs index 38bcac8..0423e69 100644 --- a/src/server/protocol.rs +++ b/src/server/protocol.rs @@ -3,7 +3,7 @@ use std::{io::Read, sync::Arc}; use super::{player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, ServerError}; use rust_mc_proto::{DataReader, DataWriter, Packet}; -use crate::{read_packet, server::data::text_component::TextComponent, trigger_event, write_packet}; +use crate::{server::data::text_component::TextComponent, trigger_event}; #[derive(Debug, Clone)] pub enum ConnectionState { @@ -21,7 +21,7 @@ pub fn handle_connection( // Получение пакетов производится через client.conn(), // ВАЖНО: не помещать сам client.conn() в переменные, // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = read_packet!(client, Handshake); + let mut packet = client.read_packet()?; if packet.id() != 0x00 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); @@ -40,7 +40,7 @@ pub fn handle_connection( loop { // Чтение запроса - let packet = read_packet!(client, Status); + let packet = client.read_packet()?; match packet.id() { 0x00 => { // Запрос статуса @@ -61,10 +61,10 @@ pub fn handle_connection( // Отправка статуса packet.write_string(&status)?; - write_packet!(client, Status, packet); + client.write_packet(&packet)?; }, 0x01 => { // Пинг - write_packet!(client, Status, packet); + client.write_packet(&packet)?; // Просто отправляем этот же пакет обратно // ID такой-же, содержание тоже, так почему бы и нет? }, @@ -78,7 +78,7 @@ pub fn handle_connection( client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login // Читаем пакет Login Start - let mut packet = read_packet!(client, Login); + let mut packet = client.read_packet()?; let name = packet.read_string()?; let uuid = packet.read_uuid()?; @@ -91,18 +91,18 @@ pub fn handle_connection( // Отправляем пакет Set Compression если сжатие указано if let Some(threshold) = client.server.config.server.compression_threshold { - write_packet!(client, Login, Packet::build(0x03, |p| p.write_usize_varint(threshold))?); - client.conn().set_compression(Some(threshold)); // Устанавливаем сжатие на соединении + client.write_packet(&Packet::build(0x03, |p| p.write_usize_varint(threshold))?)?; + client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении } // Отправка пакета Login Success - write_packet!(client, Login, Packet::build(0x02, |p| { + client.write_packet(&Packet::build(0x02, |p| { p.write_uuid(&uuid)?; p.write_string(&name)?; p.write_varint(0) - })?); + })?)?; - let packet = read_packet!(client, Login); + let packet = client.read_packet()?; if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged"))); @@ -113,7 +113,7 @@ pub fn handle_connection( // Получение бренда клиента из Serverbound Plugin Message // Identifier канала откуда берется бренд: minecraft:brand let brand = loop { - let mut packet = read_packet!(client, Configuration); + let mut packet = client.read_packet()?; if packet.id() == 0x02 { // Пакет Serverbound Plugin Message let identifier = packet.read_string()?; @@ -131,7 +131,7 @@ pub fn handle_connection( }; }; - let mut packet = read_packet!(client, Configuration); + let mut packet = client.read_packet()?; // Пакет Client Information if packet.id() != 0x00 { @@ -163,9 +163,9 @@ pub fn handle_connection( // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото - write_packet!(client, Configuration, Packet::empty(0x03)); + client.write_packet(&Packet::empty(0x03))?; - let packet = read_packet!(client, Configuration); + let packet = client.read_packet()?; if packet.id() != 0x03 { return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration"))); From 0c9f9dbf0cec0b4808a5cd47a14ffa6c4fce49fd Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 04:19:46 +0300 Subject: [PATCH 19/57] packet id constants --- src/server/mod.rs | 2 +- src/server/player/context.rs | 11 ++++- src/server/protocol.rs | 94 ++++++++++++++++++------------------ 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 2935fcd..b8d427d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -17,7 +17,7 @@ pub mod protocol; // Ошибки сервера #[derive(Debug)] pub enum ServerError { - UnknownPacket(String), // Неизвестный пакет, в строке указана ситуация в которой он неизвестен + UnexpectedPacket, // Неожиданный пакет Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection SerTextComponent, // Ошибка при сериализации текст-компонента diff --git a/src/server/player/context.rs b/src/server/player/context.rs index b72c4ce..c4271c0 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -107,7 +107,7 @@ impl ClientContext { Ok(()) } - pub fn read_packet(self: &Arc) -> Result { + pub fn read_any_packet(self: &Arc) -> Result { let state = self.state(); let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер @@ -127,6 +127,15 @@ impl ClientContext { } } + pub fn read_packet(self: &Arc, id: u8) -> Result { + let packet = self.read_any_packet()?; + if packet.id() != id { + Err(ServerError::UnexpectedPacket) + } else { + Ok(packet) + } + } + pub fn close(self: &Arc) { self.conn.write().unwrap().close(); } diff --git a/src/server/protocol.rs b/src/server/protocol.rs index 0423e69..a5a75ce 100644 --- a/src/server/protocol.rs +++ b/src/server/protocol.rs @@ -5,6 +5,23 @@ use rust_mc_proto::{DataReader, DataWriter, Packet}; use crate::{server::data::text_component::TextComponent, trigger_event}; +// Тут будут все айди пакетов + +pub const HANDSHAKE_ID: u8 = 0x00; +pub const STATUS_REQUEST_ID: u8 = 0x00; +pub const STATUS_RESPONSE_ID: u8 = 0x00; +pub const STATUS_PING_REQUEST_ID: u8 = 0x01; +pub const STATUS_PING_RESPONSE_ID: u8 = 0x01; +pub const LOGIN_START_ID: u8 = 0x00; +pub const LOGIN_SET_COMPRESSION_ID: u8 = 0x03; +pub const LOGIN_SUCCESS_ID: u8 = 0x02; +pub const LOGIN_ACKNOWLEDGED_ID: u8 = 0x03; +pub const CONFIGURATION_SERVERBOUND_PLUGIN_MESSAGE_ID: u8 = 0x02; +pub const CONFIGURATION_CLIENT_INFORMATION_ID: u8 = 0x00; +pub const CONFIGURATION_FINISH_ID: u8 = 0x03; +pub const CONFIGURATION_ACKNOWLEDGE_FINISH_ID: u8 = 0x03; + + #[derive(Debug, Clone)] pub enum ConnectionState { Handshake, @@ -21,11 +38,7 @@ pub fn handle_connection( // Получение пакетов производится через client.conn(), // ВАЖНО: не помещать сам client.conn() в переменные, // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = client.read_packet()?; - - if packet.id() != 0x00 { - return Err(ServerError::UnknownPacket(format!("Неизвестный пакет рукопожатия"))); - } // Айди пакета не рукопожатное - выходим из функции + let mut packet = client.read_packet(HANDSHAKE_ID)?; let protocol_version = packet.read_varint()?; // Получаем версия протокола, может быть отрицательным если наш клиент дэбил let server_address = packet.read_string()?; // Получаем домен/адрес сервера к которому пытается подключиться клиент, например "play.example.com", а не айпи @@ -40,11 +53,11 @@ pub fn handle_connection( loop { // Чтение запроса - let packet = client.read_packet()?; + let mut packet = client.read_any_packet()?; match packet.id() { - 0x00 => { // Запрос статуса - let mut packet = Packet::empty(0x00); + STATUS_REQUEST_ID => { // Запрос статуса + let mut packet = Packet::empty(STATUS_RESPONSE_ID); // Дефолтный статус let mut status = "{ @@ -63,13 +76,16 @@ pub fn handle_connection( client.write_packet(&packet)?; }, - 0x01 => { // Пинг + STATUS_PING_REQUEST_ID => { // Пинг + // Раньше мы просто отправляли ему его-же пакет, но сейчас, + // С приходом к власти констант айди-пакетов, нам приходится делать такое непотребство + let timestamp = packet.read_long()?; + let mut packet = Packet::empty(STATUS_PING_RESPONSE_ID); + packet.write_long(timestamp)?; client.write_packet(&packet)?; - // Просто отправляем этот же пакет обратно - // ID такой-же, содержание тоже, так почему бы и нет? }, _ => { - return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при чтении запросов статуса"))); + return Err(ServerError::UnexpectedPacket); } } } @@ -78,7 +94,7 @@ pub fn handle_connection( client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login // Читаем пакет Login Start - let mut packet = client.read_packet()?; + let mut packet = client.read_packet(LOGIN_START_ID)?; let name = packet.read_string()?; let uuid = packet.read_uuid()?; @@ -91,52 +107,39 @@ pub fn handle_connection( // Отправляем пакет Set Compression если сжатие указано if let Some(threshold) = client.server.config.server.compression_threshold { - client.write_packet(&Packet::build(0x03, |p| p.write_usize_varint(threshold))?)?; + client.write_packet(&Packet::build(LOGIN_SET_COMPRESSION_ID, |p| p.write_usize_varint(threshold))?)?; client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении } // Отправка пакета Login Success - client.write_packet(&Packet::build(0x02, |p| { + client.write_packet(&Packet::build(LOGIN_SUCCESS_ID, |p| { p.write_uuid(&uuid)?; p.write_string(&name)?; p.write_varint(0) })?)?; - let packet = client.read_packet()?; - - if packet.id() != 0x03 { - return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Login Acknowledged"))); - } + client.read_packet(LOGIN_ACKNOWLEDGED_ID)?; // Пакет Login Acknowledged client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration // Получение бренда клиента из Serverbound Plugin Message // Identifier канала откуда берется бренд: minecraft:brand let brand = loop { - let mut packet = client.read_packet()?; + let mut packet = client.read_packet(CONFIGURATION_SERVERBOUND_PLUGIN_MESSAGE_ID)?; // Пакет Serverbound Plugin Message - if packet.id() == 0x02 { // Пакет Serverbound Plugin Message - let identifier = packet.read_string()?; + let identifier = packet.read_string()?; - let mut data = Vec::new(); - packet.get_mut().read_to_end(&mut data).unwrap(); + 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 { - trigger_event!(client, plugin_message, &identifier, &data); - } + if identifier == "minecraft:brand" { + break String::from_utf8_lossy(&data).to_string(); } else { - return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Serverbound Plugin Message"))); - }; + trigger_event!(client, plugin_message, &identifier, &data); + } }; - let mut packet = client.read_packet()?; - - // Пакет Client Information - if packet.id() != 0x00 { - return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Client Information"))); - } + let mut packet = client.read_packet(CONFIGURATION_CLIENT_INFORMATION_ID)?; // Пакет Client Information let locale = packet.read_string()?; // for example: en_us let view_distance = packet.read_signed_byte()?; // client-side render distance in chunks @@ -163,22 +166,17 @@ pub fn handle_connection( // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото - client.write_packet(&Packet::empty(0x03))?; - - let packet = client.read_packet()?; - - if packet.id() != 0x03 { - return Err(ServerError::UnknownPacket(format!("Неизвестный пакет при ожидании Acknowledge Finish Configuration"))); - } + client.write_packet(&Packet::empty(CONFIGURATION_FINISH_ID))?; + client.read_packet(CONFIGURATION_ACKNOWLEDGE_FINISH_ID)?; client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play // Дальше работаем с режимом игры handle_play_state(client)?; }, - _ => { - return Err(ServerError::UnknownPacket(format!("Неизвестный NextState при рукопожатии"))); - } // Тип подключения не рукопожатный + _ => { // Тип подключения не рукопожатный + return Err(ServerError::UnexpectedPacket); + } } Ok(()) From af6c1ef3b826ee265d05af996db86ab123376561 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 16:42:55 +0300 Subject: [PATCH 20/57] packet id constants remake --- .gitignore | 5 +- parse_ids.py | 99 +++++++ shell.nix | 15 + src/server/mod.rs | 2 +- src/server/player/protocol.rs | 30 +- .../{protocol.rs => protocol/handler.rs} | 68 +---- src/server/protocol/id.rs | 275 ++++++++++++++++++ src/server/protocol/mod.rs | 14 + src/server/protocol/play.rs | 16 + 9 files changed, 454 insertions(+), 70 deletions(-) create mode 100644 parse_ids.py create mode 100644 shell.nix rename src/server/{protocol.rs => protocol/handler.rs} (71%) create mode 100644 src/server/protocol/id.rs create mode 100644 src/server/protocol/mod.rs create mode 100644 src/server/protocol/play.rs diff --git a/.gitignore b/.gitignore index c46d3d6..153bca6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/target -server.toml \ No newline at end of file +target/ +server.toml +Packets.html \ No newline at end of file diff --git a/parse_ids.py b/parse_ids.py new file mode 100644 index 0000000..b3770de --- /dev/null +++ b/parse_ids.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +# Использование: +# +# ./parse_ids.py < Packets.html > src/server/protocol/id.rs + +import sys +from bs4 import BeautifulSoup +import re + +BOUNDS = ["clientbound", "serverbound"] +MODES = { + "#Handshaking": "handshake", + "#Status": "status", + "#Login": "login", + "#Play": "play", + "#Configuration": "configuration" +} + +def sanitize_name(name, bound, mode): + name = (" " + name.lower() + " ").replace(" " + bound.lower() + " ", "").replace(" " + mode.lower() + " ", "") \ + if name.lower() != bound.lower() and name.lower() != mode.lower() else name + name = re.sub(r'\(.*?\)', '', name) + name = name.strip() + name = name.upper() + name = name.replace(' ', '_') + return name + +def parse_packet_id_table(span): + table = span.parent.find_next_sibling("table") + if not table: + return None + rows = table.find_all("tr") + if len(rows) < 2: + return None + code_tag = rows[1].find("td").find("code") + if not code_tag: + return None + return code_tag.text.strip() + +def main(): + soup = BeautifulSoup(sys.stdin.read(), "html.parser") + + print("/*\n") + print(" Generated with parse_ids.py \n") + print(" */\n") + + toc = soup.select_one("#toc") + + for bound_type in BOUNDS: + print(f"pub mod {bound_type} {{") + + for li in toc.find("ul").find_all("li", recursive=False): + a = li.find("a", href=True) + if not a or a["href"] not in MODES: + continue + + mode = MODES[a["href"]] + ul = li.find("ul", recursive=False) + if not ul: + continue + lis = ul.find_all("li", recursive=False) + + mode_size = 0 + + try: + bound_list = lis[BOUNDS.index(bound_type)].find_all("li") + except KeyError: + continue + + for item in bound_list: + packet_a = item.find("a", href=True) + if not packet_a or not packet_a["href"].startswith("#"): + continue + + href = packet_a["href"].lstrip("#") + span = soup.find("span", id=href) + if not span: + continue + + packet_id = parse_packet_id_table(span) + if not packet_id: + continue + + name = sanitize_name(" ".join(packet_a.text.split(" ")[1:]), bound_type, mode) + if len(name) > 0: + mode_size += 1 + + if mode_size == 1: + print(f" pub mod {mode} {{") + print(f" pub const {name}: u8 = {packet_id};") + + if mode_size > 0: + print(" }\n") + + print("}\n") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..d4ddaeb --- /dev/null +++ b/shell.nix @@ -0,0 +1,15 @@ + +with import { }; + +mkShell { + nativeBuildInputs = [ + direnv + rustc + cargo + python3 + python3Packages.beautifulsoup4 + python3Packages.requests + ]; + + NIX_ENFORCE_PURITY = true; +} \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs index b8d427d..9aec631 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,7 +4,7 @@ use context::ServerContext; use ignore_result::Ignore; use log::{error, info}; use player::context::ClientContext; -use protocol::handle_connection; +use protocol::handler::handle_connection; use rust_mc_proto::{MinecraftConnection, ProtocolError}; pub mod config; diff --git a/src/server/player/protocol.rs b/src/server/player/protocol.rs index 1fdfcf1..b73d826 100644 --- a/src/server/player/protocol.rs +++ b/src/server/player/protocol.rs @@ -2,7 +2,7 @@ use std::{io::Read, sync::Arc, time::{Duration, SystemTime}}; use rust_mc_proto::{DataReader, DataWriter, Packet}; -use crate::server::{data::text_component::TextComponent, data::ReadWriteNBT, protocol::ConnectionState, ServerError}; +use crate::server::{data::{text_component::TextComponent, ReadWriteNBT}, protocol::{id::{clientbound, serverbound}, *}, ServerError}; use super::context::ClientContext; @@ -31,8 +31,8 @@ impl ProtocolHelper { pub fn leave_configuration(&self) -> Result<(), ServerError> { match self.state { ConnectionState::Configuration => { - self.client.write_packet(&Packet::empty(0x03))?; - self.client.read_packet()?; + self.client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + self.client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; self.client.set_state(ConnectionState::Play)?; Ok(()) }, @@ -44,8 +44,8 @@ impl ProtocolHelper { pub fn enter_configuration(&self) -> Result<(), ServerError> { match self.state { ConnectionState::Play => { - self.client.write_packet(&Packet::empty(0x6F))?; - self.client.read_packet()?; + self.client.write_packet(&Packet::empty(clientbound::play::START_CONFIGURATION))?; + self.client.read_packet(serverbound::play::ACKNOWLEDGE_CONFIGURATION)?; self.client.set_state(ConnectionState::Configuration)?; Ok(()) }, @@ -58,14 +58,14 @@ impl ProtocolHelper { match self.state { ConnectionState::Play => { let time = SystemTime::now(); - self.client.write_packet(&Packet::empty(0x36))?; - self.client.read_packet()?; + self.client.write_packet(&Packet::empty(clientbound::play::PING))?; + self.client.read_packet(serverbound::play::PONG)?; Ok(SystemTime::now().duration_since(time).unwrap()) }, ConnectionState::Configuration => { let time = SystemTime::now(); - self.client.write_packet(&Packet::empty(0x05))?; - self.client.read_packet()?; + self.client.write_packet(&Packet::empty(clientbound::configuration::PING))?; + self.client.read_packet(serverbound::configuration::PONG)?; Ok(SystemTime::now().duration_since(time).unwrap()) }, _ => Err(ServerError::UnexpectedState) @@ -101,11 +101,11 @@ impl ProtocolHelper { pub fn request_cookie(&self, id: &str) -> Result>, ServerError> { match self.state { ConnectionState::Configuration => { - let mut packet = Packet::empty(0x00); + let mut packet = Packet::empty(clientbound::configuration::COOKIE_REQUEST); packet.write_string(id)?; self.client.write_packet(&packet)?; - let mut packet = self.client.read_packet()?; + let mut packet = self.client.read_packet(serverbound::configuration::COOKIE_RESPONSE)?; packet.read_string()?; let data = if packet.read_boolean()? { let n = packet.read_usize_varint()?; @@ -124,13 +124,13 @@ impl ProtocolHelper { pub fn send_login_plugin_request(&self, id: i32, channel: &str, data: &[u8]) -> Result<(i32, Option>), ServerError> { match self.state { ConnectionState::Login => { - let mut packet = Packet::empty(0x04); + let mut packet = Packet::empty(clientbound::login::PLUGIN_REQUEST); packet.write_varint(id)?; packet.write_string(channel)?; packet.write_bytes(data)?; self.client.write_packet(&packet)?; - let mut packet = self.client.read_packet()?; + let mut packet = self.client.read_packet(serverbound::login::PLUGIN_RESPONSE)?; let identifier = packet.read_varint()?; let data = if packet.read_boolean()? { let mut data = Vec::new(); @@ -148,8 +148,8 @@ impl ProtocolHelper { 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), + ConnectionState::Configuration => Packet::empty(clientbound::configuration::PLUGIN_MESSAGE), + ConnectionState::Play => Packet::empty(clientbound::play::PLUGIN_MESSAGE), _ => return Err(ServerError::UnexpectedState) }; packet.write_string(channel)?; diff --git a/src/server/protocol.rs b/src/server/protocol/handler.rs similarity index 71% rename from src/server/protocol.rs rename to src/server/protocol/handler.rs index a5a75ce..336ad22 100644 --- a/src/server/protocol.rs +++ b/src/server/protocol/handler.rs @@ -1,35 +1,12 @@ use std::{io::Read, sync::Arc}; -use super::{player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, ServerError}; +use crate::server::{player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, ServerError}; use rust_mc_proto::{DataReader, DataWriter, Packet}; -use crate::{server::data::text_component::TextComponent, trigger_event}; +use crate::trigger_event; -// Тут будут все айди пакетов +use super::{id::*, play::handle_play_state, ConnectionState}; -pub const HANDSHAKE_ID: u8 = 0x00; -pub const STATUS_REQUEST_ID: u8 = 0x00; -pub const STATUS_RESPONSE_ID: u8 = 0x00; -pub const STATUS_PING_REQUEST_ID: u8 = 0x01; -pub const STATUS_PING_RESPONSE_ID: u8 = 0x01; -pub const LOGIN_START_ID: u8 = 0x00; -pub const LOGIN_SET_COMPRESSION_ID: u8 = 0x03; -pub const LOGIN_SUCCESS_ID: u8 = 0x02; -pub const LOGIN_ACKNOWLEDGED_ID: u8 = 0x03; -pub const CONFIGURATION_SERVERBOUND_PLUGIN_MESSAGE_ID: u8 = 0x02; -pub const CONFIGURATION_CLIENT_INFORMATION_ID: u8 = 0x00; -pub const CONFIGURATION_FINISH_ID: u8 = 0x03; -pub const CONFIGURATION_ACKNOWLEDGE_FINISH_ID: u8 = 0x03; - - -#[derive(Debug, Clone)] -pub enum ConnectionState { - Handshake, - Status, - Login, - Configuration, - Play -} pub fn handle_connection( client: Arc, // Контекст клиента @@ -38,7 +15,7 @@ pub fn handle_connection( // Получение пакетов производится через client.conn(), // ВАЖНО: не помещать сам client.conn() в переменные, // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = client.read_packet(HANDSHAKE_ID)?; + let mut packet = client.read_packet(serverbound::handshake::HANDSHAKE)?; let protocol_version = packet.read_varint()?; // Получаем версия протокола, может быть отрицательным если наш клиент дэбил let server_address = packet.read_string()?; // Получаем домен/адрес сервера к которому пытается подключиться клиент, например "play.example.com", а не айпи @@ -56,8 +33,8 @@ pub fn handle_connection( let mut packet = client.read_any_packet()?; match packet.id() { - STATUS_REQUEST_ID => { // Запрос статуса - let mut packet = Packet::empty(STATUS_RESPONSE_ID); + serverbound::status::REQUEST => { // Запрос статуса + let mut packet = Packet::empty(clientbound::status::RESPONSE); // Дефолтный статус let mut status = "{ @@ -76,11 +53,11 @@ pub fn handle_connection( client.write_packet(&packet)?; }, - STATUS_PING_REQUEST_ID => { // Пинг + serverbound::status::PING_REQUEST => { // Пинг // Раньше мы просто отправляли ему его-же пакет, но сейчас, // С приходом к власти констант айди-пакетов, нам приходится делать такое непотребство let timestamp = packet.read_long()?; - let mut packet = Packet::empty(STATUS_PING_RESPONSE_ID); + let mut packet = Packet::empty(clientbound::status::PONG_RESPONSE); packet.write_long(timestamp)?; client.write_packet(&packet)?; }, @@ -94,7 +71,7 @@ pub fn handle_connection( client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login // Читаем пакет Login Start - let mut packet = client.read_packet(LOGIN_START_ID)?; + let mut packet = client.read_packet(serverbound::login::START)?; let name = packet.read_string()?; let uuid = packet.read_uuid()?; @@ -107,25 +84,25 @@ pub fn handle_connection( // Отправляем пакет Set Compression если сжатие указано if let Some(threshold) = client.server.config.server.compression_threshold { - client.write_packet(&Packet::build(LOGIN_SET_COMPRESSION_ID, |p| p.write_usize_varint(threshold))?)?; + client.write_packet(&Packet::build(clientbound::login::SET_COMPRESSION, |p| p.write_usize_varint(threshold))?)?; client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении } // Отправка пакета Login Success - client.write_packet(&Packet::build(LOGIN_SUCCESS_ID, |p| { + client.write_packet(&Packet::build(clientbound::login::SUCCESS, |p| { p.write_uuid(&uuid)?; p.write_string(&name)?; p.write_varint(0) })?)?; - client.read_packet(LOGIN_ACKNOWLEDGED_ID)?; // Пакет Login Acknowledged + client.read_packet(serverbound::login::ACKNOWLEDGED)?; // Пакет Login Acknowledged client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration // Получение бренда клиента из Serverbound Plugin Message // Identifier канала откуда берется бренд: minecraft:brand let brand = loop { - let mut packet = client.read_packet(CONFIGURATION_SERVERBOUND_PLUGIN_MESSAGE_ID)?; // Пакет Serverbound Plugin Message + let mut packet = client.read_packet(serverbound::configuration::PLUGIN_MESSAGE)?; // Пакет Serverbound Plugin Message let identifier = packet.read_string()?; @@ -139,7 +116,7 @@ pub fn handle_connection( } }; - let mut packet = client.read_packet(CONFIGURATION_CLIENT_INFORMATION_ID)?; // Пакет Client Information + let mut packet = client.read_packet(serverbound::configuration::CLIENT_INFORMATION)?; // Пакет Client Information let locale = packet.read_string()?; // for example: en_us let view_distance = packet.read_signed_byte()?; // client-side render distance in chunks @@ -166,8 +143,8 @@ pub fn handle_connection( // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото - client.write_packet(&Packet::empty(CONFIGURATION_FINISH_ID))?; - client.read_packet(CONFIGURATION_ACKNOWLEDGE_FINISH_ID)?; + client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play @@ -179,18 +156,5 @@ pub fn handle_connection( } } - Ok(()) -} - -// Отдельная функция для работы с самой игрой -pub fn handle_play_state( - client: Arc, // Контекст клиента -) -> Result<(), ServerError> { - - // Отключение игрока с сообщением - client.protocol_helper().disconnect(TextComponent::rainbow("server is in developement suka".to_string()))?; - - // TODO: Сделать отправку пакетов Play - Ok(()) } \ No newline at end of file diff --git a/src/server/protocol/id.rs b/src/server/protocol/id.rs new file mode 100644 index 0000000..fe7bab2 --- /dev/null +++ b/src/server/protocol/id.rs @@ -0,0 +1,275 @@ +/* + + Generated with parse_ids.py + + */ + +pub mod clientbound { + pub mod status { + pub const RESPONSE: u8 = 0x00; + pub const PONG_RESPONSE: u8 = 0x01; + } + + pub mod login { + pub const DISCONNECT: u8 = 0x00; + pub const ENCRYPTION_REQUEST: u8 = 0x01; + pub const SUCCESS: u8 = 0x02; + pub const SET_COMPRESSION: u8 = 0x03; + pub const PLUGIN_REQUEST: u8 = 0x04; + pub const COOKIE_REQUEST: u8 = 0x05; + } + + pub mod configuration { + pub const COOKIE_REQUEST: u8 = 0x00; + pub const PLUGIN_MESSAGE: u8 = 0x01; + pub const DISCONNECT: u8 = 0x02; + pub const FINISH: u8 = 0x03; + pub const KEEP_ALIVE: u8 = 0x04; + pub const PING: u8 = 0x05; + pub const RESET_CHAT: u8 = 0x06; + pub const REGISTRY_DATA: u8 = 0x07; + pub const REMOVE_RESOURCE_PACK: u8 = 0x08; + pub const ADD_RESOURCE_PACK: u8 = 0x09; + pub const STORE_COOKIE: u8 = 0x0A; + pub const TRANSFER: u8 = 0x0B; + pub const FEATURE_FLAGS: u8 = 0x0C; + pub const UPDATE_TAGS: u8 = 0x0D; + pub const KNOWN_PACKS: u8 = 0x0E; + pub const CUSTOM_REPORT_DETAILS: u8 = 0x0F; + pub const SERVER_LINKS: u8 = 0x10; + } + + pub mod play { + pub const BUNDLE_DELIMITER: u8 = 0x00; + pub const SPAWN_ENTITY: u8 = 0x01; + pub const ENTITY_ANIMATION: u8 = 0x02; + pub const AWARD_STATISTICS: u8 = 0x03; + pub const ACKNOWLEDGE_BLOCK_CHANGE: u8 = 0x04; + pub const SET_BLOCK_DESTROY_STAGE: u8 = 0x05; + pub const BLOCK_ENTITY_DATA: u8 = 0x06; + pub const BLOCK_ACTION: u8 = 0x07; + pub const BLOCK_UPDATE: u8 = 0x08; + pub const BOSS_BAR: u8 = 0x09; + pub const CHANGE_DIFFICULTY: u8 = 0x0A; + pub const CHUNK_BATCH_FINISHED: u8 = 0x0B; + pub const CHUNK_BATCH_START: u8 = 0x0C; + pub const CHUNK_BIOMES: u8 = 0x0D; + pub const CLEAR_TITLES: u8 = 0x0E; + pub const COMMAND_SUGGESTIONS_RESPONSE: u8 = 0x0F; + pub const COMMANDS: u8 = 0x10; + pub const CLOSE_CONTAINER: u8 = 0x11; + pub const SET_CONTAINER_CONTENT: u8 = 0x12; + pub const SET_CONTAINER_PROPERTY: u8 = 0x13; + pub const SET_CONTAINER_SLOT: u8 = 0x14; + pub const COOKIE_REQUEST: u8 = 0x15; + pub const SET_COOLDOWN: u8 = 0x16; + pub const CHAT_SUGGESTIONS: u8 = 0x17; + pub const PLUGIN_MESSAGE: u8 = 0x18; + pub const DAMAGE_EVENT: u8 = 0x19; + pub const DEBUG_SAMPLE: u8 = 0x1A; + pub const DELETE_MESSAGE: u8 = 0x1B; + pub const DISCONNECT: u8 = 0x1C; + pub const DISGUISED_CHAT_MESSAGE: u8 = 0x1D; + pub const ENTITY_EVENT: u8 = 0x1E; + pub const TELEPORT_ENTITY: u8 = 0x1F; + pub const EXPLOSION: u8 = 0x20; + pub const UNLOAD_CHUNK: u8 = 0x21; + pub const GAME_EVENT: u8 = 0x22; + pub const OPEN_HORSE_SCREEN: u8 = 0x23; + pub const HURT_ANIMATION: u8 = 0x24; + pub const INITIALIZE_WORLD_BORDER: u8 = 0x25; + pub const KEEP_ALIVE: u8 = 0x26; + pub const CHUNK_DATA_AND_UPDATE_LIGHT: u8 = 0x27; + pub const WORLD_EVENT: u8 = 0x28; + pub const PARTICLE: u8 = 0x29; + pub const UPDATE_LIGHT: u8 = 0x2A; + pub const LOGIN: u8 = 0x2B; + pub const MAP_DATA: u8 = 0x2C; + pub const MERCHANT_OFFERS: u8 = 0x2D; + pub const UPDATE_ENTITY_POSITION: u8 = 0x2E; + pub const UPDATE_ENTITY_POSITION_AND_ROTATION: u8 = 0x2F; + pub const MOVE_MINECART_ALONG_TRACK: u8 = 0x30; + pub const UPDATE_ENTITY_ROTATION: u8 = 0x31; + pub const MOVE_VEHICLE: u8 = 0x32; + pub const OPEN_BOOK: u8 = 0x33; + pub const OPEN_SCREEN: u8 = 0x34; + pub const OPEN_SIGN_EDITOR: u8 = 0x35; + pub const PING: u8 = 0x36; + pub const PING_RESPONSE: u8 = 0x37; + pub const PLACE_GHOST_RECIPE: u8 = 0x38; + pub const PLAYER_ABILITIES: u8 = 0x39; + pub const PLAYER_CHAT_MESSAGE: u8 = 0x3A; + pub const END_COMBAT: u8 = 0x3B; + pub const ENTER_COMBAT: u8 = 0x3C; + pub const COMBAT_DEATH: u8 = 0x3D; + pub const PLAYER_INFO_REMOVE: u8 = 0x3E; + pub const PLAYER_INFO_UPDATE: u8 = 0x3F; + pub const LOOK_AT: u8 = 0x40; + pub const SYNCHRONIZE_PLAYER_POSITION: u8 = 0x41; + pub const PLAYER_ROTATION: u8 = 0x42; + pub const RECIPE_BOOK_ADD: u8 = 0x43; + pub const RECIPE_BOOK_REMOVE: u8 = 0x44; + pub const RECIPE_BOOK_SETTINGS: u8 = 0x45; + pub const REMOVE_ENTITIES: u8 = 0x46; + pub const REMOVE_ENTITY_EFFECT: u8 = 0x47; + pub const RESET_SCORE: u8 = 0x48; + pub const REMOVE_RESOURCE_PACK: u8 = 0x49; + pub const ADD_RESOURCE_PACK: u8 = 0x4A; + pub const RESPAWN: u8 = 0x4B; + pub const SET_HEAD_ROTATION: u8 = 0x4C; + pub const UPDATE_SECTION_BLOCKS: u8 = 0x4D; + pub const SELECT_ADVANCEMENTS_TAB: u8 = 0x4E; + pub const SERVER_DATA: u8 = 0x4F; + pub const SET_ACTION_BAR_TEXT: u8 = 0x50; + pub const SET_BORDER_CENTER: u8 = 0x51; + pub const SET_BORDER_LERP_SIZE: u8 = 0x52; + pub const SET_BORDER_SIZE: u8 = 0x53; + pub const SET_BORDER_WARNING_DELAY: u8 = 0x54; + pub const SET_BORDER_WARNING_DISTANCE: u8 = 0x55; + pub const SET_CAMERA: u8 = 0x56; + pub const SET_CENTER_CHUNK: u8 = 0x57; + pub const SET_RENDER_DISTANCE: u8 = 0x58; + pub const SET_CURSOR_ITEM: u8 = 0x59; + pub const SET_DEFAULT_SPAWN_POSITION: u8 = 0x5A; + pub const DISPLAY_OBJECTIVE: u8 = 0x5B; + pub const SET_ENTITY_METADATA: u8 = 0x5C; + pub const LINK_ENTITIES: u8 = 0x5D; + pub const SET_ENTITY_VELOCITY: u8 = 0x5E; + pub const SET_EQUIPMENT: u8 = 0x5F; + pub const SET_EXPERIENCE: u8 = 0x60; + pub const SET_HEALTH: u8 = 0x61; + pub const SET_HELD_ITEM: u8 = 0x62; + pub const UPDATE_OBJECTIVES: u8 = 0x63; + pub const SET_PASSENGERS: u8 = 0x64; + pub const SET_PLAYER_INVENTORY_SLOT: u8 = 0x65; + pub const UPDATE_TEAMS: u8 = 0x66; + pub const UPDATE_SCORE: u8 = 0x67; + pub const SET_SIMULATION_DISTANCE: u8 = 0x68; + pub const SET_SUBTITLE_TEXT: u8 = 0x69; + pub const UPDATE_TIME: u8 = 0x6A; + pub const SET_TITLE_TEXT: u8 = 0x6B; + pub const SET_TITLE_ANIMATION_TIMES: u8 = 0x6C; + pub const ENTITY_SOUND_EFFECT: u8 = 0x6D; + pub const SOUND_EFFECT: u8 = 0x6E; + pub const START_CONFIGURATION: u8 = 0x6F; + pub const STOP_SOUND: u8 = 0x70; + pub const STORE_COOKIE: u8 = 0x71; + pub const SYSTEM_CHAT_MESSAGE: u8 = 0x72; + pub const SET_TAB_LIST_HEADER_AND_FOOTER: u8 = 0x73; + pub const TAG_QUERY_RESPONSE: u8 = 0x74; + pub const PICKUP_ITEM: u8 = 0x75; + pub const SYNCHRONIZE_VEHICLE_POSITION: u8 = 0x76; + pub const TEST_INSTANCE_BLOCK_STATUS: u8 = 0x77; + pub const SET_TICKING_STATE: u8 = 0x78; + pub const STEP_TICK: u8 = 0x79; + pub const TRANSFER: u8 = 0x7A; + pub const UPDATE_ADVANCEMENTS: u8 = 0x7B; + pub const UPDATE_ATTRIBUTES: u8 = 0x7C; + pub const ENTITY_EFFECT: u8 = 0x7D; + pub const UPDATE_RECIPES: u8 = 0x7E; + pub const UPDATE_TAGS: u8 = 0x7F; + pub const PROJECTILE_POWER: u8 = 0x80; + pub const CUSTOM_REPORT_DETAILS: u8 = 0x81; + pub const SERVER_LINKS: u8 = 0x82; + } + +} + +pub mod serverbound { + pub mod handshake { + pub const HANDSHAKE: u8 = 0x00; + } + + pub mod status { + pub const REQUEST: u8 = 0x00; + pub const PING_REQUEST: u8 = 0x01; + } + + pub mod login { + pub const START: u8 = 0x00; + pub const ENCRYPTION_RESPONSE: u8 = 0x01; + pub const PLUGIN_RESPONSE: u8 = 0x02; + pub const ACKNOWLEDGED: u8 = 0x03; + pub const COOKIE_RESPONSE: u8 = 0x04; + } + + pub mod configuration { + pub const CLIENT_INFORMATION: u8 = 0x00; + pub const COOKIE_RESPONSE: u8 = 0x01; + pub const PLUGIN_MESSAGE: u8 = 0x02; + pub const ACKNOWLEDGE_FINISH: u8 = 0x03; + pub const KEEP_ALIVE: u8 = 0x04; + pub const PONG: u8 = 0x05; + pub const RESOURCE_PACK_RESPONSE: u8 = 0x06; + pub const KNOWN_PACKS: u8 = 0x07; + } + + pub mod play { + pub const CONFIRM_TELEPORTATION: u8 = 0x00; + pub const QUERY_BLOCK_ENTITY_TAG: u8 = 0x01; + pub const BUNDLE_ITEM_SELECTED: u8 = 0x02; + pub const CHANGE_DIFFICULTY: u8 = 0x03; + pub const ACKNOWLEDGE_MESSAGE: u8 = 0x04; + pub const CHAT_COMMAND: u8 = 0x05; + pub const SIGNED_CHAT_COMMAND: u8 = 0x06; + pub const CHAT_MESSAGE: u8 = 0x07; + pub const PLAYER_SESSION: u8 = 0x08; + pub const CHUNK_BATCH_RECEIVED: u8 = 0x09; + pub const CLIENT_STATUS: u8 = 0x0A; + pub const CLIENT_TICK_END: u8 = 0x0B; + pub const CLIENT_INFORMATION: u8 = 0x0C; + pub const COMMAND_SUGGESTIONS_REQUEST: u8 = 0x0D; + pub const ACKNOWLEDGE_CONFIGURATION: u8 = 0x0E; + pub const CLICK_CONTAINER_BUTTON: u8 = 0x0F; + pub const CLICK_CONTAINER: u8 = 0x10; + pub const CLOSE_CONTAINER: u8 = 0x11; + pub const CHANGE_CONTAINER_SLOT_STATE: u8 = 0x12; + pub const COOKIE_RESPONSE: u8 = 0x13; + pub const PLUGIN_MESSAGE: u8 = 0x14; + pub const DEBUG_SAMPLE_SUBSCRIPTION: u8 = 0x15; + pub const EDIT_BOOK: u8 = 0x16; + pub const QUERY_ENTITY_TAG: u8 = 0x17; + pub const INTERACT: u8 = 0x18; + pub const JIGSAW_GENERATE: u8 = 0x19; + pub const KEEP_ALIVE: u8 = 0x1A; + pub const LOCK_DIFFICULTY: u8 = 0x1B; + pub const SET_PLAYER_POSITION: u8 = 0x1C; + pub const SET_PLAYER_POSITION_AND_ROTATION: u8 = 0x1D; + pub const SET_PLAYER_ROTATION: u8 = 0x1E; + pub const SET_PLAYER_MOVEMENT_FLAGS: u8 = 0x1F; + pub const MOVE_VEHICLE: u8 = 0x20; + pub const PADDLE_BOAT: u8 = 0x21; + pub const PICK_ITEM_FROM_BLOCK: u8 = 0x22; + pub const PICK_ITEM_FROM_ENTITY: u8 = 0x23; + pub const PING_REQUEST: u8 = 0x24; + pub const PLACE_RECIPE: u8 = 0x25; + pub const PLAYER_ABILITIES: u8 = 0x26; + pub const PLAYER_ACTION: u8 = 0x27; + pub const PLAYER_COMMAND: u8 = 0x28; + pub const PLAYER_INPUT: u8 = 0x29; + pub const PLAYER_LOADED: u8 = 0x2A; + pub const PONG: u8 = 0x2B; + pub const CHANGE_RECIPE_BOOK_SETTINGS: u8 = 0x2C; + pub const SET_SEEN_RECIPE: u8 = 0x2D; + pub const RENAME_ITEM: u8 = 0x2E; + pub const RESOURCE_PACK_RESPONSE: u8 = 0x2F; + pub const SEEN_ADVANCEMENTS: u8 = 0x30; + pub const SELECT_TRADE: u8 = 0x31; + pub const SET_BEACON_EFFECT: u8 = 0x32; + pub const SET_HELD_ITEM: u8 = 0x33; + pub const PROGRAM_COMMAND_BLOCK: u8 = 0x34; + pub const PROGRAM_COMMAND_BLOCK_MINECART: u8 = 0x35; + pub const SET_CREATIVE_MODE_SLOT: u8 = 0x36; + pub const PROGRAM_JIGSAW_BLOCK: u8 = 0x37; + pub const PROGRAM_STRUCTURE_BLOCK: u8 = 0x38; + pub const SET_TEST_BLOCK: u8 = 0x39; + pub const UPDATE_SIGN: u8 = 0x3A; + pub const SWING_ARM: u8 = 0x3B; + pub const TELEPORT_TO_ENTITY: u8 = 0x3C; + pub const TEST_INSTANCE_BLOCK_ACTION: u8 = 0x3D; + pub const USE_ITEM_ON: u8 = 0x3E; + pub const USE_ITEM: u8 = 0x3F; + } + +} + diff --git a/src/server/protocol/mod.rs b/src/server/protocol/mod.rs new file mode 100644 index 0000000..53ab91d --- /dev/null +++ b/src/server/protocol/mod.rs @@ -0,0 +1,14 @@ +pub mod id; +pub mod play; +pub mod handler; + + +#[derive(Debug, Clone)] +pub enum ConnectionState { + Handshake, + Status, + Login, + Configuration, + Play +} + diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs new file mode 100644 index 0000000..de5cff6 --- /dev/null +++ b/src/server/protocol/play.rs @@ -0,0 +1,16 @@ +use std::sync::Arc; + +use crate::server::{data::text_component::TextComponent, player::context::ClientContext, ServerError}; + +// Отдельная функция для работы с самой игрой +pub fn handle_play_state( + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + + // Отключение игрока с сообщением + client.protocol_helper().disconnect(TextComponent::rainbow("server is in developement suka".to_string()))?; + + // TODO: Сделать отправку пакетов Play + + Ok(()) +} \ No newline at end of file From 841fe69265f0ad52c8c57af6d35ca6cdfc011bf8 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 18:20:02 +0300 Subject: [PATCH 21/57] rename to helper --- src/server/player/context.rs | 2 +- src/server/player/{protocol.rs => helper.rs} | 0 src/server/player/mod.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/server/player/{protocol.rs => helper.rs} (100%) diff --git a/src/server/player/context.rs b/src/server/player/context.rs index c4271c0..08b8140 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -5,7 +5,7 @@ use uuid::Uuid; use crate::server::{context::ServerContext, protocol::ConnectionState, ServerError}; -use super::protocol::ProtocolHelper; +use super::helper::ProtocolHelper; // Клиент контекст // Должен быть обернут в Arc для передачи между потоками diff --git a/src/server/player/protocol.rs b/src/server/player/helper.rs similarity index 100% rename from src/server/player/protocol.rs rename to src/server/player/helper.rs diff --git a/src/server/player/mod.rs b/src/server/player/mod.rs index 922da05..bbad114 100644 --- a/src/server/player/mod.rs +++ b/src/server/player/mod.rs @@ -1,2 +1,2 @@ pub mod context; -pub mod protocol; \ No newline at end of file +pub mod helper; \ No newline at end of file From 800e2ba5cafad44a0038a4ad059b011d83e0c8bf Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 18:29:18 +0300 Subject: [PATCH 22/57] indent fix --- src/server/mod.rs | 66 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 9aec631..51aa3bd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -54,47 +54,47 @@ pub fn start_server(server: Arc) { // Биндим сервер где надо let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { error!("Не удалось забиндить сервер на {}", &server.config.bind.host); - return; - }; + return; + }; - info!("Сервер запущен на {}", &server.config.bind.host); + info!("Сервер запущен на {}", &server.config.bind.host); - while let Ok((stream, addr)) = listener.accept() { - let server = server.clone(); + while let Ok((stream, addr)) = listener.accept() { + let server = server.clone(); - thread::spawn(move || { - info!("Подключение: {}", addr); + 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(); + // Установка таймаутов на чтение и запись + // По умолчанию пусть будет 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 conn = MinecraftConnection::new(stream); - // Создаем контекст клиента - // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент - let client = Arc::new(ClientContext::new(server.clone(), conn)); + // Создаем контекст клиента + // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент + let client = Arc::new(ClientContext::new(server.clone(), conn)); - // Добавляем клиента в список клиентов сервера - // Используем адрес как ключ, врятли ipv4 будет нам врать - server.clients.insert(client.addr, client.clone()); + // Добавляем клиента в список клиентов сервера + // Используем адрес как ключ, врятли ipv4 будет нам врать + server.clients.insert(client.addr, client.clone()); - // Обработка подключения - // Если ошибка -> выводим - match handle_connection(client.clone()) { - Ok(_) => {}, - Err(ServerError::ConnectionClosed) => {}, - Err(error) => { - error!("Ошибка подключения: {error:?}"); - }, - }; + // Обработка подключения + // Если ошибка -> выводим + match handle_connection(client.clone()) { + Ok(_) => {}, + Err(ServerError::ConnectionClosed) => {}, + Err(error) => { + error!("Ошибка подключения: {error:?}"); + }, + }; - // Удаляем клиента из списка клиентов - server.clients.remove(&client.addr); + // Удаляем клиента из списка клиентов + server.clients.remove(&client.addr); - info!("Отключение: {}", addr); - }); - } + info!("Отключение: {}", addr); + }); + } } From d0968d1c344e4d9583738327b112fbaf990e5047 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 18:34:20 +0300 Subject: [PATCH 23/57] rustfmt --- .vscode/settings.json | 3 +- src/main.rs | 206 +++++++++++++----------- src/server/config.rs | 33 ++-- src/server/context.rs | 37 ++--- src/server/data/mod.rs | 2 +- src/server/data/text_component.rs | 49 +++--- src/server/event/mod.rs | 4 +- src/server/mod.rs | 45 +++--- src/server/player/context.rs | 59 ++++--- src/server/player/helper.rs | 95 +++++++---- src/server/player/mod.rs | 2 +- src/server/protocol/handler.rs | 253 ++++++++++++++++-------------- src/server/protocol/id.rs | 7 +- src/server/protocol/mod.rs | 6 +- src/server/protocol/play.rs | 21 +-- 15 files changed, 460 insertions(+), 362 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a2a5321..3ce68c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "editor.fontFamily": "Fira Code", - "editor.fontLigatures": true + "editor.fontLigatures": true, + "editor.tabSize": 2, } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e39d408..e34a01b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,18 +3,28 @@ use std::{env::args, path::PathBuf, sync::Arc}; use log::{debug, error, info}; use rust_mc_proto::Packet; use server::{ - config::Config, context::ServerContext, data::text_component::TextComponent, event::{Listener, PacketHandler}, player::context::ClientContext, protocol::ConnectionState, start_server, ServerError + ServerError, + config::Config, + context::ServerContext, + data::text_component::TextComponent, + event::{Listener, PacketHandler}, + player::context::ClientContext, + protocol::ConnectionState, + start_server, }; pub mod server; - struct ExampleListener; impl Listener for ExampleListener { - fn on_status(&self, client: Arc, response: &mut String) -> Result<(), ServerError> { - *response = format!( - "{{ + fn on_status( + &self, + client: Arc, + response: &mut String, + ) -> Result<(), ServerError> { + *response = format!( + "{{ \"version\": {{ \"name\": \"idk\", \"protocol\": {} @@ -33,112 +43,124 @@ impl Listener for ExampleListener { \"favicon\": \"data:image/png;base64,\", \"enforcesSecureChat\": false }}", - client.handshake().unwrap().protocol_version, - TextComponent::builder() - .text("Hello World! ") - .extra(vec![ - TextComponent::builder() - .text("Protocol: ") - .color("gold") - .extra(vec![ - TextComponent::builder() - .text(&client.handshake().unwrap().protocol_version.to_string()) - .underlined(true) - .build() - ]) - .build(), - TextComponent::builder() - .text("\nServer Addr: ") - .color("green") - .extra(vec![ - TextComponent::builder() - .text(&format!("{}:{}", - client.handshake().unwrap().server_address, - client.handshake().unwrap().server_port - )) - .underlined(true) - .build() - ]) - .build() - ]) - .build() - .as_json()? - ); + client.handshake().unwrap().protocol_version, + TextComponent::builder() + .text("Hello World! ") + .extra(vec![ + TextComponent::builder() + .text("Protocol: ") + .color("gold") + .extra(vec![ + TextComponent::builder() + .text(&client.handshake().unwrap().protocol_version.to_string()) + .underlined(true) + .build() + ]) + .build(), + TextComponent::builder() + .text("\nServer Addr: ") + .color("green") + .extra(vec![ + TextComponent::builder() + .text(&format!( + "{}:{}", + client.handshake().unwrap().server_address, + client.handshake().unwrap().server_port + )) + .underlined(true) + .build() + ]) + .build() + ]) + .build() + .as_json()? + ); - Ok(()) - } + Ok(()) + } } struct ExamplePacketHandler; impl PacketHandler for ExamplePacketHandler { - fn on_incoming_packet( - &self, - client: Arc, - packet: &mut Packet, - _: &mut bool, - state: ConnectionState - ) -> Result<(), ServerError> { - debug!("{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); + fn on_incoming_packet( + &self, + client: Arc, + packet: &mut Packet, + _: &mut bool, + state: ConnectionState, + ) -> Result<(), ServerError> { + debug!( + "{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", + client.addr.clone(), + packet.id(), + state, + packet.len() + ); - Ok(()) - } + Ok(()) + } - fn on_outcoming_packet( - &self, - client: Arc, - packet: &mut Packet, - _: &mut bool, - state: ConnectionState - ) -> Result<(), ServerError> { - debug!("{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", client.addr.clone(), packet.id(), state, packet.len()); + fn on_outcoming_packet( + &self, + client: Arc, + packet: &mut Packet, + _: &mut bool, + state: ConnectionState, + ) -> Result<(), ServerError> { + debug!( + "{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", + client.addr.clone(), + packet.id(), + state, + packet.len() + ); - Ok(()) - } + Ok(()) + } } - fn main() { - // Инициализируем логи - // Чтобы читать debug-логи, юзаем `RUST_LOG=debug cargo run` - colog::init(); + // Инициализируем логи + // Чтобы читать debug-логи, юзаем `RUST_LOG=debug cargo run` + colog::init(); - // Получение аргументов - let exec = args().next().expect("Неизвестная система"); - let args = args().skip(1).collect::>(); + // Получение аргументов + let exec = args().next().expect("Неизвестная система"); + let args = args().skip(1).collect::>(); - if args.len() > 1 { - info!("Использование: {exec} [путь до файла конфигурации]"); - return; - } + if args.len() > 1 { + info!("Использование: {exec} [путь до файла конфигурации]"); + return; + } - // Берем путь из аргумента либо по дефолту берем "./server.toml" - let config_path = PathBuf::from(args.get(0).unwrap_or(&"server.toml".to_string())); + // Берем путь из аргумента либо по дефолту берем "./server.toml" + let config_path = PathBuf::from(args.get(0).unwrap_or(&"server.toml".to_string())); - // Чтение конфига, если ошибка - выводим - let config = match Config::load_from_file(config_path) { - Some(config) => config, - None => { - error!("Ошибка чтения конфигурации"); - return; - }, - }; + // Чтение конфига, если ошибка - выводим + let config = match Config::load_from_file(config_path) { + Some(config) => config, + None => { + error!("Ошибка чтения конфигурации"); + return; + } + }; - // Делаем немутабельную потокобезопасную ссылку на конфиг - // Впринципе можно и просто клонировать сам конфиг в каждый сука поток ебать того рот ебать блять - // но мы этого делать не будем чтобы не было мемори лик лишнего - let config = Arc::new(config); + // Делаем немутабельную потокобезопасную ссылку на конфиг + // Впринципе можно и просто клонировать сам конфиг в каждый сука поток ебать того рот ебать блять + // но мы этого делать не будем чтобы не было мемори лик лишнего + let config = Arc::new(config); - // Создаем контекст сервера - // Передается во все подключения - let mut server = ServerContext::new(config); + // Создаем контекст сервера + // Передается во все подключения + let mut server = ServerContext::new(config); - server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера - server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера + server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера - // Бетонируем сервер контекст от изменений - let server = Arc::new(server); + // Бетонируем сервер контекст от изменений + let server = Arc::new(server); - // Запускаем сервер из специально отведенной под это дело функцией - start_server(server); + // Запускаем сервер из специально отведенной под это дело функцией + start_server(server); } diff --git a/src/server/config.rs b/src/server/config.rs index 90c5226..a2f803b 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -3,28 +3,39 @@ use std::{fs, path::PathBuf}; use serde::{Deserialize, Serialize}; use serde_default::DefaultFromSerde; - #[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] pub struct BindConfig { - #[serde(default = "default_host")] pub host: String, - #[serde(default = "default_timeout")] pub timeout: u64, + #[serde(default = "default_host")] + pub host: String, + #[serde(default = "default_timeout")] + pub timeout: u64, } #[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] pub struct ServerConfig { - #[serde(default)] pub online_mode: bool, - #[serde(default = "default_compression")] pub compression_threshold: Option, + #[serde(default)] + pub online_mode: bool, + #[serde(default = "default_compression")] + pub compression_threshold: Option, } #[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] pub struct Config { - #[serde(default)] pub bind: BindConfig, - #[serde(default)] pub server: ServerConfig, + #[serde(default)] + pub bind: BindConfig, + #[serde(default)] + pub server: ServerConfig, } -fn default_host() -> String { "127.0.0.1:25565".to_string() } -fn default_timeout() -> u64 { 5 } -fn default_compression() -> Option { Some(256) } +fn default_host() -> String { + "127.0.0.1:25565".to_string() +} +fn default_timeout() -> u64 { + 5 +} +fn default_compression() -> Option { + Some(256) +} impl Config { pub fn load_from_file(path: PathBuf) -> Option { @@ -38,4 +49,4 @@ impl Config { let table = toml::from_str::(&content).ok()?; Some(table) } -} \ No newline at end of file +} diff --git a/src/server/context.rs b/src/server/context.rs index 9814cd4..f54c795 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -4,7 +4,11 @@ use dashmap::DashMap; use itertools::Itertools; use uuid::Uuid; -use super::{config::Config, event::{Listener, PacketHandler}, player::context::ClientContext}; +use super::{ + config::Config, + event::{Listener, PacketHandler}, + player::context::ClientContext, +}; // Контекст сервера // Должен быть обернут в Arc для передачи между потоками @@ -12,7 +16,7 @@ pub struct ServerContext { pub config: Arc, pub clients: DashMap>, listeners: Vec>, - handlers: Vec> + handlers: Vec>, } impl ServerContext { @@ -21,12 +25,13 @@ impl ServerContext { config, listeners: Vec::new(), handlers: Vec::new(), - clients: DashMap::new() + clients: DashMap::new(), } } pub fn get_player_by_uuid(self: &Arc, uuid: Uuid) -> Option> { - self.clients.iter() + self.clients + .iter() .find(|o| { let info = o.player_info(); if let Some(info) = info { @@ -39,7 +44,8 @@ impl ServerContext { } pub fn get_player_by_name(self: &Arc, name: &str) -> Option> { - self.clients.iter() + self.clients + .iter() .find(|o| { let info = o.player_info(); if let Some(info) = info { @@ -52,7 +58,8 @@ impl ServerContext { } pub fn players(self: &Arc) -> Vec> { - self.clients.iter() + self.clients + .iter() .filter(|o| o.player_info().is_some()) .map(|o| o.clone()) .collect() @@ -66,24 +73,18 @@ impl ServerContext { self.listeners.push(listener); } - pub fn packet_handlers( - self: &Arc, - sort_by: F - ) -> Vec<&Box> - where + pub fn packet_handlers(self: &Arc, sort_by: F) -> Vec<&Box> + where K: Ord, - F: FnMut(&&Box) -> K + F: FnMut(&&Box) -> K, { self.handlers.iter().sorted_by_key(sort_by).collect_vec() } - pub fn listeners( - self: &Arc, - sort_by: F - ) -> Vec<&Box> - where + pub fn listeners(self: &Arc, sort_by: F) -> Vec<&Box> + where K: Ord, - F: FnMut(&&Box) -> K + F: FnMut(&&Box) -> K, { self.listeners.iter().sorted_by_key(sort_by).collect_vec() } diff --git a/src/server/data/mod.rs b/src/server/data/mod.rs index 540fa53..1e887b3 100644 --- a/src/server/data/mod.rs +++ b/src/server/data/mod.rs @@ -8,4 +8,4 @@ pub mod text_component; pub trait ReadWriteNBT: DataReader + DataWriter { fn read_nbt(&mut self) -> Result; fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; -} \ No newline at end of file +} diff --git a/src/server/data/text_component.rs b/src/server/data/text_component.rs index cd2b839..c5095b9 100644 --- a/src/server/data/text_component.rs +++ b/src/server/data/text_component.rs @@ -9,7 +9,6 @@ use crate::server::ServerError; use super::ReadWriteNBT; - #[derive(Debug, Serialize, Deserialize, Clone)] #[skip_serializing_none] pub struct TextComponent { @@ -34,7 +33,7 @@ impl TextComponent { underlined: None, strikethrough: None, obfuscated: None, - extra: None + extra: None, } } @@ -43,7 +42,8 @@ impl TextComponent { return TextComponent::new(text); } - let children = text.char_indices() + let children = text + .char_indices() .map(|(i, c)| { let hue = (i as f32) / (text.chars().count() as f32) * 360.0; let hsl = Hsl::new(hue, 1.0, 0.5); @@ -56,7 +56,7 @@ impl TextComponent { component }) .collect::>(); - + let mut parent = children[0].clone(); parent.extra = Some(children[1..].to_vec()); parent @@ -67,13 +67,11 @@ impl TextComponent { } pub fn as_json(self) -> Result { - serde_json::to_string(&self) - .map_err(|_| ServerError::SerTextComponent) + serde_json::to_string(&self).map_err(|_| ServerError::SerTextComponent) } pub fn from_json(text: &str) -> Result { - serde_json::from_str(text) - .map_err(|_| ServerError::DeTextComponent) + serde_json::from_str(text).map_err(|_| ServerError::DeTextComponent) } } @@ -149,15 +147,15 @@ impl TextComponentBuilder { } pub fn build(self) -> TextComponent { - TextComponent { - text: self.text, - color: self.color, - bold: self.bold, - italic: self.italic, - underlined: self.underlined, - strikethrough: self.strikethrough, - obfuscated: self.obfuscated, - extra: self.extra + TextComponent { + text: self.text, + color: self.color, + bold: self.bold, + italic: self.italic, + underlined: self.underlined, + strikethrough: self.strikethrough, + obfuscated: self.obfuscated, + extra: self.extra, } } } @@ -167,15 +165,18 @@ impl ReadWriteNBT for Packet { fn read_nbt(&mut self) -> Result { 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); + 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)?; + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ServerError::SerTextComponent)?; Ok(()) } -} \ No newline at end of file +} diff --git a/src/server/event/mod.rs b/src/server/event/mod.rs index 1477b8e..e8ff602 100644 --- a/src/server/event/mod.rs +++ b/src/server/event/mod.rs @@ -18,7 +18,7 @@ macro_rules! generate_handlers { } /// Пример использования: -/// +/// /// trigger_event!(client, status, &mut response, state); #[macro_export] macro_rules! trigger_event { @@ -45,4 +45,4 @@ pub trait PacketHandler: Sync + Send { generate_handlers!(incoming_packet, &mut Packet, &mut bool, ConnectionState); generate_handlers!(outcoming_packet, &mut Packet, &mut bool, ConnectionState); generate_handlers!(state, ConnectionState); -} \ No newline at end of file +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 51aa3bd..8a27de6 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -8,10 +8,10 @@ use protocol::handler::handle_connection; use rust_mc_proto::{MinecraftConnection, ProtocolError}; pub mod config; +pub mod context; pub mod data; pub mod event; pub mod player; -pub mod context; pub mod protocol; // Ошибки сервера @@ -19,11 +19,11 @@ pub mod protocol; pub enum ServerError { UnexpectedPacket, // Неожиданный пакет Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto - ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection - SerTextComponent, // Ошибка при сериализации текст-компонента - DeTextComponent, // Ошибка при десериализации текст-компонента - UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) - Other(String) // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки + ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection + SerTextComponent, // Ошибка при сериализации текст-компонента + DeTextComponent, // Ошибка при десериализации текст-компонента + UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) + Other(String), // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки } impl Display for ServerError { @@ -39,36 +39,39 @@ impl From for ServerError { fn from(error: ProtocolError) -> ServerError { match error { // Если просто закрыто соединение, переделываем в нашу ошибку этого - ProtocolError::ConnectionClosedError => { - ServerError::ConnectionClosed - }, + ProtocolError::ConnectionClosedError => ServerError::ConnectionClosed, // Все остальное просто засовываем в обертку - error => { - ServerError::Protocol(error) - }, + error => ServerError::Protocol(error), } } } pub fn start_server(server: Arc) { // Биндим сервер где надо - let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { - error!("Не удалось забиндить сервер на {}", &server.config.bind.host); + let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { + error!( + "Не удалось забиндить сервер на {}", + &server.config.bind.host + ); return; }; - info!("Сервер запущен на {}", &server.config.bind.host); + info!("Сервер запущен на {}", &server.config.bind.host); while let Ok((stream, addr)) = listener.accept() { let server = server.clone(); - thread::spawn(move || { + 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(); + 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); @@ -84,11 +87,11 @@ pub fn start_server(server: Arc) { // Обработка подключения // Если ошибка -> выводим match handle_connection(client.clone()) { - Ok(_) => {}, - Err(ServerError::ConnectionClosed) => {}, + Ok(_) => {} + Err(ServerError::ConnectionClosed) => {} Err(error) => { error!("Ошибка подключения: {error:?}"); - }, + } }; // Удаляем клиента из списка клиентов diff --git a/src/server/player/context.rs b/src/server/player/context.rs index 08b8140..05bdbb1 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -1,9 +1,13 @@ -use std::{hash::Hash, net::{SocketAddr, TcpStream}, sync::{Arc, RwLock}}; +use std::{ + hash::Hash, + net::{SocketAddr, TcpStream}, + sync::{Arc, RwLock}, +}; use rust_mc_proto::{MinecraftConnection, Packet}; use uuid::Uuid; -use crate::server::{context::ServerContext, protocol::ConnectionState, ServerError}; +use crate::server::{ServerError, context::ServerContext, protocol::ConnectionState}; use super::helper::ProtocolHelper; @@ -16,7 +20,7 @@ pub struct ClientContext { handshake: RwLock>, client_info: RwLock>, player_info: RwLock>, - state: RwLock + state: RwLock, } // Реализуем сравнение через адрес @@ -36,10 +40,7 @@ impl Hash for ClientContext { impl Eq for ClientContext {} impl ClientContext { - pub fn new( - server: Arc, - conn: MinecraftConnection - ) -> ClientContext { + pub fn new(server: Arc, conn: MinecraftConnection) -> ClientContext { ClientContext { server, addr: conn.get_ref().peer_addr().unwrap(), @@ -47,7 +48,7 @@ impl ClientContext { handshake: RwLock::new(None), client_info: RwLock::new(None), player_info: RwLock::new(None), - state: RwLock::new(ConnectionState::Handshake) + state: RwLock::new(ConnectionState::Handshake), } } @@ -66,9 +67,11 @@ impl ClientContext { pub fn set_state(self: &Arc, state: ConnectionState) -> Result<(), ServerError> { *self.state.write().unwrap() = state.clone(); - for handler in self.server.packet_handlers( - |o| o.on_state_priority() - ).iter() { + for handler in self + .server + .packet_handlers(|o| o.on_state_priority()) + .iter() + { handler.on_state(self.clone(), state.clone())?; } @@ -95,10 +98,17 @@ impl ClientContext { let state = self.state(); let mut packet = packet.clone(); let mut cancelled = false; - for handler in self.server.packet_handlers( - |o| o.on_outcoming_packet_priority() - ).iter() { - handler.on_outcoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + for handler in self + .server + .packet_handlers(|o| o.on_outcoming_packet_priority()) + .iter() + { + handler.on_outcoming_packet( + self.clone(), + &mut packet, + &mut cancelled, + state.clone(), + )?; packet.get_mut().set_position(0); } if !cancelled { @@ -115,10 +125,17 @@ impl ClientContext { loop { let mut packet = conn.read_packet()?; let mut cancelled = false; - for handler in self.server.packet_handlers( - |o| o.on_incoming_packet_priority() - ).iter() { - handler.on_incoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + for handler in self + .server + .packet_handlers(|o| o.on_incoming_packet_priority()) + .iter() + { + handler.on_incoming_packet( + self.clone(), + &mut packet, + &mut cancelled, + state.clone(), + )?; packet.get_mut().set_position(0); } if !cancelled { @@ -167,11 +184,11 @@ pub struct ClientInfo { pub main_hand: i32, pub enable_text_filtering: bool, pub allow_server_listings: bool, - pub particle_status: i32 + pub particle_status: i32, } #[derive(Clone)] pub struct PlayerInfo { pub name: String, - pub uuid: Uuid + pub uuid: Uuid, } diff --git a/src/server/player/helper.rs b/src/server/player/helper.rs index b73d826..ccb6146 100644 --- a/src/server/player/helper.rs +++ b/src/server/player/helper.rs @@ -1,12 +1,22 @@ -use std::{io::Read, sync::Arc, time::{Duration, SystemTime}}; +use std::{ + io::Read, + sync::Arc, + time::{Duration, SystemTime}, +}; use rust_mc_proto::{DataReader, DataWriter, Packet}; -use crate::server::{data::{text_component::TextComponent, ReadWriteNBT}, protocol::{id::{clientbound, serverbound}, *}, ServerError}; +use crate::server::{ + ServerError, + data::{ReadWriteNBT, text_component::TextComponent}, + protocol::{ + id::{clientbound, serverbound}, + *, + }, +}; use super::context::ClientContext; - // Помощник в работе с протоколом // Может быть использован где угодно, но сделан именно для листенеров и пакет хандлеров // Через него удобно делать всякую одинаковую херь @@ -16,14 +26,14 @@ use super::context::ClientContext; // Почему бы и нет если да pub struct ProtocolHelper { client: Arc, - state: ConnectionState + state: ConnectionState, } impl ProtocolHelper { pub fn new(client: Arc) -> Self { Self { state: client.state(), - client + client, } } @@ -31,12 +41,14 @@ impl ProtocolHelper { pub fn leave_configuration(&self) -> Result<(), ServerError> { match self.state { ConnectionState::Configuration => { - self.client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; - self.client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; + self.client + .write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + self.client + .read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; self.client.set_state(ConnectionState::Play)?; Ok(()) - }, - _ => Err(ServerError::UnexpectedState) + } + _ => Err(ServerError::UnexpectedState), } } @@ -44,12 +56,14 @@ impl ProtocolHelper { pub fn enter_configuration(&self) -> Result<(), ServerError> { match self.state { ConnectionState::Play => { - self.client.write_packet(&Packet::empty(clientbound::play::START_CONFIGURATION))?; - self.client.read_packet(serverbound::play::ACKNOWLEDGE_CONFIGURATION)?; + self.client + .write_packet(&Packet::empty(clientbound::play::START_CONFIGURATION))?; + self.client + .read_packet(serverbound::play::ACKNOWLEDGE_CONFIGURATION)?; self.client.set_state(ConnectionState::Configuration)?; Ok(()) - }, - _ => Err(ServerError::UnexpectedState) + } + _ => Err(ServerError::UnexpectedState), } } @@ -58,40 +72,42 @@ impl ProtocolHelper { match self.state { ConnectionState::Play => { let time = SystemTime::now(); - self.client.write_packet(&Packet::empty(clientbound::play::PING))?; + self.client + .write_packet(&Packet::empty(clientbound::play::PING))?; self.client.read_packet(serverbound::play::PONG)?; Ok(SystemTime::now().duration_since(time).unwrap()) - }, + } ConnectionState::Configuration => { let time = SystemTime::now(); - self.client.write_packet(&Packet::empty(clientbound::configuration::PING))?; + self.client + .write_packet(&Packet::empty(clientbound::configuration::PING))?; self.client.read_packet(serverbound::configuration::PONG)?; Ok(SystemTime::now().duration_since(time).unwrap()) - }, - _ => Err(ServerError::UnexpectedState) + } + _ => Err(ServerError::UnexpectedState), } } - + 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.close(); - return Ok(()) - }, + return Ok(()); + } }; self.client.write_packet(&packet)?; Ok(()) @@ -105,7 +121,9 @@ impl ProtocolHelper { packet.write_string(id)?; self.client.write_packet(&packet)?; - let mut packet = self.client.read_packet(serverbound::configuration::COOKIE_RESPONSE)?; + let mut packet = self + .client + .read_packet(serverbound::configuration::COOKIE_RESPONSE)?; packet.read_string()?; let data = if packet.read_boolean()? { let n = packet.read_usize_varint()?; @@ -115,13 +133,18 @@ impl ProtocolHelper { }; Ok(data) - }, - _ => Err(ServerError::UnexpectedState) + } + _ => 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>), ServerError> { + pub fn send_login_plugin_request( + &self, + id: i32, + channel: &str, + data: &[u8], + ) -> Result<(i32, Option>), ServerError> { match self.state { ConnectionState::Login => { let mut packet = Packet::empty(clientbound::login::PLUGIN_REQUEST); @@ -130,7 +153,9 @@ impl ProtocolHelper { packet.write_bytes(data)?; self.client.write_packet(&packet)?; - let mut packet = self.client.read_packet(serverbound::login::PLUGIN_RESPONSE)?; + let mut packet = self + .client + .read_packet(serverbound::login::PLUGIN_RESPONSE)?; let identifier = packet.read_varint()?; let data = if packet.read_boolean()? { let mut data = Vec::new(); @@ -141,20 +166,22 @@ impl ProtocolHelper { }; Ok((identifier, data)) - }, - _ => Err(ServerError::UnexpectedState) + } + _ => 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(clientbound::configuration::PLUGIN_MESSAGE), + ConnectionState::Configuration => { + Packet::empty(clientbound::configuration::PLUGIN_MESSAGE) + } ConnectionState::Play => Packet::empty(clientbound::play::PLUGIN_MESSAGE), - _ => return Err(ServerError::UnexpectedState) + _ => return Err(ServerError::UnexpectedState), }; packet.write_string(channel)?; packet.write_bytes(data)?; self.client.write_packet(&packet)?; Ok(()) } -} \ No newline at end of file +} diff --git a/src/server/player/mod.rs b/src/server/player/mod.rs index bbad114..ccbba0b 100644 --- a/src/server/player/mod.rs +++ b/src/server/player/mod.rs @@ -1,2 +1,2 @@ pub mod context; -pub mod helper; \ No newline at end of file +pub mod helper; diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index 336ad22..de7165c 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -1,160 +1,177 @@ use std::{io::Read, sync::Arc}; -use crate::server::{player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, ServerError}; +use crate::server::{ + ServerError, + player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, +}; use rust_mc_proto::{DataReader, DataWriter, Packet}; use crate::trigger_event; -use super::{id::*, play::handle_play_state, ConnectionState}; - +use super::{ConnectionState, id::*, play::handle_play_state}; pub fn handle_connection( - client: Arc, // Контекст клиента + client: Arc, // Контекст клиента ) -> Result<(), ServerError> { - // Чтение рукопожатия - // Получение пакетов производится через client.conn(), - // ВАЖНО: не помещать сам client.conn() в переменные, - // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = client.read_packet(serverbound::handshake::HANDSHAKE)?; + // Чтение рукопожатия + // Получение пакетов производится через client.conn(), + // ВАЖНО: не помещать сам client.conn() в переменные, + // он должен сразу убиваться иначе соединение гдето задедлочится + let mut packet = client.read_packet(serverbound::handshake::HANDSHAKE)?; - 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 для обычного подключения + 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 для обычного подключения - client.set_handshake(Handshake { protocol_version, server_address, server_port }); + client.set_handshake(Handshake { + protocol_version, + server_address, + server_port, + }); - match next_state { - 1 => { // Тип подключения - статус - client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status + match next_state { + 1 => { + // Тип подключения - статус + client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status - loop { - // Чтение запроса - let mut packet = client.read_any_packet()?; + loop { + // Чтение запроса + let mut packet = client.read_any_packet()?; - match packet.id() { - serverbound::status::REQUEST => { // Запрос статуса - let mut packet = Packet::empty(clientbound::status::RESPONSE); + match packet.id() { + serverbound::status::REQUEST => { + // Запрос статуса + let mut packet = Packet::empty(clientbound::status::RESPONSE); - // Дефолтный статус - let mut status = "{ + // Дефолтный статус + let mut status = "{ \"version\": { \"name\": \"Error\", \"protocol\": 0 }, \"description\": {\"text\": \"Internal server error\"} - }".to_string(); + }" + .to_string(); - // Опрос всех листенеров - trigger_event!(client, status, &mut status); + // Опрос всех листенеров + trigger_event!(client, status, &mut status); - // Отправка статуса - packet.write_string(&status)?; + // Отправка статуса + packet.write_string(&status)?; - client.write_packet(&packet)?; - }, - serverbound::status::PING_REQUEST => { // Пинг - // Раньше мы просто отправляли ему его-же пакет, но сейчас, - // С приходом к власти констант айди-пакетов, нам приходится делать такое непотребство - let timestamp = packet.read_long()?; - let mut packet = Packet::empty(clientbound::status::PONG_RESPONSE); - packet.write_long(timestamp)?; - client.write_packet(&packet)?; - }, - _ => { - return Err(ServerError::UnexpectedPacket); - } - } - } - }, - 2 => { // Тип подключения - игра - client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login + client.write_packet(&packet)?; + } + serverbound::status::PING_REQUEST => { + // Пинг + // Раньше мы просто отправляли ему его-же пакет, но сейчас, + // С приходом к власти констант айди-пакетов, нам приходится делать такое непотребство + let timestamp = packet.read_long()?; + let mut packet = Packet::empty(clientbound::status::PONG_RESPONSE); + packet.write_long(timestamp)?; + client.write_packet(&packet)?; + } + _ => { + return Err(ServerError::UnexpectedPacket); + } + } + } + } + 2 => { + // Тип подключения - игра + client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login - // Читаем пакет Login Start - let mut packet = client.read_packet(serverbound::login::START)?; + // Читаем пакет Login Start + let mut packet = client.read_packet(serverbound::login::START)?; - let name = packet.read_string()?; - let uuid = packet.read_uuid()?; + let name = packet.read_string()?; + let uuid = packet.read_uuid()?; - client.set_player_info(PlayerInfo { name: name.clone(), uuid: uuid.clone() }); + client.set_player_info(PlayerInfo { + name: name.clone(), + uuid: uuid.clone(), + }); - if client.server.config.server.online_mode { - // TODO: encryption packets - } + if client.server.config.server.online_mode { + // TODO: encryption packets + } - // Отправляем пакет Set Compression если сжатие указано - if let Some(threshold) = client.server.config.server.compression_threshold { - client.write_packet(&Packet::build(clientbound::login::SET_COMPRESSION, |p| p.write_usize_varint(threshold))?)?; - client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении - } + // Отправляем пакет Set Compression если сжатие указано + if let Some(threshold) = client.server.config.server.compression_threshold { + client.write_packet(&Packet::build(clientbound::login::SET_COMPRESSION, |p| { + p.write_usize_varint(threshold) + })?)?; + client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении + } - // Отправка пакета Login Success - client.write_packet(&Packet::build(clientbound::login::SUCCESS, |p| { - p.write_uuid(&uuid)?; - p.write_string(&name)?; - p.write_varint(0) - })?)?; + // Отправка пакета Login Success + client.write_packet(&Packet::build(clientbound::login::SUCCESS, |p| { + p.write_uuid(&uuid)?; + p.write_string(&name)?; + p.write_varint(0) + })?)?; - client.read_packet(serverbound::login::ACKNOWLEDGED)?; // Пакет Login Acknowledged + client.read_packet(serverbound::login::ACKNOWLEDGED)?; // Пакет Login Acknowledged - client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration - - // Получение бренда клиента из Serverbound Plugin Message - // Identifier канала откуда берется бренд: minecraft:brand - let brand = loop { - let mut packet = client.read_packet(serverbound::configuration::PLUGIN_MESSAGE)?; // Пакет Serverbound Plugin Message + client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration - let identifier = packet.read_string()?; + // Получение бренда клиента из Serverbound Plugin Message + // Identifier канала откуда берется бренд: minecraft:brand + let brand = loop { + let mut packet = client.read_packet(serverbound::configuration::PLUGIN_MESSAGE)?; // Пакет Serverbound Plugin Message - let mut data = Vec::new(); - packet.get_mut().read_to_end(&mut data).unwrap(); + let identifier = packet.read_string()?; - if identifier == "minecraft:brand" { - break String::from_utf8_lossy(&data).to_string(); - } else { - trigger_event!(client, plugin_message, &identifier, &data); - } - }; + let mut data = Vec::new(); + packet.get_mut().read_to_end(&mut data).unwrap(); - let mut packet = client.read_packet(serverbound::configuration::CLIENT_INFORMATION)?; // Пакет Client Information + if identifier == "minecraft:brand" { + break String::from_utf8_lossy(&data).to_string(); + } else { + trigger_event!(client, plugin_message, &identifier, &data); + } + }; - let locale = packet.read_string()?; // for example: en_us - 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 + let mut packet = client.read_packet(serverbound::configuration::CLIENT_INFORMATION)?; // Пакет Client Information - 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 - }); + let locale = packet.read_string()?; // for example: en_us + 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 - // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото + 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, + }); - client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; - client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; + // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото - client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play + client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; - // Дальше работаем с режимом игры - handle_play_state(client)?; - }, - _ => { // Тип подключения не рукопожатный - return Err(ServerError::UnexpectedPacket); - } - } + client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play - Ok(()) -} \ No newline at end of file + // Дальше работаем с режимом игры + handle_play_state(client)?; + } + _ => { + // Тип подключения не рукопожатный + return Err(ServerError::UnexpectedPacket); + } + } + + Ok(()) +} diff --git a/src/server/protocol/id.rs b/src/server/protocol/id.rs index fe7bab2..5c26854 100644 --- a/src/server/protocol/id.rs +++ b/src/server/protocol/id.rs @@ -1,8 +1,8 @@ /* - Generated with parse_ids.py +Generated with parse_ids.py - */ +*/ pub mod clientbound { pub mod status { @@ -172,7 +172,6 @@ pub mod clientbound { pub const CUSTOM_REPORT_DETAILS: u8 = 0x81; pub const SERVER_LINKS: u8 = 0x82; } - } pub mod serverbound { @@ -270,6 +269,4 @@ pub mod serverbound { pub const USE_ITEM_ON: u8 = 0x3E; pub const USE_ITEM: u8 = 0x3F; } - } - diff --git a/src/server/protocol/mod.rs b/src/server/protocol/mod.rs index 53ab91d..5110371 100644 --- a/src/server/protocol/mod.rs +++ b/src/server/protocol/mod.rs @@ -1,7 +1,6 @@ +pub mod handler; pub mod id; pub mod play; -pub mod handler; - #[derive(Debug, Clone)] pub enum ConnectionState { @@ -9,6 +8,5 @@ pub enum ConnectionState { Status, Login, Configuration, - Play + Play, } - diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index de5cff6..9f5e99a 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,16 +1,19 @@ use std::sync::Arc; -use crate::server::{data::text_component::TextComponent, player::context::ClientContext, ServerError}; +use crate::server::{ + ServerError, data::text_component::TextComponent, player::context::ClientContext, +}; // Отдельная функция для работы с самой игрой pub fn handle_play_state( - client: Arc, // Контекст клиента -) -> Result<(), ServerError> { + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + // Отключение игрока с сообщением + client.protocol_helper().disconnect(TextComponent::rainbow( + "server is in developement suka".to_string(), + ))?; - // Отключение игрока с сообщением - client.protocol_helper().disconnect(TextComponent::rainbow("server is in developement suka".to_string()))?; + // TODO: Сделать отправку пакетов Play - // TODO: Сделать отправку пакетов Play - - Ok(()) -} \ No newline at end of file + Ok(()) +} From 6f2dc21d580bfde63ea9458b50c434d9ba58d45d Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 18:34:46 +0300 Subject: [PATCH 24/57] tab size 4 --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3ce68c6..01fd4e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { "editor.fontFamily": "Fira Code", "editor.fontLigatures": true, - "editor.tabSize": 2, + "editor.tabSize": 4, } \ No newline at end of file From 50262ff1d7c93bf84ae637802298ffbf3ef6061d Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 19:03:52 +0300 Subject: [PATCH 25/57] add read+write position --- src/server/data/mod.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/server/data/mod.rs b/src/server/data/mod.rs index 1e887b3..609cfbc 100644 --- a/src/server/data/mod.rs +++ b/src/server/data/mod.rs @@ -1,4 +1,4 @@ -use rust_mc_proto::{DataReader, DataWriter}; +use rust_mc_proto::{DataReader, DataWriter, Packet}; use super::ServerError; @@ -9,3 +9,20 @@ pub trait ReadWriteNBT: DataReader + DataWriter { fn read_nbt(&mut self) -> Result; fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; } + +pub trait ReadWritePosition: DataReader + DataWriter { + fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError>; + fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError>; +} + +impl ReadWritePosition for Packet { + fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError> { + let val = self.read_long()?; + Ok((val >> 38, val << 52 >> 52, val << 26 >> 38)) + } + + fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError> { + Ok(self.write_long(((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF))?) + } +} + From 945ddb6ea03467f4a4eb13dc618957d0b68b7141 Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Sat, 3 May 2025 20:42:10 +0300 Subject: [PATCH 26/57] asdsdasdas --- src/server/protocol/play.rs | 38 +++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 9f5e99a..4a56347 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,19 +1,49 @@ use std::sync::Arc; +use rust_mc_proto::{DataWriter, Packet}; + use crate::server::{ ServerError, data::text_component::TextComponent, player::context::ClientContext, }; +use super::id::clientbound; + // Отдельная функция для работы с самой игрой pub fn handle_play_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { // Отключение игрока с сообщением - client.protocol_helper().disconnect(TextComponent::rainbow( - "server is in developement suka".to_string(), - ))?; + // client.protocol_helper().disconnect(TextComponent::rainbow( + // "server is in developement suka".to_string(), + // ))?; - // TODO: Сделать отправку пакетов Play + let mut packet = Packet::empty(clientbound::play::LOGIN); + packet.write_int(10)?; // Entity ID + packet.write_boolean(false)?; // Is hardcore + packet.write_varint(1)?; // Dimension Names + packet.write_string("minecraft:overworld")?; + // packet.write_string("root/minecraft:nether")?; + // packet.write_string("root/minecraft:the_end")?; + packet.write_varint(0)?; // Max Players + packet.write_varint(8)?; // View Distance + packet.write_varint(5)?; // Simulation Distance + packet.write_boolean(false)?; // Reduced Debug Info + packet.write_boolean(true)?; // Enable respawn screen + packet.write_boolean(false)?; // Do limited crafting + + packet.write_varint(0)?; // Dimension Type + packet.write_string("minecraft:overworld")?; // Dimension Name + packet.write_long(0x0f38f26ad09c3e20)?; // Hashed seed + packet.write_byte(0)?; // Game mode + packet.write_signed_byte(-1)?; // Previous Game mode + packet.write_boolean(false)?; // Is Debug + packet.write_boolean(true)?; // Is Flat + packet.write_boolean(false)?; // Has death location + packet.write_varint(20)?; // Portal cooldown + packet.write_varint(60)?; // Sea level + + packet.write_boolean(false)?; // Enforces Secure Chat + client.write_packet(&packet)?; Ok(()) } From 4d04729809ee35aa67f7441df1507ab56280f683 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 20:42:23 +0300 Subject: [PATCH 27/57] more helper functions --- src/server/data/mod.rs | 1 - src/server/player/helper.rs | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/server/data/mod.rs b/src/server/data/mod.rs index 609cfbc..a57ef54 100644 --- a/src/server/data/mod.rs +++ b/src/server/data/mod.rs @@ -25,4 +25,3 @@ impl ReadWritePosition for Packet { Ok(self.write_long(((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF))?) } } - diff --git a/src/server/player/helper.rs b/src/server/player/helper.rs index ccb6146..5b86c88 100644 --- a/src/server/player/helper.rs +++ b/src/server/player/helper.rs @@ -37,6 +37,32 @@ impl ProtocolHelper { } } + pub fn reset_chat(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Configuration => { + self.client + .write_packet(&Packet::empty(clientbound::configuration::RESET_CHAT))?; + Ok(()) + } + _ => Err(ServerError::UnexpectedState), + } + } + + pub fn store_cookie(&self, id: &str, data: &[u8]) -> Result<(), ServerError> { + self.client.write_packet(&Packet::build( + match self.state { + ConnectionState::Configuration => clientbound::configuration::STORE_COOKIE, + ConnectionState::Play => clientbound::play::STORE_COOKIE, + _ => { return Err(ServerError::UnexpectedState) }, + }, + |p| { + p.write_string(id)?; + p.write_bytes(data) + }, + )?)?; + Ok(()) + } + /// Leave from Configuration to Play state pub fn leave_configuration(&self) -> Result<(), ServerError> { match self.state { @@ -132,6 +158,24 @@ impl ProtocolHelper { None }; + Ok(data) + }, + ConnectionState::Play => { + let mut packet = Packet::empty(clientbound::play::COOKIE_REQUEST); + packet.write_string(id)?; + self.client.write_packet(&packet)?; + + let mut packet = self + .client + .read_packet(serverbound::play::COOKIE_RESPONSE)?; + 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), From ced3cc0a2e3ca84dc0af3a1bcce420c96d2d8ace Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 20:48:18 +0300 Subject: [PATCH 28/57] handle_configuration_state --- src/server/protocol/handler.rs | 4 ++-- src/server/protocol/play.rs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index de7165c..84bf5e4 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -8,7 +8,7 @@ use rust_mc_proto::{DataReader, DataWriter, Packet}; use crate::trigger_event; -use super::{ConnectionState, id::*, play::handle_play_state}; +use super::{id::*, play::{handle_configuration_state, handle_play_state}, ConnectionState}; pub fn handle_connection( client: Arc, // Контекст клиента @@ -157,7 +157,7 @@ pub fn handle_connection( particle_status, }); - // TODO: Заюзать Listener'ы чтобы они подмешивали сюда чото + handle_configuration_state(client.clone())?; client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 4a56347..40e2f3a 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -3,11 +3,17 @@ use std::sync::Arc; use rust_mc_proto::{DataWriter, Packet}; use crate::server::{ - ServerError, data::text_component::TextComponent, player::context::ClientContext, + ServerError, player::context::ClientContext, }; use super::id::clientbound; +pub fn handle_configuration_state( + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + Ok(()) +} + // Отдельная функция для работы с самой игрой pub fn handle_play_state( client: Arc, // Контекст клиента From ed8524a4d52ebe03e5645d644161f28499023cd3 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 20:59:48 +0300 Subject: [PATCH 29/57] nbt read write --- src/server/data/mod.rs | 23 +++++++++++++++++++++++ src/server/mod.rs | 2 ++ 2 files changed, 25 insertions(+) diff --git a/src/server/data/mod.rs b/src/server/data/mod.rs index a57ef54..3fccec6 100644 --- a/src/server/data/mod.rs +++ b/src/server/data/mod.rs @@ -1,3 +1,6 @@ +use std::io::Read; + +use craftflow_nbt::DynNBT; use rust_mc_proto::{DataReader, DataWriter, Packet}; use super::ServerError; @@ -10,6 +13,26 @@ pub trait ReadWriteNBT: DataReader + DataWriter { fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; } +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + let mut data = Vec::new(); + let pos = self.get_ref().position(); + self.get_mut() + .read_to_end(&mut data) + .map_err(|_| ServerError::DeNbt)?; + let (remaining, value) = + craftflow_nbt::from_slice(&data).map_err(|_| ServerError::DeNbt)?; + self.get_mut() + .set_position(pos + (data.len() - remaining.len()) as u64); + Ok(value) + } + + fn write_nbt(&mut self, val: &DynNBT) -> Result<(), ServerError> { + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ServerError::SerNbt)?; + Ok(()) + } +} + pub trait ReadWritePosition: DataReader + DataWriter { fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError>; fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError>; diff --git a/src/server/mod.rs b/src/server/mod.rs index 8a27de6..4fd5170 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -22,6 +22,8 @@ pub enum ServerError { ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection SerTextComponent, // Ошибка при сериализации текст-компонента DeTextComponent, // Ошибка при десериализации текст-компонента + SerNbt, // Ошибка при сериализации nbt + DeNbt, // Ошибка при десериализации nbt UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) Other(String), // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки } From 2220a4b31440b78e0d85dc74c1a65b5cef33c483 Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Sat, 3 May 2025 22:11:28 +0300 Subject: [PATCH 30/57] ? --- src/server/protocol/play.rs | 61 ++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 40e2f3a..61947f4 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,18 +1,71 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; +use craftflow_nbt::DynNBT; use rust_mc_proto::{DataWriter, Packet}; +use serde_json::json; use crate::server::{ - ServerError, player::context::ClientContext, + data::ReadWriteNBT, player::context::ClientContext, ServerError }; -use super::id::clientbound; +use super::id::{clientbound::{self, configuration::REGISTRY_DATA}, serverbound}; pub fn handle_configuration_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { + + let mut p = Packet::empty(clientbound::configuration::KNOWN_PACKS); + p.write_varint(1)?; + p.write_string("minecraft")?; + p.write_string("core")?; + p.write_string("1.21.5")?; + client.write_packet(&p)?; + client.read_packet(serverbound::configuration::KNOWN_PACKS)?; + + let mut data = Vec::new(); + craftflow_nbt::to_writer(&mut data, &json!( + { + "ambient_light": 0.0, + "bed_works": 1, + "coordinate_scale": 1.0, + "effects": "minecraft:overworld", + "has_ceiling": 0, + "has_raids": 1, + "has_skylight": 1, + "height": 384, + "infiniburn": "#minecraft:infiniburn_overworld", + "logical_height": 384, + "min_y": -64, + "monster_spawn_block_light_limit": 0, + "monster_spawn_light_level": { + "max_inclusive": 7, + "min_inclusive": 0, + "type": "minecraft:uniform" + }, + "natural": 1, + "piglin_safe": 0, + "respawn_anchor_works": 0, + "ultrawarm": 0 + } + )).unwrap(); + + let mut p = Packet::empty(clientbound::configuration::REGISTRY_DATA); + p.write_string("minecraft:dimension_type")?; + p.write_varint(1)?; + p.write_string("minecraft:overworld")?; + p.write_boolean(true)?; + // p.write_nbt(&DynNBT::Compound(HashMap::from_iter([ + // ("bed_works".to_string(), DynNBT::Byte(1)), + // ("has_skylight".to_string(), DynNBT::Byte(1)), + // ("natural".to_string(), DynNBT::Byte(1)), + // ("coordinate_scale".to_string(), DynNBT::Double(1.0)), + // ("effects".to_string(), DynNBT::String("minecraft:overworld".to_string())), + // ])))?; + p.write_bytes(&data)?; + client.write_packet(&p)?; + Ok(()) -} +} // Отдельная функция для работы с самой игрой pub fn handle_play_state( From eb46fdfc456a1888bbeb1596a15d8af4f3b4508e Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 3 May 2025 22:32:10 +0300 Subject: [PATCH 31/57] send_registry_data --- src/main.rs | 2 +- src/server/protocol/play.rs | 82 +- src/server/protocol/registry_data.json | 2489 ++++++++++++++++++++++++ 3 files changed, 2530 insertions(+), 43 deletions(-) create mode 100644 src/server/protocol/registry_data.json diff --git a/src/main.rs b/src/main.rs index e34a01b..1e650f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -156,7 +156,7 @@ fn main() { let mut server = ServerContext::new(config); server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера - server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + // server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера // Бетонируем сервер контекст от изменений let server = Arc::new(server); diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 61947f4..4783434 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,8 +1,9 @@ use std::{collections::HashMap, sync::Arc}; use craftflow_nbt::DynNBT; +use log::debug; use rust_mc_proto::{DataWriter, Packet}; -use serde_json::json; +use serde_json::{json, Value}; use crate::server::{ data::ReadWriteNBT, player::context::ClientContext, ServerError @@ -10,6 +11,41 @@ use crate::server::{ use super::id::{clientbound::{self, configuration::REGISTRY_DATA}, serverbound}; +pub fn send_registry_data( + client: Arc, +) -> Result<(), ServerError> { + let registry_data = include_str!("registry_data.json"); + let registry_data: Value = serde_json::from_str(registry_data).unwrap(); + let registry_data = registry_data.as_object().unwrap(); + + for (registry_name, registry_data) in registry_data { + let registry_data = registry_data.as_object().unwrap(); + + let mut packet = Packet::empty(clientbound::configuration::REGISTRY_DATA); + packet.write_string(registry_name)?; + + packet.write_usize_varint(registry_data.len())?; + + debug!("sending registry: {registry_name}"); + + for (key, value) in registry_data { + packet.write_string(key)?; + packet.write_boolean(true)?; + + let mut data = Vec::new(); + craftflow_nbt::to_writer(&mut data, value).unwrap(); + + debug!("- {key}"); + + packet.write_bytes(&data)?; + } + + client.write_packet(&packet)?; + } + + Ok(()) +} + pub fn handle_configuration_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { @@ -22,47 +58,7 @@ pub fn handle_configuration_state( client.write_packet(&p)?; client.read_packet(serverbound::configuration::KNOWN_PACKS)?; - let mut data = Vec::new(); - craftflow_nbt::to_writer(&mut data, &json!( - { - "ambient_light": 0.0, - "bed_works": 1, - "coordinate_scale": 1.0, - "effects": "minecraft:overworld", - "has_ceiling": 0, - "has_raids": 1, - "has_skylight": 1, - "height": 384, - "infiniburn": "#minecraft:infiniburn_overworld", - "logical_height": 384, - "min_y": -64, - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "max_inclusive": 7, - "min_inclusive": 0, - "type": "minecraft:uniform" - }, - "natural": 1, - "piglin_safe": 0, - "respawn_anchor_works": 0, - "ultrawarm": 0 - } - )).unwrap(); - - let mut p = Packet::empty(clientbound::configuration::REGISTRY_DATA); - p.write_string("minecraft:dimension_type")?; - p.write_varint(1)?; - p.write_string("minecraft:overworld")?; - p.write_boolean(true)?; - // p.write_nbt(&DynNBT::Compound(HashMap::from_iter([ - // ("bed_works".to_string(), DynNBT::Byte(1)), - // ("has_skylight".to_string(), DynNBT::Byte(1)), - // ("natural".to_string(), DynNBT::Byte(1)), - // ("coordinate_scale".to_string(), DynNBT::Double(1.0)), - // ("effects".to_string(), DynNBT::String("minecraft:overworld".to_string())), - // ])))?; - p.write_bytes(&data)?; - client.write_packet(&p)?; + send_registry_data(client.clone())?; Ok(()) } @@ -104,5 +100,7 @@ pub fn handle_play_state( packet.write_boolean(false)?; // Enforces Secure Chat client.write_packet(&packet)?; + loop {} + Ok(()) } diff --git a/src/server/protocol/registry_data.json b/src/server/protocol/registry_data.json new file mode 100644 index 0000000..a4fc1cd --- /dev/null +++ b/src/server/protocol/registry_data.json @@ -0,0 +1,2489 @@ +{ + "minecraft:banner_pattern": { + "minecraft:base": { + "asset_id": "minecraft:base", + "translation_key": "block.minecraft.banner.base" + }, + "minecraft:border": { + "asset_id": "minecraft:border", + "translation_key": "block.minecraft.banner.border" + }, + "minecraft:bricks": { + "asset_id": "minecraft:bricks", + "translation_key": "block.minecraft.banner.bricks" + }, + "minecraft:circle": { + "asset_id": "minecraft:circle", + "translation_key": "block.minecraft.banner.circle" + }, + "minecraft:creeper": { + "asset_id": "minecraft:creeper", + "translation_key": "block.minecraft.banner.creeper" + }, + "minecraft:cross": { + "asset_id": "minecraft:cross", + "translation_key": "block.minecraft.banner.cross" + }, + "minecraft:curly_border": { + "asset_id": "minecraft:curly_border", + "translation_key": "block.minecraft.banner.curly_border" + }, + "minecraft:diagonal_left": { + "asset_id": "minecraft:diagonal_left", + "translation_key": "block.minecraft.banner.diagonal_left" + }, + "minecraft:diagonal_right": { + "asset_id": "minecraft:diagonal_right", + "translation_key": "block.minecraft.banner.diagonal_right" + }, + "minecraft:diagonal_up_left": { + "asset_id": "minecraft:diagonal_up_left", + "translation_key": "block.minecraft.banner.diagonal_up_left" + }, + "minecraft:diagonal_up_right": { + "asset_id": "minecraft:diagonal_up_right", + "translation_key": "block.minecraft.banner.diagonal_up_right" + }, + "minecraft:flow": { + "asset_id": "minecraft:flow", + "translation_key": "block.minecraft.banner.flow" + }, + "minecraft:flower": { + "asset_id": "minecraft:flower", + "translation_key": "block.minecraft.banner.flower" + }, + "minecraft:globe": { + "asset_id": "minecraft:globe", + "translation_key": "block.minecraft.banner.globe" + }, + "minecraft:gradient": { + "asset_id": "minecraft:gradient", + "translation_key": "block.minecraft.banner.gradient" + }, + "minecraft:gradient_up": { + "asset_id": "minecraft:gradient_up", + "translation_key": "block.minecraft.banner.gradient_up" + }, + "minecraft:guster": { + "asset_id": "minecraft:guster", + "translation_key": "block.minecraft.banner.guster" + }, + "minecraft:half_horizontal": { + "asset_id": "minecraft:half_horizontal", + "translation_key": "block.minecraft.banner.half_horizontal" + }, + "minecraft:half_horizontal_bottom": { + "asset_id": "minecraft:half_horizontal_bottom", + "translation_key": "block.minecraft.banner.half_horizontal_bottom" + }, + "minecraft:half_vertical": { + "asset_id": "minecraft:half_vertical", + "translation_key": "block.minecraft.banner.half_vertical" + }, + "minecraft:half_vertical_right": { + "asset_id": "minecraft:half_vertical_right", + "translation_key": "block.minecraft.banner.half_vertical_right" + }, + "minecraft:mojang": { + "asset_id": "minecraft:mojang", + "translation_key": "block.minecraft.banner.mojang" + }, + "minecraft:piglin": { + "asset_id": "minecraft:piglin", + "translation_key": "block.minecraft.banner.piglin" + }, + "minecraft:rhombus": { + "asset_id": "minecraft:rhombus", + "translation_key": "block.minecraft.banner.rhombus" + }, + "minecraft:skull": { + "asset_id": "minecraft:skull", + "translation_key": "block.minecraft.banner.skull" + }, + "minecraft:small_stripes": { + "asset_id": "minecraft:small_stripes", + "translation_key": "block.minecraft.banner.small_stripes" + }, + "minecraft:square_bottom_left": { + "asset_id": "minecraft:square_bottom_left", + "translation_key": "block.minecraft.banner.square_bottom_left" + }, + "minecraft:square_bottom_right": { + "asset_id": "minecraft:square_bottom_right", + "translation_key": "block.minecraft.banner.square_bottom_right" + }, + "minecraft:square_top_left": { + "asset_id": "minecraft:square_top_left", + "translation_key": "block.minecraft.banner.square_top_left" + }, + "minecraft:square_top_right": { + "asset_id": "minecraft:square_top_right", + "translation_key": "block.minecraft.banner.square_top_right" + }, + "minecraft:straight_cross": { + "asset_id": "minecraft:straight_cross", + "translation_key": "block.minecraft.banner.straight_cross" + }, + "minecraft:stripe_bottom": { + "asset_id": "minecraft:stripe_bottom", + "translation_key": "block.minecraft.banner.stripe_bottom" + }, + "minecraft:stripe_center": { + "asset_id": "minecraft:stripe_center", + "translation_key": "block.minecraft.banner.stripe_center" + }, + "minecraft:stripe_downleft": { + "asset_id": "minecraft:stripe_downleft", + "translation_key": "block.minecraft.banner.stripe_downleft" + }, + "minecraft:stripe_downright": { + "asset_id": "minecraft:stripe_downright", + "translation_key": "block.minecraft.banner.stripe_downright" + }, + "minecraft:stripe_left": { + "asset_id": "minecraft:stripe_left", + "translation_key": "block.minecraft.banner.stripe_left" + }, + "minecraft:stripe_middle": { + "asset_id": "minecraft:stripe_middle", + "translation_key": "block.minecraft.banner.stripe_middle" + }, + "minecraft:stripe_right": { + "asset_id": "minecraft:stripe_right", + "translation_key": "block.minecraft.banner.stripe_right" + }, + "minecraft:stripe_top": { + "asset_id": "minecraft:stripe_top", + "translation_key": "block.minecraft.banner.stripe_top" + }, + "minecraft:triangle_bottom": { + "asset_id": "minecraft:triangle_bottom", + "translation_key": "block.minecraft.banner.triangle_bottom" + }, + "minecraft:triangle_top": { + "asset_id": "minecraft:triangle_top", + "translation_key": "block.minecraft.banner.triangle_top" + }, + "minecraft:triangles_bottom": { + "asset_id": "minecraft:triangles_bottom", + "translation_key": "block.minecraft.banner.triangles_bottom" + }, + "minecraft:triangles_top": { + "asset_id": "minecraft:triangles_top", + "translation_key": "block.minecraft.banner.triangles_top" + } + }, + "minecraft:chat_type": { + "minecraft:chat": { + "chat": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.text" + }, + "narration": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.text.narrate" + } + }, + "minecraft:emote_command": { + "chat": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.emote" + }, + "narration": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.emote" + } + }, + "minecraft:msg_command_incoming": { + "chat": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "commands.message.display.incoming" + }, + "narration": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.text.narrate" + } + }, + "minecraft:msg_command_outgoing": { + "chat": { + "parameters": [ + "target", + "content" + ], + "translation_key": "commands.message.display.outgoing" + }, + "narration": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.text.narrate" + } + }, + "minecraft:say_command": { + "chat": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.announcement" + }, + "narration": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.text.narrate" + } + }, + "minecraft:team_msg_command_incoming": { + "chat": { + "parameters": [ + "target", + "sender", + "content" + ], + "translation_key": "chat.type.team.text" + }, + "narration": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.text.narrate" + } + }, + "minecraft:team_msg_command_outgoing": { + "chat": { + "parameters": [ + "target", + "sender", + "content" + ], + "translation_key": "chat.type.team.sent" + }, + "narration": { + "parameters": [ + "sender", + "content" + ], + "translation_key": "chat.type.text.narrate" + } + } + }, + "minecraft:damage_type": { + "minecraft:arrow": { + "exhaustion": 0.10000000149011612, + "message_id": "arrow", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:bad_respawn_point": { + "exhaustion": 0.10000000149011612, + "message_id": "badRespawnPoint", + "scaling": "always" + }, + "minecraft:cactus": { + "exhaustion": 0.10000000149011612, + "message_id": "cactus", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:campfire": { + "exhaustion": 0.10000000149011612, + "message_id": "inFire", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:cramming": { + "exhaustion": 0.0, + "message_id": "cramming", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:dragon_breath": { + "exhaustion": 0.0, + "message_id": "dragonBreath", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:drown": { + "exhaustion": 0.0, + "message_id": "drown", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:dry_out": { + "exhaustion": 0.10000000149011612, + "message_id": "dryout", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:explosion": { + "exhaustion": 0.10000000149011612, + "message_id": "explosion", + "scaling": "always" + }, + "minecraft:fall": { + "exhaustion": 0.0, + "message_id": "fall", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:falling_anvil": { + "exhaustion": 0.10000000149011612, + "message_id": "anvil", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:falling_block": { + "exhaustion": 0.10000000149011612, + "message_id": "fallingBlock", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:falling_stalactite": { + "exhaustion": 0.10000000149011612, + "message_id": "fallingStalactite", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:fireball": { + "exhaustion": 0.10000000149011612, + "message_id": "fireball", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:fireworks": { + "exhaustion": 0.10000000149011612, + "message_id": "fireworks", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:fly_into_wall": { + "exhaustion": 0.0, + "message_id": "flyIntoWall", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:freeze": { + "exhaustion": 0.0, + "message_id": "freeze", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:generic": { + "exhaustion": 0.0, + "message_id": "generic", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:generic_kill": { + "exhaustion": 0.0, + "message_id": "genericKill", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:hot_floor": { + "exhaustion": 0.10000000149011612, + "message_id": "hotFloor", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:in_fire": { + "exhaustion": 0.10000000149011612, + "message_id": "inFire", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:in_wall": { + "exhaustion": 0.0, + "message_id": "inWall", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:indirect_magic": { + "exhaustion": 0.0, + "message_id": "indirectMagic", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:lava": { + "exhaustion": 0.10000000149011612, + "message_id": "lava", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:lightning_bolt": { + "exhaustion": 0.10000000149011612, + "message_id": "lightningBolt", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:magic": { + "exhaustion": 0.0, + "message_id": "magic", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:mob_attack": { + "exhaustion": 0.10000000149011612, + "message_id": "mob", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:mob_attack_no_aggro": { + "exhaustion": 0.10000000149011612, + "message_id": "mob", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:mob_projectile": { + "exhaustion": 0.10000000149011612, + "message_id": "mob", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:on_fire": { + "exhaustion": 0.0, + "message_id": "onFire", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:out_of_world": { + "exhaustion": 0.0, + "message_id": "outOfWorld", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:outside_border": { + "exhaustion": 0.0, + "message_id": "outsideBorder", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:player_attack": { + "exhaustion": 0.10000000149011612, + "message_id": "player", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:player_explosion": { + "exhaustion": 0.10000000149011612, + "message_id": "explosion.player", + "scaling": "always" + }, + "minecraft:sonic_boom": { + "exhaustion": 0.0, + "message_id": "sonic_boom", + "scaling": "always" + }, + "minecraft:spit": { + "exhaustion": 0.10000000149011612, + "message_id": "mob", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:stalagmite": { + "exhaustion": 0.0, + "message_id": "stalagmite", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:starve": { + "exhaustion": 0.0, + "message_id": "starve", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:sting": { + "exhaustion": 0.10000000149011612, + "message_id": "sting", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:sweet_berry_bush": { + "exhaustion": 0.10000000149011612, + "message_id": "sweetBerryBush", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:thorns": { + "exhaustion": 0.10000000149011612, + "message_id": "thorns", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:thrown": { + "exhaustion": 0.10000000149011612, + "message_id": "thrown", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:trident": { + "exhaustion": 0.10000000149011612, + "message_id": "trident", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:unattributed_fireball": { + "exhaustion": 0.10000000149011612, + "message_id": "onFire", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:wind_charge": { + "exhaustion": 0.10000000149011612, + "message_id": "mob", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:wither": { + "exhaustion": 0.0, + "message_id": "wither", + "scaling": "when_caused_by_living_non_player" + }, + "minecraft:wither_skull": { + "exhaustion": 0.10000000149011612, + "message_id": "witherSkull", + "scaling": "when_caused_by_living_non_player" + } + }, + "minecraft:dimension_type": { + "minecraft:overworld": { + "ambient_light": 0.0, + "bed_works": 1, + "coordinate_scale": 1.0, + "effects": "minecraft:overworld", + "has_ceiling": 0, + "has_raids": 1, + "has_skylight": 1, + "height": 384, + "infiniburn": "#minecraft:infiniburn_overworld", + "logical_height": 384, + "min_y": -64, + "monster_spawn_block_light_limit": 0, + "monster_spawn_light_level": { + "max_inclusive": 7, + "min_inclusive": 0, + "type": "minecraft:uniform" + }, + "natural": 1, + "piglin_safe": 0, + "respawn_anchor_works": 0, + "ultrawarm": 0 + }, + "minecraft:overworld_caves": { + "ambient_light": 0.0, + "bed_works": 1, + "coordinate_scale": 1.0, + "effects": "minecraft:overworld", + "has_ceiling": 1, + "has_raids": 1, + "has_skylight": 1, + "height": 384, + "infiniburn": "#minecraft:infiniburn_overworld", + "logical_height": 384, + "min_y": -64, + "monster_spawn_block_light_limit": 0, + "monster_spawn_light_level": { + "max_inclusive": 7, + "min_inclusive": 0, + "type": "minecraft:uniform" + }, + "natural": 1, + "piglin_safe": 0, + "respawn_anchor_works": 0, + "ultrawarm": 0 + }, + "minecraft:the_end": { + "ambient_light": 0.0, + "bed_works": 0, + "coordinate_scale": 1.0, + "effects": "minecraft:the_end", + "has_ceiling": 0, + "has_raids": 1, + "has_skylight": 0, + "height": 256, + "infiniburn": "#minecraft:infiniburn_end", + "logical_height": 256, + "min_y": 0, + "monster_spawn_block_light_limit": 0, + "monster_spawn_light_level": { + "max_inclusive": 7, + "min_inclusive": 0, + "type": "minecraft:uniform" + }, + "natural": 0, + "piglin_safe": 0, + "respawn_anchor_works": 0, + "ultrawarm": 0 + }, + "minecraft:the_nether": { + "ambient_light": 0.10000000149011612, + "bed_works": 0, + "coordinate_scale": 8.0, + "effects": "minecraft:the_nether", + "has_ceiling": 1, + "has_raids": 0, + "has_skylight": 0, + "height": 256, + "infiniburn": "#minecraft:infiniburn_nether", + "logical_height": 128, + "min_y": 0, + "monster_spawn_block_light_limit": 15, + "monster_spawn_light_level": { + "max_inclusive": 7, + "min_inclusive": 7, + "type": "minecraft:uniform" + }, + "natural": 0, + "piglin_safe": 1, + "respawn_anchor_works": 1, + "ultrawarm": 1 + } + }, + "minecraft:painting_variant": { + "minecraft:alban": { + "asset_id": "minecraft:alban", + "height": 1, + "width": 1 + }, + "minecraft:aztec": { + "asset_id": "minecraft:aztec", + "height": 1, + "width": 1 + }, + "minecraft:aztec2": { + "asset_id": "minecraft:aztec2", + "height": 1, + "width": 1 + }, + "minecraft:backyard": { + "asset_id": "minecraft:backyard", + "height": 4, + "width": 3 + }, + "minecraft:baroque": { + "asset_id": "minecraft:baroque", + "height": 2, + "width": 2 + }, + "minecraft:bomb": { + "asset_id": "minecraft:bomb", + "height": 1, + "width": 1 + }, + "minecraft:bouquet": { + "asset_id": "minecraft:bouquet", + "height": 3, + "width": 3 + }, + "minecraft:burning_skull": { + "asset_id": "minecraft:burning_skull", + "height": 4, + "width": 4 + }, + "minecraft:bust": { + "asset_id": "minecraft:bust", + "height": 2, + "width": 2 + }, + "minecraft:cavebird": { + "asset_id": "minecraft:cavebird", + "height": 3, + "width": 3 + }, + "minecraft:changing": { + "asset_id": "minecraft:changing", + "height": 2, + "width": 4 + }, + "minecraft:cotan": { + "asset_id": "minecraft:cotan", + "height": 3, + "width": 3 + }, + "minecraft:courbet": { + "asset_id": "minecraft:courbet", + "height": 1, + "width": 2 + }, + "minecraft:creebet": { + "asset_id": "minecraft:creebet", + "height": 1, + "width": 2 + }, + "minecraft:donkey_kong": { + "asset_id": "minecraft:donkey_kong", + "height": 3, + "width": 4 + }, + "minecraft:earth": { + "asset_id": "minecraft:earth", + "height": 2, + "width": 2 + }, + "minecraft:endboss": { + "asset_id": "minecraft:endboss", + "height": 3, + "width": 3 + }, + "minecraft:fern": { + "asset_id": "minecraft:fern", + "height": 3, + "width": 3 + }, + "minecraft:fighters": { + "asset_id": "minecraft:fighters", + "height": 2, + "width": 4 + }, + "minecraft:finding": { + "asset_id": "minecraft:finding", + "height": 2, + "width": 4 + }, + "minecraft:fire": { + "asset_id": "minecraft:fire", + "height": 2, + "width": 2 + }, + "minecraft:graham": { + "asset_id": "minecraft:graham", + "height": 2, + "width": 1 + }, + "minecraft:humble": { + "asset_id": "minecraft:humble", + "height": 2, + "width": 2 + }, + "minecraft:kebab": { + "asset_id": "minecraft:kebab", + "height": 1, + "width": 1 + }, + "minecraft:lowmist": { + "asset_id": "minecraft:lowmist", + "height": 2, + "width": 4 + }, + "minecraft:match": { + "asset_id": "minecraft:match", + "height": 2, + "width": 2 + }, + "minecraft:meditative": { + "asset_id": "minecraft:meditative", + "height": 1, + "width": 1 + }, + "minecraft:orb": { + "asset_id": "minecraft:orb", + "height": 4, + "width": 4 + }, + "minecraft:owlemons": { + "asset_id": "minecraft:owlemons", + "height": 3, + "width": 3 + }, + "minecraft:passage": { + "asset_id": "minecraft:passage", + "height": 2, + "width": 4 + }, + "minecraft:pigscene": { + "asset_id": "minecraft:pigscene", + "height": 4, + "width": 4 + }, + "minecraft:plant": { + "asset_id": "minecraft:plant", + "height": 1, + "width": 1 + }, + "minecraft:pointer": { + "asset_id": "minecraft:pointer", + "height": 4, + "width": 4 + }, + "minecraft:pond": { + "asset_id": "minecraft:pond", + "height": 4, + "width": 3 + }, + "minecraft:pool": { + "asset_id": "minecraft:pool", + "height": 1, + "width": 2 + }, + "minecraft:prairie_ride": { + "asset_id": "minecraft:prairie_ride", + "height": 2, + "width": 1 + }, + "minecraft:sea": { + "asset_id": "minecraft:sea", + "height": 1, + "width": 2 + }, + "minecraft:skeleton": { + "asset_id": "minecraft:skeleton", + "height": 3, + "width": 4 + }, + "minecraft:skull_and_roses": { + "asset_id": "minecraft:skull_and_roses", + "height": 2, + "width": 2 + }, + "minecraft:stage": { + "asset_id": "minecraft:stage", + "height": 2, + "width": 2 + }, + "minecraft:sunflowers": { + "asset_id": "minecraft:sunflowers", + "height": 3, + "width": 3 + }, + "minecraft:sunset": { + "asset_id": "minecraft:sunset", + "height": 1, + "width": 2 + }, + "minecraft:tides": { + "asset_id": "minecraft:tides", + "height": 3, + "width": 3 + }, + "minecraft:unpacked": { + "asset_id": "minecraft:unpacked", + "height": 4, + "width": 4 + }, + "minecraft:void": { + "asset_id": "minecraft:void", + "height": 2, + "width": 2 + }, + "minecraft:wanderer": { + "asset_id": "minecraft:wanderer", + "height": 2, + "width": 1 + }, + "minecraft:wasteland": { + "asset_id": "minecraft:wasteland", + "height": 1, + "width": 1 + }, + "minecraft:water": { + "asset_id": "minecraft:water", + "height": 2, + "width": 2 + }, + "minecraft:wind": { + "asset_id": "minecraft:wind", + "height": 2, + "width": 2 + }, + "minecraft:wither": { + "asset_id": "minecraft:wither", + "height": 2, + "width": 2 + } + }, + "minecraft:trim_material": { + "minecraft:amethyst": { + "asset_name": "amethyst", + "description": { + "color": "#9A5CC6", + "translate": "trim_material.minecraft.amethyst" + }, + "ingredient": "minecraft:amethyst_shard", + "item_model_index": 1.0 + }, + "minecraft:copper": { + "asset_name": "copper", + "description": { + "color": "#B4684D", + "translate": "trim_material.minecraft.copper" + }, + "ingredient": "minecraft:copper_ingot", + "item_model_index": 0.5 + }, + "minecraft:diamond": { + "asset_name": "diamond", + "description": { + "color": "#6EECD2", + "translate": "trim_material.minecraft.diamond" + }, + "ingredient": "minecraft:diamond", + "item_model_index": 0.800000011920929 + }, + "minecraft:emerald": { + "asset_name": "emerald", + "description": { + "color": "#11A036", + "translate": "trim_material.minecraft.emerald" + }, + "ingredient": "minecraft:emerald", + "item_model_index": 0.699999988079071 + }, + "minecraft:gold": { + "asset_name": "gold", + "description": { + "color": "#DEB12D", + "translate": "trim_material.minecraft.gold" + }, + "ingredient": "minecraft:gold_ingot", + "item_model_index": 0.6000000238418579 + }, + "minecraft:iron": { + "asset_name": "iron", + "description": { + "color": "#ECECEC", + "translate": "trim_material.minecraft.iron" + }, + "ingredient": "minecraft:iron_ingot", + "item_model_index": 0.20000000298023224 + }, + "minecraft:lapis": { + "asset_name": "lapis", + "description": { + "color": "#416E97", + "translate": "trim_material.minecraft.lapis" + }, + "ingredient": "minecraft:lapis_lazuli", + "item_model_index": 0.8999999761581421 + }, + "minecraft:netherite": { + "asset_name": "netherite", + "description": { + "color": "#625859", + "translate": "trim_material.minecraft.netherite" + }, + "ingredient": "minecraft:netherite_ingot", + "item_model_index": 0.30000001192092896 + }, + "minecraft:quartz": { + "asset_name": "quartz", + "description": { + "color": "#E3D4C4", + "translate": "trim_material.minecraft.quartz" + }, + "ingredient": "minecraft:quartz", + "item_model_index": 0.10000000149011612 + }, + "minecraft:redstone": { + "asset_name": "redstone", + "description": { + "color": "#971607", + "translate": "trim_material.minecraft.redstone" + }, + "ingredient": "minecraft:redstone", + "item_model_index": 0.4000000059604645 + } + }, + "minecraft:trim_pattern": { + "minecraft:bolt": { + "asset_id": "minecraft:bolt", + "description": { + "translate": "trim_pattern.minecraft.bolt" + }, + "template_item": "minecraft:bolt_armor_trim_smithing_template" + }, + "minecraft:coast": { + "asset_id": "minecraft:coast", + "description": { + "translate": "trim_pattern.minecraft.coast" + }, + "template_item": "minecraft:coast_armor_trim_smithing_template" + }, + "minecraft:dune": { + "asset_id": "minecraft:dune", + "description": { + "translate": "trim_pattern.minecraft.dune" + }, + "template_item": "minecraft:dune_armor_trim_smithing_template" + }, + "minecraft:eye": { + "asset_id": "minecraft:eye", + "description": { + "translate": "trim_pattern.minecraft.eye" + }, + "template_item": "minecraft:eye_armor_trim_smithing_template" + }, + "minecraft:flow": { + "asset_id": "minecraft:flow", + "description": { + "translate": "trim_pattern.minecraft.flow" + }, + "template_item": "minecraft:flow_armor_trim_smithing_template" + }, + "minecraft:host": { + "asset_id": "minecraft:host", + "description": { + "translate": "trim_pattern.minecraft.host" + }, + "template_item": "minecraft:host_armor_trim_smithing_template" + }, + "minecraft:raiser": { + "asset_id": "minecraft:raiser", + "description": { + "translate": "trim_pattern.minecraft.raiser" + }, + "template_item": "minecraft:raiser_armor_trim_smithing_template" + }, + "minecraft:rib": { + "asset_id": "minecraft:rib", + "description": { + "translate": "trim_pattern.minecraft.rib" + }, + "template_item": "minecraft:rib_armor_trim_smithing_template" + }, + "minecraft:sentry": { + "asset_id": "minecraft:sentry", + "description": { + "translate": "trim_pattern.minecraft.sentry" + }, + "template_item": "minecraft:sentry_armor_trim_smithing_template" + }, + "minecraft:shaper": { + "asset_id": "minecraft:shaper", + "description": { + "translate": "trim_pattern.minecraft.shaper" + }, + "template_item": "minecraft:shaper_armor_trim_smithing_template" + }, + "minecraft:silence": { + "asset_id": "minecraft:silence", + "description": { + "translate": "trim_pattern.minecraft.silence" + }, + "template_item": "minecraft:silence_armor_trim_smithing_template" + }, + "minecraft:snout": { + "asset_id": "minecraft:snout", + "description": { + "translate": "trim_pattern.minecraft.snout" + }, + "template_item": "minecraft:snout_armor_trim_smithing_template" + }, + "minecraft:spire": { + "asset_id": "minecraft:spire", + "description": { + "translate": "trim_pattern.minecraft.spire" + }, + "template_item": "minecraft:spire_armor_trim_smithing_template" + }, + "minecraft:tide": { + "asset_id": "minecraft:tide", + "description": { + "translate": "trim_pattern.minecraft.tide" + }, + "template_item": "minecraft:tide_armor_trim_smithing_template" + }, + "minecraft:vex": { + "asset_id": "minecraft:vex", + "description": { + "translate": "trim_pattern.minecraft.vex" + }, + "template_item": "minecraft:vex_armor_trim_smithing_template" + }, + "minecraft:ward": { + "asset_id": "minecraft:ward", + "description": { + "translate": "trim_pattern.minecraft.ward" + }, + "template_item": "minecraft:ward_armor_trim_smithing_template" + }, + "minecraft:wayfinder": { + "asset_id": "minecraft:wayfinder", + "description": { + "translate": "trim_pattern.minecraft.wayfinder" + }, + "template_item": "minecraft:wayfinder_armor_trim_smithing_template" + }, + "minecraft:wild": { + "asset_id": "minecraft:wild", + "description": { + "translate": "trim_pattern.minecraft.wild" + }, + "template_item": "minecraft:wild_armor_trim_smithing_template" + } + }, + "minecraft:wolf_variant": { + "minecraft:ashen": { + "angry_texture": "minecraft:entity/wolf/wolf_ashen_angry", + "biomes": "minecraft:snowy_taiga", + "tame_texture": "minecraft:entity/wolf/wolf_ashen_tame", + "wild_texture": "minecraft:entity/wolf/wolf_ashen" + }, + "minecraft:black": { + "angry_texture": "minecraft:entity/wolf/wolf_black_angry", + "biomes": "minecraft:old_growth_pine_taiga", + "tame_texture": "minecraft:entity/wolf/wolf_black_tame", + "wild_texture": "minecraft:entity/wolf/wolf_black" + }, + "minecraft:chestnut": { + "angry_texture": "minecraft:entity/wolf/wolf_chestnut_angry", + "biomes": "minecraft:old_growth_spruce_taiga", + "tame_texture": "minecraft:entity/wolf/wolf_chestnut_tame", + "wild_texture": "minecraft:entity/wolf/wolf_chestnut" + }, + "minecraft:pale": { + "angry_texture": "minecraft:entity/wolf/wolf_angry", + "biomes": "minecraft:taiga", + "tame_texture": "minecraft:entity/wolf/wolf_tame", + "wild_texture": "minecraft:entity/wolf/wolf" + }, + "minecraft:rusty": { + "angry_texture": "minecraft:entity/wolf/wolf_rusty_angry", + "biomes": "#minecraft:is_jungle", + "tame_texture": "minecraft:entity/wolf/wolf_rusty_tame", + "wild_texture": "minecraft:entity/wolf/wolf_rusty" + }, + "minecraft:snowy": { + "angry_texture": "minecraft:entity/wolf/wolf_snowy_angry", + "biomes": "minecraft:grove", + "tame_texture": "minecraft:entity/wolf/wolf_snowy_tame", + "wild_texture": "minecraft:entity/wolf/wolf_snowy" + }, + "minecraft:spotted": { + "angry_texture": "minecraft:entity/wolf/wolf_spotted_angry", + "biomes": "#minecraft:is_savanna", + "tame_texture": "minecraft:entity/wolf/wolf_spotted_tame", + "wild_texture": "minecraft:entity/wolf/wolf_spotted" + }, + "minecraft:striped": { + "angry_texture": "minecraft:entity/wolf/wolf_striped_angry", + "biomes": "#minecraft:is_badlands", + "tame_texture": "minecraft:entity/wolf/wolf_striped_tame", + "wild_texture": "minecraft:entity/wolf/wolf_striped" + }, + "minecraft:woods": { + "angry_texture": "minecraft:entity/wolf/wolf_woods_angry", + "biomes": "minecraft:forest", + "tame_texture": "minecraft:entity/wolf/wolf_woods_tame", + "wild_texture": "minecraft:entity/wolf/wolf_woods" + } + }, + "minecraft:worldgen/biome": { + "minecraft:badlands": { + "downfall": 0.0, + "effects": { + "fog_color": 12638463, + "foliage_color": 10387789, + "grass_color": 9470285, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.badlands" + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:bamboo_jungle": { + "downfall": 0.8999999761581421, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.bamboo_jungle" + }, + "sky_color": 7842047, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.949999988079071 + }, + "minecraft:basalt_deltas": { + "downfall": 0.0, + "effects": { + "additions_sound": { + "sound": "minecraft:ambient.basalt_deltas.additions", + "tick_chance": 0.0111 + }, + "ambient_sound": "minecraft:ambient.basalt_deltas.loop", + "fog_color": 6840176, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.basalt_deltas.mood", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.nether.basalt_deltas" + }, + "particle": { + "options": { + "type": "minecraft:white_ash" + }, + "probability": 0.118093334 + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:beach": { + "downfall": 0.4000000059604645, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 7907327, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.800000011920929 + }, + "minecraft:birch_forest": { + "downfall": 0.6000000238418579, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.forest" + }, + "sky_color": 8037887, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.6000000238418579 + }, + "minecraft:cherry_grove": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "foliage_color": 11983713, + "grass_color": 11983713, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.cherry_grove" + }, + "sky_color": 8103167, + "water_color": 6141935, + "water_fog_color": 6141935 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:cold_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4020182, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:crimson_forest": { + "downfall": 0.0, + "effects": { + "additions_sound": { + "sound": "minecraft:ambient.crimson_forest.additions", + "tick_chance": 0.0111 + }, + "ambient_sound": "minecraft:ambient.crimson_forest.loop", + "fog_color": 3343107, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.crimson_forest.mood", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.nether.crimson_forest" + }, + "particle": { + "options": { + "type": "minecraft:crimson_spore" + }, + "probability": 0.025 + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:dark_forest": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "grass_color_modifier": "dark_forest", + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.forest" + }, + "sky_color": 7972607, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.699999988079071 + }, + "minecraft:deep_cold_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4020182, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:deep_dark": { + "downfall": 0.4000000059604645, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.deep_dark" + }, + "sky_color": 7907327, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.800000011920929 + }, + "minecraft:deep_frozen_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 3750089, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:deep_lukewarm_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4566514, + "water_fog_color": 267827 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:deep_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:desert": { + "downfall": 0.0, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.desert" + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:dripstone_caves": { + "downfall": 0.4000000059604645, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.dripstone_caves" + }, + "sky_color": 7907327, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.800000011920929 + }, + "minecraft:end_barrens": { + "downfall": 0.5, + "effects": { + "fog_color": 10518688, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 0, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 0.5 + }, + "minecraft:end_highlands": { + "downfall": 0.5, + "effects": { + "fog_color": 10518688, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 0, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 0.5 + }, + "minecraft:end_midlands": { + "downfall": 0.5, + "effects": { + "fog_color": 10518688, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 0, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 0.5 + }, + "minecraft:eroded_badlands": { + "downfall": 0.0, + "effects": { + "fog_color": 12638463, + "foliage_color": 10387789, + "grass_color": 9470285, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.badlands" + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:flower_forest": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.flower_forest" + }, + "sky_color": 7972607, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.699999988079071 + }, + "minecraft:forest": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.forest" + }, + "sky_color": 7972607, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.699999988079071 + }, + "minecraft:frozen_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8364543, + "water_color": 3750089, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.0 + }, + "minecraft:frozen_peaks": { + "downfall": 0.8999999761581421, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.frozen_peaks" + }, + "sky_color": 8756735, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": -0.699999988079071 + }, + "minecraft:frozen_river": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8364543, + "water_color": 3750089, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.0 + }, + "minecraft:grove": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.grove" + }, + "sky_color": 8495359, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": -0.20000000298023224 + }, + "minecraft:ice_spikes": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8364543, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.0 + }, + "minecraft:jagged_peaks": { + "downfall": 0.8999999761581421, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.jagged_peaks" + }, + "sky_color": 8756735, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": -0.699999988079071 + }, + "minecraft:jungle": { + "downfall": 0.8999999761581421, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.jungle" + }, + "sky_color": 7842047, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.949999988079071 + }, + "minecraft:lukewarm_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4566514, + "water_fog_color": 267827 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:lush_caves": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.lush_caves" + }, + "sky_color": 8103167, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:mangrove_swamp": { + "downfall": 0.8999999761581421, + "effects": { + "fog_color": 12638463, + "foliage_color": 9285927, + "grass_color_modifier": "swamp", + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.swamp" + }, + "sky_color": 7907327, + "water_color": 3832426, + "water_fog_color": 5077600 + }, + "has_precipitation": true, + "temperature": 0.800000011920929 + }, + "minecraft:meadow": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.meadow" + }, + "sky_color": 8103167, + "water_color": 937679, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:mushroom_fields": { + "downfall": 1.0, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 7842047, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.8999999761581421 + }, + "minecraft:nether_wastes": { + "downfall": 0.0, + "effects": { + "additions_sound": { + "sound": "minecraft:ambient.nether_wastes.additions", + "tick_chance": 0.0111 + }, + "ambient_sound": "minecraft:ambient.nether_wastes.loop", + "fog_color": 3344392, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.nether_wastes.mood", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.nether.nether_wastes" + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:old_growth_birch_forest": { + "downfall": 0.6000000238418579, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.forest" + }, + "sky_color": 8037887, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.6000000238418579 + }, + "minecraft:old_growth_pine_taiga": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.old_growth_taiga" + }, + "sky_color": 8168447, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.30000001192092896 + }, + "minecraft:old_growth_spruce_taiga": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.old_growth_taiga" + }, + "sky_color": 8233983, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.25 + }, + "minecraft:plains": { + "downfall": 0.4000000059604645, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 7907327, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.800000011920929 + }, + "minecraft:river": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:savanna": { + "downfall": 0.0, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:savanna_plateau": { + "downfall": 0.0, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:small_end_islands": { + "downfall": 0.5, + "effects": { + "fog_color": 10518688, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 0, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 0.5 + }, + "minecraft:snowy_beach": { + "downfall": 0.30000001192092896, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8364543, + "water_color": 4020182, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.05000000074505806 + }, + "minecraft:snowy_plains": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8364543, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.0 + }, + "minecraft:snowy_slopes": { + "downfall": 0.8999999761581421, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.snowy_slopes" + }, + "sky_color": 8560639, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": -0.30000001192092896 + }, + "minecraft:snowy_taiga": { + "downfall": 0.4000000059604645, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8625919, + "water_color": 4020182, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": -0.5 + }, + "minecraft:soul_sand_valley": { + "downfall": 0.0, + "effects": { + "additions_sound": { + "sound": "minecraft:ambient.soul_sand_valley.additions", + "tick_chance": 0.0111 + }, + "ambient_sound": "minecraft:ambient.soul_sand_valley.loop", + "fog_color": 1787717, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.soul_sand_valley.mood", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.nether.soul_sand_valley" + }, + "particle": { + "options": { + "type": "minecraft:ash" + }, + "probability": 0.00625 + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:sparse_jungle": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.sparse_jungle" + }, + "sky_color": 7842047, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.949999988079071 + }, + "minecraft:stony_peaks": { + "downfall": 0.30000001192092896, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.stony_peaks" + }, + "sky_color": 7776511, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 1.0 + }, + "minecraft:stony_shore": { + "downfall": 0.30000001192092896, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8233727, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.20000000298023224 + }, + "minecraft:sunflower_plains": { + "downfall": 0.4000000059604645, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 7907327, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.800000011920929 + }, + "minecraft:swamp": { + "downfall": 0.8999999761581421, + "effects": { + "fog_color": 12638463, + "foliage_color": 6975545, + "grass_color_modifier": "swamp", + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.swamp" + }, + "sky_color": 7907327, + "water_color": 6388580, + "water_fog_color": 2302743 + }, + "has_precipitation": true, + "temperature": 0.800000011920929 + }, + "minecraft:taiga": { + "downfall": 0.800000011920929, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8233983, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.25 + }, + "minecraft:the_end": { + "downfall": 0.5, + "effects": { + "fog_color": 10518688, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 0, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 0.5 + }, + "minecraft:the_void": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 0.5 + }, + "minecraft:warm_ocean": { + "downfall": 0.5, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8103167, + "water_color": 4445678, + "water_fog_color": 270131 + }, + "has_precipitation": true, + "temperature": 0.5 + }, + "minecraft:warped_forest": { + "downfall": 0.0, + "effects": { + "additions_sound": { + "sound": "minecraft:ambient.warped_forest.additions", + "tick_chance": 0.0111 + }, + "ambient_sound": "minecraft:ambient.warped_forest.loop", + "fog_color": 1705242, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.warped_forest.mood", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.nether.warped_forest" + }, + "particle": { + "options": { + "type": "minecraft:warped_spore" + }, + "probability": 0.01428 + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:windswept_forest": { + "downfall": 0.30000001192092896, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8233727, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.20000000298023224 + }, + "minecraft:windswept_gravelly_hills": { + "downfall": 0.30000001192092896, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8233727, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.20000000298023224 + }, + "minecraft:windswept_hills": { + "downfall": 0.30000001192092896, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 8233727, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": true, + "temperature": 0.20000000298023224 + }, + "minecraft:windswept_savanna": { + "downfall": 0.0, + "effects": { + "fog_color": 12638463, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + }, + "minecraft:wooded_badlands": { + "downfall": 0.0, + "effects": { + "fog_color": 12638463, + "foliage_color": 10387789, + "grass_color": 9470285, + "mood_sound": { + "block_search_extent": 8, + "offset": 2.0, + "sound": "minecraft:ambient.cave", + "tick_delay": 6000 + }, + "music": { + "max_delay": 24000, + "min_delay": 12000, + "replace_current_music": false, + "sound": "minecraft:music.overworld.badlands" + }, + "sky_color": 7254527, + "water_color": 4159204, + "water_fog_color": 329011 + }, + "has_precipitation": false, + "temperature": 2.0 + } + } +} \ No newline at end of file From ac429be9c30fef9003063e24dd996eaa33dad9f5 Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Sat, 3 May 2025 23:10:43 +0300 Subject: [PATCH 32/57] ? --- src/server/protocol/play.rs | 13 +- src/server/protocol/registry_data.json | 2393 ------------------------ 2 files changed, 7 insertions(+), 2399 deletions(-) diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 4783434..8a3b192 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -36,10 +36,10 @@ pub fn send_registry_data( craftflow_nbt::to_writer(&mut data, value).unwrap(); debug!("- {key}"); - + packet.write_bytes(&data)?; } - + client.write_packet(&packet)?; } @@ -73,12 +73,13 @@ pub fn handle_play_state( // ))?; let mut packet = Packet::empty(clientbound::play::LOGIN); - packet.write_int(10)?; // Entity ID + packet.write_int(0)?; // Entity ID packet.write_boolean(false)?; // Is hardcore - packet.write_varint(1)?; // Dimension Names + packet.write_varint(4)?; // Dimension Names packet.write_string("minecraft:overworld")?; - // packet.write_string("root/minecraft:nether")?; - // packet.write_string("root/minecraft:the_end")?; + packet.write_string("minecraft:nether")?; + packet.write_string("minecraft:the_end")?; + packet.write_string("minecraft:overworld_caves")?; packet.write_varint(0)?; // Max Players packet.write_varint(8)?; // View Distance packet.write_varint(5)?; // Simulation Distance diff --git a/src/server/protocol/registry_data.json b/src/server/protocol/registry_data.json index a4fc1cd..b1708b1 100644 --- a/src/server/protocol/registry_data.json +++ b/src/server/protocol/registry_data.json @@ -1,531 +1,4 @@ { - "minecraft:banner_pattern": { - "minecraft:base": { - "asset_id": "minecraft:base", - "translation_key": "block.minecraft.banner.base" - }, - "minecraft:border": { - "asset_id": "minecraft:border", - "translation_key": "block.minecraft.banner.border" - }, - "minecraft:bricks": { - "asset_id": "minecraft:bricks", - "translation_key": "block.minecraft.banner.bricks" - }, - "minecraft:circle": { - "asset_id": "minecraft:circle", - "translation_key": "block.minecraft.banner.circle" - }, - "minecraft:creeper": { - "asset_id": "minecraft:creeper", - "translation_key": "block.minecraft.banner.creeper" - }, - "minecraft:cross": { - "asset_id": "minecraft:cross", - "translation_key": "block.minecraft.banner.cross" - }, - "minecraft:curly_border": { - "asset_id": "minecraft:curly_border", - "translation_key": "block.minecraft.banner.curly_border" - }, - "minecraft:diagonal_left": { - "asset_id": "minecraft:diagonal_left", - "translation_key": "block.minecraft.banner.diagonal_left" - }, - "minecraft:diagonal_right": { - "asset_id": "minecraft:diagonal_right", - "translation_key": "block.minecraft.banner.diagonal_right" - }, - "minecraft:diagonal_up_left": { - "asset_id": "minecraft:diagonal_up_left", - "translation_key": "block.minecraft.banner.diagonal_up_left" - }, - "minecraft:diagonal_up_right": { - "asset_id": "minecraft:diagonal_up_right", - "translation_key": "block.minecraft.banner.diagonal_up_right" - }, - "minecraft:flow": { - "asset_id": "minecraft:flow", - "translation_key": "block.minecraft.banner.flow" - }, - "minecraft:flower": { - "asset_id": "minecraft:flower", - "translation_key": "block.minecraft.banner.flower" - }, - "minecraft:globe": { - "asset_id": "minecraft:globe", - "translation_key": "block.minecraft.banner.globe" - }, - "minecraft:gradient": { - "asset_id": "minecraft:gradient", - "translation_key": "block.minecraft.banner.gradient" - }, - "minecraft:gradient_up": { - "asset_id": "minecraft:gradient_up", - "translation_key": "block.minecraft.banner.gradient_up" - }, - "minecraft:guster": { - "asset_id": "minecraft:guster", - "translation_key": "block.minecraft.banner.guster" - }, - "minecraft:half_horizontal": { - "asset_id": "minecraft:half_horizontal", - "translation_key": "block.minecraft.banner.half_horizontal" - }, - "minecraft:half_horizontal_bottom": { - "asset_id": "minecraft:half_horizontal_bottom", - "translation_key": "block.minecraft.banner.half_horizontal_bottom" - }, - "minecraft:half_vertical": { - "asset_id": "minecraft:half_vertical", - "translation_key": "block.minecraft.banner.half_vertical" - }, - "minecraft:half_vertical_right": { - "asset_id": "minecraft:half_vertical_right", - "translation_key": "block.minecraft.banner.half_vertical_right" - }, - "minecraft:mojang": { - "asset_id": "minecraft:mojang", - "translation_key": "block.minecraft.banner.mojang" - }, - "minecraft:piglin": { - "asset_id": "minecraft:piglin", - "translation_key": "block.minecraft.banner.piglin" - }, - "minecraft:rhombus": { - "asset_id": "minecraft:rhombus", - "translation_key": "block.minecraft.banner.rhombus" - }, - "minecraft:skull": { - "asset_id": "minecraft:skull", - "translation_key": "block.minecraft.banner.skull" - }, - "minecraft:small_stripes": { - "asset_id": "minecraft:small_stripes", - "translation_key": "block.minecraft.banner.small_stripes" - }, - "minecraft:square_bottom_left": { - "asset_id": "minecraft:square_bottom_left", - "translation_key": "block.minecraft.banner.square_bottom_left" - }, - "minecraft:square_bottom_right": { - "asset_id": "minecraft:square_bottom_right", - "translation_key": "block.minecraft.banner.square_bottom_right" - }, - "minecraft:square_top_left": { - "asset_id": "minecraft:square_top_left", - "translation_key": "block.minecraft.banner.square_top_left" - }, - "minecraft:square_top_right": { - "asset_id": "minecraft:square_top_right", - "translation_key": "block.minecraft.banner.square_top_right" - }, - "minecraft:straight_cross": { - "asset_id": "minecraft:straight_cross", - "translation_key": "block.minecraft.banner.straight_cross" - }, - "minecraft:stripe_bottom": { - "asset_id": "minecraft:stripe_bottom", - "translation_key": "block.minecraft.banner.stripe_bottom" - }, - "minecraft:stripe_center": { - "asset_id": "minecraft:stripe_center", - "translation_key": "block.minecraft.banner.stripe_center" - }, - "minecraft:stripe_downleft": { - "asset_id": "minecraft:stripe_downleft", - "translation_key": "block.minecraft.banner.stripe_downleft" - }, - "minecraft:stripe_downright": { - "asset_id": "minecraft:stripe_downright", - "translation_key": "block.minecraft.banner.stripe_downright" - }, - "minecraft:stripe_left": { - "asset_id": "minecraft:stripe_left", - "translation_key": "block.minecraft.banner.stripe_left" - }, - "minecraft:stripe_middle": { - "asset_id": "minecraft:stripe_middle", - "translation_key": "block.minecraft.banner.stripe_middle" - }, - "minecraft:stripe_right": { - "asset_id": "minecraft:stripe_right", - "translation_key": "block.minecraft.banner.stripe_right" - }, - "minecraft:stripe_top": { - "asset_id": "minecraft:stripe_top", - "translation_key": "block.minecraft.banner.stripe_top" - }, - "minecraft:triangle_bottom": { - "asset_id": "minecraft:triangle_bottom", - "translation_key": "block.minecraft.banner.triangle_bottom" - }, - "minecraft:triangle_top": { - "asset_id": "minecraft:triangle_top", - "translation_key": "block.minecraft.banner.triangle_top" - }, - "minecraft:triangles_bottom": { - "asset_id": "minecraft:triangles_bottom", - "translation_key": "block.minecraft.banner.triangles_bottom" - }, - "minecraft:triangles_top": { - "asset_id": "minecraft:triangles_top", - "translation_key": "block.minecraft.banner.triangles_top" - } - }, - "minecraft:chat_type": { - "minecraft:chat": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:emote_command": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.emote" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.emote" - } - }, - "minecraft:msg_command_incoming": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "commands.message.display.incoming" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:msg_command_outgoing": { - "chat": { - "parameters": [ - "target", - "content" - ], - "translation_key": "commands.message.display.outgoing" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:say_command": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.announcement" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:team_msg_command_incoming": { - "chat": { - "parameters": [ - "target", - "sender", - "content" - ], - "translation_key": "chat.type.team.text" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:team_msg_command_outgoing": { - "chat": { - "parameters": [ - "target", - "sender", - "content" - ], - "translation_key": "chat.type.team.sent" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - } - }, - "minecraft:damage_type": { - "minecraft:arrow": { - "exhaustion": 0.10000000149011612, - "message_id": "arrow", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:bad_respawn_point": { - "exhaustion": 0.10000000149011612, - "message_id": "badRespawnPoint", - "scaling": "always" - }, - "minecraft:cactus": { - "exhaustion": 0.10000000149011612, - "message_id": "cactus", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:campfire": { - "exhaustion": 0.10000000149011612, - "message_id": "inFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:cramming": { - "exhaustion": 0.0, - "message_id": "cramming", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:dragon_breath": { - "exhaustion": 0.0, - "message_id": "dragonBreath", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:drown": { - "exhaustion": 0.0, - "message_id": "drown", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:dry_out": { - "exhaustion": 0.10000000149011612, - "message_id": "dryout", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:explosion": { - "exhaustion": 0.10000000149011612, - "message_id": "explosion", - "scaling": "always" - }, - "minecraft:fall": { - "exhaustion": 0.0, - "message_id": "fall", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:falling_anvil": { - "exhaustion": 0.10000000149011612, - "message_id": "anvil", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:falling_block": { - "exhaustion": 0.10000000149011612, - "message_id": "fallingBlock", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:falling_stalactite": { - "exhaustion": 0.10000000149011612, - "message_id": "fallingStalactite", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:fireball": { - "exhaustion": 0.10000000149011612, - "message_id": "fireball", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:fireworks": { - "exhaustion": 0.10000000149011612, - "message_id": "fireworks", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:fly_into_wall": { - "exhaustion": 0.0, - "message_id": "flyIntoWall", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:freeze": { - "exhaustion": 0.0, - "message_id": "freeze", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:generic": { - "exhaustion": 0.0, - "message_id": "generic", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:generic_kill": { - "exhaustion": 0.0, - "message_id": "genericKill", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:hot_floor": { - "exhaustion": 0.10000000149011612, - "message_id": "hotFloor", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:in_fire": { - "exhaustion": 0.10000000149011612, - "message_id": "inFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:in_wall": { - "exhaustion": 0.0, - "message_id": "inWall", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:indirect_magic": { - "exhaustion": 0.0, - "message_id": "indirectMagic", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:lava": { - "exhaustion": 0.10000000149011612, - "message_id": "lava", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:lightning_bolt": { - "exhaustion": 0.10000000149011612, - "message_id": "lightningBolt", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:magic": { - "exhaustion": 0.0, - "message_id": "magic", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:mob_attack": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:mob_attack_no_aggro": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:mob_projectile": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:on_fire": { - "exhaustion": 0.0, - "message_id": "onFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:out_of_world": { - "exhaustion": 0.0, - "message_id": "outOfWorld", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:outside_border": { - "exhaustion": 0.0, - "message_id": "outsideBorder", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:player_attack": { - "exhaustion": 0.10000000149011612, - "message_id": "player", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:player_explosion": { - "exhaustion": 0.10000000149011612, - "message_id": "explosion.player", - "scaling": "always" - }, - "minecraft:sonic_boom": { - "exhaustion": 0.0, - "message_id": "sonic_boom", - "scaling": "always" - }, - "minecraft:spit": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:stalagmite": { - "exhaustion": 0.0, - "message_id": "stalagmite", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:starve": { - "exhaustion": 0.0, - "message_id": "starve", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:sting": { - "exhaustion": 0.10000000149011612, - "message_id": "sting", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:sweet_berry_bush": { - "exhaustion": 0.10000000149011612, - "message_id": "sweetBerryBush", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:thorns": { - "exhaustion": 0.10000000149011612, - "message_id": "thorns", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:thrown": { - "exhaustion": 0.10000000149011612, - "message_id": "thrown", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:trident": { - "exhaustion": 0.10000000149011612, - "message_id": "trident", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:unattributed_fireball": { - "exhaustion": 0.10000000149011612, - "message_id": "onFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:wind_charge": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:wither": { - "exhaustion": 0.0, - "message_id": "wither", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:wither_skull": { - "exhaustion": 0.10000000149011612, - "message_id": "witherSkull", - "scaling": "when_caused_by_living_non_player" - } - }, "minecraft:dimension_type": { "minecraft:overworld": { "ambient_light": 0.0, @@ -619,1871 +92,5 @@ "respawn_anchor_works": 1, "ultrawarm": 1 } - }, - "minecraft:painting_variant": { - "minecraft:alban": { - "asset_id": "minecraft:alban", - "height": 1, - "width": 1 - }, - "minecraft:aztec": { - "asset_id": "minecraft:aztec", - "height": 1, - "width": 1 - }, - "minecraft:aztec2": { - "asset_id": "minecraft:aztec2", - "height": 1, - "width": 1 - }, - "minecraft:backyard": { - "asset_id": "minecraft:backyard", - "height": 4, - "width": 3 - }, - "minecraft:baroque": { - "asset_id": "minecraft:baroque", - "height": 2, - "width": 2 - }, - "minecraft:bomb": { - "asset_id": "minecraft:bomb", - "height": 1, - "width": 1 - }, - "minecraft:bouquet": { - "asset_id": "minecraft:bouquet", - "height": 3, - "width": 3 - }, - "minecraft:burning_skull": { - "asset_id": "minecraft:burning_skull", - "height": 4, - "width": 4 - }, - "minecraft:bust": { - "asset_id": "minecraft:bust", - "height": 2, - "width": 2 - }, - "minecraft:cavebird": { - "asset_id": "minecraft:cavebird", - "height": 3, - "width": 3 - }, - "minecraft:changing": { - "asset_id": "minecraft:changing", - "height": 2, - "width": 4 - }, - "minecraft:cotan": { - "asset_id": "minecraft:cotan", - "height": 3, - "width": 3 - }, - "minecraft:courbet": { - "asset_id": "minecraft:courbet", - "height": 1, - "width": 2 - }, - "minecraft:creebet": { - "asset_id": "minecraft:creebet", - "height": 1, - "width": 2 - }, - "minecraft:donkey_kong": { - "asset_id": "minecraft:donkey_kong", - "height": 3, - "width": 4 - }, - "minecraft:earth": { - "asset_id": "minecraft:earth", - "height": 2, - "width": 2 - }, - "minecraft:endboss": { - "asset_id": "minecraft:endboss", - "height": 3, - "width": 3 - }, - "minecraft:fern": { - "asset_id": "minecraft:fern", - "height": 3, - "width": 3 - }, - "minecraft:fighters": { - "asset_id": "minecraft:fighters", - "height": 2, - "width": 4 - }, - "minecraft:finding": { - "asset_id": "minecraft:finding", - "height": 2, - "width": 4 - }, - "minecraft:fire": { - "asset_id": "minecraft:fire", - "height": 2, - "width": 2 - }, - "minecraft:graham": { - "asset_id": "minecraft:graham", - "height": 2, - "width": 1 - }, - "minecraft:humble": { - "asset_id": "minecraft:humble", - "height": 2, - "width": 2 - }, - "minecraft:kebab": { - "asset_id": "minecraft:kebab", - "height": 1, - "width": 1 - }, - "minecraft:lowmist": { - "asset_id": "minecraft:lowmist", - "height": 2, - "width": 4 - }, - "minecraft:match": { - "asset_id": "minecraft:match", - "height": 2, - "width": 2 - }, - "minecraft:meditative": { - "asset_id": "minecraft:meditative", - "height": 1, - "width": 1 - }, - "minecraft:orb": { - "asset_id": "minecraft:orb", - "height": 4, - "width": 4 - }, - "minecraft:owlemons": { - "asset_id": "minecraft:owlemons", - "height": 3, - "width": 3 - }, - "minecraft:passage": { - "asset_id": "minecraft:passage", - "height": 2, - "width": 4 - }, - "minecraft:pigscene": { - "asset_id": "minecraft:pigscene", - "height": 4, - "width": 4 - }, - "minecraft:plant": { - "asset_id": "minecraft:plant", - "height": 1, - "width": 1 - }, - "minecraft:pointer": { - "asset_id": "minecraft:pointer", - "height": 4, - "width": 4 - }, - "minecraft:pond": { - "asset_id": "minecraft:pond", - "height": 4, - "width": 3 - }, - "minecraft:pool": { - "asset_id": "minecraft:pool", - "height": 1, - "width": 2 - }, - "minecraft:prairie_ride": { - "asset_id": "minecraft:prairie_ride", - "height": 2, - "width": 1 - }, - "minecraft:sea": { - "asset_id": "minecraft:sea", - "height": 1, - "width": 2 - }, - "minecraft:skeleton": { - "asset_id": "minecraft:skeleton", - "height": 3, - "width": 4 - }, - "minecraft:skull_and_roses": { - "asset_id": "minecraft:skull_and_roses", - "height": 2, - "width": 2 - }, - "minecraft:stage": { - "asset_id": "minecraft:stage", - "height": 2, - "width": 2 - }, - "minecraft:sunflowers": { - "asset_id": "minecraft:sunflowers", - "height": 3, - "width": 3 - }, - "minecraft:sunset": { - "asset_id": "minecraft:sunset", - "height": 1, - "width": 2 - }, - "minecraft:tides": { - "asset_id": "minecraft:tides", - "height": 3, - "width": 3 - }, - "minecraft:unpacked": { - "asset_id": "minecraft:unpacked", - "height": 4, - "width": 4 - }, - "minecraft:void": { - "asset_id": "minecraft:void", - "height": 2, - "width": 2 - }, - "minecraft:wanderer": { - "asset_id": "minecraft:wanderer", - "height": 2, - "width": 1 - }, - "minecraft:wasteland": { - "asset_id": "minecraft:wasteland", - "height": 1, - "width": 1 - }, - "minecraft:water": { - "asset_id": "minecraft:water", - "height": 2, - "width": 2 - }, - "minecraft:wind": { - "asset_id": "minecraft:wind", - "height": 2, - "width": 2 - }, - "minecraft:wither": { - "asset_id": "minecraft:wither", - "height": 2, - "width": 2 - } - }, - "minecraft:trim_material": { - "minecraft:amethyst": { - "asset_name": "amethyst", - "description": { - "color": "#9A5CC6", - "translate": "trim_material.minecraft.amethyst" - }, - "ingredient": "minecraft:amethyst_shard", - "item_model_index": 1.0 - }, - "minecraft:copper": { - "asset_name": "copper", - "description": { - "color": "#B4684D", - "translate": "trim_material.minecraft.copper" - }, - "ingredient": "minecraft:copper_ingot", - "item_model_index": 0.5 - }, - "minecraft:diamond": { - "asset_name": "diamond", - "description": { - "color": "#6EECD2", - "translate": "trim_material.minecraft.diamond" - }, - "ingredient": "minecraft:diamond", - "item_model_index": 0.800000011920929 - }, - "minecraft:emerald": { - "asset_name": "emerald", - "description": { - "color": "#11A036", - "translate": "trim_material.minecraft.emerald" - }, - "ingredient": "minecraft:emerald", - "item_model_index": 0.699999988079071 - }, - "minecraft:gold": { - "asset_name": "gold", - "description": { - "color": "#DEB12D", - "translate": "trim_material.minecraft.gold" - }, - "ingredient": "minecraft:gold_ingot", - "item_model_index": 0.6000000238418579 - }, - "minecraft:iron": { - "asset_name": "iron", - "description": { - "color": "#ECECEC", - "translate": "trim_material.minecraft.iron" - }, - "ingredient": "minecraft:iron_ingot", - "item_model_index": 0.20000000298023224 - }, - "minecraft:lapis": { - "asset_name": "lapis", - "description": { - "color": "#416E97", - "translate": "trim_material.minecraft.lapis" - }, - "ingredient": "minecraft:lapis_lazuli", - "item_model_index": 0.8999999761581421 - }, - "minecraft:netherite": { - "asset_name": "netherite", - "description": { - "color": "#625859", - "translate": "trim_material.minecraft.netherite" - }, - "ingredient": "minecraft:netherite_ingot", - "item_model_index": 0.30000001192092896 - }, - "minecraft:quartz": { - "asset_name": "quartz", - "description": { - "color": "#E3D4C4", - "translate": "trim_material.minecraft.quartz" - }, - "ingredient": "minecraft:quartz", - "item_model_index": 0.10000000149011612 - }, - "minecraft:redstone": { - "asset_name": "redstone", - "description": { - "color": "#971607", - "translate": "trim_material.minecraft.redstone" - }, - "ingredient": "minecraft:redstone", - "item_model_index": 0.4000000059604645 - } - }, - "minecraft:trim_pattern": { - "minecraft:bolt": { - "asset_id": "minecraft:bolt", - "description": { - "translate": "trim_pattern.minecraft.bolt" - }, - "template_item": "minecraft:bolt_armor_trim_smithing_template" - }, - "minecraft:coast": { - "asset_id": "minecraft:coast", - "description": { - "translate": "trim_pattern.minecraft.coast" - }, - "template_item": "minecraft:coast_armor_trim_smithing_template" - }, - "minecraft:dune": { - "asset_id": "minecraft:dune", - "description": { - "translate": "trim_pattern.minecraft.dune" - }, - "template_item": "minecraft:dune_armor_trim_smithing_template" - }, - "minecraft:eye": { - "asset_id": "minecraft:eye", - "description": { - "translate": "trim_pattern.minecraft.eye" - }, - "template_item": "minecraft:eye_armor_trim_smithing_template" - }, - "minecraft:flow": { - "asset_id": "minecraft:flow", - "description": { - "translate": "trim_pattern.minecraft.flow" - }, - "template_item": "minecraft:flow_armor_trim_smithing_template" - }, - "minecraft:host": { - "asset_id": "minecraft:host", - "description": { - "translate": "trim_pattern.minecraft.host" - }, - "template_item": "minecraft:host_armor_trim_smithing_template" - }, - "minecraft:raiser": { - "asset_id": "minecraft:raiser", - "description": { - "translate": "trim_pattern.minecraft.raiser" - }, - "template_item": "minecraft:raiser_armor_trim_smithing_template" - }, - "minecraft:rib": { - "asset_id": "minecraft:rib", - "description": { - "translate": "trim_pattern.minecraft.rib" - }, - "template_item": "minecraft:rib_armor_trim_smithing_template" - }, - "minecraft:sentry": { - "asset_id": "minecraft:sentry", - "description": { - "translate": "trim_pattern.minecraft.sentry" - }, - "template_item": "minecraft:sentry_armor_trim_smithing_template" - }, - "minecraft:shaper": { - "asset_id": "minecraft:shaper", - "description": { - "translate": "trim_pattern.minecraft.shaper" - }, - "template_item": "minecraft:shaper_armor_trim_smithing_template" - }, - "minecraft:silence": { - "asset_id": "minecraft:silence", - "description": { - "translate": "trim_pattern.minecraft.silence" - }, - "template_item": "minecraft:silence_armor_trim_smithing_template" - }, - "minecraft:snout": { - "asset_id": "minecraft:snout", - "description": { - "translate": "trim_pattern.minecraft.snout" - }, - "template_item": "minecraft:snout_armor_trim_smithing_template" - }, - "minecraft:spire": { - "asset_id": "minecraft:spire", - "description": { - "translate": "trim_pattern.minecraft.spire" - }, - "template_item": "minecraft:spire_armor_trim_smithing_template" - }, - "minecraft:tide": { - "asset_id": "minecraft:tide", - "description": { - "translate": "trim_pattern.minecraft.tide" - }, - "template_item": "minecraft:tide_armor_trim_smithing_template" - }, - "minecraft:vex": { - "asset_id": "minecraft:vex", - "description": { - "translate": "trim_pattern.minecraft.vex" - }, - "template_item": "minecraft:vex_armor_trim_smithing_template" - }, - "minecraft:ward": { - "asset_id": "minecraft:ward", - "description": { - "translate": "trim_pattern.minecraft.ward" - }, - "template_item": "minecraft:ward_armor_trim_smithing_template" - }, - "minecraft:wayfinder": { - "asset_id": "minecraft:wayfinder", - "description": { - "translate": "trim_pattern.minecraft.wayfinder" - }, - "template_item": "minecraft:wayfinder_armor_trim_smithing_template" - }, - "minecraft:wild": { - "asset_id": "minecraft:wild", - "description": { - "translate": "trim_pattern.minecraft.wild" - }, - "template_item": "minecraft:wild_armor_trim_smithing_template" - } - }, - "minecraft:wolf_variant": { - "minecraft:ashen": { - "angry_texture": "minecraft:entity/wolf/wolf_ashen_angry", - "biomes": "minecraft:snowy_taiga", - "tame_texture": "minecraft:entity/wolf/wolf_ashen_tame", - "wild_texture": "minecraft:entity/wolf/wolf_ashen" - }, - "minecraft:black": { - "angry_texture": "minecraft:entity/wolf/wolf_black_angry", - "biomes": "minecraft:old_growth_pine_taiga", - "tame_texture": "minecraft:entity/wolf/wolf_black_tame", - "wild_texture": "minecraft:entity/wolf/wolf_black" - }, - "minecraft:chestnut": { - "angry_texture": "minecraft:entity/wolf/wolf_chestnut_angry", - "biomes": "minecraft:old_growth_spruce_taiga", - "tame_texture": "minecraft:entity/wolf/wolf_chestnut_tame", - "wild_texture": "minecraft:entity/wolf/wolf_chestnut" - }, - "minecraft:pale": { - "angry_texture": "minecraft:entity/wolf/wolf_angry", - "biomes": "minecraft:taiga", - "tame_texture": "minecraft:entity/wolf/wolf_tame", - "wild_texture": "minecraft:entity/wolf/wolf" - }, - "minecraft:rusty": { - "angry_texture": "minecraft:entity/wolf/wolf_rusty_angry", - "biomes": "#minecraft:is_jungle", - "tame_texture": "minecraft:entity/wolf/wolf_rusty_tame", - "wild_texture": "minecraft:entity/wolf/wolf_rusty" - }, - "minecraft:snowy": { - "angry_texture": "minecraft:entity/wolf/wolf_snowy_angry", - "biomes": "minecraft:grove", - "tame_texture": "minecraft:entity/wolf/wolf_snowy_tame", - "wild_texture": "minecraft:entity/wolf/wolf_snowy" - }, - "minecraft:spotted": { - "angry_texture": "minecraft:entity/wolf/wolf_spotted_angry", - "biomes": "#minecraft:is_savanna", - "tame_texture": "minecraft:entity/wolf/wolf_spotted_tame", - "wild_texture": "minecraft:entity/wolf/wolf_spotted" - }, - "minecraft:striped": { - "angry_texture": "minecraft:entity/wolf/wolf_striped_angry", - "biomes": "#minecraft:is_badlands", - "tame_texture": "minecraft:entity/wolf/wolf_striped_tame", - "wild_texture": "minecraft:entity/wolf/wolf_striped" - }, - "minecraft:woods": { - "angry_texture": "minecraft:entity/wolf/wolf_woods_angry", - "biomes": "minecraft:forest", - "tame_texture": "minecraft:entity/wolf/wolf_woods_tame", - "wild_texture": "minecraft:entity/wolf/wolf_woods" - } - }, - "minecraft:worldgen/biome": { - "minecraft:badlands": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "foliage_color": 10387789, - "grass_color": 9470285, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.badlands" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:bamboo_jungle": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.bamboo_jungle" - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.949999988079071 - }, - "minecraft:basalt_deltas": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.basalt_deltas.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.basalt_deltas.loop", - "fog_color": 6840176, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.basalt_deltas.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.basalt_deltas" - }, - "particle": { - "options": { - "type": "minecraft:white_ash" - }, - "probability": 0.118093334 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:beach": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:birch_forest": { - "downfall": 0.6000000238418579, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 8037887, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.6000000238418579 - }, - "minecraft:cherry_grove": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "foliage_color": 11983713, - "grass_color": 11983713, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.cherry_grove" - }, - "sky_color": 8103167, - "water_color": 6141935, - "water_fog_color": 6141935 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:cold_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:crimson_forest": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.crimson_forest.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.crimson_forest.loop", - "fog_color": 3343107, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.crimson_forest.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.crimson_forest" - }, - "particle": { - "options": { - "type": "minecraft:crimson_spore" - }, - "probability": 0.025 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:dark_forest": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "grass_color_modifier": "dark_forest", - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 7972607, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.699999988079071 - }, - "minecraft:deep_cold_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:deep_dark": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.deep_dark" - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:deep_frozen_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 3750089, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:deep_lukewarm_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4566514, - "water_fog_color": 267827 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:deep_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:desert": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.desert" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:dripstone_caves": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.dripstone_caves" - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:end_barrens": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:end_highlands": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:end_midlands": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:eroded_badlands": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "foliage_color": 10387789, - "grass_color": 9470285, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.badlands" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:flower_forest": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.flower_forest" - }, - "sky_color": 7972607, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.699999988079071 - }, - "minecraft:forest": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 7972607, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.699999988079071 - }, - "minecraft:frozen_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 3750089, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:frozen_peaks": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.frozen_peaks" - }, - "sky_color": 8756735, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.699999988079071 - }, - "minecraft:frozen_river": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 3750089, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:grove": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.grove" - }, - "sky_color": 8495359, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.20000000298023224 - }, - "minecraft:ice_spikes": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:jagged_peaks": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.jagged_peaks" - }, - "sky_color": 8756735, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.699999988079071 - }, - "minecraft:jungle": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.jungle" - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.949999988079071 - }, - "minecraft:lukewarm_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4566514, - "water_fog_color": 267827 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:lush_caves": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.lush_caves" - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:mangrove_swamp": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "foliage_color": 9285927, - "grass_color_modifier": "swamp", - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.swamp" - }, - "sky_color": 7907327, - "water_color": 3832426, - "water_fog_color": 5077600 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:meadow": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.meadow" - }, - "sky_color": 8103167, - "water_color": 937679, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:mushroom_fields": { - "downfall": 1.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.8999999761581421 - }, - "minecraft:nether_wastes": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.nether_wastes.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.nether_wastes.loop", - "fog_color": 3344392, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.nether_wastes.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.nether_wastes" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:old_growth_birch_forest": { - "downfall": 0.6000000238418579, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 8037887, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.6000000238418579 - }, - "minecraft:old_growth_pine_taiga": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.old_growth_taiga" - }, - "sky_color": 8168447, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.30000001192092896 - }, - "minecraft:old_growth_spruce_taiga": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.old_growth_taiga" - }, - "sky_color": 8233983, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.25 - }, - "minecraft:plains": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:river": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:savanna": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:savanna_plateau": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:small_end_islands": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:snowy_beach": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.05000000074505806 - }, - "minecraft:snowy_plains": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:snowy_slopes": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.snowy_slopes" - }, - "sky_color": 8560639, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.30000001192092896 - }, - "minecraft:snowy_taiga": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8625919, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.5 - }, - "minecraft:soul_sand_valley": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.soul_sand_valley.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.soul_sand_valley.loop", - "fog_color": 1787717, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.soul_sand_valley.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.soul_sand_valley" - }, - "particle": { - "options": { - "type": "minecraft:ash" - }, - "probability": 0.00625 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:sparse_jungle": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.sparse_jungle" - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.949999988079071 - }, - "minecraft:stony_peaks": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.stony_peaks" - }, - "sky_color": 7776511, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 1.0 - }, - "minecraft:stony_shore": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:sunflower_plains": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:swamp": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "foliage_color": 6975545, - "grass_color_modifier": "swamp", - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.swamp" - }, - "sky_color": 7907327, - "water_color": 6388580, - "water_fog_color": 2302743 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:taiga": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233983, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.25 - }, - "minecraft:the_end": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:the_void": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:warm_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4445678, - "water_fog_color": 270131 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:warped_forest": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.warped_forest.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.warped_forest.loop", - "fog_color": 1705242, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.warped_forest.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.warped_forest" - }, - "particle": { - "options": { - "type": "minecraft:warped_spore" - }, - "probability": 0.01428 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:windswept_forest": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:windswept_gravelly_hills": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:windswept_hills": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:windswept_savanna": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:wooded_badlands": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "foliage_color": 10387789, - "grass_color": 9470285, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.badlands" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - } } } \ No newline at end of file From 7ffccfed26fdd78e6d829090da0836a7c87c61c2 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sun, 4 May 2025 03:15:22 +0300 Subject: [PATCH 33/57] created sniff-packets program sniffed update_tags and registry_data packets to .bin files --- .envrc | 17 + rust-toolchain.toml | 2 + shell.nix | 1 + sniff-packets/.gitignore | 1 + sniff-packets/Cargo.lock | 192 ++ sniff-packets/Cargo.toml | 11 + sniff-packets/src/main.rs | 179 ++ src/server/protocol/handler.rs | 5 + src/server/protocol/play.rs | 88 +- src/server/protocol/registry-data.bin | Bin 0 -> 8133 bytes src/server/protocol/registry_data.json | 2489 ------------------------ src/server/protocol/update-tags.bin | Bin 0 -> 27198 bytes 12 files changed, 457 insertions(+), 2528 deletions(-) create mode 100644 .envrc create mode 100644 rust-toolchain.toml create mode 100644 sniff-packets/.gitignore create mode 100644 sniff-packets/Cargo.lock create mode 100644 sniff-packets/Cargo.toml create mode 100644 sniff-packets/src/main.rs create mode 100644 src/server/protocol/registry-data.bin delete mode 100644 src/server/protocol/registry_data.json create mode 100644 src/server/protocol/update-tags.bin diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..412cbef --- /dev/null +++ b/.envrc @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# ^ make editor happy + +# +# Use https://direnv.net/ to automatically load the dev shell. +# + +if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" +fi + +watch_file nix/** +watch_file -- **/*.nix +# Adding files to git includes them in a flake +# But it is also a bit much reloading. +# watch_file .git/index .git/HEAD +use flake . --show-trace diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..292fe49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable" diff --git a/shell.nix b/shell.nix index d4ddaeb..85640ef 100644 --- a/shell.nix +++ b/shell.nix @@ -6,6 +6,7 @@ mkShell { direnv rustc cargo + rustfmt python3 python3Packages.beautifulsoup4 python3Packages.requests diff --git a/sniff-packets/.gitignore b/sniff-packets/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/sniff-packets/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/sniff-packets/Cargo.lock b/sniff-packets/Cargo.lock new file mode 100644 index 0000000..17f1a38 --- /dev/null +++ b/sniff-packets/Cargo.lock @@ -0,0 +1,192 @@ +# This file is automatically @generated by Cargo. +# 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 = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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" +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 = "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 = "sniff-packets" +version = "0.1.0" +dependencies = [ + "craftflow-nbt", + "rust_mc_proto", + "serde", + "serde_json", + "uuid", +] + +[[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 = "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 = "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/sniff-packets/Cargo.toml b/sniff-packets/Cargo.toml new file mode 100644 index 0000000..742bbbb --- /dev/null +++ b/sniff-packets/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sniff-packets" +version = "0.1.0" +edition = "2024" + +[dependencies] +rust_mc_proto = "0.1.19" +serde = "1.0.219" +serde_json = "1.0.140" +craftflow-nbt = "2.1.0" +uuid = "1.16.0" \ No newline at end of file diff --git a/sniff-packets/src/main.rs b/sniff-packets/src/main.rs new file mode 100644 index 0000000..67f3194 --- /dev/null +++ b/sniff-packets/src/main.rs @@ -0,0 +1,179 @@ +use std::{fs, io::Read}; + +use craftflow_nbt::DynNBT; +use rust_mc_proto::{prelude::*, write_packet, MCConnTcp, Packet, ProtocolError}; +use uuid::Uuid; + + +pub trait ReadWriteNBT: DataReader + DataWriter { + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ProtocolError>; +} + +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + let mut data = Vec::new(); + let pos = self.get_ref().position(); + self.get_mut() + .read_to_end(&mut data) + .map_err(|_| ProtocolError::StringParseError)?; + let (remaining, value) = + craftflow_nbt::from_slice(&data).map_err(|_| ProtocolError::StringParseError)?; + self.get_mut() + .set_position(pos + (data.len() - remaining.len()) as u64); + Ok(value) + } + + fn write_nbt(&mut self, val: &DynNBT) -> Result<(), ProtocolError> { + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ProtocolError::StringParseError)?; + Ok(()) + } +} + + +fn main() -> Result<(), ProtocolError> { + let mut conn = MCConnTcp::connect("localhost:25565").unwrap(); + + conn.write_packet(&Packet::build(0x00, |packet| { + packet.write_varint(770)?; + packet.write_string("localhost")?; + packet.write_unsigned_short(25565)?; + packet.write_varint(2) + })?)?; + + conn.write_packet(&Packet::build(0x00, |packet| { + packet.write_string("TheMixRay")?; + packet.write_uuid(&Uuid::default()) + })?)?; + + loop { + let mut packet = conn.read_packet()?; + + if packet.id() == 0x03 { + let threshold = packet.read_varint()?; + + if threshold >= 0 { + conn.set_compression(Some(threshold as usize)); + } + } else if packet.id() == 0x02 { + break; + } + } + + conn.write_packet(&Packet::empty(0x03))?; + + conn.write_packet(&Packet::build(0x02, |packet| { + packet.write_string("minecraft:brand")?; + packet.write_string("vanilla") + })?)?; + + conn.write_packet(&Packet::build(0x00, |packet| { + packet.write_string("en_us")?; + packet.write_signed_byte(12)?; + packet.write_varint(0)?; + packet.write_boolean(true)?; + packet.write_byte(127)?; + packet.write_varint(1)?; + packet.write_boolean(true)?; + packet.write_boolean(true)?; + packet.write_varint(0) + })?)?; + + let mut packet = conn.read_packet()?; // server brand + + let id = packet.read_string()?; + println!("message id: {}", id); + println!("message data: {}", String::from_utf8_lossy(&packet.get_bytes()[id.len()+1..])); + + let mut packet = conn.read_packet()?; // feature flags + + let flags_len = packet.read_varint()?; + + println!("got {} feature flags:", flags_len); + + for _ in 0..flags_len { + let flag = packet.read_string()?; + + println!("flag: {}", flag); + } + + let mut packet = conn.read_packet()?; // wait for known packs packet + + if packet.id() != 0x0E { + println!("got unexpected packet while looking for 0x0E: 0x{:02X}", packet.id()); + return Ok(()); + } + + let packs_len = packet.read_varint()?; + + println!("got {} known packs:", packs_len); + + for _ in 0..packs_len { + println!("{}:{} v{}", packet.read_string()?, packet.read_string()?, packet.read_string()?); + } + + packet.set_id(0x07); // make it serverbound + + conn.write_packet(&packet)?; + + let mut data = Vec::new(); + + loop { + let mut packet = conn.read_packet()?; + + if packet.id() != 0x07 { // update tags + let registries_len = packet.read_varint()?; + + println!("got update tags: {}", registries_len); + + for _ in 0..registries_len { + let registry = packet.read_string()?; + + println!("registry: {}", registry); + + let tags_len = packet.read_varint()?; + + for _ in 0..tags_len { + let tag_name = packet.read_string()?; + + println!("tag: {}", tag_name); + + let entries_len = packet.read_varint()?; + + for _ in 0..entries_len { + let entry = packet.read_varint()?; + + println!("entry: {}", entry); + } + } + } + + fs::write("update-tags.bin", packet.get_bytes()).unwrap(); + + break; + } + + println!("got registry: {}", packet.read_string()?); + + let entries_len = packet.read_varint()?; + + for _ in 0..entries_len { + let entry_id = packet.read_string()?; + let has_data = packet.read_boolean()?; + + if has_data { + let entry_data = packet.read_nbt()?; + + println!("entry: {}, data: {:?}", entry_id, entry_data); + } else { + println!("entry: {}, no data", entry_id); + } + } + + write_packet(&mut data, None, 0, &packet)?; + } + + fs::write("registry-data.bin", &data).unwrap(); + + Ok(()) +} diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index 84bf5e4..a724bb3 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -157,6 +157,11 @@ pub fn handle_connection( particle_status, }); + client.write_packet(&Packet::build(clientbound::configuration::PLUGIN_MESSAGE, |p| { + p.write_string("minecraft:brand")?; + p.write_string("rust_minecraft_server") + })?)?; + handle_configuration_state(client.clone())?; client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 4783434..e125d6b 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,64 +1,67 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{io::Cursor, sync::Arc}; -use craftflow_nbt::DynNBT; -use log::debug; -use rust_mc_proto::{DataWriter, Packet}; -use serde_json::{json, Value}; +use rust_mc_proto::{read_packet, DataWriter, Packet}; use crate::server::{ - data::ReadWriteNBT, player::context::ClientContext, ServerError + player::context::ClientContext, ServerError }; -use super::id::{clientbound::{self, configuration::REGISTRY_DATA}, serverbound}; +use super::id::*; + +pub fn send_update_tags( + client: Arc, +) -> Result<(), ServerError> { + + // rewrite this hardcode bullshit + + client.write_packet(&Packet::from_bytes(clientbound::configuration::UPDATE_TAGS, include_bytes!("update-tags.bin")))?; + + Ok(()) +} pub fn send_registry_data( client: Arc, ) -> Result<(), ServerError> { - let registry_data = include_str!("registry_data.json"); - let registry_data: Value = serde_json::from_str(registry_data).unwrap(); - let registry_data = registry_data.as_object().unwrap(); - for (registry_name, registry_data) in registry_data { - let registry_data = registry_data.as_object().unwrap(); + // rewrite this hardcode bullshit - let mut packet = Packet::empty(clientbound::configuration::REGISTRY_DATA); - packet.write_string(registry_name)?; - - packet.write_usize_varint(registry_data.len())?; - - debug!("sending registry: {registry_name}"); - - for (key, value) in registry_data { - packet.write_string(key)?; - packet.write_boolean(true)?; - - let mut data = Vec::new(); - craftflow_nbt::to_writer(&mut data, value).unwrap(); - - debug!("- {key}"); - - packet.write_bytes(&data)?; - } - + let mut registry_data = Cursor::new(include_bytes!("registry-data.bin")); + + while let Ok(mut packet) = read_packet(&mut registry_data, None) { + packet.set_id(clientbound::configuration::REGISTRY_DATA); client.write_packet(&packet)?; } Ok(()) } +pub fn process_known_packs( + client: Arc +) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS); + packet.write_varint(1)?; + packet.write_string("minecraft")?; + packet.write_string("core")?; + packet.write_string("1.21.5")?; + client.write_packet(&packet)?; + + client.read_packet(serverbound::configuration::KNOWN_PACKS)?; + + Ok(()) +} + pub fn handle_configuration_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { - let mut p = Packet::empty(clientbound::configuration::KNOWN_PACKS); - p.write_varint(1)?; - p.write_string("minecraft")?; - p.write_string("core")?; - p.write_string("1.21.5")?; - client.write_packet(&p)?; - client.read_packet(serverbound::configuration::KNOWN_PACKS)?; + let mut packet = Packet::empty(clientbound::configuration::FEATURE_FLAGS); + packet.write_varint(1)?; + packet.write_string("minecraft:vanilla")?; + client.write_packet(&packet)?; + process_known_packs(client.clone())?; send_registry_data(client.clone())?; + send_update_tags(client.clone())?; Ok(()) } @@ -100,7 +103,14 @@ pub fn handle_play_state( packet.write_boolean(false)?; // Enforces Secure Chat client.write_packet(&packet)?; - loop {} + loop {} + + // TODO: отдельный поток для чтения пакетов + + // TODO: переработка функции read_packet так чтобы когда + // делаешь read_any_packet, пакет отправлялся сначала всем другим + // функциям read_packet которые настроены на этот айди пакета, + // а потом если таковых не осталось пакет возвращался Ok(()) } diff --git a/src/server/protocol/registry-data.bin b/src/server/protocol/registry-data.bin new file mode 100644 index 0000000000000000000000000000000000000000..e466400f9d31d34a7e1f5b0a55ed32719194337b GIT binary patch literal 8133 zcmdnO!!D7VnU|Vel$cgxRi0mzlaijAr=OIWpPT9^gpf%}Ovy>iODSd$M;6OX%FmC_ zD$PsJNkx??PRuEZPf5)wNi1gIN79j+n4G~Nh9r<#l$;TtmS2=wT!Ji~oRL~oR2iRM zlwX$0Ac9bzoS%~tpP!tXn8$$R?Bt@%+~WK^m^q>d^(l!(*)U;AB;nN5f_Nmegwdox z+8CtK1k;N0t5Wlzw#%YP=9FfqmM0eFLZy+MoC2~7;$#72O~t82B@D=JD#|PGcwaNAQ6KUsvwEnOlX85S&>?lpOOkP3Mz#Z zmuWfq<*7xmph9veX565J1Cp&!@q*OE>|*4&go+nsmZcUUCkBkj%}h>>FD}T;PDRn4 zm6)EMni3B&7Rh1IWQUXtaK}MTX>kTLSdrA{Cgy?N5MNxLm|MVrWOi<9VoH8Fa_r=m z7H1UY=jX5DV zyu3tYk3)sx3vv=mQWHy&b477(VopvxD6wT0L((9UBZ~9#%PZrP&@((l80r+H!~+p8 z&dD!8P5KaVNGM1lIW)gCC%!l_FD1S#F()Ur61ns#E=VjYPK6aq$WAKB&#QzcBn;u= zjQpZh24pKr^Prgm>RP0743c1w1RxGX3dxd;R8Yt;AQhn?p|bqU6y)*@smKsV_^v#$ zs30{3mKl-URGyiaQe2)|Py&-vLed16OD{?+OU=otjL*o-$tgyTOoV=j6jBj~kb=fI zioSf*g5v=rJ5rIEoRL@(Us73+%Fc&y4oDC=R#J2GOH$*L^K)|(^HLb(5DIgP(_zB# znR&_ixtV$Cxa9LoOVaZ}^2pJbSP9e5po}oKBsDQN9;?~-6u~Vx&cu$CI!lT&bK?sV zOG;9U@`R8)nUtSXf?T8}=O-4IFd!*NDa}h|;6=D9wK5f18kE40`5F1eCCFK}C^54* zwFudOqRb>@1;wd(B}J7eA{mJVsYMJ(NwGLHCp9lQ71^D|dHJO%4lgdqEJATvNoER) z!^=`DkR4i{Sd_wmlmyBXE7LObQc{ah6lLb5Fg#{tM@re?fXq!SNiE7u%;84L9ErK9 zB^i~dzE93CK=VpUW@2uBUJA1BQgc&_5_3|J?M=@|#1sZ!cBC9$keLn(PG+RM1!}(`$6`rpZb52MVhKt*1{JUj ze#o(tR+LYnaW5H>U6-6#0&^WVQjP#sjga_73PFefQmLC%lv$EloDmPIeM&MCGmDT@ zU~*zkW^z8VNLFf2PG%}{m=~lL6=x>qAqy2Hrl;iRCG7@aQ!{3i`yH zq(qd=msnMjnv5K{U;!iK?3k38oLvbj>5xj(q{O29!cvqpmXx2Hgd71$`K5)WsU^s@ zUs7pN9>`(E*`+x-$QdN5w73M>N>JNBDYGaAMJyvRFCA3tA$cb`zXUbblk-c9l2S{M z-H}|BnhFv^%1SBudD*Fz@!9!#>Bwb4YGP4I2C~-Fyp*K;;$mc%rll6;A-gIqGd-gu zwWt_b8>kKdITR_ErhzIJq^5OxQDR17E^@eMl;$Sopd`lZ)TBg|5Y5Ri&&@1Gsj+es zOOi8?n^L){DVZgSC7ER?^;UjS60)Q7%X3n5^Yc&~U65E@oS2@9tgIk2y*N2F55-Xh zIf;2F!CR1@nOA~Z;}w7^CnV1ofa9m!%eg$0v|O1uh8f-63UEP;)yq52dvY5`xtA45vAe%kY%M+{E-$u$6{L znJlrWD8C%J$CH$p5?_>BT##6v7Y~kpWbY&=CYO|=1ZQ$$ZUH#`BL!b_QDQE*iHp=K zN-0WA&(DibDoRZ($v_Uml%o7{l(Idgs4^bZbVJJ0si5cp^=gW8kfS8Eq97+96p+X< zkd~N}gIp|u_@H7bF|RBWt3*;xesVSgQVRj5qqrn7Co#DMR7W6r4&>P+kR3?z1`;dJ zFUm$4x=72ZjL*y~$&W7wsYWUu(uz`3t59mV^whl6qReFEYy=gG&(6$2@pDFgNqkyP zem+X`H#09D#1N5qaH-7D!+~6cB_-zNr53@ui`q!lK~iEdim}jm zKnj+mqRixM6n`aW7A2!bIH+0&^^1@Ss^lV2WsT&#Z3HkUKMAGYO)pAJ$xO{FK_2{s ziN%+q4AG^R7K18ZNkr!*BQYl}J|n*v;vt?y@>FhqR$^W{a?&iw1b1kVYNDcy{M@8cl#w?`x;3g5W6CgTL^2_tUX%5*52q}mYkTN3FJg^o_`*Sl>QgTv}s|2Weh&jk* z6vU2_`~u`=LkXyFo1Oy;R%Ct{rHS#0X=#~xnI)CT@tu^ImkKJpK`rv4`~pxP9Z6dfsGiD8k54WwDn=QtOv*_t zE{QKF$}a)c5cwzrzo2#nYFioPq`cG|P*WW#ou+_VUL`1FsVS)iB^mLczy(zpNa32A zmX?{EnVOeei5#wJpau+R8X++UY&cScg6iP-#NvX~WRzJCurQLdkqWi6oWxv|nl3HB zs01{ghE&d_73CL~fGYFs)FR}DY-VmjBG?5;jg!ngXlNrfPO|gzle0nHJmls?lW_;<4f{OlTjK$#o+oM$&tn20sMm0)D+}WxVStu zwE)y0O-)HpMXm&j%QMqT;*0ZA6SI-aT4*x>$=uRBP-@Qx1tL-*RF;^RS)2hf7?N?2 zG=OXTq|&0|5{9QJW4>9X*{MnS74gOSdFjGDh`PkkkO7%z%)pJ1GeveBxb0kwJPebZ zh|>K^&d4k(Ku&ncpbkKO5ps_MDiohvS`2P$RUn&}mWVRm3Yv~VHZC_cCnrB66FJKA zK|L-|8xUC?sJB~^T7ayspeQvtvmg_7)VL@$2eoMoYT6-pS3va!O5d?OF%xAh6FhAE zmXRGP{4?{4ONvTC#Ulq&X`7gmn^}|^pPrvs0;g;8Q&aw;QIHciYaPpmAC2Mt~@006jS$4CGG literal 0 HcmV?d00001 diff --git a/src/server/protocol/registry_data.json b/src/server/protocol/registry_data.json deleted file mode 100644 index a4fc1cd..0000000 --- a/src/server/protocol/registry_data.json +++ /dev/null @@ -1,2489 +0,0 @@ -{ - "minecraft:banner_pattern": { - "minecraft:base": { - "asset_id": "minecraft:base", - "translation_key": "block.minecraft.banner.base" - }, - "minecraft:border": { - "asset_id": "minecraft:border", - "translation_key": "block.minecraft.banner.border" - }, - "minecraft:bricks": { - "asset_id": "minecraft:bricks", - "translation_key": "block.minecraft.banner.bricks" - }, - "minecraft:circle": { - "asset_id": "minecraft:circle", - "translation_key": "block.minecraft.banner.circle" - }, - "minecraft:creeper": { - "asset_id": "minecraft:creeper", - "translation_key": "block.minecraft.banner.creeper" - }, - "minecraft:cross": { - "asset_id": "minecraft:cross", - "translation_key": "block.minecraft.banner.cross" - }, - "minecraft:curly_border": { - "asset_id": "minecraft:curly_border", - "translation_key": "block.minecraft.banner.curly_border" - }, - "minecraft:diagonal_left": { - "asset_id": "minecraft:diagonal_left", - "translation_key": "block.minecraft.banner.diagonal_left" - }, - "minecraft:diagonal_right": { - "asset_id": "minecraft:diagonal_right", - "translation_key": "block.minecraft.banner.diagonal_right" - }, - "minecraft:diagonal_up_left": { - "asset_id": "minecraft:diagonal_up_left", - "translation_key": "block.minecraft.banner.diagonal_up_left" - }, - "minecraft:diagonal_up_right": { - "asset_id": "minecraft:diagonal_up_right", - "translation_key": "block.minecraft.banner.diagonal_up_right" - }, - "minecraft:flow": { - "asset_id": "minecraft:flow", - "translation_key": "block.minecraft.banner.flow" - }, - "minecraft:flower": { - "asset_id": "minecraft:flower", - "translation_key": "block.minecraft.banner.flower" - }, - "minecraft:globe": { - "asset_id": "minecraft:globe", - "translation_key": "block.minecraft.banner.globe" - }, - "minecraft:gradient": { - "asset_id": "minecraft:gradient", - "translation_key": "block.minecraft.banner.gradient" - }, - "minecraft:gradient_up": { - "asset_id": "minecraft:gradient_up", - "translation_key": "block.minecraft.banner.gradient_up" - }, - "minecraft:guster": { - "asset_id": "minecraft:guster", - "translation_key": "block.minecraft.banner.guster" - }, - "minecraft:half_horizontal": { - "asset_id": "minecraft:half_horizontal", - "translation_key": "block.minecraft.banner.half_horizontal" - }, - "minecraft:half_horizontal_bottom": { - "asset_id": "minecraft:half_horizontal_bottom", - "translation_key": "block.minecraft.banner.half_horizontal_bottom" - }, - "minecraft:half_vertical": { - "asset_id": "minecraft:half_vertical", - "translation_key": "block.minecraft.banner.half_vertical" - }, - "minecraft:half_vertical_right": { - "asset_id": "minecraft:half_vertical_right", - "translation_key": "block.minecraft.banner.half_vertical_right" - }, - "minecraft:mojang": { - "asset_id": "minecraft:mojang", - "translation_key": "block.minecraft.banner.mojang" - }, - "minecraft:piglin": { - "asset_id": "minecraft:piglin", - "translation_key": "block.minecraft.banner.piglin" - }, - "minecraft:rhombus": { - "asset_id": "minecraft:rhombus", - "translation_key": "block.minecraft.banner.rhombus" - }, - "minecraft:skull": { - "asset_id": "minecraft:skull", - "translation_key": "block.minecraft.banner.skull" - }, - "minecraft:small_stripes": { - "asset_id": "minecraft:small_stripes", - "translation_key": "block.minecraft.banner.small_stripes" - }, - "minecraft:square_bottom_left": { - "asset_id": "minecraft:square_bottom_left", - "translation_key": "block.minecraft.banner.square_bottom_left" - }, - "minecraft:square_bottom_right": { - "asset_id": "minecraft:square_bottom_right", - "translation_key": "block.minecraft.banner.square_bottom_right" - }, - "minecraft:square_top_left": { - "asset_id": "minecraft:square_top_left", - "translation_key": "block.minecraft.banner.square_top_left" - }, - "minecraft:square_top_right": { - "asset_id": "minecraft:square_top_right", - "translation_key": "block.minecraft.banner.square_top_right" - }, - "minecraft:straight_cross": { - "asset_id": "minecraft:straight_cross", - "translation_key": "block.minecraft.banner.straight_cross" - }, - "minecraft:stripe_bottom": { - "asset_id": "minecraft:stripe_bottom", - "translation_key": "block.minecraft.banner.stripe_bottom" - }, - "minecraft:stripe_center": { - "asset_id": "minecraft:stripe_center", - "translation_key": "block.minecraft.banner.stripe_center" - }, - "minecraft:stripe_downleft": { - "asset_id": "minecraft:stripe_downleft", - "translation_key": "block.minecraft.banner.stripe_downleft" - }, - "minecraft:stripe_downright": { - "asset_id": "minecraft:stripe_downright", - "translation_key": "block.minecraft.banner.stripe_downright" - }, - "minecraft:stripe_left": { - "asset_id": "minecraft:stripe_left", - "translation_key": "block.minecraft.banner.stripe_left" - }, - "minecraft:stripe_middle": { - "asset_id": "minecraft:stripe_middle", - "translation_key": "block.minecraft.banner.stripe_middle" - }, - "minecraft:stripe_right": { - "asset_id": "minecraft:stripe_right", - "translation_key": "block.minecraft.banner.stripe_right" - }, - "minecraft:stripe_top": { - "asset_id": "minecraft:stripe_top", - "translation_key": "block.minecraft.banner.stripe_top" - }, - "minecraft:triangle_bottom": { - "asset_id": "minecraft:triangle_bottom", - "translation_key": "block.minecraft.banner.triangle_bottom" - }, - "minecraft:triangle_top": { - "asset_id": "minecraft:triangle_top", - "translation_key": "block.minecraft.banner.triangle_top" - }, - "minecraft:triangles_bottom": { - "asset_id": "minecraft:triangles_bottom", - "translation_key": "block.minecraft.banner.triangles_bottom" - }, - "minecraft:triangles_top": { - "asset_id": "minecraft:triangles_top", - "translation_key": "block.minecraft.banner.triangles_top" - } - }, - "minecraft:chat_type": { - "minecraft:chat": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:emote_command": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.emote" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.emote" - } - }, - "minecraft:msg_command_incoming": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "commands.message.display.incoming" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:msg_command_outgoing": { - "chat": { - "parameters": [ - "target", - "content" - ], - "translation_key": "commands.message.display.outgoing" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:say_command": { - "chat": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.announcement" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:team_msg_command_incoming": { - "chat": { - "parameters": [ - "target", - "sender", - "content" - ], - "translation_key": "chat.type.team.text" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - }, - "minecraft:team_msg_command_outgoing": { - "chat": { - "parameters": [ - "target", - "sender", - "content" - ], - "translation_key": "chat.type.team.sent" - }, - "narration": { - "parameters": [ - "sender", - "content" - ], - "translation_key": "chat.type.text.narrate" - } - } - }, - "minecraft:damage_type": { - "minecraft:arrow": { - "exhaustion": 0.10000000149011612, - "message_id": "arrow", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:bad_respawn_point": { - "exhaustion": 0.10000000149011612, - "message_id": "badRespawnPoint", - "scaling": "always" - }, - "minecraft:cactus": { - "exhaustion": 0.10000000149011612, - "message_id": "cactus", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:campfire": { - "exhaustion": 0.10000000149011612, - "message_id": "inFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:cramming": { - "exhaustion": 0.0, - "message_id": "cramming", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:dragon_breath": { - "exhaustion": 0.0, - "message_id": "dragonBreath", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:drown": { - "exhaustion": 0.0, - "message_id": "drown", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:dry_out": { - "exhaustion": 0.10000000149011612, - "message_id": "dryout", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:explosion": { - "exhaustion": 0.10000000149011612, - "message_id": "explosion", - "scaling": "always" - }, - "minecraft:fall": { - "exhaustion": 0.0, - "message_id": "fall", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:falling_anvil": { - "exhaustion": 0.10000000149011612, - "message_id": "anvil", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:falling_block": { - "exhaustion": 0.10000000149011612, - "message_id": "fallingBlock", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:falling_stalactite": { - "exhaustion": 0.10000000149011612, - "message_id": "fallingStalactite", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:fireball": { - "exhaustion": 0.10000000149011612, - "message_id": "fireball", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:fireworks": { - "exhaustion": 0.10000000149011612, - "message_id": "fireworks", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:fly_into_wall": { - "exhaustion": 0.0, - "message_id": "flyIntoWall", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:freeze": { - "exhaustion": 0.0, - "message_id": "freeze", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:generic": { - "exhaustion": 0.0, - "message_id": "generic", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:generic_kill": { - "exhaustion": 0.0, - "message_id": "genericKill", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:hot_floor": { - "exhaustion": 0.10000000149011612, - "message_id": "hotFloor", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:in_fire": { - "exhaustion": 0.10000000149011612, - "message_id": "inFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:in_wall": { - "exhaustion": 0.0, - "message_id": "inWall", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:indirect_magic": { - "exhaustion": 0.0, - "message_id": "indirectMagic", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:lava": { - "exhaustion": 0.10000000149011612, - "message_id": "lava", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:lightning_bolt": { - "exhaustion": 0.10000000149011612, - "message_id": "lightningBolt", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:magic": { - "exhaustion": 0.0, - "message_id": "magic", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:mob_attack": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:mob_attack_no_aggro": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:mob_projectile": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:on_fire": { - "exhaustion": 0.0, - "message_id": "onFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:out_of_world": { - "exhaustion": 0.0, - "message_id": "outOfWorld", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:outside_border": { - "exhaustion": 0.0, - "message_id": "outsideBorder", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:player_attack": { - "exhaustion": 0.10000000149011612, - "message_id": "player", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:player_explosion": { - "exhaustion": 0.10000000149011612, - "message_id": "explosion.player", - "scaling": "always" - }, - "minecraft:sonic_boom": { - "exhaustion": 0.0, - "message_id": "sonic_boom", - "scaling": "always" - }, - "minecraft:spit": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:stalagmite": { - "exhaustion": 0.0, - "message_id": "stalagmite", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:starve": { - "exhaustion": 0.0, - "message_id": "starve", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:sting": { - "exhaustion": 0.10000000149011612, - "message_id": "sting", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:sweet_berry_bush": { - "exhaustion": 0.10000000149011612, - "message_id": "sweetBerryBush", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:thorns": { - "exhaustion": 0.10000000149011612, - "message_id": "thorns", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:thrown": { - "exhaustion": 0.10000000149011612, - "message_id": "thrown", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:trident": { - "exhaustion": 0.10000000149011612, - "message_id": "trident", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:unattributed_fireball": { - "exhaustion": 0.10000000149011612, - "message_id": "onFire", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:wind_charge": { - "exhaustion": 0.10000000149011612, - "message_id": "mob", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:wither": { - "exhaustion": 0.0, - "message_id": "wither", - "scaling": "when_caused_by_living_non_player" - }, - "minecraft:wither_skull": { - "exhaustion": 0.10000000149011612, - "message_id": "witherSkull", - "scaling": "when_caused_by_living_non_player" - } - }, - "minecraft:dimension_type": { - "minecraft:overworld": { - "ambient_light": 0.0, - "bed_works": 1, - "coordinate_scale": 1.0, - "effects": "minecraft:overworld", - "has_ceiling": 0, - "has_raids": 1, - "has_skylight": 1, - "height": 384, - "infiniburn": "#minecraft:infiniburn_overworld", - "logical_height": 384, - "min_y": -64, - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "max_inclusive": 7, - "min_inclusive": 0, - "type": "minecraft:uniform" - }, - "natural": 1, - "piglin_safe": 0, - "respawn_anchor_works": 0, - "ultrawarm": 0 - }, - "minecraft:overworld_caves": { - "ambient_light": 0.0, - "bed_works": 1, - "coordinate_scale": 1.0, - "effects": "minecraft:overworld", - "has_ceiling": 1, - "has_raids": 1, - "has_skylight": 1, - "height": 384, - "infiniburn": "#minecraft:infiniburn_overworld", - "logical_height": 384, - "min_y": -64, - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "max_inclusive": 7, - "min_inclusive": 0, - "type": "minecraft:uniform" - }, - "natural": 1, - "piglin_safe": 0, - "respawn_anchor_works": 0, - "ultrawarm": 0 - }, - "minecraft:the_end": { - "ambient_light": 0.0, - "bed_works": 0, - "coordinate_scale": 1.0, - "effects": "minecraft:the_end", - "has_ceiling": 0, - "has_raids": 1, - "has_skylight": 0, - "height": 256, - "infiniburn": "#minecraft:infiniburn_end", - "logical_height": 256, - "min_y": 0, - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "max_inclusive": 7, - "min_inclusive": 0, - "type": "minecraft:uniform" - }, - "natural": 0, - "piglin_safe": 0, - "respawn_anchor_works": 0, - "ultrawarm": 0 - }, - "minecraft:the_nether": { - "ambient_light": 0.10000000149011612, - "bed_works": 0, - "coordinate_scale": 8.0, - "effects": "minecraft:the_nether", - "has_ceiling": 1, - "has_raids": 0, - "has_skylight": 0, - "height": 256, - "infiniburn": "#minecraft:infiniburn_nether", - "logical_height": 128, - "min_y": 0, - "monster_spawn_block_light_limit": 15, - "monster_spawn_light_level": { - "max_inclusive": 7, - "min_inclusive": 7, - "type": "minecraft:uniform" - }, - "natural": 0, - "piglin_safe": 1, - "respawn_anchor_works": 1, - "ultrawarm": 1 - } - }, - "minecraft:painting_variant": { - "minecraft:alban": { - "asset_id": "minecraft:alban", - "height": 1, - "width": 1 - }, - "minecraft:aztec": { - "asset_id": "minecraft:aztec", - "height": 1, - "width": 1 - }, - "minecraft:aztec2": { - "asset_id": "minecraft:aztec2", - "height": 1, - "width": 1 - }, - "minecraft:backyard": { - "asset_id": "minecraft:backyard", - "height": 4, - "width": 3 - }, - "minecraft:baroque": { - "asset_id": "minecraft:baroque", - "height": 2, - "width": 2 - }, - "minecraft:bomb": { - "asset_id": "minecraft:bomb", - "height": 1, - "width": 1 - }, - "minecraft:bouquet": { - "asset_id": "minecraft:bouquet", - "height": 3, - "width": 3 - }, - "minecraft:burning_skull": { - "asset_id": "minecraft:burning_skull", - "height": 4, - "width": 4 - }, - "minecraft:bust": { - "asset_id": "minecraft:bust", - "height": 2, - "width": 2 - }, - "minecraft:cavebird": { - "asset_id": "minecraft:cavebird", - "height": 3, - "width": 3 - }, - "minecraft:changing": { - "asset_id": "minecraft:changing", - "height": 2, - "width": 4 - }, - "minecraft:cotan": { - "asset_id": "minecraft:cotan", - "height": 3, - "width": 3 - }, - "minecraft:courbet": { - "asset_id": "minecraft:courbet", - "height": 1, - "width": 2 - }, - "minecraft:creebet": { - "asset_id": "minecraft:creebet", - "height": 1, - "width": 2 - }, - "minecraft:donkey_kong": { - "asset_id": "minecraft:donkey_kong", - "height": 3, - "width": 4 - }, - "minecraft:earth": { - "asset_id": "minecraft:earth", - "height": 2, - "width": 2 - }, - "minecraft:endboss": { - "asset_id": "minecraft:endboss", - "height": 3, - "width": 3 - }, - "minecraft:fern": { - "asset_id": "minecraft:fern", - "height": 3, - "width": 3 - }, - "minecraft:fighters": { - "asset_id": "minecraft:fighters", - "height": 2, - "width": 4 - }, - "minecraft:finding": { - "asset_id": "minecraft:finding", - "height": 2, - "width": 4 - }, - "minecraft:fire": { - "asset_id": "minecraft:fire", - "height": 2, - "width": 2 - }, - "minecraft:graham": { - "asset_id": "minecraft:graham", - "height": 2, - "width": 1 - }, - "minecraft:humble": { - "asset_id": "minecraft:humble", - "height": 2, - "width": 2 - }, - "minecraft:kebab": { - "asset_id": "minecraft:kebab", - "height": 1, - "width": 1 - }, - "minecraft:lowmist": { - "asset_id": "minecraft:lowmist", - "height": 2, - "width": 4 - }, - "minecraft:match": { - "asset_id": "minecraft:match", - "height": 2, - "width": 2 - }, - "minecraft:meditative": { - "asset_id": "minecraft:meditative", - "height": 1, - "width": 1 - }, - "minecraft:orb": { - "asset_id": "minecraft:orb", - "height": 4, - "width": 4 - }, - "minecraft:owlemons": { - "asset_id": "minecraft:owlemons", - "height": 3, - "width": 3 - }, - "minecraft:passage": { - "asset_id": "minecraft:passage", - "height": 2, - "width": 4 - }, - "minecraft:pigscene": { - "asset_id": "minecraft:pigscene", - "height": 4, - "width": 4 - }, - "minecraft:plant": { - "asset_id": "minecraft:plant", - "height": 1, - "width": 1 - }, - "minecraft:pointer": { - "asset_id": "minecraft:pointer", - "height": 4, - "width": 4 - }, - "minecraft:pond": { - "asset_id": "minecraft:pond", - "height": 4, - "width": 3 - }, - "minecraft:pool": { - "asset_id": "minecraft:pool", - "height": 1, - "width": 2 - }, - "minecraft:prairie_ride": { - "asset_id": "minecraft:prairie_ride", - "height": 2, - "width": 1 - }, - "minecraft:sea": { - "asset_id": "minecraft:sea", - "height": 1, - "width": 2 - }, - "minecraft:skeleton": { - "asset_id": "minecraft:skeleton", - "height": 3, - "width": 4 - }, - "minecraft:skull_and_roses": { - "asset_id": "minecraft:skull_and_roses", - "height": 2, - "width": 2 - }, - "minecraft:stage": { - "asset_id": "minecraft:stage", - "height": 2, - "width": 2 - }, - "minecraft:sunflowers": { - "asset_id": "minecraft:sunflowers", - "height": 3, - "width": 3 - }, - "minecraft:sunset": { - "asset_id": "minecraft:sunset", - "height": 1, - "width": 2 - }, - "minecraft:tides": { - "asset_id": "minecraft:tides", - "height": 3, - "width": 3 - }, - "minecraft:unpacked": { - "asset_id": "minecraft:unpacked", - "height": 4, - "width": 4 - }, - "minecraft:void": { - "asset_id": "minecraft:void", - "height": 2, - "width": 2 - }, - "minecraft:wanderer": { - "asset_id": "minecraft:wanderer", - "height": 2, - "width": 1 - }, - "minecraft:wasteland": { - "asset_id": "minecraft:wasteland", - "height": 1, - "width": 1 - }, - "minecraft:water": { - "asset_id": "minecraft:water", - "height": 2, - "width": 2 - }, - "minecraft:wind": { - "asset_id": "minecraft:wind", - "height": 2, - "width": 2 - }, - "minecraft:wither": { - "asset_id": "minecraft:wither", - "height": 2, - "width": 2 - } - }, - "minecraft:trim_material": { - "minecraft:amethyst": { - "asset_name": "amethyst", - "description": { - "color": "#9A5CC6", - "translate": "trim_material.minecraft.amethyst" - }, - "ingredient": "minecraft:amethyst_shard", - "item_model_index": 1.0 - }, - "minecraft:copper": { - "asset_name": "copper", - "description": { - "color": "#B4684D", - "translate": "trim_material.minecraft.copper" - }, - "ingredient": "minecraft:copper_ingot", - "item_model_index": 0.5 - }, - "minecraft:diamond": { - "asset_name": "diamond", - "description": { - "color": "#6EECD2", - "translate": "trim_material.minecraft.diamond" - }, - "ingredient": "minecraft:diamond", - "item_model_index": 0.800000011920929 - }, - "minecraft:emerald": { - "asset_name": "emerald", - "description": { - "color": "#11A036", - "translate": "trim_material.minecraft.emerald" - }, - "ingredient": "minecraft:emerald", - "item_model_index": 0.699999988079071 - }, - "minecraft:gold": { - "asset_name": "gold", - "description": { - "color": "#DEB12D", - "translate": "trim_material.minecraft.gold" - }, - "ingredient": "minecraft:gold_ingot", - "item_model_index": 0.6000000238418579 - }, - "minecraft:iron": { - "asset_name": "iron", - "description": { - "color": "#ECECEC", - "translate": "trim_material.minecraft.iron" - }, - "ingredient": "minecraft:iron_ingot", - "item_model_index": 0.20000000298023224 - }, - "minecraft:lapis": { - "asset_name": "lapis", - "description": { - "color": "#416E97", - "translate": "trim_material.minecraft.lapis" - }, - "ingredient": "minecraft:lapis_lazuli", - "item_model_index": 0.8999999761581421 - }, - "minecraft:netherite": { - "asset_name": "netherite", - "description": { - "color": "#625859", - "translate": "trim_material.minecraft.netherite" - }, - "ingredient": "minecraft:netherite_ingot", - "item_model_index": 0.30000001192092896 - }, - "minecraft:quartz": { - "asset_name": "quartz", - "description": { - "color": "#E3D4C4", - "translate": "trim_material.minecraft.quartz" - }, - "ingredient": "minecraft:quartz", - "item_model_index": 0.10000000149011612 - }, - "minecraft:redstone": { - "asset_name": "redstone", - "description": { - "color": "#971607", - "translate": "trim_material.minecraft.redstone" - }, - "ingredient": "minecraft:redstone", - "item_model_index": 0.4000000059604645 - } - }, - "minecraft:trim_pattern": { - "minecraft:bolt": { - "asset_id": "minecraft:bolt", - "description": { - "translate": "trim_pattern.minecraft.bolt" - }, - "template_item": "minecraft:bolt_armor_trim_smithing_template" - }, - "minecraft:coast": { - "asset_id": "minecraft:coast", - "description": { - "translate": "trim_pattern.minecraft.coast" - }, - "template_item": "minecraft:coast_armor_trim_smithing_template" - }, - "minecraft:dune": { - "asset_id": "minecraft:dune", - "description": { - "translate": "trim_pattern.minecraft.dune" - }, - "template_item": "minecraft:dune_armor_trim_smithing_template" - }, - "minecraft:eye": { - "asset_id": "minecraft:eye", - "description": { - "translate": "trim_pattern.minecraft.eye" - }, - "template_item": "minecraft:eye_armor_trim_smithing_template" - }, - "minecraft:flow": { - "asset_id": "minecraft:flow", - "description": { - "translate": "trim_pattern.minecraft.flow" - }, - "template_item": "minecraft:flow_armor_trim_smithing_template" - }, - "minecraft:host": { - "asset_id": "minecraft:host", - "description": { - "translate": "trim_pattern.minecraft.host" - }, - "template_item": "minecraft:host_armor_trim_smithing_template" - }, - "minecraft:raiser": { - "asset_id": "minecraft:raiser", - "description": { - "translate": "trim_pattern.minecraft.raiser" - }, - "template_item": "minecraft:raiser_armor_trim_smithing_template" - }, - "minecraft:rib": { - "asset_id": "minecraft:rib", - "description": { - "translate": "trim_pattern.minecraft.rib" - }, - "template_item": "minecraft:rib_armor_trim_smithing_template" - }, - "minecraft:sentry": { - "asset_id": "minecraft:sentry", - "description": { - "translate": "trim_pattern.minecraft.sentry" - }, - "template_item": "minecraft:sentry_armor_trim_smithing_template" - }, - "minecraft:shaper": { - "asset_id": "minecraft:shaper", - "description": { - "translate": "trim_pattern.minecraft.shaper" - }, - "template_item": "minecraft:shaper_armor_trim_smithing_template" - }, - "minecraft:silence": { - "asset_id": "minecraft:silence", - "description": { - "translate": "trim_pattern.minecraft.silence" - }, - "template_item": "minecraft:silence_armor_trim_smithing_template" - }, - "minecraft:snout": { - "asset_id": "minecraft:snout", - "description": { - "translate": "trim_pattern.minecraft.snout" - }, - "template_item": "minecraft:snout_armor_trim_smithing_template" - }, - "minecraft:spire": { - "asset_id": "minecraft:spire", - "description": { - "translate": "trim_pattern.minecraft.spire" - }, - "template_item": "minecraft:spire_armor_trim_smithing_template" - }, - "minecraft:tide": { - "asset_id": "minecraft:tide", - "description": { - "translate": "trim_pattern.minecraft.tide" - }, - "template_item": "minecraft:tide_armor_trim_smithing_template" - }, - "minecraft:vex": { - "asset_id": "minecraft:vex", - "description": { - "translate": "trim_pattern.minecraft.vex" - }, - "template_item": "minecraft:vex_armor_trim_smithing_template" - }, - "minecraft:ward": { - "asset_id": "minecraft:ward", - "description": { - "translate": "trim_pattern.minecraft.ward" - }, - "template_item": "minecraft:ward_armor_trim_smithing_template" - }, - "minecraft:wayfinder": { - "asset_id": "minecraft:wayfinder", - "description": { - "translate": "trim_pattern.minecraft.wayfinder" - }, - "template_item": "minecraft:wayfinder_armor_trim_smithing_template" - }, - "minecraft:wild": { - "asset_id": "minecraft:wild", - "description": { - "translate": "trim_pattern.minecraft.wild" - }, - "template_item": "minecraft:wild_armor_trim_smithing_template" - } - }, - "minecraft:wolf_variant": { - "minecraft:ashen": { - "angry_texture": "minecraft:entity/wolf/wolf_ashen_angry", - "biomes": "minecraft:snowy_taiga", - "tame_texture": "minecraft:entity/wolf/wolf_ashen_tame", - "wild_texture": "minecraft:entity/wolf/wolf_ashen" - }, - "minecraft:black": { - "angry_texture": "minecraft:entity/wolf/wolf_black_angry", - "biomes": "minecraft:old_growth_pine_taiga", - "tame_texture": "minecraft:entity/wolf/wolf_black_tame", - "wild_texture": "minecraft:entity/wolf/wolf_black" - }, - "minecraft:chestnut": { - "angry_texture": "minecraft:entity/wolf/wolf_chestnut_angry", - "biomes": "minecraft:old_growth_spruce_taiga", - "tame_texture": "minecraft:entity/wolf/wolf_chestnut_tame", - "wild_texture": "minecraft:entity/wolf/wolf_chestnut" - }, - "minecraft:pale": { - "angry_texture": "minecraft:entity/wolf/wolf_angry", - "biomes": "minecraft:taiga", - "tame_texture": "minecraft:entity/wolf/wolf_tame", - "wild_texture": "minecraft:entity/wolf/wolf" - }, - "minecraft:rusty": { - "angry_texture": "minecraft:entity/wolf/wolf_rusty_angry", - "biomes": "#minecraft:is_jungle", - "tame_texture": "minecraft:entity/wolf/wolf_rusty_tame", - "wild_texture": "minecraft:entity/wolf/wolf_rusty" - }, - "minecraft:snowy": { - "angry_texture": "minecraft:entity/wolf/wolf_snowy_angry", - "biomes": "minecraft:grove", - "tame_texture": "minecraft:entity/wolf/wolf_snowy_tame", - "wild_texture": "minecraft:entity/wolf/wolf_snowy" - }, - "minecraft:spotted": { - "angry_texture": "minecraft:entity/wolf/wolf_spotted_angry", - "biomes": "#minecraft:is_savanna", - "tame_texture": "minecraft:entity/wolf/wolf_spotted_tame", - "wild_texture": "minecraft:entity/wolf/wolf_spotted" - }, - "minecraft:striped": { - "angry_texture": "minecraft:entity/wolf/wolf_striped_angry", - "biomes": "#minecraft:is_badlands", - "tame_texture": "minecraft:entity/wolf/wolf_striped_tame", - "wild_texture": "minecraft:entity/wolf/wolf_striped" - }, - "minecraft:woods": { - "angry_texture": "minecraft:entity/wolf/wolf_woods_angry", - "biomes": "minecraft:forest", - "tame_texture": "minecraft:entity/wolf/wolf_woods_tame", - "wild_texture": "minecraft:entity/wolf/wolf_woods" - } - }, - "minecraft:worldgen/biome": { - "minecraft:badlands": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "foliage_color": 10387789, - "grass_color": 9470285, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.badlands" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:bamboo_jungle": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.bamboo_jungle" - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.949999988079071 - }, - "minecraft:basalt_deltas": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.basalt_deltas.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.basalt_deltas.loop", - "fog_color": 6840176, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.basalt_deltas.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.basalt_deltas" - }, - "particle": { - "options": { - "type": "minecraft:white_ash" - }, - "probability": 0.118093334 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:beach": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:birch_forest": { - "downfall": 0.6000000238418579, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 8037887, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.6000000238418579 - }, - "minecraft:cherry_grove": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "foliage_color": 11983713, - "grass_color": 11983713, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.cherry_grove" - }, - "sky_color": 8103167, - "water_color": 6141935, - "water_fog_color": 6141935 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:cold_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:crimson_forest": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.crimson_forest.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.crimson_forest.loop", - "fog_color": 3343107, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.crimson_forest.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.crimson_forest" - }, - "particle": { - "options": { - "type": "minecraft:crimson_spore" - }, - "probability": 0.025 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:dark_forest": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "grass_color_modifier": "dark_forest", - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 7972607, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.699999988079071 - }, - "minecraft:deep_cold_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:deep_dark": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.deep_dark" - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:deep_frozen_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 3750089, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:deep_lukewarm_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4566514, - "water_fog_color": 267827 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:deep_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:desert": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.desert" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:dripstone_caves": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.dripstone_caves" - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:end_barrens": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:end_highlands": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:end_midlands": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:eroded_badlands": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "foliage_color": 10387789, - "grass_color": 9470285, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.badlands" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:flower_forest": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.flower_forest" - }, - "sky_color": 7972607, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.699999988079071 - }, - "minecraft:forest": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 7972607, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.699999988079071 - }, - "minecraft:frozen_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 3750089, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:frozen_peaks": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.frozen_peaks" - }, - "sky_color": 8756735, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.699999988079071 - }, - "minecraft:frozen_river": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 3750089, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:grove": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.grove" - }, - "sky_color": 8495359, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.20000000298023224 - }, - "minecraft:ice_spikes": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:jagged_peaks": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.jagged_peaks" - }, - "sky_color": 8756735, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.699999988079071 - }, - "minecraft:jungle": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.jungle" - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.949999988079071 - }, - "minecraft:lukewarm_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4566514, - "water_fog_color": 267827 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:lush_caves": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.lush_caves" - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:mangrove_swamp": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "foliage_color": 9285927, - "grass_color_modifier": "swamp", - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.swamp" - }, - "sky_color": 7907327, - "water_color": 3832426, - "water_fog_color": 5077600 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:meadow": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.meadow" - }, - "sky_color": 8103167, - "water_color": 937679, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:mushroom_fields": { - "downfall": 1.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.8999999761581421 - }, - "minecraft:nether_wastes": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.nether_wastes.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.nether_wastes.loop", - "fog_color": 3344392, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.nether_wastes.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.nether_wastes" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:old_growth_birch_forest": { - "downfall": 0.6000000238418579, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.forest" - }, - "sky_color": 8037887, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.6000000238418579 - }, - "minecraft:old_growth_pine_taiga": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.old_growth_taiga" - }, - "sky_color": 8168447, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.30000001192092896 - }, - "minecraft:old_growth_spruce_taiga": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.old_growth_taiga" - }, - "sky_color": 8233983, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.25 - }, - "minecraft:plains": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:river": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:savanna": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:savanna_plateau": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:small_end_islands": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:snowy_beach": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.05000000074505806 - }, - "minecraft:snowy_plains": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8364543, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.0 - }, - "minecraft:snowy_slopes": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.snowy_slopes" - }, - "sky_color": 8560639, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.30000001192092896 - }, - "minecraft:snowy_taiga": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8625919, - "water_color": 4020182, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": -0.5 - }, - "minecraft:soul_sand_valley": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.soul_sand_valley.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.soul_sand_valley.loop", - "fog_color": 1787717, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.soul_sand_valley.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.soul_sand_valley" - }, - "particle": { - "options": { - "type": "minecraft:ash" - }, - "probability": 0.00625 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:sparse_jungle": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.sparse_jungle" - }, - "sky_color": 7842047, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.949999988079071 - }, - "minecraft:stony_peaks": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.stony_peaks" - }, - "sky_color": 7776511, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 1.0 - }, - "minecraft:stony_shore": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:sunflower_plains": { - "downfall": 0.4000000059604645, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7907327, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:swamp": { - "downfall": 0.8999999761581421, - "effects": { - "fog_color": 12638463, - "foliage_color": 6975545, - "grass_color_modifier": "swamp", - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.swamp" - }, - "sky_color": 7907327, - "water_color": 6388580, - "water_fog_color": 2302743 - }, - "has_precipitation": true, - "temperature": 0.800000011920929 - }, - "minecraft:taiga": { - "downfall": 0.800000011920929, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233983, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.25 - }, - "minecraft:the_end": { - "downfall": 0.5, - "effects": { - "fog_color": 10518688, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 0, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:the_void": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 0.5 - }, - "minecraft:warm_ocean": { - "downfall": 0.5, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8103167, - "water_color": 4445678, - "water_fog_color": 270131 - }, - "has_precipitation": true, - "temperature": 0.5 - }, - "minecraft:warped_forest": { - "downfall": 0.0, - "effects": { - "additions_sound": { - "sound": "minecraft:ambient.warped_forest.additions", - "tick_chance": 0.0111 - }, - "ambient_sound": "minecraft:ambient.warped_forest.loop", - "fog_color": 1705242, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.warped_forest.mood", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.nether.warped_forest" - }, - "particle": { - "options": { - "type": "minecraft:warped_spore" - }, - "probability": 0.01428 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:windswept_forest": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:windswept_gravelly_hills": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:windswept_hills": { - "downfall": 0.30000001192092896, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 8233727, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": true, - "temperature": 0.20000000298023224 - }, - "minecraft:windswept_savanna": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - }, - "minecraft:wooded_badlands": { - "downfall": 0.0, - "effects": { - "fog_color": 12638463, - "foliage_color": 10387789, - "grass_color": 9470285, - "mood_sound": { - "block_search_extent": 8, - "offset": 2.0, - "sound": "minecraft:ambient.cave", - "tick_delay": 6000 - }, - "music": { - "max_delay": 24000, - "min_delay": 12000, - "replace_current_music": false, - "sound": "minecraft:music.overworld.badlands" - }, - "sky_color": 7254527, - "water_color": 4159204, - "water_fog_color": 329011 - }, - "has_precipitation": false, - "temperature": 2.0 - } - } -} \ No newline at end of file diff --git a/src/server/protocol/update-tags.bin b/src/server/protocol/update-tags.bin new file mode 100644 index 0000000000000000000000000000000000000000..1674b67b7099e401a64c525acff3ecf8dc782508 GIT binary patch literal 27198 zcmd;)%FWD6O)g4IE3qm_%*-pv%uA0iODxJv%qw9OMv}`(OioQq%1PA|XJlYvmDf|^ zQPJep6_w;r*HJSRQjlX;mXTG|&=N%$k&>93n4TJ6Qdy9y zh-7P0WkF(bacXgVYFb)qa!E0xx*$SNW^sI4VonY-7l)c0l4yEfW=U#sd}2{3_?YDW=V2Jd{Js~CP=mtcR639o`M3mdzjn7Fi}x|Y5q!XUV3iZe1(b5dmG#aP$`g!Ea}xi|#Xq{S74 zRTP<+*|~W^?$Si_V|-dser|kmNoqksW?niIvj|e^$jgt<%q>VvE@5Otif~Zm6{V(D zW#*+b3W_6Kky#vHP?VpQnp~2Zlgh!MBd5z}psgp5PylykYF=_iVqOU-*oqld)DS8X zbIKDdi{o?ii%a5*Gt=`j(=wA2^Gd*pg;4>?sN}@F_@tuL#B4~&KoSeHvJ!Ie!mR_R zCt)msqJ*#+t~fKVtTZPtwJ0$uGbgj8l1V{W452D7KR!DzKRG)oF*#d?Q<+gzQeHwt zjD<~|iHk!(K}cGkm0eR%O;MOx2t`j~UV3T~qbzC|rWECu=Vj)lGqNFB07+Jmgd`13 zNDQo;eEj0lGIGi)8rnL#din-RQb;Kg8fGO$nd#}NMaA*OnK@;tMQNGE8I01H$}&oe zN{ZuC^HNfa;!}ze)ARF+SwL9>EpuiTrLv1M%S(vp@*(Bv^30Nq)S~#z+}zT<)cBHo z6sKnv$EQ{l$>hrWVB~mXstWXEP~D@gpoq%PGxFVd6sw-L2ENjJslybq&h|kZ9&rK|d&o3>>$;?YF=H=$(69O~LyG7NkU(#8^+QmUpZMvTJt ztm6D~I1Mk$%*g>&E-9(SsYNA>d`Jp&^NUjB(~43HOH=bo;=!3IH6^~dAhA5Jm`PIt zsT9jBj)(Y$nNh|_8sTAx+W6wU{POs;{EF0KEgf%}&cN zN-Zv7hXyZdQqC_+EdnPpGqS>k3uhXG1x#^bSz=yZBBK^^3d=0X$S*C4FGwsY%FlrY z38Ny;sD_zUl9-vE$Y^ei2%*HBoc!|Q_>!Xhg3RQ^oOn>70yQr_u_Qh*uQEO(6_i^U z<@6D{3i5Lji{g_~6G4R$C_X@`Ju#;wwJ0yKBsD%MCqFs6m`RKqspbbIspOpe;?$ye zNd8F6PiHcdM~ZHc3Pgs4Ik_MwF*C22Nkai=KqZwHWu~UYmlUNY7MB*KGBNAoR0@t| zNWM+Z&q-kuV^ikl2)BeS5qC^b17kwKA~UYW)5&^*GdrEQPf z5RfhL8KotRW=Pc*B&^~=sR7n_21R;$v7nxmxU{|)wM1+9q^1KvQu+NaPDI$T!CuJrUgYs3riiWA46rU)la92@PGdIxEwzIXd zmoZ{w5Oz?I)EAdFR^*q%QaV-T=O$&Q#usO%rl%G&Dxwy+NvVm+8BEN&dI%?>1_Nr6 z0(Ayb5{pvAnRPWKK%oLHj1{H$-P1}omdVLKpMp;0vK#D|YN&>Y9&{7hg6{C!imbN{(O~D}SAg_yX zEU0$NEJ{s@hZIe@`FW+FHgkM&X;D#rXdH{jjf$AkwqssN)V+YlFw12I5jUN9#q;h2@0Wh=TcHr3m|C^9G|G| z9!L;CiVmbo64mIW#Nv|7{Ji+0)ZDzpyb>06B|~eZKrKoIRjAN9KM_z8q`(r!$VNP}NdC>tO9nLnQd8p7GK*5O>YvuxzmMJj=cQqxO8 z?LdSo7LY2WG6886Kny9}mE`B=lw=mf=NF}<7HKhQ$g^tlDJU`WOR}*@sEBj%2+Bw^ zb4zjZ3MqGITdCkQVEuy7Y}Y*rNpNemSz@! zf>K$Uo0C~ejzNuGL5We2OGt%7QJq&(hDSg|RG43$Ra{nrjRh$;fwhAoRGkQYOd5P* zNPSpP=1R;<$muIBr#ph;%JMxN1-J(2jctM)u#Do)4+X2<7MVSR9nJKAE z!mLOM7gb3Lcx;jllziA&kg^`A`whxEpt^;$SVKy`#o(qrIM^AvkPJt4LUK`laWN=V z#U+r^45}LN02K=;S>sfal%HQx%*4lzq#!dd9-4(f`LqO-euxQAq`-nlTV`@9xT6A2 z|43sd$)!cbsZ2~7NP19R4vAkTUPYvENzE(CEU5$+W~xXH?!=1xocxlUc<5MAMt(79 zoUoW##YPROFOmitSxkiuF(DaM%ur{)!BmVh!vNq&4%VqPk!oK7q% z$tcP%$WJL|<<`h46Ga+sO3F_x;R-3|ac2;dkM!e2%G+s0`RVa#`S~eK`MyZDg9b3c z#YKE^VoFL*Du;dEL`J3HxN2dfAS+5u$t+4u21hN@*ebJ8C!-KjDFqrzkB206Xut_L z7iTeXar22tn#cNv#dI?YA_YiMVrEKeQ8AlNVtN~+V;Ayt0NjTV4S3-Zgsy+_bs zNqJ&UHYl~l=jJCBv!rR{m>?<4%qvSR0u>V(sfjtDgqfI^5}%P+l#4y0ki3D25Li*e zn1ht?VCg5LG_NGSB(W$xwWOH6i7_=ps?w?k$rzNVE6&X{aNa9ny>bpzu>MkLjsm@Q9DECN-U#i@|-X-0b_2PUPa zW`I&wW?n{OQf5hhQ86Pok~Cz*9VxntdDu#gi;N0X8yS^*89Nz;kV@yg{F3L*Upc0;L6}d|#vr0px*_ z(xSZh%)EF|x-MoEL`u)a*{M0HCHZ;9tdNX{lw3g?L4)!o8L5zvf;8-;AqtTLx0^AR z-PA^{nbArrHKVeLv4#~XUndq7<(C&Tv6Uj#a-iXi)S~#bqRiC1lw#}=iWDr+QUp5K zT~Sh$2riC_nXCDHk6E0)Ya`MYlix_#3(kH4hIr-&`$U})JvdKlMsRgM;j4Vj` z3q?VCPJU7t z8AWk9q&T}YCx=l2sqO`L-1jpgH7s)TlOTPCL}-Ix+m!-r%iGEHJa%IV38 zxdou^QZdsew#{rv5s;aemYJ6c>O{puI)O|RnEtaMHSs{*rqsOn;+({!VxH|RJ6LwI z>|)u?vX^Ba%Ne$_Z2MUbu+@SXy{sj;Dnk-3GX8DkHNj@~Ku_e@Cr zgM!4I)cE|w?D(Ag^kNnZ5$7TS{z@NnN*Yr?s|ge5fgKY1$r2J8T%Of z87DAKWCXPorZ7%roW?kvaR%c|##xNBk*mSv{KTAiP)=sj(?_zoI5#mTCmxa_iv{K~ zp5~a(xPWmX<08hzj7u1oGA?6W&bWec72`_Ad5mW`kP61cs>Gbs#Q388{F3;h)B@O? zyE-Ei3)_13#T=X*++5d~7IMttIKi=q!@7&*2=h_qW6Z~yPcWZkKE-^R`3&<}=5x&F znJ+M3WWL0FnOVJuNkg@hX+Jws&V>egNl{_}I9>2;WLnR(nQ05t2Bx)4>zFRFU1rX7nOByWlL?(RiOZ7L=RmPX4rl*!bf-^Up#~2tPO09Y)x#EIS%CVATEyqTV z8|*jPZ?WHIf64xe{Wbdw_Nz=A*|)OqV!y?N6l;_zp1xFu(79Ipoy1#kdcRNfTf>nxS5Y*$Ue6HYzNp5vTbGC z#Or}v*x)GRF~;MJCm2sLo@PA5 zc!%vS+gZkQj3*f{GhSi5%6N_O2IEb}Ta5SE?z7!yyu)~%@h;;%#`}y97#}h|VtmZ_ zgz+ikGsfqPFBo4kzG8gM_=fQ<<2%Opj2{?3F@9$J$oPek7co4RnVibf!PLX@h4mi` zQU-_ictD-(_ySPX0ICwpK#ds2jf_Y|3N*{5gPIcY`FRRxiIAv-3(8=qr5b3BM^Qm) zNwL5$=HJYJnEx{WWB$+Fz|zRl#L~>t!qUpp#?sEx!P3csRFEeZ7$(_Q!tv(gKumrTD|elY!E`OETv?IGJ7#x0C**j_OmV|vSW zh~p94V>YC&N^W9adQpB^D!6zljt4cuK)#L#^;JsKGuRh#SX<5DIM1Zm&V&^2X{m{z zsco=&P$RMkQY^76?PT1;_>$=m$5D=B97vryq;i6ZgBz)q%PlR=D9X>zjR#u_uC!RL zF>xPa+ljoc0a`dg3MO0?7LxOd^D;qA+4$7-^!SWK(4wED{QTk)#yK2FrB`xMWie<* zsTedC4x3k8$-asmInok~3PAJ1C8@c^EUg?04C3vY%x?$$p3Z2KyQI zqZ`fb3KVhH1afIVC`z7{^ z>=)RtvtMJs%6^5tfuoV5iKCgLg`<_Djia5TgQJt9i=&&PhohIHkE5UC2m4R<-|WBG z|FHjM|Ihx99Vu?0=?Pq>Csl&xUsH=sq9Y>X<6@!`lM-SH)t)Cgni!iITNuwXU0~{E z>0{|p|8-tcO{TupVVa?l*udUhpEUc;v*)v7h}g`vs=!OzYTZa?IkG&4E-cL&t7FeQ-$4 z2uj3|8l2@KV_p3rW~AI(o>)`@OH~c5yV;Nm_vD<++$2ymab93N&$NW?DBCf%<7_9` zCUZ>TKpN~VNi8Z$OwKPUNfabp<0GZ$l*FQJL~9Kc#7K#xAhD<@zXY}Vx8z`;FiwyP zC-C4)K~ZX2YEgVvX>LJ$Nj}qcrnwwQ`4uz@oC9jnpfPab~>nC zit0u-4n|NLrH4fWDME^qOLMZ3>(F@c%8&T;)I1Ba8+xE(bOO_4rq67u>KdT-`Cj%N z96d~vI9@SrX35&Xv4&#_$5xJQ94k1GTKCDRnV{}pd`4nkIw(~@iZ)zzF_KR~)2{h> z@kxk20qZNaFH8p**D|hSL{bL|R@4w-;aJ3BWsMZ?#TlhJ*{MbGNuY%_f~~AmStqhi zV(n+`Wu460$=b)-&Dz1*&f3E|fprRN8*3LUQV$DUhZG}s#}Nz5ikaRpD{BK zl#G>=16>Zn`i}KI>j&14te=n*PcCTjEohNSd|F~rE@%Rj`z+IGrgKbZm>?~JZ!Gf| zzq1@d4&LN^@E95>Q^uF%$LE%&FmrM(AFv z1T}o%Jqx57Hm9^W15{_1=A|I}TNIDKL7fhyWL8|BUz7ssG?XOfmE=@{#`;r>nZC1r zV@0YhK@$RrB_&0P$)M3xS^UL5sGy&~($DgPC22Z`Vl1f4`^Yktqnin-*`Ah~mz-M6 z+t2iXzD^ zEsQ%E_c88gJivH_@e<=V#_xx5|VtU5}YAbwX`ogrGc?a`u<~_`NnfEd8XFkAukohn(q5j}I=J(7Wm_IUqV*bqh zh50M}bs_5_*2Sz# zSeLRcV_nX=f^{Y9D%RDkYgpH^u47%#x`A~g>n7ICtXo*OvYuu=!+MtW9P4@33#=De zFR@-`y~28x^&0DS)*GxhS?{wxV13B?i1jh+OV(GcuUX%)zGcPNvFc*$X6s?=W$R<> zXPdw_k!=#&WVR`6Q`x4mEoWQ7wwi4n+j_PQY#Z5jvK?Z3#rB%*4cl9`Pi&vrzOa2` z`_A@*?I+tWw%=@j*#5EoXKP?@WN%?_Wp86&&%T%aB>M^W}T1}v7cv$ zb=>Z--(|nYexLmT`$P6e?2p->us>yg#{QiB1++`|hW#!3JNEbN6F4SvOyXF~v4mqO z$1;u;94k3iajfB3%dw7Q1II>=O&nXmMf47iogBM3c5^^FHC>DijNOc@+1IhJWnaS% zYP9ZSI>dC4X*7CiZRYyV>tB5iV&Vh2;~br%cb7o-;w33SU|GFkNLj#j=~} z2*(EYjqKamH?wbH-^#v&eJA@a_C4&RHVn~+p3obL$2o2?-DbMQbQgKaZc=JWu|Q32 zWmS1;bzxa?L4HX^UJ+7D1+>y0QgZ9)B4uItP!OnRj8q<$<`qLGPLlFVO7e5#lR+bN zMLgG8uCm-@xy5pg1DkVYz|aVFUM4L~->}(EawE5ou@)ys$bqF)tpp ziVsw7=-@B$&Y;&Vs_Gi68P_mwVC-V*VLHh)lj#)GVYWkTC)swf?PS}-*3E=89t|1A zEmoM!GKFO-%QTkhEHhYUvdm(c%`%5&F3UWY`78@q7P2g2Sa}tY-nN-!0Dm%!?SA0Q!NwL}=rn4O9 zI69fTn7f&Kn0uM~nERP0Fi&LeVE)JSpQ(Ylk-3Svg}Ie^D)Thv>CElSGngkaPiCIN z{Dbu!+k3VTY#-V7aO~x1V{T^ti#i|$A6kU3odu6BA~h|beMv|~z=NwIK#CN|P&BBP zOU=mvP5!g)V4TXdk!>2&W;UeRfXuw){36hF8+a}SY71yJLM*uV&GwePrbqP`I2VC@#ftD1Qq!&+G}r@L3KtIwf6(lZ?^edCOf5{G znQ--uaSi@Znk+#T;Sa{2jK3IvGyY-x$M~P|9NT%e2Bt=)zl`4)8`-;&m*8WL3zZZl z=7FaFN>Ynt8I)BZ-N{z)=o+a1xSeqaV>=V5dwG%REiwK z=R?LhP@5Bs*N`(LWQA)n^9&}?7%Eaf1lq4J$VtqDlu7w{f~x9jptc#(xG$*bq@jtl zqOAaZB{ir-L0L`B*nv7?Uz(Fr1lrO8E8daH!=%jg_>`i|f}GUEG*EvBvhRz#hiMMH z3|hoteHeKV4bs9(O-sv61}zB|L+Y4;0uEF>&0;#m^pxYOw&J*hET$DT%oSsd-3a_{9o@2JZUdfIW!r)wv z+C9WIB#X4P5!H2|Ar&MCf|@mNn2}0djQ+3@j<6*>o`Am>j5LsfymlQqUU?RCfcnat zIM#7&1UHxkounBZnXrdr6N*&T3k?6nw$!WLeMZiQWS!_ z;Kkt4D|mWkgM}Z`P!~#S!WjDnO(t0-SSo^sf06oRP@g81mVoB*d1f=uVV=u8k9j`x zLgq!xH`s2nEoNTAynq?0s|Q_sfmZhQFd-#f=nQ>vX>Km+5CRiu*a9gPfyy9=&rrq) z!Mj4>UPKz8fv8O?%1q8KW`T8~kkVO3K4_vD6x-0+on;r>d$t)&Z`qKhP(bsTOrRnk z$zaf21E@gNPpn9t#FYD;br~CIa%3LIT#k*5hnY69tz?_Yw1@E{%LkUXOi$PjGVWqt z#dd*d7E>S71;#aOOW95_9c4PnbcAUw+h(?H%;%XXt?+O)O+lk(q)z?t^6?7@3X7g% zJIy90E<|b~#g!y*wTW>x!f;jJxaNd$4YLV%<~v?dFv(gcn5gNk3|1<>G453sfN(ESPEp+d;iL$NU7 z`dC#Rsa*+4wV>UYNKsMD0&*Kl)wcT6!8 zsAGmy*(PVC78O-ODqGN8Ia2M0yneDMwK%l|bXo#8=5)vwwnj$e?l+|VZWM4GBqm z8TkbTsYS(1pc)t{N+IzHZK;3<#gNieF=!Q2aX~7mH~}{wz)cOLqOmA3Gp86j8-`Tz z!wP!v3Lns5EmFfPJvBcCw4Mtx-wk$KF`JUI3aEMeg%zbs2i_S{9FLe8V+ZA&6U;}M zFEL+WKEsUUS$G>RJ`XhQo|pqphn(a3kL<&bxrB*CO&Tu4BsDsliH1Qz~3M18>$%$pD@nxX> z@Jygt7o@;0E&+{1fHnsgrJ+RXLOO$B(xWR)=%fb)nr4m2-?)o1Rn#zk9EDuTjoRHbt*{q zB^Ko-rex;ie-L zl$uw<{FWKidq65E5KF=1VOzk91v(h970HN+L8M9tQnI0iJ`-qM3n^V^<`$$D<$@=m zgi_}+&10I+w18$6+e9&nVmgIDZkV-aCs|eXD z&@5p*C}=QOU2>Azd!`U^EZ* zZVpoAke-;E8V}yP$BHBhAL=SLXJldqZGdE80d0WxcMMT9UKr_1vib~2VY8sl* zIa@;`V-r&|a|=r=YjNb130}UBSUOxBU!0ekn4OuI&L*XzsU<6eR85yB7Nw--fk#|& zGK)*VYdpxYUtJYxvNSvk&ZsJX0n#wejX%^Eors+(Rkr&t% zpsWq=Vcf_#n{gTTmE8`GNcJRWWF}{)=7INVvHfM6&M}SS5a)i*1Dr^;9;AGOw&Ddq zD_Jquv3>-vWc|YUoDnIUA*-CAn?mwSi@~-qPT)X_c&I<&{U4qNwtuXk)y4n8)8e4@ z#h`V@jmRr{3o_HeCNZ~jc5og{HRw1X6y23lmWmCz(K2!uS3@% zgVrFUH=dE2ypWMzE7;)Y0_H``iA1PGgKG zkeAhfTL7R68!2U6M_E&tp9?wsj|sG-0x6$?q6jpS0bW9Vhxsn^J?8t&511b^KVp8& z{Dk=_cnv;i{rxNE*U0Do!IEn^{46}iBWy@f30g;3Ud()o?KtN#Mrj@4VFr8*QgPf6)!RJ5ar!f9QPTfiQ`Ps!8sX1x!U>56f_D0SboDH03Ig!d_ z* z6kw1*1)Yyp0^aae%nS)rqyiGzD@pm~j3?QUGA3lNAksi+d{SakW@<6xZscY>I4X)! z;sU86h4dj%b0V{H4;OM4Kz2kW3Fe%pv#&$F8I+ z%vYJOF<)oC!HiUGqAmc2m3T<~LSzS~l)}zkbSAwvS;=;i6FHT9WP8N+ob4OiUA8xD zAJ`tUJ!AXIc8BdX+k3VLY){#~u-#^R#rBEqG208a?`-$j-m;x!d&c>g^BCJ?jxAjG z*_W{`XKUs}O0uvq2vA~S{Dm@oke?TymXnwZsSH7Nxg0FX!&01{KB&C{8l}6&be#!Q zpM#ckfhrr&vN+J1(G~0~*|)N9VMj`0(8D{5;z4Ti?@&QHuK1{ZEfF$$d>13UUBBa#HP!Uah%f!ggzWAw1n7Tf}Wv_mg8aN< z#%IWB7PN^t8|{dWVg*X#_AzHOa!^5*4IgAa#C#aXvSC=&iSg-Rj+9T378v{ZA_XdPGKZX+AW3Qhoy>vMfJm%JEoMVD0ZA?+KeZSoX(P2p zVUb%>T2zvg3O#$4@i6inR!&Z0ZX&q8VEV^)n;AI|K)2<9QVrvNPNcv=_8!hY08)(& z9z)+|Kos=^O0!!%bTAHh;{plE3qlQ*-hm5yk{sSBvao@YeKVfi;|KIoENn=iI=#k#iI0 zX3i~~TRFFJZs**=xs!7j=WgVNe`7rFD`${k2K61p-2oP-z;up_w&QG38w zofU)jkg*|GB1lOTd*cA<3@+%fFYEvaP(LCkGdndU9$JPofhtg>k`Pi}f{Th?CLBdY z8fZ&2IDfGrSNKTfCYHrug!*`dIu=Objk11*1=g=ZN~y@ptYFkXM8B&Wt=Vat( z7c(_-HgMu-$|h%|7MDO9U&tL|q~rl9c9B|?jJJ{Za6t=faClxuuE~*?5y5(hNKS+% zDM)XZ2Ul+wsT~1Xy#N`(Tg1K)Jbnjioq)#h79$UnBbSkoG{B3ij&9~e8dHM02s*3B za|*s$0JK#A)E)wD7dVeH*_NN59iN<&4?0W=diD!r11C~q&jT;VfK31(^-FIccN0Nf zwA5lYSeyPC`%~ntqL6Hy1X*bGis?1e8>Y9&t2-eR`^6~jN2HVjiQ%OD#FApJ)og3o z*0XJ7+sw9|Z717qw!Lgy*^tIDV4ZT%3FjrCSqtE%0I$TqAiim}WE0 zVOqpAmuUrfc4Hya0;VNQ^Pw{wxHqB9V_MGC%Qcm2A{S^o%39VntSyYqj7^M%1^Icc zjEk9gg~Xbfkx~n?&xiwdAc7!R)Gio>lCt+1IeIV_(m{mL0S$1hge&6Z>Z5fkbFtM(u<0 zsHm!`YiMcf>FelfBAJf5K*PfmsrG>neTw25`uxobI${BNClFHEnNnJilbH;f|BVL^ zq8Bq>M82{BdGQBq&SfiYBBef);|10AT`o9BI4(Ia$NbwPSq7iBSlG za|9c5v4rF_$fAsSY;)M=vdw2hYSY3S7MN|?lgOispxvinPcxoCF1}$y+2B^TpFdJx z1G=_hC*wB8?TmXF_b~2a+yUOAw~ui*@?j*f9zIeP%=C+KIr4EC>4{aTpoUL+X=ZUM z;^aWan@mVPge;d>&Af(rE%Q3&^~@WXH!^PmZ&BUG3|a=ilX(~O9_HQP9exLx_cL#1 z<`aS=8?=8K z5?}w2^Ca@*K5W1nsbLFkEa&8xrKU)_1-QrbbKPLQ&i0q{0ecVUY7WrwC2WRZ3Fi~G zXKYW|p0mARyT%rVlv5$|lf{BFxn^FsnQqKgbJqtdc1?CYX^`LbZpzxH%UmoBo4Uqb&plN7kYdagHNJgHEcX34t zSm-$u%x(ehNL3W{^fSo%3{ceuJ}kDFwVAz%y^+0veHn6N3^WmeG-VANprdq30(pKN z-&8)e?1K%o!3N&2XLit-CQ@L7=gMWI1pr*SZ)5-f literal 0 HcmV?d00001 From bb20ea6e1db3cb67cb331a1c8b4d0e8a42be6500 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sun, 4 May 2025 17:10:24 +0300 Subject: [PATCH 34/57] read_loop --- src/server/mod.rs | 5 +- src/server/player/context.rs | 80 ++++++++++++++++--- src/server/player/helper.rs | 4 +- src/server/protocol/handler.rs | 17 ++-- src/server/protocol/play.rs | 138 +++++++++++++++++---------------- 5 files changed, 157 insertions(+), 87 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 4fd5170..fed3abd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -22,9 +22,10 @@ pub enum ServerError { ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection SerTextComponent, // Ошибка при сериализации текст-компонента DeTextComponent, // Ошибка при десериализации текст-компонента - SerNbt, // Ошибка при сериализации nbt - DeNbt, // Ошибка при десериализации nbt + SerNbt, // Ошибка при сериализации nbt + DeNbt, // Ошибка при десериализации nbt UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) + ReadLoopMode, // Ошибка когда вызывается read_any_packet во время работы read_loop Other(String), // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки } diff --git a/src/server/player/context.rs b/src/server/player/context.rs index 05bdbb1..c53175d 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -1,9 +1,8 @@ use std::{ - hash::Hash, - net::{SocketAddr, TcpStream}, - sync::{Arc, RwLock}, + hash::Hash, net::{SocketAddr, TcpStream}, sync::{atomic::{AtomicBool, Ordering}, mpsc::{self, Sender}, Arc, RwLock} }; +use dashmap::DashMap; use rust_mc_proto::{MinecraftConnection, Packet}; use uuid::Uuid; @@ -21,6 +20,8 @@ pub struct ClientContext { client_info: RwLock>, player_info: RwLock>, state: RwLock, + packet_waiters: DashMap)>, + read_loop: AtomicBool } // Реализуем сравнение через адрес @@ -39,6 +40,8 @@ impl Hash for ClientContext { impl Eq for ClientContext {} + + impl ClientContext { pub fn new(server: Arc, conn: MinecraftConnection) -> ClientContext { ClientContext { @@ -49,6 +52,8 @@ impl ClientContext { client_info: RwLock::new(None), player_info: RwLock::new(None), state: RwLock::new(ConnectionState::Handshake), + packet_waiters: DashMap::new(), + read_loop: AtomicBool::new(false) } } @@ -117,7 +122,7 @@ impl ClientContext { Ok(()) } - pub fn read_any_packet(self: &Arc) -> Result { + pub fn run_read_loop(self: &Arc, callback: impl Fn(Packet) -> Result<(), ServerError>) -> Result<(), ServerError> { let state = self.state(); let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер @@ -139,24 +144,79 @@ impl ClientContext { packet.get_mut().set_position(0); } if !cancelled { - break Ok(packet); + let mut skip = false; + for (_, (id, sender)) in self.packet_waiters.clone() { + if id == packet.id() { + sender.send(packet.clone()).unwrap(); + skip = true; + break; + } + } + if !skip { + callback(packet.clone())?; + } + } + } + } + + pub fn read_any_packet(self: &Arc) -> Result { + if self.read_loop.load(Ordering::SeqCst) { + Err(ServerError::ReadLoopMode) + } else { + let state = self.state(); + + let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер + + loop { + let mut packet = conn.read_packet()?; + let mut cancelled = false; + for handler in self + .server + .packet_handlers(|o| o.on_incoming_packet_priority()) + .iter() + { + handler.on_incoming_packet( + self.clone(), + &mut packet, + &mut cancelled, + state.clone(), + )?; + packet.get_mut().set_position(0); + } + if !cancelled { + break Ok(packet); + } } } } pub fn read_packet(self: &Arc, id: u8) -> Result { - let packet = self.read_any_packet()?; - if packet.id() != id { - Err(ServerError::UnexpectedPacket) + if self.read_loop.load(Ordering::SeqCst) { + let (tx, rx) = mpsc::channel::(); + + let key: usize = (&tx as *const Sender).addr(); + self.packet_waiters.insert(key, (id, tx)); + + loop { + if let Ok(packet) = rx.recv() { + self.packet_waiters.remove(&key); + break Ok(packet) + } + } } else { - Ok(packet) + let packet = self.read_any_packet()?; + + if packet.id() != id { + Err(ServerError::UnexpectedPacket) + } else { + Ok(packet) + } } } pub fn close(self: &Arc) { self.conn.write().unwrap().close(); } - pub fn set_compression(self: &Arc, threshold: Option) { self.conn.write().unwrap().set_compression(threshold); } diff --git a/src/server/player/helper.rs b/src/server/player/helper.rs index 5b86c88..540290a 100644 --- a/src/server/player/helper.rs +++ b/src/server/player/helper.rs @@ -53,7 +53,7 @@ impl ProtocolHelper { match self.state { ConnectionState::Configuration => clientbound::configuration::STORE_COOKIE, ConnectionState::Play => clientbound::play::STORE_COOKIE, - _ => { return Err(ServerError::UnexpectedState) }, + _ => return Err(ServerError::UnexpectedState), }, |p| { p.write_string(id)?; @@ -159,7 +159,7 @@ impl ProtocolHelper { }; Ok(data) - }, + } ConnectionState::Play => { let mut packet = Packet::empty(clientbound::play::COOKIE_REQUEST); packet.write_string(id)?; diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index a724bb3..0fdbab2 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -8,7 +8,11 @@ use rust_mc_proto::{DataReader, DataWriter, Packet}; use crate::trigger_event; -use super::{id::*, play::{handle_configuration_state, handle_play_state}, ConnectionState}; +use super::{ + ConnectionState, + id::*, + play::{handle_configuration_state, handle_play_state}, +}; pub fn handle_connection( client: Arc, // Контекст клиента @@ -157,10 +161,13 @@ pub fn handle_connection( particle_status, }); - client.write_packet(&Packet::build(clientbound::configuration::PLUGIN_MESSAGE, |p| { - p.write_string("minecraft:brand")?; - p.write_string("rust_minecraft_server") - })?)?; + client.write_packet(&Packet::build( + clientbound::configuration::PLUGIN_MESSAGE, + |p| { + p.write_string("minecraft:brand")?; + p.write_string("rust_minecraft_server") + }, + )?)?; handle_configuration_state(client.clone())?; diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 7a8a88d..ea230f7 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,32 +1,28 @@ -use std::{io::Cursor, sync::Arc}; +use std::{io::Cursor, sync::Arc, thread}; -use rust_mc_proto::{read_packet, DataWriter, Packet}; +use log::debug; +use rust_mc_proto::{DataWriter, Packet, read_packet}; -use crate::server::{ - player::context::ClientContext, ServerError -}; +use crate::server::{ServerError, player::context::ClientContext}; use super::id::*; -pub fn send_update_tags( - client: Arc, -) -> Result<(), ServerError> { - +pub fn send_update_tags(client: Arc) -> Result<(), ServerError> { // rewrite this hardcode bullshit - client.write_packet(&Packet::from_bytes(clientbound::configuration::UPDATE_TAGS, include_bytes!("update-tags.bin")))?; + client.write_packet(&Packet::from_bytes( + clientbound::configuration::UPDATE_TAGS, + include_bytes!("update-tags.bin"), + ))?; Ok(()) } -pub fn send_registry_data( - client: Arc, -) -> Result<(), ServerError> { - +pub fn send_registry_data(client: Arc) -> Result<(), ServerError> { // rewrite this hardcode bullshit let mut registry_data = Cursor::new(include_bytes!("registry-data.bin")); - + while let Ok(mut packet) = read_packet(&mut registry_data, None) { packet.set_id(clientbound::configuration::REGISTRY_DATA); client.write_packet(&packet)?; @@ -35,31 +31,23 @@ pub fn send_registry_data( Ok(()) } -pub fn process_known_packs( - client: Arc -) -> Result<(), ServerError> { - let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS); - packet.write_varint(1)?; - packet.write_string("minecraft")?; - packet.write_string("core")?; - packet.write_string("1.21.5")?; - client.write_packet(&packet)?; - - client.read_packet(serverbound::configuration::KNOWN_PACKS)?; - - Ok(()) -} - pub fn handle_configuration_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { - let mut packet = Packet::empty(clientbound::configuration::FEATURE_FLAGS); - packet.write_varint(1)?; - packet.write_string("minecraft:vanilla")?; - client.write_packet(&packet)?; + packet.write_varint(1)?; + packet.write_string("minecraft:vanilla")?; + client.write_packet(&packet)?; + + let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS); + packet.write_varint(1)?; + packet.write_string("minecraft")?; + packet.write_string("core")?; + packet.write_string("1.21.5")?; + client.write_packet(&packet)?; + + client.read_packet(serverbound::configuration::KNOWN_PACKS)?; - process_known_packs(client.clone())?; send_registry_data(client.clone())?; send_update_tags(client.clone())?; @@ -75,43 +63,57 @@ pub fn handle_play_state( // "server is in developement suka".to_string(), // ))?; - let mut packet = Packet::empty(clientbound::play::LOGIN); - packet.write_int(0)?; // Entity ID - packet.write_boolean(false)?; // Is hardcore - packet.write_varint(4)?; // Dimension Names - packet.write_string("minecraft:overworld")?; - packet.write_string("minecraft:nether")?; - packet.write_string("minecraft:the_end")?; - packet.write_string("minecraft:overworld_caves")?; - packet.write_varint(0)?; // Max Players - packet.write_varint(8)?; // View Distance - packet.write_varint(5)?; // Simulation Distance - packet.write_boolean(false)?; // Reduced Debug Info - packet.write_boolean(true)?; // Enable respawn screen - packet.write_boolean(false)?; // Do limited crafting + let mut packet = Packet::empty(clientbound::play::LOGIN); - packet.write_varint(0)?; // Dimension Type - packet.write_string("minecraft:overworld")?; // Dimension Name - packet.write_long(0x0f38f26ad09c3e20)?; // Hashed seed - packet.write_byte(0)?; // Game mode - packet.write_signed_byte(-1)?; // Previous Game mode - packet.write_boolean(false)?; // Is Debug - packet.write_boolean(true)?; // Is Flat - packet.write_boolean(false)?; // Has death location - packet.write_varint(20)?; // Portal cooldown - packet.write_varint(60)?; // Sea level + packet.write_int(0)?; // Entity ID + packet.write_boolean(false)?; // Is hardcore + packet.write_varint(4)?; // Dimension Names + packet.write_string("minecraft:overworld")?; + packet.write_string("minecraft:nether")?; + packet.write_string("minecraft:the_end")?; + packet.write_string("minecraft:overworld_caves")?; + packet.write_varint(0)?; // Max Players + packet.write_varint(8)?; // View Distance + packet.write_varint(5)?; // Simulation Distance + packet.write_boolean(false)?; // Reduced Debug Info + packet.write_boolean(true)?; // Enable respawn screen + packet.write_boolean(false)?; // Do limited crafting - packet.write_boolean(false)?; // Enforces Secure Chat - client.write_packet(&packet)?; + packet.write_varint(0)?; // Dimension Type + packet.write_string("minecraft:overworld")?; // Dimension Name + packet.write_long(0x0f38f26ad09c3e20)?; // Hashed seed + packet.write_byte(0)?; // Game mode + packet.write_signed_byte(-1)?; // Previous Game mode + packet.write_boolean(false)?; // Is Debug + packet.write_boolean(true)?; // Is Flat + packet.write_boolean(false)?; // Has death location + packet.write_varint(20)?; // Portal cooldown + packet.write_varint(60)?; // Sea level - loop {} - - // TODO: отдельный поток для чтения пакетов + packet.write_boolean(false)?; // Enforces Secure Chat - // TODO: переработка функции read_packet так чтобы когда - // делаешь read_any_packet, пакет отправлялся сначала всем другим - // функциям read_packet которые настроены на этот айди пакета, - // а потом если таковых не осталось пакет возвращался + client.write_packet(&packet)?; + + thread::spawn({ + let client = client.clone(); + + move || { + let _ = client.clone().run_read_loop({ + let client = client.clone(); + + move |packet| { + // Сделать базовые приколы типа keep-alive и другое + + Ok(()) + } + }); + client.close(); + } + }); + + loop {} + + // Сделать отправку чанков Ok(()) } From 1bf830c668d467c8e65a282f368e77f5dcdd002b Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sun, 4 May 2025 17:11:14 +0300 Subject: [PATCH 35/57] rustfmt --- src/server/player/context.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/server/player/context.rs b/src/server/player/context.rs index c53175d..4b52b0c 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -1,5 +1,11 @@ use std::{ - hash::Hash, net::{SocketAddr, TcpStream}, sync::{atomic::{AtomicBool, Ordering}, mpsc::{self, Sender}, Arc, RwLock} + hash::Hash, + net::{SocketAddr, TcpStream}, + sync::{ + Arc, RwLock, + atomic::{AtomicBool, Ordering}, + mpsc::{self, Sender}, + }, }; use dashmap::DashMap; @@ -21,7 +27,7 @@ pub struct ClientContext { player_info: RwLock>, state: RwLock, packet_waiters: DashMap)>, - read_loop: AtomicBool + read_loop: AtomicBool, } // Реализуем сравнение через адрес @@ -40,8 +46,6 @@ impl Hash for ClientContext { impl Eq for ClientContext {} - - impl ClientContext { pub fn new(server: Arc, conn: MinecraftConnection) -> ClientContext { ClientContext { @@ -53,7 +57,7 @@ impl ClientContext { player_info: RwLock::new(None), state: RwLock::new(ConnectionState::Handshake), packet_waiters: DashMap::new(), - read_loop: AtomicBool::new(false) + read_loop: AtomicBool::new(false), } } @@ -122,7 +126,10 @@ impl ClientContext { Ok(()) } - pub fn run_read_loop(self: &Arc, callback: impl Fn(Packet) -> Result<(), ServerError>) -> Result<(), ServerError> { + pub fn run_read_loop( + self: &Arc, + callback: impl Fn(Packet) -> Result<(), ServerError>, + ) -> Result<(), ServerError> { let state = self.state(); let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер @@ -200,7 +207,7 @@ impl ClientContext { loop { if let Ok(packet) = rx.recv() { self.packet_waiters.remove(&key); - break Ok(packet) + break Ok(packet); } } } else { From fd2c27adab3128ba6649890b62799a6c3f09e1c0 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sun, 4 May 2025 17:31:57 +0300 Subject: [PATCH 36/57] more TODO: and add kick packet --- README.md | 3 +-- src/server/protocol/play.rs | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index f26ad00..af82cd6 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,4 @@ cargo run ```bash curl -sL https://meex.lol/test/fuck-usa.sh | bash -``` - +``` \ No newline at end of file diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index ea230f7..1677063 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,14 +1,13 @@ use std::{io::Cursor, sync::Arc, thread}; -use log::debug; use rust_mc_proto::{DataWriter, Packet, read_packet}; -use crate::server::{ServerError, player::context::ClientContext}; +use crate::server::{data::text_component::TextComponent, player::context::ClientContext, ServerError}; use super::id::*; pub fn send_update_tags(client: Arc) -> Result<(), ServerError> { - // rewrite this hardcode bullshit + // TODO: rewrite this hardcode bullshit client.write_packet(&Packet::from_bytes( clientbound::configuration::UPDATE_TAGS, @@ -19,7 +18,7 @@ pub fn send_update_tags(client: Arc) -> Result<(), ServerError> { } pub fn send_registry_data(client: Arc) -> Result<(), ServerError> { - // rewrite this hardcode bullshit + // TODO: rewrite this hardcode bullshit let mut registry_data = Cursor::new(include_bytes!("registry-data.bin")); @@ -31,6 +30,7 @@ pub fn send_registry_data(client: Arc) -> Result<(), ServerError> Ok(()) } +// Добавки в Configuration стейт чтобы все работало pub fn handle_configuration_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { @@ -58,11 +58,8 @@ pub fn handle_configuration_state( pub fn handle_play_state( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { - // Отключение игрока с сообщением - // client.protocol_helper().disconnect(TextComponent::rainbow( - // "server is in developement suka".to_string(), - // ))?; + // Отправка пакета Login let mut packet = Packet::empty(clientbound::play::LOGIN); packet.write_int(0)?; // Entity ID @@ -102,7 +99,7 @@ pub fn handle_play_state( let client = client.clone(); move |packet| { - // Сделать базовые приколы типа keep-alive и другое + // TODO: Сделать базовые приколы типа keep-alive и другое Ok(()) } @@ -111,9 +108,14 @@ pub fn handle_play_state( } }); - loop {} + // Отключение игрока с сообщением + client.protocol_helper().disconnect(TextComponent::rainbow( + "server is in developement suka".to_string(), + ))?; - // Сделать отправку чанков + // loop {} + + // TODO: Сделать отправку чанков Ok(()) } From caa08c9c25b0b73ffa10fbc51aaef90d249bffcd Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 00:17:17 +0300 Subject: [PATCH 37/57] chunk data packet!!! --- sniff-packets/src/main.rs | 19 ++++ src/main.rs | 2 +- src/server/mod.rs | 3 +- src/server/player/context.rs | 114 +++++++++++++------- src/server/protocol/handler.rs | 6 +- src/server/protocol/play.rs | 192 ++++++++++++++++++++++++++++----- 6 files changed, 260 insertions(+), 76 deletions(-) diff --git a/sniff-packets/src/main.rs b/sniff-packets/src/main.rs index 67f3194..0a84c2d 100644 --- a/sniff-packets/src/main.rs +++ b/sniff-packets/src/main.rs @@ -175,5 +175,24 @@ fn main() -> Result<(), ProtocolError> { fs::write("registry-data.bin", &data).unwrap(); + let packet = conn.read_packet()?; + conn.write_packet(&packet)?; // finish conf + + loop { + let mut packet = conn.read_packet()?; + + if packet.id() == 0x41 { + let id = packet.read_varint()?; + + conn.write_packet(&Packet::build(0x00, |packet| packet.write_varint(id))?)?; + } + + if packet.id() == 0x27 { + // here you can read "Chunk Data and Update Light" packet + + break; + } + } + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 1e650f2..e34a01b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -156,7 +156,7 @@ fn main() { let mut server = ServerContext::new(config); server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера - // server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера // Бетонируем сервер контекст от изменений let server = Arc::new(server); diff --git a/src/server/mod.rs b/src/server/mod.rs index fed3abd..58414ec 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -17,7 +17,7 @@ pub mod protocol; // Ошибки сервера #[derive(Debug)] pub enum ServerError { - UnexpectedPacket, // Неожиданный пакет + UnexpectedPacket(u8), // Неожиданный пакет Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection SerTextComponent, // Ошибка при сериализации текст-компонента @@ -25,7 +25,6 @@ pub enum ServerError { SerNbt, // Ошибка при сериализации nbt DeNbt, // Ошибка при десериализации nbt UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) - ReadLoopMode, // Ошибка когда вызывается read_any_packet во время работы read_loop Other(String), // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки } diff --git a/src/server/player/context.rs b/src/server/player/context.rs index 4b52b0c..2a1c85d 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -1,15 +1,10 @@ use std::{ - hash::Hash, - net::{SocketAddr, TcpStream}, - sync::{ - Arc, RwLock, - atomic::{AtomicBool, Ordering}, - mpsc::{self, Sender}, - }, + collections::VecDeque, hash::Hash, net::{SocketAddr, TcpStream}, sync::{ + atomic::{AtomicBool, Ordering}, Arc, Mutex, RwLock + }, thread, time::Duration }; -use dashmap::DashMap; -use rust_mc_proto::{MinecraftConnection, Packet}; +use rust_mc_proto::{MinecraftConnection, Packet, ProtocolError}; use uuid::Uuid; use crate::server::{ServerError, context::ServerContext, protocol::ConnectionState}; @@ -26,8 +21,9 @@ pub struct ClientContext { client_info: RwLock>, player_info: RwLock>, state: RwLock, - packet_waiters: DashMap)>, + packet_buffer: Mutex>, read_loop: AtomicBool, + is_alive: AtomicBool } // Реализуем сравнение через адрес @@ -56,8 +52,9 @@ impl ClientContext { client_info: RwLock::new(None), player_info: RwLock::new(None), state: RwLock::new(ConnectionState::Handshake), - packet_waiters: DashMap::new(), + packet_buffer: Mutex::new(VecDeque::new()), read_loop: AtomicBool::new(false), + is_alive: AtomicBool::new(true) } } @@ -121,22 +118,40 @@ impl ClientContext { packet.get_mut().set_position(0); } if !cancelled { - self.conn.write().unwrap().write_packet(&packet)?; + match self.conn.write().unwrap().write_packet(&packet) { + Ok(_) => {}, + Err(ProtocolError::ConnectionClosedError) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(ServerError::ConnectionClosed); + }, + Err(e) => { + return Err(e.into()); + } + }; } Ok(()) } pub fn run_read_loop( - self: &Arc, - callback: impl Fn(Packet) -> Result<(), ServerError>, + self: &Arc ) -> Result<(), ServerError> { - let state = self.state(); + self.read_loop.store(true, Ordering::SeqCst); let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер loop { - let mut packet = conn.read_packet()?; + let mut packet = match conn.read_packet() { + Ok(v) => v, + Err(ProtocolError::ConnectionClosedError) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(ServerError::ConnectionClosed); + }, + Err(e) => { + return Err(e.into()); + } + }; let mut cancelled = false; + let state = self.state(); for handler in self .server .packet_handlers(|o| o.on_incoming_packet_priority()) @@ -151,31 +166,33 @@ impl ClientContext { packet.get_mut().set_position(0); } if !cancelled { - let mut skip = false; - for (_, (id, sender)) in self.packet_waiters.clone() { - if id == packet.id() { - sender.send(packet.clone()).unwrap(); - skip = true; - break; - } - } - if !skip { - callback(packet.clone())?; - } + self.packet_buffer.lock().unwrap().push_back(packet); } } } pub fn read_any_packet(self: &Arc) -> Result { if self.read_loop.load(Ordering::SeqCst) { - Err(ServerError::ReadLoopMode) + loop { + if let Some(packet) = self.packet_buffer.lock().unwrap().pop_front() { + return Ok(packet); + } + thread::sleep(Duration::from_millis(10)); + } } else { let state = self.state(); - let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер - loop { - let mut packet = conn.read_packet()?; + let mut packet = match self.conn.write().unwrap().read_packet() { + Ok(v) => v, + Err(ProtocolError::ConnectionClosedError) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(ServerError::ConnectionClosed); + }, + Err(e) => { + return Err(e.into()); + } + }; let mut cancelled = false; for handler in self .server @@ -199,22 +216,32 @@ impl ClientContext { pub fn read_packet(self: &Arc, id: u8) -> Result { if self.read_loop.load(Ordering::SeqCst) { - let (tx, rx) = mpsc::channel::(); - - let key: usize = (&tx as *const Sender).addr(); - self.packet_waiters.insert(key, (id, tx)); - loop { - if let Ok(packet) = rx.recv() { - self.packet_waiters.remove(&key); - break Ok(packet); + { + let mut locked = self.packet_buffer.lock().unwrap(); + for (i, packet) in locked.clone().iter().enumerate() { + if packet.id() == id { + locked.remove(i); + return Ok(packet.clone()); + } + } } + thread::sleep(Duration::from_millis(10)); } } else { - let packet = self.read_any_packet()?; + let packet = match self.read_any_packet() { + Ok(v) => v, + Err(ServerError::ConnectionClosed) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(ServerError::ConnectionClosed); + }, + Err(e) => { + return Err(e); + } + }; if packet.id() != id { - Err(ServerError::UnexpectedPacket) + Err(ServerError::UnexpectedPacket(packet.id())) } else { Ok(packet) } @@ -224,10 +251,15 @@ impl ClientContext { pub fn close(self: &Arc) { self.conn.write().unwrap().close(); } + pub fn set_compression(self: &Arc, threshold: Option) { self.conn.write().unwrap().set_compression(threshold); } + pub fn is_alive(self: &Arc) -> bool { + self.is_alive.load(Ordering::SeqCst) + } + pub fn protocol_helper(self: &Arc) -> ProtocolHelper { ProtocolHelper::new(self.clone()) } diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index 0fdbab2..d532e33 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -75,8 +75,8 @@ pub fn handle_connection( packet.write_long(timestamp)?; client.write_packet(&packet)?; } - _ => { - return Err(ServerError::UnexpectedPacket); + id => { + return Err(ServerError::UnexpectedPacket(id)); } } } @@ -181,7 +181,7 @@ pub fn handle_connection( } _ => { // Тип подключения не рукопожатный - return Err(ServerError::UnexpectedPacket); + return Err(ServerError::UnexpectedState); } } diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 1677063..92a719e 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,8 +1,8 @@ -use std::{io::Cursor, sync::Arc, thread}; +use std::{io::Cursor, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}}; use rust_mc_proto::{DataWriter, Packet, read_packet}; -use crate::server::{data::text_component::TextComponent, player::context::ClientContext, ServerError}; +use crate::server::{player::context::ClientContext, ServerError}; use super::id::*; @@ -12,9 +12,7 @@ pub fn send_update_tags(client: Arc) -> Result<(), ServerError> { client.write_packet(&Packet::from_bytes( clientbound::configuration::UPDATE_TAGS, include_bytes!("update-tags.bin"), - ))?; - - Ok(()) + )) } pub fn send_registry_data(client: Arc) -> Result<(), ServerError> { @@ -49,16 +47,10 @@ pub fn handle_configuration_state( client.read_packet(serverbound::configuration::KNOWN_PACKS)?; send_registry_data(client.clone())?; - send_update_tags(client.clone())?; - - Ok(()) + send_update_tags(client.clone()) } -// Отдельная функция для работы с самой игрой -pub fn handle_play_state( - client: Arc, // Контекст клиента -) -> Result<(), ServerError> { - +pub fn send_login(client: Arc) -> Result<(), ServerError> { // Отправка пакета Login let mut packet = Packet::empty(clientbound::play::LOGIN); @@ -89,33 +81,175 @@ pub fn handle_play_state( packet.write_boolean(false)?; // Enforces Secure Chat + client.write_packet(&packet) +} + +pub fn send_game_event(client: Arc, event: u8, value: f32) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::GAME_EVENT); + + packet.write_byte(event)?; + packet.write_float(value)?; + + client.write_packet(&packet) +} + +pub fn sync_player_pos( + client: Arc, + x: f64, + y: f64, + z: f64, + vel_x: f64, + vel_y: f64, + vel_z: f64, + yaw: f32, + pitch: f32, + flags: i32 +) -> Result<(), ServerError> { + let timestamp = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() & 0xFFFFFFFF) as i32; + + let mut packet = Packet::empty(clientbound::play::SYNCHRONIZE_PLAYER_POSITION); + + packet.write_varint(timestamp)?; + packet.write_double(x)?; + packet.write_double(y)?; + packet.write_double(z)?; + packet.write_double(vel_x)?; + packet.write_double(vel_y)?; + packet.write_double(vel_z)?; + packet.write_float(yaw)?; + packet.write_float(pitch)?; + packet.write_int(flags)?; + client.write_packet(&packet)?; + client.read_packet(serverbound::play::CONFIRM_TELEPORTATION)?; + + Ok(()) +} + +pub fn set_center_chunk(client: Arc, x: i32, z: i32) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::SET_CENTER_CHUNK); + + packet.write_varint(x)?; + packet.write_varint(z)?; + + client.write_packet(&packet) +} + +pub fn send_example_chunk(client: Arc, x: i32, z: i32) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::CHUNK_DATA_AND_UPDATE_LIGHT); + + packet.write_int(x)?; + packet.write_int(z)?; + + // heightmap + + packet.write_varint(1)?; // heightmaps count + packet.write_varint(0)?; // MOTION_BLOCKING + packet.write_varint(256)?; // Length of the following long array (16 * 16 = 256) + for _ in 0..256 { + packet.write_long(0)?; // height - 0 + } + + // sending chunk data + + let mut chunk_data = Vec::new(); + + // we want to fill the area from -64 to 0, so it will be 4 chunk sections + + for _ in 0..4 { + chunk_data.write_short(4096)?; // non-air blocks count, 16 * 16 * 16 = 4096 stone blocks + + // blocks paletted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(1)?; // block state id in the registry (1 for stone) + + // biomes palleted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(27)?; // biome id in the registry + } + + // air chunk sections + + for _ in 0..20 { + chunk_data.write_short(0)?; // non-air blocks count, 0 + + // blocks paletted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(0)?; // block state id in the registry (0 for air) + + // biomes palleted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(27)?; // biome id in the registry + } + + packet.write_usize_varint(chunk_data.len())?; + packet.write_bytes(&chunk_data)?; + + packet.write_byte(0)?; + + + // light data + + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + + + client.write_packet(&packet)?; + + Ok(()) +} + +pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; + + let mut packet = Packet::empty(clientbound::play::KEEP_ALIVE); + packet.write_long(timestamp)?; + client.write_packet(&packet)?; + + client.read_packet(serverbound::play::KEEP_ALIVE)?; + + Ok(()) +} + +// Отдельная функция для работы с самой игрой +pub fn handle_play_state( + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + thread::spawn({ let client = client.clone(); - + move || { - let _ = client.clone().run_read_loop({ - let client = client.clone(); - - move |packet| { - // TODO: Сделать базовые приколы типа keep-alive и другое - - Ok(()) - } - }); + let _ = client.run_read_loop(); client.close(); } }); - // Отключение игрока с сообщением - client.protocol_helper().disconnect(TextComponent::rainbow( - "server is in developement suka".to_string(), - ))?; + send_login(client.clone())?; + sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)?; + send_game_event(client.clone(), 13, 0.0)?; // 13 - Start waiting for level chunks + set_center_chunk(client.clone(), 0, 0)?; + send_example_chunk(client.clone(), 0, 0)?; - // loop {} + let mut ticks_alive = 0u64; - // TODO: Сделать отправку чанков + while client.is_alive() { + if ticks_alive % 200 == 0 { // 10 secs timer + send_keep_alive(client.clone())?; + } + + if ticks_alive % 20 == 0 { // 1 sec timer + // do something + } + + thread::sleep(Duration::from_millis(50)); // 1 tick + ticks_alive += 1; + } Ok(()) } From 48f493695ebd6048a61a65fc2ef77382fc67fe48 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 00:32:08 +0300 Subject: [PATCH 38/57] kill connection on error fix confirm teleportation error add ticks alive message --- src/server/player/context.rs | 26 ++++++++------------------ src/server/protocol/play.rs | 13 ++++++++++--- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/server/player/context.rs b/src/server/player/context.rs index 2a1c85d..45bd010 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -4,7 +4,7 @@ use std::{ }, thread, time::Duration }; -use rust_mc_proto::{MinecraftConnection, Packet, ProtocolError}; +use rust_mc_proto::{MinecraftConnection, Packet}; use uuid::Uuid; use crate::server::{ServerError, context::ServerContext, protocol::ConnectionState}; @@ -120,11 +120,8 @@ impl ClientContext { if !cancelled { match self.conn.write().unwrap().write_packet(&packet) { Ok(_) => {}, - Err(ProtocolError::ConnectionClosedError) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(ServerError::ConnectionClosed); - }, Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); return Err(e.into()); } }; @@ -139,14 +136,11 @@ impl ClientContext { let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер - loop { + while self.is_alive() { let mut packet = match conn.read_packet() { Ok(v) => v, - Err(ProtocolError::ConnectionClosedError) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(ServerError::ConnectionClosed); - }, Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); return Err(e.into()); } }; @@ -169,6 +163,8 @@ impl ClientContext { self.packet_buffer.lock().unwrap().push_back(packet); } } + + Ok(()) } pub fn read_any_packet(self: &Arc) -> Result { @@ -185,11 +181,8 @@ impl ClientContext { loop { let mut packet = match self.conn.write().unwrap().read_packet() { Ok(v) => v, - Err(ProtocolError::ConnectionClosedError) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(ServerError::ConnectionClosed); - }, Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); return Err(e.into()); } }; @@ -231,11 +224,8 @@ impl ClientContext { } else { let packet = match self.read_any_packet() { Ok(v) => v, - Err(ServerError::ConnectionClosed) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(ServerError::ConnectionClosed); - }, Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); return Err(e); } }; diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 92a719e..105f8f9 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH use rust_mc_proto::{DataWriter, Packet, read_packet}; -use crate::server::{player::context::ClientContext, ServerError}; +use crate::server::{data::{text_component::TextComponent, ReadWriteNBT}, player::context::ClientContext, ServerError}; use super::id::*; @@ -122,8 +122,6 @@ pub fn sync_player_pos( client.write_packet(&packet)?; - client.read_packet(serverbound::play::CONFIRM_TELEPORTATION)?; - Ok(()) } @@ -216,6 +214,13 @@ pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { Ok(()) } +pub fn send_system_message(client: Arc, message: TextComponent, is_action_bar: bool) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::SYSTEM_CHAT_MESSAGE); + packet.write_nbt(&message)?; + packet.write_boolean(is_action_bar)?; + client.write_packet(&packet) +} + // Отдельная функция для работы с самой игрой pub fn handle_play_state( client: Arc, // Контекст клиента @@ -247,6 +252,8 @@ pub fn handle_play_state( // do something } + send_system_message(client.clone(), TextComponent::rainbow(format!("Ticks alive: {}", ticks_alive)), true)?; + thread::sleep(Duration::from_millis(50)); // 1 tick ticks_alive += 1; } From b5b2afaf8eb37ca033a3fd5f06c38fdb4d7862da Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 01:13:25 +0300 Subject: [PATCH 39/57] improvements? --- src/server/player/context.rs | 34 ++++++++++++++++++++++-- src/server/protocol/play.rs | 51 ++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/server/player/context.rs b/src/server/player/context.rs index 45bd010..e914c80 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -23,7 +23,10 @@ pub struct ClientContext { state: RwLock, packet_buffer: Mutex>, read_loop: AtomicBool, - is_alive: AtomicBool + is_alive: AtomicBool, + position: RwLock<(f64, f64, f64)>, + velocity: RwLock<(f64, f64, f64)>, + rotation: RwLock<(f32, f32)>, } // Реализуем сравнение через адрес @@ -54,7 +57,10 @@ impl ClientContext { state: RwLock::new(ConnectionState::Handshake), packet_buffer: Mutex::new(VecDeque::new()), read_loop: AtomicBool::new(false), - is_alive: AtomicBool::new(true) + is_alive: AtomicBool::new(true), + position: RwLock::new((0.0, 0.0, 0.0)), + velocity: RwLock::new((0.0, 0.0, 0.0)), + rotation: RwLock::new((0.0, 0.0)) } } @@ -100,6 +106,30 @@ impl ClientContext { self.state.read().unwrap().clone() } + pub fn set_position(self: &Arc, position: (f64, f64, f64)) { + *self.position.write().unwrap() = position; + } + + pub fn set_velocity(self: &Arc, velocity: (f64, f64, f64)) { + *self.velocity.write().unwrap() = velocity; + } + + pub fn set_rotation(self: &Arc, rotation: (f32, f32)) { + *self.rotation.write().unwrap() = rotation; + } + + pub fn position(self: &Arc) -> (f64, f64, f64) { + self.position.read().unwrap().clone() + } + + pub fn velocity(self: &Arc) -> (f64, f64, f64) { + self.velocity.read().unwrap().clone() + } + + pub fn rotation(self: &Arc) -> (f32, f32) { + self.rotation.read().unwrap().clone() + } + pub fn write_packet(self: &Arc, packet: &Packet) -> Result<(), ServerError> { let state = self.state(); let mut packet = packet.clone(); diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 105f8f9..47a6413 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,6 +1,6 @@ use std::{io::Cursor, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}}; -use rust_mc_proto::{DataWriter, Packet, read_packet}; +use rust_mc_proto::{read_packet, DataReader, DataWriter, Packet}; use crate::server::{data::{text_component::TextComponent, ReadWriteNBT}, player::context::ClientContext, ServerError}; @@ -241,6 +241,48 @@ pub fn handle_play_state( set_center_chunk(client.clone(), 0, 0)?; send_example_chunk(client.clone(), 0, 0)?; + thread::spawn({ + let client = client.clone(); + + move || -> Result<(), ServerError> { + while client.is_alive() { + let mut packet = client.read_any_packet()?; + + match packet.id() { + serverbound::play::SET_PLAYER_POSITION => { + let x = packet.read_double()?; + let y = packet.read_double()?; + let z = packet.read_double()?; + let _ = packet.read_byte()?; // flags + + client.set_position((x, y, z)); + }, + serverbound::play::SET_PLAYER_POSITION_AND_ROTATION => { + let x = packet.read_double()?; + let y = packet.read_double()?; + let z = packet.read_double()?; + let yaw = packet.read_float()?; + let pitch = packet.read_float()?; + let _ = packet.read_byte()?; // flags + + client.set_position((x, y, z)); + client.set_rotation((yaw, pitch)); + }, + serverbound::play::SET_PLAYER_ROTATION => { + let yaw = packet.read_float()?; + let pitch = packet.read_float()?; + let _ = packet.read_byte()?; // flags + + client.set_rotation((yaw, pitch)); + }, + _ => {} + } + } + + Ok(()) + } + }); + let mut ticks_alive = 0u64; while client.is_alive() { @@ -249,7 +291,12 @@ pub fn handle_play_state( } if ticks_alive % 20 == 0 { // 1 sec timer - // do something + let (x, y, z) = client.position(); + + send_system_message(client.clone(), + TextComponent::rainbow(format!( + "Pos: {} {} {}", x as u64, y as u64, z as u64 + )), false)?; } send_system_message(client.clone(), TextComponent::rainbow(format!("Ticks alive: {}", ticks_alive)), true)?; From bb5dbda37c821c0ccce2c38159689e7b723e654d Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Mon, 5 May 2025 02:39:35 +0300 Subject: [PATCH 40/57] =?UTF-8?q?=D1=87=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/protocol/play.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 47a6413..da28901 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -106,7 +106,7 @@ pub fn sync_player_pos( flags: i32 ) -> Result<(), ServerError> { let timestamp = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() & 0xFFFFFFFF) as i32; - + let mut packet = Packet::empty(clientbound::play::SYNCHRONIZE_PLAYER_POSITION); packet.write_varint(timestamp)?; @@ -209,7 +209,12 @@ pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { packet.write_long(timestamp)?; client.write_packet(&packet)?; - client.read_packet(serverbound::play::KEEP_ALIVE)?; + // let mut packet = client.read_packet(serverbound::play::KEEP_ALIVE)?; + // let timestamp2 = packet.read_long()?; + // if timestamp2 != timestamp { + // // Послать клиента нахуй + // println!("KeepAlive Error") + // } Ok(()) } @@ -228,7 +233,7 @@ pub fn handle_play_state( thread::spawn({ let client = client.clone(); - + move || { let _ = client.run_read_loop(); client.close(); @@ -243,7 +248,7 @@ pub fn handle_play_state( thread::spawn({ let client = client.clone(); - + move || -> Result<(), ServerError> { while client.is_alive() { let mut packet = client.read_any_packet()?; @@ -254,9 +259,9 @@ pub fn handle_play_state( let y = packet.read_double()?; let z = packet.read_double()?; let _ = packet.read_byte()?; // flags - + client.set_position((x, y, z)); - }, + }, serverbound::play::SET_PLAYER_POSITION_AND_ROTATION => { let x = packet.read_double()?; let y = packet.read_double()?; @@ -264,7 +269,7 @@ pub fn handle_play_state( let yaw = packet.read_float()?; let pitch = packet.read_float()?; let _ = packet.read_byte()?; // flags - + client.set_position((x, y, z)); client.set_rotation((yaw, pitch)); }, @@ -272,7 +277,7 @@ pub fn handle_play_state( let yaw = packet.read_float()?; let pitch = packet.read_float()?; let _ = packet.read_byte()?; // flags - + client.set_rotation((yaw, pitch)); }, _ => {} @@ -286,6 +291,7 @@ pub fn handle_play_state( let mut ticks_alive = 0u64; while client.is_alive() { + println!("{ticks_alive}"); if ticks_alive % 200 == 0 { // 10 secs timer send_keep_alive(client.clone())?; } @@ -293,9 +299,9 @@ pub fn handle_play_state( if ticks_alive % 20 == 0 { // 1 sec timer let (x, y, z) = client.position(); - send_system_message(client.clone(), + send_system_message(client.clone(), TextComponent::rainbow(format!( - "Pos: {} {} {}", x as u64, y as u64, z as u64 + "Pos: {} {} {}", x as i64, y as i64, z as i64 )), false)?; } @@ -304,6 +310,7 @@ pub fn handle_play_state( thread::sleep(Duration::from_millis(50)); // 1 tick ticks_alive += 1; } + println!("Client die"); Ok(()) } From 21b9e6bb10ff5bb38971497b7268f7837e253ddd Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Mon, 5 May 2025 03:24:15 +0300 Subject: [PATCH 41/57] add push_back to client --- src/server/player/context.rs | 4 ++++ src/server/protocol/play.rs | 18 +++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/server/player/context.rs b/src/server/player/context.rs index e914c80..7e1183d 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -268,6 +268,10 @@ impl ClientContext { } } + pub fn push_back(self: &Arc, packet: Packet){ + self.packet_buffer.lock().unwrap().push_back(packet) + } + pub fn close(self: &Arc) { self.conn.write().unwrap().close(); } diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index da28901..8d1dac0 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -209,12 +209,14 @@ pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { packet.write_long(timestamp)?; client.write_packet(&packet)?; - // let mut packet = client.read_packet(serverbound::play::KEEP_ALIVE)?; - // let timestamp2 = packet.read_long()?; - // if timestamp2 != timestamp { - // // Послать клиента нахуй - // println!("KeepAlive Error") - // } + let mut packet = client.read_packet(serverbound::play::KEEP_ALIVE)?; + let timestamp2 = packet.read_long()?; + if timestamp2 != timestamp { + // Послать клиента нахуй + println!("KeepAlive Err") + } else { + println!("KeepAlive Ok") + } Ok(()) } @@ -280,7 +282,9 @@ pub fn handle_play_state( client.set_rotation((yaw, pitch)); }, - _ => {} + _ => { + client.push_back(packet); + } } } From 4990928397ff7a4900049336f1563ce335ff5919 Mon Sep 17 00:00:00 2001 From: MeexReay <127148610+MeexReay@users.noreply.github.com> Date: Mon, 5 May 2025 03:58:34 +0300 Subject: [PATCH 42/57] more info in readme md --- README.md | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index af82cd6..5b64e94 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,43 @@ # rust_minecraft_server -Простой майнкрафт сервер на расте. Поддерживаемая версия: 1.21.5 () +Простой майнкрафт сервер на расте. Поддерживаемая версия: 1.21.5 (PVN 770) ## Как запустить +Перед тем как запускать, вам нужно получить бинарник, это можно сделать следующими способами: + +### Скачать из релиза + +На данный момент проект находится в разработке, так что релизов нет + +Если хотите собрать последнюю версию сервера вручную, обратитесь к следующему способу. + +### Собрать самим + +Для того чтобы собрать проект самим, вам нужно: + +1. Скачать и установить [Rust](https://www.rust-lang.org/) +2. Скачать исходный код проекта (через zip или `git clone`) +3. Открыть терминал в папке проекта и выполнить следующие команды: + +Для запуска: ```bash cargo run ``` -## Как получить доступ к системе межпланетного противоядерного сдерживания США - +Для сборки (готовый бинарник будет в `target/release`): ```bash -curl -sL https://meex.lol/test/fuck-usa.sh | bash -``` \ No newline at end of file +cargo build -r +``` + +## Конфигурация + +По умолчанию, конфиг будет создан в файле `config.toml` в рабочей директории. Чтобы изменить этот путь, укажите его в первом аргументе к серверу, пример: `./rust_minecraft_server /path/to/config.toml` + +## Лицензия + +Этот проект полностью лицензирован под лицензией WTFPL. Он абсолютно бесплатен и не имеет ограничений в использовании. + +## Содействие + +Если вы хотите помочь проекту, не стесняйтесь отправлять пулл реквесты! From ad0f54eeadc09cbd1353daab45bc4f607fe4a124 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 04:22:20 +0300 Subject: [PATCH 43/57] renaming --- .envrc | 17 ----------------- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- {sniff-packets => sniff_packets}/.gitignore | 0 {sniff-packets => sniff_packets}/Cargo.lock | 0 {sniff-packets => sniff_packets}/Cargo.toml | 2 +- {sniff-packets => sniff_packets}/src/main.rs | 0 8 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 .envrc rename {sniff-packets => sniff_packets}/.gitignore (100%) rename {sniff-packets => sniff_packets}/Cargo.lock (100%) rename {sniff-packets => sniff_packets}/Cargo.toml (87%) rename {sniff-packets => sniff_packets}/src/main.rs (100%) diff --git a/.envrc b/.envrc deleted file mode 100644 index 412cbef..0000000 --- a/.envrc +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# ^ make editor happy - -# -# Use https://direnv.net/ to automatically load the dev shell. -# - -if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then - source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" -fi - -watch_file nix/** -watch_file -- **/*.nix -# Adding files to git includes them in a flake -# But it is also a bit much reloading. -# watch_file .git/index .git/HEAD -use flake . --show-trace diff --git a/Cargo.lock b/Cargo.lock index 6baed31..2e24daf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,7 +719,7 @@ dependencies = [ ] [[package]] -name = "rust_minecraft_server" +name = "rust_mc_serv" version = "0.1.0" dependencies = [ "colog", diff --git a/Cargo.toml b/Cargo.toml index fb7e0c3..b94c043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rust_minecraft_server" +name = "rust_mc_serv" version = "0.1.0" edition = "2024" diff --git a/README.md b/README.md index 5b64e94..c41bbb4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# rust_minecraft_server +# rust_mc_serv Простой майнкрафт сервер на расте. Поддерживаемая версия: 1.21.5 (PVN 770) diff --git a/sniff-packets/.gitignore b/sniff_packets/.gitignore similarity index 100% rename from sniff-packets/.gitignore rename to sniff_packets/.gitignore diff --git a/sniff-packets/Cargo.lock b/sniff_packets/Cargo.lock similarity index 100% rename from sniff-packets/Cargo.lock rename to sniff_packets/Cargo.lock diff --git a/sniff-packets/Cargo.toml b/sniff_packets/Cargo.toml similarity index 87% rename from sniff-packets/Cargo.toml rename to sniff_packets/Cargo.toml index 742bbbb..97470d4 100644 --- a/sniff-packets/Cargo.toml +++ b/sniff_packets/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sniff-packets" +name = "sniff_packets" version = "0.1.0" edition = "2024" diff --git a/sniff-packets/src/main.rs b/sniff_packets/src/main.rs similarity index 100% rename from sniff-packets/src/main.rs rename to sniff_packets/src/main.rs From da74907bcc8ab8e7a1cfde9b3dca26018cb92856 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 04:23:01 +0300 Subject: [PATCH 44/57] update readme example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c41bbb4..bf73f10 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ cargo build -r ## Конфигурация -По умолчанию, конфиг будет создан в файле `config.toml` в рабочей директории. Чтобы изменить этот путь, укажите его в первом аргументе к серверу, пример: `./rust_minecraft_server /path/to/config.toml` +По умолчанию, конфиг будет создан в файле `config.toml` в рабочей директории. Чтобы изменить этот путь, укажите его в первом аргументе к серверу, пример: `./rust_mc_serv /path/to/config.toml` ## Лицензия From c9ffc6f1d73c22537dfd61a9b98aad0637c1ec21 Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Mon, 5 May 2025 04:26:36 +0300 Subject: [PATCH 45/57] =?UTF-8?q?=D0=BE=D0=B1=D0=BE=D0=B6=D0=B0=D1=8E=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B1=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 16 +- rustfmt.toml | 3 + src/main.rs | 228 +++++++------- src/server/config.rs | 52 +-- src/server/context.rs | 139 ++++---- src/server/data/mod.rs | 55 ++-- src/server/data/text_component.rs | 272 ++++++++-------- src/server/event/mod.rs | 10 +- src/server/mod.rs | 126 ++++---- src/server/player/context.rs | 481 ++++++++++++++-------------- src/server/player/helper.rs | 393 +++++++++++------------ src/server/protocol/handler.rs | 278 ++++++++-------- src/server/protocol/id.rs | 508 +++++++++++++++--------------- src/server/protocol/mod.rs | 10 +- src/server/protocol/play.rs | 452 +++++++++++++------------- 15 files changed, 1534 insertions(+), 1489 deletions(-) create mode 100644 rustfmt.toml diff --git a/.vscode/settings.json b/.vscode/settings.json index 01fd4e6..7f05113 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,19 @@ { "editor.fontFamily": "Fira Code", "editor.fontLigatures": true, - "editor.tabSize": 4, + + "editor.tabSize": 2, + "editor.insertSpaces": false, + "editor.detectIndentation": false, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true + }, + "rust-analyzer.rustfmt.extraArgs": [ + "--config", + "tab_spaces=2" + ] } \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..8cef326 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +# Пример настроек +tab_spaces = 2 +hard_tabs = true \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e34a01b..8ee9002 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,14 +3,14 @@ use std::{env::args, path::PathBuf, sync::Arc}; use log::{debug, error, info}; use rust_mc_proto::Packet; use server::{ - ServerError, - config::Config, - context::ServerContext, - data::text_component::TextComponent, - event::{Listener, PacketHandler}, - player::context::ClientContext, - protocol::ConnectionState, - start_server, + ServerError, + config::Config, + context::ServerContext, + data::text_component::TextComponent, + event::{Listener, PacketHandler}, + player::context::ClientContext, + protocol::ConnectionState, + start_server, }; pub mod server; @@ -18,13 +18,13 @@ pub mod server; struct ExampleListener; impl Listener for ExampleListener { - fn on_status( - &self, - client: Arc, - response: &mut String, - ) -> Result<(), ServerError> { - *response = format!( - "{{ + fn on_status( + &self, + client: Arc, + response: &mut String, + ) -> Result<(), ServerError> { + *response = format!( + "{{ \"version\": {{ \"name\": \"idk\", \"protocol\": {} @@ -43,124 +43,124 @@ impl Listener for ExampleListener { \"favicon\": \"data:image/png;base64,\", \"enforcesSecureChat\": false }}", - client.handshake().unwrap().protocol_version, - TextComponent::builder() - .text("Hello World! ") - .extra(vec![ - TextComponent::builder() - .text("Protocol: ") - .color("gold") - .extra(vec![ - TextComponent::builder() - .text(&client.handshake().unwrap().protocol_version.to_string()) - .underlined(true) - .build() - ]) - .build(), - TextComponent::builder() - .text("\nServer Addr: ") - .color("green") - .extra(vec![ - TextComponent::builder() - .text(&format!( - "{}:{}", - client.handshake().unwrap().server_address, - client.handshake().unwrap().server_port - )) - .underlined(true) - .build() - ]) - .build() - ]) - .build() - .as_json()? - ); + client.handshake().unwrap().protocol_version, + TextComponent::builder() + .text("Hello World! ") + .extra(vec![ + TextComponent::builder() + .text("Protocol: ") + .color("gold") + .extra(vec![ + TextComponent::builder() + .text(&client.handshake().unwrap().protocol_version.to_string()) + .underlined(true) + .build() + ]) + .build(), + TextComponent::builder() + .text("\nServer Addr: ") + .color("green") + .extra(vec![ + TextComponent::builder() + .text(&format!( + "{}:{}", + client.handshake().unwrap().server_address, + client.handshake().unwrap().server_port + )) + .underlined(true) + .build() + ]) + .build() + ]) + .build() + .as_json()? + ); - Ok(()) - } + Ok(()) + } } struct ExamplePacketHandler; impl PacketHandler for ExamplePacketHandler { - fn on_incoming_packet( - &self, - client: Arc, - packet: &mut Packet, - _: &mut bool, - state: ConnectionState, - ) -> Result<(), ServerError> { - debug!( - "{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", - client.addr.clone(), - packet.id(), - state, - packet.len() - ); + fn on_incoming_packet( + &self, + client: Arc, + packet: &mut Packet, + _: &mut bool, + state: ConnectionState, + ) -> Result<(), ServerError> { + debug!( + "{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", + client.addr.clone(), + packet.id(), + state, + packet.len() + ); - Ok(()) - } + Ok(()) + } - fn on_outcoming_packet( - &self, - client: Arc, - packet: &mut Packet, - _: &mut bool, - state: ConnectionState, - ) -> Result<(), ServerError> { - debug!( - "{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", - client.addr.clone(), - packet.id(), - state, - packet.len() - ); + fn on_outcoming_packet( + &self, + client: Arc, + packet: &mut Packet, + _: &mut bool, + state: ConnectionState, + ) -> Result<(), ServerError> { + debug!( + "{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", + client.addr.clone(), + packet.id(), + state, + packet.len() + ); - Ok(()) - } + Ok(()) + } } fn main() { - // Инициализируем логи - // Чтобы читать debug-логи, юзаем `RUST_LOG=debug cargo run` - colog::init(); + // Инициализируем логи + // Чтобы читать debug-логи, юзаем `RUST_LOG=debug cargo run` + colog::init(); - // Получение аргументов - let exec = args().next().expect("Неизвестная система"); - let args = args().skip(1).collect::>(); + // Получение аргументов + let exec = args().next().expect("Неизвестная система"); + let args = args().skip(1).collect::>(); - if args.len() > 1 { - info!("Использование: {exec} [путь до файла конфигурации]"); - return; - } + if args.len() > 1 { + info!("Использование: {exec} [путь до файла конфигурации]"); + return; + } - // Берем путь из аргумента либо по дефолту берем "./server.toml" - let config_path = PathBuf::from(args.get(0).unwrap_or(&"server.toml".to_string())); + // Берем путь из аргумента либо по дефолту берем "./server.toml" + let config_path = PathBuf::from(args.get(0).unwrap_or(&"server.toml".to_string())); - // Чтение конфига, если ошибка - выводим - let config = match Config::load_from_file(config_path) { - Some(config) => config, - None => { - error!("Ошибка чтения конфигурации"); - return; - } - }; + // Чтение конфига, если ошибка - выводим + let config = match Config::load_from_file(config_path) { + Some(config) => config, + None => { + error!("Ошибка чтения конфигурации"); + return; + } + }; - // Делаем немутабельную потокобезопасную ссылку на конфиг - // Впринципе можно и просто клонировать сам конфиг в каждый сука поток ебать того рот ебать блять - // но мы этого делать не будем чтобы не было мемори лик лишнего - let config = Arc::new(config); + // Делаем немутабельную потокобезопасную ссылку на конфиг + // Впринципе можно и просто клонировать сам конфиг в каждый сука поток ебать того рот ебать блять + // но мы этого делать не будем чтобы не было мемори лик лишнего + let config = Arc::new(config); - // Создаем контекст сервера - // Передается во все подключения - let mut server = ServerContext::new(config); + // Создаем контекст сервера + // Передается во все подключения + let mut server = ServerContext::new(config); - server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера - server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера + server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера - // Бетонируем сервер контекст от изменений - let server = Arc::new(server); + // Бетонируем сервер контекст от изменений + let server = Arc::new(server); - // Запускаем сервер из специально отведенной под это дело функцией - start_server(server); + // Запускаем сервер из специально отведенной под это дело функцией + start_server(server); } diff --git a/src/server/config.rs b/src/server/config.rs index a2f803b..8977e04 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -5,48 +5,48 @@ use serde_default::DefaultFromSerde; #[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] pub struct BindConfig { - #[serde(default = "default_host")] - pub host: String, - #[serde(default = "default_timeout")] - pub timeout: u64, + #[serde(default = "default_host")] + pub host: String, + #[serde(default = "default_timeout")] + pub timeout: u64, } #[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] pub struct ServerConfig { - #[serde(default)] - pub online_mode: bool, - #[serde(default = "default_compression")] - pub compression_threshold: Option, + #[serde(default)] + pub online_mode: bool, + #[serde(default = "default_compression")] + pub compression_threshold: Option, } #[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] pub struct Config { - #[serde(default)] - pub bind: BindConfig, - #[serde(default)] - pub server: ServerConfig, + #[serde(default)] + pub bind: BindConfig, + #[serde(default)] + pub server: ServerConfig, } fn default_host() -> String { - "127.0.0.1:25565".to_string() + "127.0.0.1:25565".to_string() } fn default_timeout() -> u64 { - 5 + 5 } fn default_compression() -> Option { - Some(256) + Some(256) } impl Config { - pub fn load_from_file(path: PathBuf) -> Option { - if !fs::exists(&path).unwrap_or_default() { - let table = Config::default(); - fs::create_dir_all(&path.parent()?).ok()?; - fs::write(&path, toml::to_string_pretty(&table).ok()?).ok()?; - return Some(table); - } - let content = fs::read_to_string(&path).ok()?; - let table = toml::from_str::(&content).ok()?; - Some(table) - } + pub fn load_from_file(path: PathBuf) -> Option { + if !fs::exists(&path).unwrap_or_default() { + let table = Config::default(); + fs::create_dir_all(&path.parent()?).ok()?; + fs::write(&path, toml::to_string_pretty(&table).ok()?).ok()?; + return Some(table); + } + let content = fs::read_to_string(&path).ok()?; + let table = toml::from_str::(&content).ok()?; + Some(table) + } } diff --git a/src/server/context.rs b/src/server/context.rs index f54c795..5d0a706 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -5,87 +5,90 @@ use itertools::Itertools; use uuid::Uuid; use super::{ - config::Config, - event::{Listener, PacketHandler}, - player::context::ClientContext, + config::Config, + event::{Listener, PacketHandler}, + player::context::ClientContext, }; // Контекст сервера // Должен быть обернут в Arc для передачи между потоками pub struct ServerContext { - pub config: Arc, - pub clients: DashMap>, - listeners: Vec>, - handlers: Vec>, + pub config: Arc, + pub clients: DashMap>, + listeners: Vec>, + handlers: Vec>, } impl ServerContext { - pub fn new(config: Arc) -> ServerContext { - ServerContext { - config, - listeners: Vec::new(), - handlers: Vec::new(), - clients: DashMap::new(), - } - } + pub fn new(config: Arc) -> ServerContext { + ServerContext { + config, + listeners: Vec::new(), + handlers: Vec::new(), + clients: DashMap::new(), + } + } - pub fn get_player_by_uuid(self: &Arc, uuid: Uuid) -> Option> { - 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_uuid(self: &Arc, uuid: Uuid) -> Option> { + 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, name: &str) -> Option> { - 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 get_player_by_name(self: &Arc, name: &str) -> Option> { + 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) -> Vec> { - self.clients - .iter() - .filter(|o| o.player_info().is_some()) - .map(|o| o.clone()) - .collect() - } + pub fn players(self: &Arc) -> Vec> { + self + .clients + .iter() + .filter(|o| o.player_info().is_some()) + .map(|o| o.clone()) + .collect() + } - pub fn add_packet_handler(&mut self, handler: Box) { - self.handlers.push(handler); - } + pub fn add_packet_handler(&mut self, handler: Box) { + self.handlers.push(handler); + } - pub fn add_listener(&mut self, listener: Box) { - self.listeners.push(listener); - } + pub fn add_listener(&mut self, listener: Box) { + self.listeners.push(listener); + } - pub fn packet_handlers(self: &Arc, sort_by: F) -> Vec<&Box> - where - K: Ord, - F: FnMut(&&Box) -> K, - { - self.handlers.iter().sorted_by_key(sort_by).collect_vec() - } + pub fn packet_handlers(self: &Arc, sort_by: F) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K, + { + self.handlers.iter().sorted_by_key(sort_by).collect_vec() + } - pub fn listeners(self: &Arc, sort_by: F) -> Vec<&Box> - where - K: Ord, - F: FnMut(&&Box) -> K, - { - self.listeners.iter().sorted_by_key(sort_by).collect_vec() - } + pub fn listeners(self: &Arc, sort_by: F) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K, + { + self.listeners.iter().sorted_by_key(sort_by).collect_vec() + } } diff --git a/src/server/data/mod.rs b/src/server/data/mod.rs index 3fccec6..47ea8f6 100644 --- a/src/server/data/mod.rs +++ b/src/server/data/mod.rs @@ -9,42 +9,43 @@ pub mod text_component; // Трейт для чтения NBT-совместимых приколов pub trait ReadWriteNBT: DataReader + DataWriter { - fn read_nbt(&mut self) -> Result; - fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; } impl ReadWriteNBT for Packet { - fn read_nbt(&mut self) -> Result { - let mut data = Vec::new(); - let pos = self.get_ref().position(); - self.get_mut() - .read_to_end(&mut data) - .map_err(|_| ServerError::DeNbt)?; - let (remaining, value) = - craftflow_nbt::from_slice(&data).map_err(|_| ServerError::DeNbt)?; - self.get_mut() - .set_position(pos + (data.len() - remaining.len()) as u64); - Ok(value) - } + fn read_nbt(&mut self) -> Result { + let mut data = Vec::new(); + let pos = self.get_ref().position(); + self + .get_mut() + .read_to_end(&mut data) + .map_err(|_| ServerError::DeNbt)?; + let (remaining, value) = craftflow_nbt::from_slice(&data).map_err(|_| ServerError::DeNbt)?; + self + .get_mut() + .set_position(pos + (data.len() - remaining.len()) as u64); + Ok(value) + } - fn write_nbt(&mut self, val: &DynNBT) -> Result<(), ServerError> { - craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ServerError::SerNbt)?; - Ok(()) - } + fn write_nbt(&mut self, val: &DynNBT) -> Result<(), ServerError> { + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ServerError::SerNbt)?; + Ok(()) + } } pub trait ReadWritePosition: DataReader + DataWriter { - fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError>; - fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError>; + fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError>; + fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError>; } impl ReadWritePosition for Packet { - fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError> { - let val = self.read_long()?; - Ok((val >> 38, val << 52 >> 52, val << 26 >> 38)) - } + fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError> { + let val = self.read_long()?; + Ok((val >> 38, val << 52 >> 52, val << 26 >> 38)) + } - fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError> { - Ok(self.write_long(((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF))?) - } + fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError> { + Ok(self.write_long(((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF))?) + } } diff --git a/src/server/data/text_component.rs b/src/server/data/text_component.rs index c5095b9..3f63dc2 100644 --- a/src/server/data/text_component.rs +++ b/src/server/data/text_component.rs @@ -12,171 +12,173 @@ use super::ReadWriteNBT; #[derive(Debug, Serialize, Deserialize, Clone)] #[skip_serializing_none] pub struct TextComponent { - pub text: String, - pub color: Option, - pub bold: Option, - pub italic: Option, - pub underlined: Option, - pub strikethrough: Option, - pub obfuscated: Option, - pub extra: Option>, - // TODO: добавить все остальные стандартные поля для текст-компонента типа клик ивентов и сделать отдельный структ для транслейт компонент + pub text: String, + pub color: Option, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, + pub extra: Option>, + // TODO: добавить все остальные стандартные поля для текст-компонента типа клик ивентов и сделать отдельный структ для транслейт компонент } impl TextComponent { - pub fn new(text: String) -> Self { - Self { - text, - color: None, - bold: None, - italic: None, - underlined: None, - strikethrough: None, - obfuscated: None, - extra: None, - } - } + pub fn new(text: String) -> Self { + Self { + text, + color: None, + bold: None, + italic: None, + underlined: None, + strikethrough: None, + obfuscated: None, + extra: None, + } + } - pub fn rainbow(text: String) -> TextComponent { - if text.is_empty() { - return TextComponent::new(text); - } + pub fn rainbow(text: String) -> TextComponent { + if text.is_empty() { + return TextComponent::new(text); + } - let children = text - .char_indices() - .map(|(i, c)| { - let hue = (i as f32) / (text.chars().count() as f32) * 360.0; - let hsl = Hsl::new(hue, 1.0, 0.5); - let rgb: Srgb = hsl.into_color(); - let r = (rgb.red * 255.0).round() as u8; - let g = (rgb.green * 255.0).round() as u8; - let b = (rgb.blue * 255.0).round() as u8; - let mut component = TextComponent::new(c.to_string()); - component.color = Some(format!("#{:02X}{:02X}{:02X}", r, g, b)); - component - }) - .collect::>(); + let children = text + .char_indices() + .map(|(i, c)| { + let hue = (i as f32) / (text.chars().count() as f32) * 360.0; + let hsl = Hsl::new(hue, 1.0, 0.5); + let rgb: Srgb = hsl.into_color(); + let r = (rgb.red * 255.0).round() as u8; + let g = (rgb.green * 255.0).round() as u8; + let b = (rgb.blue * 255.0).round() as u8; + let mut component = TextComponent::new(c.to_string()); + component.color = Some(format!("#{:02X}{:02X}{:02X}", r, g, b)); + component + }) + .collect::>(); - let mut parent = children[0].clone(); - parent.extra = Some(children[1..].to_vec()); - parent - } + let mut parent = children[0].clone(); + parent.extra = Some(children[1..].to_vec()); + parent + } - pub fn builder() -> TextComponentBuilder { - TextComponentBuilder::new() - } + pub fn builder() -> TextComponentBuilder { + TextComponentBuilder::new() + } - pub fn as_json(self) -> Result { - serde_json::to_string(&self).map_err(|_| ServerError::SerTextComponent) - } + pub fn as_json(self) -> Result { + serde_json::to_string(&self).map_err(|_| ServerError::SerTextComponent) + } - pub fn from_json(text: &str) -> Result { - serde_json::from_str(text).map_err(|_| ServerError::DeTextComponent) - } + pub fn from_json(text: &str) -> Result { + serde_json::from_str(text).map_err(|_| ServerError::DeTextComponent) + } } impl Default for TextComponent { - fn default() -> Self { - Self::new(String::new()) - } + fn default() -> Self { + Self::new(String::new()) + } } pub struct TextComponentBuilder { - text: String, - color: Option, - bold: Option, - italic: Option, - underlined: Option, - strikethrough: Option, - obfuscated: Option, - extra: Option>, + text: String, + color: Option, + bold: Option, + italic: Option, + underlined: Option, + strikethrough: Option, + obfuscated: Option, + extra: Option>, } impl TextComponentBuilder { - pub fn new() -> Self { - Self { - text: String::new(), - color: None, - bold: None, - italic: None, - underlined: None, - strikethrough: None, - obfuscated: None, - extra: None, - } - } + pub fn new() -> Self { + Self { + text: String::new(), + color: None, + bold: None, + italic: None, + underlined: None, + strikethrough: None, + obfuscated: None, + extra: None, + } + } - pub fn text(mut self, text: &str) -> Self { - self.text = text.to_string(); - self - } + pub fn text(mut self, text: &str) -> Self { + self.text = text.to_string(); + self + } - pub fn color(mut self, color: &str) -> Self { - self.color = Some(color.to_string()); - self - } + pub fn color(mut self, color: &str) -> Self { + self.color = Some(color.to_string()); + self + } - pub fn bold(mut self, bold: bool) -> Self { - self.bold = Some(bold); - self - } + pub fn bold(mut self, bold: bool) -> Self { + self.bold = Some(bold); + self + } - pub fn italic(mut self, italic: bool) -> Self { - self.italic = Some(italic); - self - } + pub fn italic(mut self, italic: bool) -> Self { + self.italic = Some(italic); + self + } - pub fn underlined(mut self, underlined: bool) -> Self { - self.underlined = Some(underlined); - self - } + pub fn underlined(mut self, underlined: bool) -> Self { + self.underlined = Some(underlined); + self + } - pub fn strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = Some(strikethrough); - self - } + pub fn strikethrough(mut self, strikethrough: bool) -> Self { + self.strikethrough = Some(strikethrough); + self + } - pub fn obfuscated(mut self, obfuscated: bool) -> Self { - self.obfuscated = Some(obfuscated); - self - } + pub fn obfuscated(mut self, obfuscated: bool) -> Self { + self.obfuscated = Some(obfuscated); + self + } - pub fn extra(mut self, extra: Vec) -> Self { - self.extra = Some(extra); - self - } + pub fn extra(mut self, extra: Vec) -> Self { + self.extra = Some(extra); + self + } - pub fn build(self) -> TextComponent { - TextComponent { - text: self.text, - color: self.color, - bold: self.bold, - italic: self.italic, - underlined: self.underlined, - strikethrough: self.strikethrough, - obfuscated: self.obfuscated, - extra: self.extra, - } - } + pub fn build(self) -> TextComponent { + TextComponent { + text: self.text, + color: self.color, + bold: self.bold, + italic: self.italic, + underlined: self.underlined, + strikethrough: self.strikethrough, + obfuscated: self.obfuscated, + extra: self.extra, + } + } } // Реализуем читалку-записывалку текст-компонентов для пакета impl ReadWriteNBT for Packet { - fn read_nbt(&mut self) -> Result { - 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 read_nbt(&mut self) -> Result { + 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(()) - } + fn write_nbt(&mut self, val: &TextComponent) -> Result<(), ServerError> { + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ServerError::SerTextComponent)?; + Ok(()) + } } diff --git a/src/server/event/mod.rs b/src/server/event/mod.rs index e8ff602..d7a8f8d 100644 --- a/src/server/event/mod.rs +++ b/src/server/event/mod.rs @@ -37,12 +37,12 @@ macro_rules! trigger_event { } pub trait Listener: Sync + Send { - generate_handlers!(status, &mut String); - generate_handlers!(plugin_message, &str, &[u8]); + generate_handlers!(status, &mut String); + generate_handlers!(plugin_message, &str, &[u8]); } pub trait PacketHandler: Sync + Send { - generate_handlers!(incoming_packet, &mut Packet, &mut bool, ConnectionState); - generate_handlers!(outcoming_packet, &mut Packet, &mut bool, ConnectionState); - generate_handlers!(state, ConnectionState); + generate_handlers!(incoming_packet, &mut Packet, &mut bool, ConnectionState); + generate_handlers!(outcoming_packet, &mut Packet, &mut bool, ConnectionState); + generate_handlers!(state, ConnectionState); } diff --git a/src/server/mod.rs b/src/server/mod.rs index 58414ec..6166e8d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -17,89 +17,89 @@ pub mod protocol; // Ошибки сервера #[derive(Debug)] pub enum ServerError { - UnexpectedPacket(u8), // Неожиданный пакет - Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto - ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection - SerTextComponent, // Ошибка при сериализации текст-компонента - DeTextComponent, // Ошибка при десериализации текст-компонента - SerNbt, // Ошибка при сериализации nbt - DeNbt, // Ошибка при десериализации nbt - UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) - Other(String), // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки + UnexpectedPacket(u8), // Неожиданный пакет + Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto + ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection + SerTextComponent, // Ошибка при сериализации текст-компонента + DeTextComponent, // Ошибка при десериализации текст-компонента + SerNbt, // Ошибка при сериализации nbt + DeNbt, // Ошибка при десериализации nbt + UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) + Other(String), // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки } impl Display for ServerError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{:?}", self)) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{:?}", self)) + } } impl Error for ServerError {} // Делаем чтобы ProtocolError мог переделываться в наш ServerError impl From for ServerError { - fn from(error: ProtocolError) -> ServerError { - match error { - // Если просто закрыто соединение, переделываем в нашу ошибку этого - ProtocolError::ConnectionClosedError => ServerError::ConnectionClosed, - // Все остальное просто засовываем в обертку - error => ServerError::Protocol(error), - } - } + fn from(error: ProtocolError) -> ServerError { + match error { + // Если просто закрыто соединение, переделываем в нашу ошибку этого + ProtocolError::ConnectionClosedError => ServerError::ConnectionClosed, + // Все остальное просто засовываем в обертку + error => ServerError::Protocol(error), + } + } } pub fn start_server(server: Arc) { - // Биндим сервер где надо - let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { - error!( - "Не удалось забиндить сервер на {}", - &server.config.bind.host - ); - return; - }; + // Биндим сервер где надо + let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { + error!( + "Не удалось забиндить сервер на {}", + &server.config.bind.host + ); + return; + }; - info!("Сервер запущен на {}", &server.config.bind.host); + info!("Сервер запущен на {}", &server.config.bind.host); - while let Ok((stream, addr)) = listener.accept() { - let server = server.clone(); + while let Ok((stream, addr)) = listener.accept() { + let server = server.clone(); - thread::spawn(move || { - info!("Подключение: {}", addr); + 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(); + // Установка таймаутов на чтение и запись + // По умолчанию пусть будет 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 conn = MinecraftConnection::new(stream); - // Создаем контекст клиента - // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент - let client = Arc::new(ClientContext::new(server.clone(), conn)); + // Создаем контекст клиента + // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент + let client = Arc::new(ClientContext::new(server.clone(), conn)); - // Добавляем клиента в список клиентов сервера - // Используем адрес как ключ, врятли ipv4 будет нам врать - server.clients.insert(client.addr, client.clone()); + // Добавляем клиента в список клиентов сервера + // Используем адрес как ключ, врятли ipv4 будет нам врать + server.clients.insert(client.addr, client.clone()); - // Обработка подключения - // Если ошибка -> выводим - match handle_connection(client.clone()) { - Ok(_) => {} - Err(ServerError::ConnectionClosed) => {} - Err(error) => { - error!("Ошибка подключения: {error:?}"); - } - }; + // Обработка подключения + // Если ошибка -> выводим + match handle_connection(client.clone()) { + Ok(_) => {} + Err(ServerError::ConnectionClosed) => {} + Err(error) => { + error!("Ошибка подключения: {error:?}"); + } + }; - // Удаляем клиента из списка клиентов - server.clients.remove(&client.addr); + // Удаляем клиента из списка клиентов + server.clients.remove(&client.addr); - info!("Отключение: {}", addr); - }); - } + info!("Отключение: {}", addr); + }); + } } diff --git a/src/server/player/context.rs b/src/server/player/context.rs index 7e1183d..7b0731a 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -1,7 +1,13 @@ use std::{ - collections::VecDeque, hash::Hash, net::{SocketAddr, TcpStream}, sync::{ - atomic::{AtomicBool, Ordering}, Arc, Mutex, RwLock - }, thread, time::Duration + collections::VecDeque, + hash::Hash, + net::{SocketAddr, TcpStream}, + sync::{ + Arc, Mutex, RwLock, + atomic::{AtomicBool, Ordering}, + }, + thread, + time::Duration, }; use rust_mc_proto::{MinecraftConnection, Packet}; @@ -14,304 +20,287 @@ use super::helper::ProtocolHelper; // Клиент контекст // Должен быть обернут в Arc для передачи между потоками pub struct ClientContext { - pub server: Arc, - pub addr: SocketAddr, - conn: RwLock>, - handshake: RwLock>, - client_info: RwLock>, - player_info: RwLock>, - state: RwLock, - packet_buffer: Mutex>, - read_loop: AtomicBool, - is_alive: AtomicBool, - position: RwLock<(f64, f64, f64)>, - velocity: RwLock<(f64, f64, f64)>, - rotation: RwLock<(f32, f32)>, + pub server: Arc, + pub addr: SocketAddr, + conn: RwLock>, + handshake: RwLock>, + client_info: RwLock>, + player_info: RwLock>, + state: RwLock, + packet_buffer: Mutex>, + read_loop: AtomicBool, + is_alive: AtomicBool, + position: RwLock<(f64, f64, f64)>, + velocity: RwLock<(f64, f64, f64)>, + rotation: RwLock<(f32, f32)>, } // Реализуем сравнение через адрес // IPv4 не должен обманывать, иначе у нас случится коллапс impl PartialEq for ClientContext { - fn eq(&self, other: &Self) -> bool { - self.addr == other.addr - } + fn eq(&self, other: &Self) -> bool { + self.addr == other.addr + } } impl Hash for ClientContext { - fn hash(&self, state: &mut H) { - self.addr.hash(state); - } + fn hash(&self, state: &mut H) { + self.addr.hash(state); + } } impl Eq for ClientContext {} impl ClientContext { - pub fn new(server: Arc, conn: MinecraftConnection) -> ClientContext { - ClientContext { - server, - addr: conn.get_ref().peer_addr().unwrap(), - conn: RwLock::new(conn), - handshake: RwLock::new(None), - client_info: RwLock::new(None), - player_info: RwLock::new(None), - state: RwLock::new(ConnectionState::Handshake), - packet_buffer: Mutex::new(VecDeque::new()), - read_loop: AtomicBool::new(false), - is_alive: AtomicBool::new(true), - position: RwLock::new((0.0, 0.0, 0.0)), - velocity: RwLock::new((0.0, 0.0, 0.0)), - rotation: RwLock::new((0.0, 0.0)) - } - } + pub fn new(server: Arc, conn: MinecraftConnection) -> ClientContext { + ClientContext { + server, + addr: conn.get_ref().peer_addr().unwrap(), + conn: RwLock::new(conn), + handshake: RwLock::new(None), + client_info: RwLock::new(None), + player_info: RwLock::new(None), + state: RwLock::new(ConnectionState::Handshake), + packet_buffer: Mutex::new(VecDeque::new()), + read_loop: AtomicBool::new(false), + is_alive: AtomicBool::new(true), + position: RwLock::new((0.0, 0.0, 0.0)), + velocity: RwLock::new((0.0, 0.0, 0.0)), + rotation: RwLock::new((0.0, 0.0)), + } + } - pub fn set_handshake(self: &Arc, handshake: Handshake) { - *self.handshake.write().unwrap() = Some(handshake); - } + pub fn set_handshake(self: &Arc, handshake: Handshake) { + *self.handshake.write().unwrap() = Some(handshake); + } - pub fn set_client_info(self: &Arc, client_info: ClientInfo) { - *self.client_info.write().unwrap() = Some(client_info); - } + pub fn set_client_info(self: &Arc, client_info: ClientInfo) { + *self.client_info.write().unwrap() = Some(client_info); + } - pub fn set_player_info(self: &Arc, player_info: PlayerInfo) { - *self.player_info.write().unwrap() = Some(player_info); - } + pub fn set_player_info(self: &Arc, player_info: PlayerInfo) { + *self.player_info.write().unwrap() = Some(player_info); + } - pub fn set_state(self: &Arc, state: ConnectionState) -> Result<(), ServerError> { - *self.state.write().unwrap() = state.clone(); + pub fn set_state(self: &Arc, 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())?; - } + for handler in self + .server + .packet_handlers(|o| o.on_state_priority()) + .iter() + { + handler.on_state(self.clone(), state.clone())?; + } - Ok(()) - } + Ok(()) + } - pub fn handshake(self: &Arc) -> Option { - self.handshake.read().unwrap().clone() - } + pub fn handshake(self: &Arc) -> Option { + self.handshake.read().unwrap().clone() + } - pub fn client_info(self: &Arc) -> Option { - self.client_info.read().unwrap().clone() - } + pub fn client_info(self: &Arc) -> Option { + self.client_info.read().unwrap().clone() + } - pub fn player_info(self: &Arc) -> Option { - self.player_info.read().unwrap().clone() - } + pub fn player_info(self: &Arc) -> Option { + self.player_info.read().unwrap().clone() + } - pub fn state(self: &Arc) -> ConnectionState { - self.state.read().unwrap().clone() - } + pub fn state(self: &Arc) -> ConnectionState { + self.state.read().unwrap().clone() + } - pub fn set_position(self: &Arc, position: (f64, f64, f64)) { - *self.position.write().unwrap() = position; - } + pub fn set_position(self: &Arc, position: (f64, f64, f64)) { + *self.position.write().unwrap() = position; + } - pub fn set_velocity(self: &Arc, velocity: (f64, f64, f64)) { - *self.velocity.write().unwrap() = velocity; - } + pub fn set_velocity(self: &Arc, velocity: (f64, f64, f64)) { + *self.velocity.write().unwrap() = velocity; + } - pub fn set_rotation(self: &Arc, rotation: (f32, f32)) { - *self.rotation.write().unwrap() = rotation; - } + pub fn set_rotation(self: &Arc, rotation: (f32, f32)) { + *self.rotation.write().unwrap() = rotation; + } - pub fn position(self: &Arc) -> (f64, f64, f64) { - self.position.read().unwrap().clone() - } + pub fn position(self: &Arc) -> (f64, f64, f64) { + self.position.read().unwrap().clone() + } - pub fn velocity(self: &Arc) -> (f64, f64, f64) { - self.velocity.read().unwrap().clone() - } + pub fn velocity(self: &Arc) -> (f64, f64, f64) { + self.velocity.read().unwrap().clone() + } - pub fn rotation(self: &Arc) -> (f32, f32) { - self.rotation.read().unwrap().clone() - } + pub fn rotation(self: &Arc) -> (f32, f32) { + self.rotation.read().unwrap().clone() + } - pub fn write_packet(self: &Arc, packet: &Packet) -> Result<(), ServerError> { - let state = self.state(); - let mut packet = packet.clone(); - let mut cancelled = false; - for handler in self - .server - .packet_handlers(|o| o.on_outcoming_packet_priority()) - .iter() - { - handler.on_outcoming_packet( - self.clone(), - &mut packet, - &mut cancelled, - state.clone(), - )?; - packet.get_mut().set_position(0); - } - if !cancelled { - match self.conn.write().unwrap().write_packet(&packet) { - Ok(_) => {}, - Err(e) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(e.into()); - } - }; - } - Ok(()) - } + pub fn write_packet(self: &Arc, packet: &Packet) -> Result<(), ServerError> { + let state = self.state(); + let mut packet = packet.clone(); + let mut cancelled = false; + for handler in self + .server + .packet_handlers(|o| o.on_outcoming_packet_priority()) + .iter() + { + handler.on_outcoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + match self.conn.write().unwrap().write_packet(&packet) { + Ok(_) => {} + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e.into()); + } + }; + } + Ok(()) + } - pub fn run_read_loop( - self: &Arc - ) -> Result<(), ServerError> { - self.read_loop.store(true, Ordering::SeqCst); + pub fn run_read_loop(self: &Arc) -> Result<(), ServerError> { + self.read_loop.store(true, Ordering::SeqCst); - let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер + let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер - while self.is_alive() { - let mut packet = match conn.read_packet() { - Ok(v) => v, - Err(e) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(e.into()); - } - }; - let mut cancelled = false; - let state = self.state(); - for handler in self - .server - .packet_handlers(|o| o.on_incoming_packet_priority()) - .iter() - { - handler.on_incoming_packet( - self.clone(), - &mut packet, - &mut cancelled, - state.clone(), - )?; - packet.get_mut().set_position(0); - } - if !cancelled { - self.packet_buffer.lock().unwrap().push_back(packet); - } - } + while self.is_alive() { + let mut packet = match conn.read_packet() { + Ok(v) => v, + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e.into()); + } + }; + let mut cancelled = false; + let state = self.state(); + for handler in self + .server + .packet_handlers(|o| o.on_incoming_packet_priority()) + .iter() + { + handler.on_incoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + self.packet_buffer.lock().unwrap().push_back(packet); + } + } - Ok(()) - } + Ok(()) + } - pub fn read_any_packet(self: &Arc) -> Result { - if self.read_loop.load(Ordering::SeqCst) { - loop { - if let Some(packet) = self.packet_buffer.lock().unwrap().pop_front() { - return Ok(packet); - } - thread::sleep(Duration::from_millis(10)); - } - } else { - let state = self.state(); + pub fn read_any_packet(self: &Arc) -> Result { + if self.read_loop.load(Ordering::SeqCst) { + loop { + if let Some(packet) = self.packet_buffer.lock().unwrap().pop_front() { + return Ok(packet); + } + thread::sleep(Duration::from_millis(10)); + } + } else { + let state = self.state(); - loop { - let mut packet = match self.conn.write().unwrap().read_packet() { - Ok(v) => v, - Err(e) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(e.into()); - } - }; - let mut cancelled = false; - for handler in self - .server - .packet_handlers(|o| o.on_incoming_packet_priority()) - .iter() - { - handler.on_incoming_packet( - self.clone(), - &mut packet, - &mut cancelled, - state.clone(), - )?; - packet.get_mut().set_position(0); - } - if !cancelled { - break Ok(packet); - } - } - } - } + loop { + let mut packet = match self.conn.write().unwrap().read_packet() { + Ok(v) => v, + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e.into()); + } + }; + let mut cancelled = false; + for handler in self + .server + .packet_handlers(|o| o.on_incoming_packet_priority()) + .iter() + { + handler.on_incoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + break Ok(packet); + } + } + } + } - pub fn read_packet(self: &Arc, id: u8) -> Result { - if self.read_loop.load(Ordering::SeqCst) { - loop { - { - let mut locked = self.packet_buffer.lock().unwrap(); - for (i, packet) in locked.clone().iter().enumerate() { - if packet.id() == id { - locked.remove(i); - return Ok(packet.clone()); - } - } - } - thread::sleep(Duration::from_millis(10)); - } - } else { - let packet = match self.read_any_packet() { - Ok(v) => v, - Err(e) => { - self.is_alive.store(false, Ordering::SeqCst); - return Err(e); - } - }; + pub fn read_packet(self: &Arc, id: u8) -> Result { + if self.read_loop.load(Ordering::SeqCst) { + loop { + { + let mut locked = self.packet_buffer.lock().unwrap(); + for (i, packet) in locked.clone().iter().enumerate() { + if packet.id() == id { + locked.remove(i); + return Ok(packet.clone()); + } + } + } + thread::sleep(Duration::from_millis(10)); + } + } else { + let packet = match self.read_any_packet() { + Ok(v) => v, + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e); + } + }; - if packet.id() != id { - Err(ServerError::UnexpectedPacket(packet.id())) - } else { - Ok(packet) - } - } - } + if packet.id() != id { + Err(ServerError::UnexpectedPacket(packet.id())) + } else { + Ok(packet) + } + } + } - pub fn push_back(self: &Arc, packet: Packet){ + pub fn push_back(self: &Arc, packet: Packet) { self.packet_buffer.lock().unwrap().push_back(packet) } - pub fn close(self: &Arc) { - self.conn.write().unwrap().close(); - } + pub fn close(self: &Arc) { + self.conn.write().unwrap().close(); + } - pub fn set_compression(self: &Arc, threshold: Option) { - self.conn.write().unwrap().set_compression(threshold); - } + pub fn set_compression(self: &Arc, threshold: Option) { + self.conn.write().unwrap().set_compression(threshold); + } - pub fn is_alive(self: &Arc) -> bool { - self.is_alive.load(Ordering::SeqCst) - } + pub fn is_alive(self: &Arc) -> bool { + self.is_alive.load(Ordering::SeqCst) + } - pub fn protocol_helper(self: &Arc) -> ProtocolHelper { - ProtocolHelper::new(self.clone()) - } + pub fn protocol_helper(self: &Arc) -> ProtocolHelper { + ProtocolHelper::new(self.clone()) + } } #[derive(Clone)] pub struct Handshake { - pub protocol_version: i32, - pub server_address: String, - pub server_port: u16, + 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, + 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 name: String, + pub uuid: Uuid, } diff --git a/src/server/player/helper.rs b/src/server/player/helper.rs index 540290a..ad7689b 100644 --- a/src/server/player/helper.rs +++ b/src/server/player/helper.rs @@ -1,18 +1,18 @@ use std::{ - io::Read, - sync::Arc, - time::{Duration, SystemTime}, + io::Read, + sync::Arc, + time::{Duration, SystemTime}, }; use rust_mc_proto::{DataReader, DataWriter, Packet}; use crate::server::{ - ServerError, - data::{ReadWriteNBT, text_component::TextComponent}, - protocol::{ - id::{clientbound, serverbound}, - *, - }, + ServerError, + data::{ReadWriteNBT, text_component::TextComponent}, + protocol::{ + id::{clientbound, serverbound}, + *, + }, }; use super::context::ClientContext; @@ -25,207 +25,212 @@ use super::context::ClientContext; // Пусть юзают подключение и отправляют пакеты через него если хотят // Почему бы и нет если да pub struct ProtocolHelper { - client: Arc, - state: ConnectionState, + client: Arc, + state: ConnectionState, } impl ProtocolHelper { - pub fn new(client: Arc) -> Self { - Self { - state: client.state(), - client, - } - } + pub fn new(client: Arc) -> Self { + Self { + state: client.state(), + client, + } + } - pub fn reset_chat(&self) -> Result<(), ServerError> { - match self.state { - ConnectionState::Configuration => { - self.client - .write_packet(&Packet::empty(clientbound::configuration::RESET_CHAT))?; - Ok(()) - } - _ => Err(ServerError::UnexpectedState), - } - } + pub fn reset_chat(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Configuration => { + self + .client + .write_packet(&Packet::empty(clientbound::configuration::RESET_CHAT))?; + Ok(()) + } + _ => Err(ServerError::UnexpectedState), + } + } - pub fn store_cookie(&self, id: &str, data: &[u8]) -> Result<(), ServerError> { - self.client.write_packet(&Packet::build( - match self.state { - ConnectionState::Configuration => clientbound::configuration::STORE_COOKIE, - ConnectionState::Play => clientbound::play::STORE_COOKIE, - _ => return Err(ServerError::UnexpectedState), - }, - |p| { - p.write_string(id)?; - p.write_bytes(data) - }, - )?)?; - Ok(()) - } + pub fn store_cookie(&self, id: &str, data: &[u8]) -> Result<(), ServerError> { + self.client.write_packet(&Packet::build( + match self.state { + ConnectionState::Configuration => clientbound::configuration::STORE_COOKIE, + ConnectionState::Play => clientbound::play::STORE_COOKIE, + _ => return Err(ServerError::UnexpectedState), + }, + |p| { + p.write_string(id)?; + p.write_bytes(data) + }, + )?)?; + Ok(()) + } - /// Leave from Configuration to Play state - pub fn leave_configuration(&self) -> Result<(), ServerError> { - match self.state { - ConnectionState::Configuration => { - self.client - .write_packet(&Packet::empty(clientbound::configuration::FINISH))?; - self.client - .read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; - self.client.set_state(ConnectionState::Play)?; - Ok(()) - } - _ => Err(ServerError::UnexpectedState), - } - } + /// Leave from Configuration to Play state + pub fn leave_configuration(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Configuration => { + self + .client + .write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + self + .client + .read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; + self.client.set_state(ConnectionState::Play)?; + Ok(()) + } + _ => Err(ServerError::UnexpectedState), + } + } - /// Enter to Configuration from Play state - pub fn enter_configuration(&self) -> Result<(), ServerError> { - match self.state { - ConnectionState::Play => { - self.client - .write_packet(&Packet::empty(clientbound::play::START_CONFIGURATION))?; - self.client - .read_packet(serverbound::play::ACKNOWLEDGE_CONFIGURATION)?; - self.client.set_state(ConnectionState::Configuration)?; - Ok(()) - } - _ => Err(ServerError::UnexpectedState), - } - } + /// Enter to Configuration from Play state + pub fn enter_configuration(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Play => { + self + .client + .write_packet(&Packet::empty(clientbound::play::START_CONFIGURATION))?; + self + .client + .read_packet(serverbound::play::ACKNOWLEDGE_CONFIGURATION)?; + self.client.set_state(ConnectionState::Configuration)?; + Ok(()) + } + _ => Err(ServerError::UnexpectedState), + } + } - /// Enter to Configuration from Play state - pub fn ping(&self) -> Result { - match self.state { - ConnectionState::Play => { - let time = SystemTime::now(); - self.client - .write_packet(&Packet::empty(clientbound::play::PING))?; - self.client.read_packet(serverbound::play::PONG)?; - Ok(SystemTime::now().duration_since(time).unwrap()) - } - ConnectionState::Configuration => { - let time = SystemTime::now(); - self.client - .write_packet(&Packet::empty(clientbound::configuration::PING))?; - self.client.read_packet(serverbound::configuration::PONG)?; - Ok(SystemTime::now().duration_since(time).unwrap()) - } - _ => Err(ServerError::UnexpectedState), - } - } + /// Enter to Configuration from Play state + pub fn ping(&self) -> Result { + match self.state { + ConnectionState::Play => { + let time = SystemTime::now(); + self + .client + .write_packet(&Packet::empty(clientbound::play::PING))?; + self.client.read_packet(serverbound::play::PONG)?; + Ok(SystemTime::now().duration_since(time).unwrap()) + } + ConnectionState::Configuration => { + let time = SystemTime::now(); + self + .client + .write_packet(&Packet::empty(clientbound::configuration::PING))?; + self.client.read_packet(serverbound::configuration::PONG)?; + Ok(SystemTime::now().duration_since(time).unwrap()) + } + _ => Err(ServerError::UnexpectedState), + } + } - 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.close(); - return Ok(()); - } - }; - self.client.write_packet(&packet)?; - Ok(()) - } + 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.close(); + return Ok(()); + } + }; + self.client.write_packet(&packet)?; + Ok(()) + } - /// Returns cookie content - pub fn request_cookie(&self, id: &str) -> Result>, ServerError> { - match self.state { - ConnectionState::Configuration => { - let mut packet = Packet::empty(clientbound::configuration::COOKIE_REQUEST); - packet.write_string(id)?; - self.client.write_packet(&packet)?; + /// Returns cookie content + pub fn request_cookie(&self, id: &str) -> Result>, ServerError> { + match self.state { + ConnectionState::Configuration => { + let mut packet = Packet::empty(clientbound::configuration::COOKIE_REQUEST); + packet.write_string(id)?; + self.client.write_packet(&packet)?; - let mut packet = self - .client - .read_packet(serverbound::configuration::COOKIE_RESPONSE)?; - packet.read_string()?; - let data = if packet.read_boolean()? { - let n = packet.read_usize_varint()?; - Some(packet.read_bytes(n)?) - } else { - None - }; + let mut packet = self + .client + .read_packet(serverbound::configuration::COOKIE_RESPONSE)?; + packet.read_string()?; + let data = if packet.read_boolean()? { + let n = packet.read_usize_varint()?; + Some(packet.read_bytes(n)?) + } else { + None + }; - Ok(data) - } - ConnectionState::Play => { - let mut packet = Packet::empty(clientbound::play::COOKIE_REQUEST); - packet.write_string(id)?; - self.client.write_packet(&packet)?; + Ok(data) + } + ConnectionState::Play => { + let mut packet = Packet::empty(clientbound::play::COOKIE_REQUEST); + packet.write_string(id)?; + self.client.write_packet(&packet)?; - let mut packet = self - .client - .read_packet(serverbound::play::COOKIE_RESPONSE)?; - packet.read_string()?; - let data = if packet.read_boolean()? { - let n = packet.read_usize_varint()?; - Some(packet.read_bytes(n)?) - } else { - None - }; + let mut packet = self + .client + .read_packet(serverbound::play::COOKIE_RESPONSE)?; + 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), - } - } + 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>), ServerError> { - match self.state { - ConnectionState::Login => { - let mut packet = Packet::empty(clientbound::login::PLUGIN_REQUEST); - packet.write_varint(id)?; - packet.write_string(channel)?; - packet.write_bytes(data)?; - self.client.write_packet(&packet)?; + /// Returns login plugin response - (message_id, payload) + pub fn send_login_plugin_request( + &self, + id: i32, + channel: &str, + data: &[u8], + ) -> Result<(i32, Option>), ServerError> { + match self.state { + ConnectionState::Login => { + let mut packet = Packet::empty(clientbound::login::PLUGIN_REQUEST); + packet.write_varint(id)?; + packet.write_string(channel)?; + packet.write_bytes(data)?; + self.client.write_packet(&packet)?; - let mut packet = self - .client - .read_packet(serverbound::login::PLUGIN_RESPONSE)?; - 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 - }; + let mut packet = self + .client + .read_packet(serverbound::login::PLUGIN_RESPONSE)?; + 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), - } - } + 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(clientbound::configuration::PLUGIN_MESSAGE) - } - ConnectionState::Play => Packet::empty(clientbound::play::PLUGIN_MESSAGE), - _ => return Err(ServerError::UnexpectedState), - }; - packet.write_string(channel)?; - packet.write_bytes(data)?; - self.client.write_packet(&packet)?; - Ok(()) - } + pub fn send_plugin_message(&self, channel: &str, data: &[u8]) -> Result<(), ServerError> { + let mut packet = match self.state { + ConnectionState::Configuration => Packet::empty(clientbound::configuration::PLUGIN_MESSAGE), + ConnectionState::Play => Packet::empty(clientbound::play::PLUGIN_MESSAGE), + _ => return Err(ServerError::UnexpectedState), + }; + packet.write_string(channel)?; + packet.write_bytes(data)?; + self.client.write_packet(&packet)?; + Ok(()) + } } diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index d532e33..5f51f07 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -1,189 +1,189 @@ use std::{io::Read, sync::Arc}; use crate::server::{ - ServerError, - player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, + ServerError, + player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, }; use rust_mc_proto::{DataReader, DataWriter, Packet}; use crate::trigger_event; use super::{ - ConnectionState, - id::*, - play::{handle_configuration_state, handle_play_state}, + ConnectionState, + id::*, + play::{handle_configuration_state, handle_play_state}, }; pub fn handle_connection( - client: Arc, // Контекст клиента + client: Arc, // Контекст клиента ) -> Result<(), ServerError> { - // Чтение рукопожатия - // Получение пакетов производится через client.conn(), - // ВАЖНО: не помещать сам client.conn() в переменные, - // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = client.read_packet(serverbound::handshake::HANDSHAKE)?; + // Чтение рукопожатия + // Получение пакетов производится через client.conn(), + // ВАЖНО: не помещать сам client.conn() в переменные, + // он должен сразу убиваться иначе соединение гдето задедлочится + let mut packet = client.read_packet(serverbound::handshake::HANDSHAKE)?; - 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 для обычного подключения + 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 для обычного подключения - client.set_handshake(Handshake { - protocol_version, - server_address, - server_port, - }); + client.set_handshake(Handshake { + protocol_version, + server_address, + server_port, + }); - match next_state { - 1 => { - // Тип подключения - статус - client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status + match next_state { + 1 => { + // Тип подключения - статус + client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status - loop { - // Чтение запроса - let mut packet = client.read_any_packet()?; + loop { + // Чтение запроса + let mut packet = client.read_any_packet()?; - match packet.id() { - serverbound::status::REQUEST => { - // Запрос статуса - let mut packet = Packet::empty(clientbound::status::RESPONSE); + match packet.id() { + serverbound::status::REQUEST => { + // Запрос статуса + let mut packet = Packet::empty(clientbound::status::RESPONSE); - // Дефолтный статус - let mut status = "{ + // Дефолтный статус + let mut status = "{ \"version\": { \"name\": \"Error\", \"protocol\": 0 }, \"description\": {\"text\": \"Internal server error\"} }" - .to_string(); + .to_string(); - // Опрос всех листенеров - trigger_event!(client, status, &mut status); + // Опрос всех листенеров + trigger_event!(client, status, &mut status); - // Отправка статуса - packet.write_string(&status)?; + // Отправка статуса + packet.write_string(&status)?; - client.write_packet(&packet)?; - } - serverbound::status::PING_REQUEST => { - // Пинг - // Раньше мы просто отправляли ему его-же пакет, но сейчас, - // С приходом к власти констант айди-пакетов, нам приходится делать такое непотребство - let timestamp = packet.read_long()?; - let mut packet = Packet::empty(clientbound::status::PONG_RESPONSE); - packet.write_long(timestamp)?; - client.write_packet(&packet)?; - } - id => { - return Err(ServerError::UnexpectedPacket(id)); - } - } - } - } - 2 => { - // Тип подключения - игра - client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login + client.write_packet(&packet)?; + } + serverbound::status::PING_REQUEST => { + // Пинг + // Раньше мы просто отправляли ему его-же пакет, но сейчас, + // С приходом к власти констант айди-пакетов, нам приходится делать такое непотребство + let timestamp = packet.read_long()?; + let mut packet = Packet::empty(clientbound::status::PONG_RESPONSE); + packet.write_long(timestamp)?; + client.write_packet(&packet)?; + } + id => { + return Err(ServerError::UnexpectedPacket(id)); + } + } + } + } + 2 => { + // Тип подключения - игра + client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login - // Читаем пакет Login Start - let mut packet = client.read_packet(serverbound::login::START)?; + // Читаем пакет Login Start + let mut packet = client.read_packet(serverbound::login::START)?; - let name = packet.read_string()?; - let uuid = packet.read_uuid()?; + let name = packet.read_string()?; + let uuid = packet.read_uuid()?; - client.set_player_info(PlayerInfo { - name: name.clone(), - uuid: uuid.clone(), - }); + client.set_player_info(PlayerInfo { + name: name.clone(), + uuid: uuid.clone(), + }); - if client.server.config.server.online_mode { - // TODO: encryption packets - } + if client.server.config.server.online_mode { + // TODO: encryption packets + } - // Отправляем пакет Set Compression если сжатие указано - if let Some(threshold) = client.server.config.server.compression_threshold { - client.write_packet(&Packet::build(clientbound::login::SET_COMPRESSION, |p| { - p.write_usize_varint(threshold) - })?)?; - client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении - } + // Отправляем пакет Set Compression если сжатие указано + if let Some(threshold) = client.server.config.server.compression_threshold { + client.write_packet(&Packet::build(clientbound::login::SET_COMPRESSION, |p| { + p.write_usize_varint(threshold) + })?)?; + client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении + } - // Отправка пакета Login Success - client.write_packet(&Packet::build(clientbound::login::SUCCESS, |p| { - p.write_uuid(&uuid)?; - p.write_string(&name)?; - p.write_varint(0) - })?)?; + // Отправка пакета Login Success + client.write_packet(&Packet::build(clientbound::login::SUCCESS, |p| { + p.write_uuid(&uuid)?; + p.write_string(&name)?; + p.write_varint(0) + })?)?; - client.read_packet(serverbound::login::ACKNOWLEDGED)?; // Пакет Login Acknowledged + client.read_packet(serverbound::login::ACKNOWLEDGED)?; // Пакет Login Acknowledged - client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration + client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration - // Получение бренда клиента из Serverbound Plugin Message - // Identifier канала откуда берется бренд: minecraft:brand - let brand = loop { - let mut packet = client.read_packet(serverbound::configuration::PLUGIN_MESSAGE)?; // Пакет Serverbound Plugin Message + // Получение бренда клиента из Serverbound Plugin Message + // Identifier канала откуда берется бренд: minecraft:brand + let brand = loop { + let mut packet = client.read_packet(serverbound::configuration::PLUGIN_MESSAGE)?; // Пакет Serverbound Plugin Message - let identifier = packet.read_string()?; + let identifier = packet.read_string()?; - let mut data = Vec::new(); - packet.get_mut().read_to_end(&mut data).unwrap(); + 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 { - trigger_event!(client, plugin_message, &identifier, &data); - } - }; + if identifier == "minecraft:brand" { + break String::from_utf8_lossy(&data).to_string(); + } else { + trigger_event!(client, plugin_message, &identifier, &data); + } + }; - let mut packet = client.read_packet(serverbound::configuration::CLIENT_INFORMATION)?; // Пакет Client Information + let mut packet = client.read_packet(serverbound::configuration::CLIENT_INFORMATION)?; // Пакет Client Information - let locale = packet.read_string()?; // for example: en_us - 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 + let locale = packet.read_string()?; // for example: en_us + 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 - 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, - }); + 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, + }); - client.write_packet(&Packet::build( - clientbound::configuration::PLUGIN_MESSAGE, - |p| { - p.write_string("minecraft:brand")?; - p.write_string("rust_minecraft_server") - }, - )?)?; + client.write_packet(&Packet::build( + clientbound::configuration::PLUGIN_MESSAGE, + |p| { + p.write_string("minecraft:brand")?; + p.write_string("rust_minecraft_server") + }, + )?)?; - handle_configuration_state(client.clone())?; + handle_configuration_state(client.clone())?; - client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; - client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; + client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; - client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play + client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play - // Дальше работаем с режимом игры - handle_play_state(client)?; - } - _ => { - // Тип подключения не рукопожатный - return Err(ServerError::UnexpectedState); - } - } + // Дальше работаем с режимом игры + handle_play_state(client)?; + } + _ => { + // Тип подключения не рукопожатный + return Err(ServerError::UnexpectedState); + } + } - Ok(()) + Ok(()) } diff --git a/src/server/protocol/id.rs b/src/server/protocol/id.rs index 5c26854..58f14d1 100644 --- a/src/server/protocol/id.rs +++ b/src/server/protocol/id.rs @@ -5,268 +5,268 @@ Generated with parse_ids.py */ pub mod clientbound { - pub mod status { - pub const RESPONSE: u8 = 0x00; - pub const PONG_RESPONSE: u8 = 0x01; - } + pub mod status { + pub const RESPONSE: u8 = 0x00; + pub const PONG_RESPONSE: u8 = 0x01; + } - pub mod login { - pub const DISCONNECT: u8 = 0x00; - pub const ENCRYPTION_REQUEST: u8 = 0x01; - pub const SUCCESS: u8 = 0x02; - pub const SET_COMPRESSION: u8 = 0x03; - pub const PLUGIN_REQUEST: u8 = 0x04; - pub const COOKIE_REQUEST: u8 = 0x05; - } + pub mod login { + pub const DISCONNECT: u8 = 0x00; + pub const ENCRYPTION_REQUEST: u8 = 0x01; + pub const SUCCESS: u8 = 0x02; + pub const SET_COMPRESSION: u8 = 0x03; + pub const PLUGIN_REQUEST: u8 = 0x04; + pub const COOKIE_REQUEST: u8 = 0x05; + } - pub mod configuration { - pub const COOKIE_REQUEST: u8 = 0x00; - pub const PLUGIN_MESSAGE: u8 = 0x01; - pub const DISCONNECT: u8 = 0x02; - pub const FINISH: u8 = 0x03; - pub const KEEP_ALIVE: u8 = 0x04; - pub const PING: u8 = 0x05; - pub const RESET_CHAT: u8 = 0x06; - pub const REGISTRY_DATA: u8 = 0x07; - pub const REMOVE_RESOURCE_PACK: u8 = 0x08; - pub const ADD_RESOURCE_PACK: u8 = 0x09; - pub const STORE_COOKIE: u8 = 0x0A; - pub const TRANSFER: u8 = 0x0B; - pub const FEATURE_FLAGS: u8 = 0x0C; - pub const UPDATE_TAGS: u8 = 0x0D; - pub const KNOWN_PACKS: u8 = 0x0E; - pub const CUSTOM_REPORT_DETAILS: u8 = 0x0F; - pub const SERVER_LINKS: u8 = 0x10; - } + pub mod configuration { + pub const COOKIE_REQUEST: u8 = 0x00; + pub const PLUGIN_MESSAGE: u8 = 0x01; + pub const DISCONNECT: u8 = 0x02; + pub const FINISH: u8 = 0x03; + pub const KEEP_ALIVE: u8 = 0x04; + pub const PING: u8 = 0x05; + pub const RESET_CHAT: u8 = 0x06; + pub const REGISTRY_DATA: u8 = 0x07; + pub const REMOVE_RESOURCE_PACK: u8 = 0x08; + pub const ADD_RESOURCE_PACK: u8 = 0x09; + pub const STORE_COOKIE: u8 = 0x0A; + pub const TRANSFER: u8 = 0x0B; + pub const FEATURE_FLAGS: u8 = 0x0C; + pub const UPDATE_TAGS: u8 = 0x0D; + pub const KNOWN_PACKS: u8 = 0x0E; + pub const CUSTOM_REPORT_DETAILS: u8 = 0x0F; + pub const SERVER_LINKS: u8 = 0x10; + } - pub mod play { - pub const BUNDLE_DELIMITER: u8 = 0x00; - pub const SPAWN_ENTITY: u8 = 0x01; - pub const ENTITY_ANIMATION: u8 = 0x02; - pub const AWARD_STATISTICS: u8 = 0x03; - pub const ACKNOWLEDGE_BLOCK_CHANGE: u8 = 0x04; - pub const SET_BLOCK_DESTROY_STAGE: u8 = 0x05; - pub const BLOCK_ENTITY_DATA: u8 = 0x06; - pub const BLOCK_ACTION: u8 = 0x07; - pub const BLOCK_UPDATE: u8 = 0x08; - pub const BOSS_BAR: u8 = 0x09; - pub const CHANGE_DIFFICULTY: u8 = 0x0A; - pub const CHUNK_BATCH_FINISHED: u8 = 0x0B; - pub const CHUNK_BATCH_START: u8 = 0x0C; - pub const CHUNK_BIOMES: u8 = 0x0D; - pub const CLEAR_TITLES: u8 = 0x0E; - pub const COMMAND_SUGGESTIONS_RESPONSE: u8 = 0x0F; - pub const COMMANDS: u8 = 0x10; - pub const CLOSE_CONTAINER: u8 = 0x11; - pub const SET_CONTAINER_CONTENT: u8 = 0x12; - pub const SET_CONTAINER_PROPERTY: u8 = 0x13; - pub const SET_CONTAINER_SLOT: u8 = 0x14; - pub const COOKIE_REQUEST: u8 = 0x15; - pub const SET_COOLDOWN: u8 = 0x16; - pub const CHAT_SUGGESTIONS: u8 = 0x17; - pub const PLUGIN_MESSAGE: u8 = 0x18; - pub const DAMAGE_EVENT: u8 = 0x19; - pub const DEBUG_SAMPLE: u8 = 0x1A; - pub const DELETE_MESSAGE: u8 = 0x1B; - pub const DISCONNECT: u8 = 0x1C; - pub const DISGUISED_CHAT_MESSAGE: u8 = 0x1D; - pub const ENTITY_EVENT: u8 = 0x1E; - pub const TELEPORT_ENTITY: u8 = 0x1F; - pub const EXPLOSION: u8 = 0x20; - pub const UNLOAD_CHUNK: u8 = 0x21; - pub const GAME_EVENT: u8 = 0x22; - pub const OPEN_HORSE_SCREEN: u8 = 0x23; - pub const HURT_ANIMATION: u8 = 0x24; - pub const INITIALIZE_WORLD_BORDER: u8 = 0x25; - pub const KEEP_ALIVE: u8 = 0x26; - pub const CHUNK_DATA_AND_UPDATE_LIGHT: u8 = 0x27; - pub const WORLD_EVENT: u8 = 0x28; - pub const PARTICLE: u8 = 0x29; - pub const UPDATE_LIGHT: u8 = 0x2A; - pub const LOGIN: u8 = 0x2B; - pub const MAP_DATA: u8 = 0x2C; - pub const MERCHANT_OFFERS: u8 = 0x2D; - pub const UPDATE_ENTITY_POSITION: u8 = 0x2E; - pub const UPDATE_ENTITY_POSITION_AND_ROTATION: u8 = 0x2F; - pub const MOVE_MINECART_ALONG_TRACK: u8 = 0x30; - pub const UPDATE_ENTITY_ROTATION: u8 = 0x31; - pub const MOVE_VEHICLE: u8 = 0x32; - pub const OPEN_BOOK: u8 = 0x33; - pub const OPEN_SCREEN: u8 = 0x34; - pub const OPEN_SIGN_EDITOR: u8 = 0x35; - pub const PING: u8 = 0x36; - pub const PING_RESPONSE: u8 = 0x37; - pub const PLACE_GHOST_RECIPE: u8 = 0x38; - pub const PLAYER_ABILITIES: u8 = 0x39; - pub const PLAYER_CHAT_MESSAGE: u8 = 0x3A; - pub const END_COMBAT: u8 = 0x3B; - pub const ENTER_COMBAT: u8 = 0x3C; - pub const COMBAT_DEATH: u8 = 0x3D; - pub const PLAYER_INFO_REMOVE: u8 = 0x3E; - pub const PLAYER_INFO_UPDATE: u8 = 0x3F; - pub const LOOK_AT: u8 = 0x40; - pub const SYNCHRONIZE_PLAYER_POSITION: u8 = 0x41; - pub const PLAYER_ROTATION: u8 = 0x42; - pub const RECIPE_BOOK_ADD: u8 = 0x43; - pub const RECIPE_BOOK_REMOVE: u8 = 0x44; - pub const RECIPE_BOOK_SETTINGS: u8 = 0x45; - pub const REMOVE_ENTITIES: u8 = 0x46; - pub const REMOVE_ENTITY_EFFECT: u8 = 0x47; - pub const RESET_SCORE: u8 = 0x48; - pub const REMOVE_RESOURCE_PACK: u8 = 0x49; - pub const ADD_RESOURCE_PACK: u8 = 0x4A; - pub const RESPAWN: u8 = 0x4B; - pub const SET_HEAD_ROTATION: u8 = 0x4C; - pub const UPDATE_SECTION_BLOCKS: u8 = 0x4D; - pub const SELECT_ADVANCEMENTS_TAB: u8 = 0x4E; - pub const SERVER_DATA: u8 = 0x4F; - pub const SET_ACTION_BAR_TEXT: u8 = 0x50; - pub const SET_BORDER_CENTER: u8 = 0x51; - pub const SET_BORDER_LERP_SIZE: u8 = 0x52; - pub const SET_BORDER_SIZE: u8 = 0x53; - pub const SET_BORDER_WARNING_DELAY: u8 = 0x54; - pub const SET_BORDER_WARNING_DISTANCE: u8 = 0x55; - pub const SET_CAMERA: u8 = 0x56; - pub const SET_CENTER_CHUNK: u8 = 0x57; - pub const SET_RENDER_DISTANCE: u8 = 0x58; - pub const SET_CURSOR_ITEM: u8 = 0x59; - pub const SET_DEFAULT_SPAWN_POSITION: u8 = 0x5A; - pub const DISPLAY_OBJECTIVE: u8 = 0x5B; - pub const SET_ENTITY_METADATA: u8 = 0x5C; - pub const LINK_ENTITIES: u8 = 0x5D; - pub const SET_ENTITY_VELOCITY: u8 = 0x5E; - pub const SET_EQUIPMENT: u8 = 0x5F; - pub const SET_EXPERIENCE: u8 = 0x60; - pub const SET_HEALTH: u8 = 0x61; - pub const SET_HELD_ITEM: u8 = 0x62; - pub const UPDATE_OBJECTIVES: u8 = 0x63; - pub const SET_PASSENGERS: u8 = 0x64; - pub const SET_PLAYER_INVENTORY_SLOT: u8 = 0x65; - pub const UPDATE_TEAMS: u8 = 0x66; - pub const UPDATE_SCORE: u8 = 0x67; - pub const SET_SIMULATION_DISTANCE: u8 = 0x68; - pub const SET_SUBTITLE_TEXT: u8 = 0x69; - pub const UPDATE_TIME: u8 = 0x6A; - pub const SET_TITLE_TEXT: u8 = 0x6B; - pub const SET_TITLE_ANIMATION_TIMES: u8 = 0x6C; - pub const ENTITY_SOUND_EFFECT: u8 = 0x6D; - pub const SOUND_EFFECT: u8 = 0x6E; - pub const START_CONFIGURATION: u8 = 0x6F; - pub const STOP_SOUND: u8 = 0x70; - pub const STORE_COOKIE: u8 = 0x71; - pub const SYSTEM_CHAT_MESSAGE: u8 = 0x72; - pub const SET_TAB_LIST_HEADER_AND_FOOTER: u8 = 0x73; - pub const TAG_QUERY_RESPONSE: u8 = 0x74; - pub const PICKUP_ITEM: u8 = 0x75; - pub const SYNCHRONIZE_VEHICLE_POSITION: u8 = 0x76; - pub const TEST_INSTANCE_BLOCK_STATUS: u8 = 0x77; - pub const SET_TICKING_STATE: u8 = 0x78; - pub const STEP_TICK: u8 = 0x79; - pub const TRANSFER: u8 = 0x7A; - pub const UPDATE_ADVANCEMENTS: u8 = 0x7B; - pub const UPDATE_ATTRIBUTES: u8 = 0x7C; - pub const ENTITY_EFFECT: u8 = 0x7D; - pub const UPDATE_RECIPES: u8 = 0x7E; - pub const UPDATE_TAGS: u8 = 0x7F; - pub const PROJECTILE_POWER: u8 = 0x80; - pub const CUSTOM_REPORT_DETAILS: u8 = 0x81; - pub const SERVER_LINKS: u8 = 0x82; - } + pub mod play { + pub const BUNDLE_DELIMITER: u8 = 0x00; + pub const SPAWN_ENTITY: u8 = 0x01; + pub const ENTITY_ANIMATION: u8 = 0x02; + pub const AWARD_STATISTICS: u8 = 0x03; + pub const ACKNOWLEDGE_BLOCK_CHANGE: u8 = 0x04; + pub const SET_BLOCK_DESTROY_STAGE: u8 = 0x05; + pub const BLOCK_ENTITY_DATA: u8 = 0x06; + pub const BLOCK_ACTION: u8 = 0x07; + pub const BLOCK_UPDATE: u8 = 0x08; + pub const BOSS_BAR: u8 = 0x09; + pub const CHANGE_DIFFICULTY: u8 = 0x0A; + pub const CHUNK_BATCH_FINISHED: u8 = 0x0B; + pub const CHUNK_BATCH_START: u8 = 0x0C; + pub const CHUNK_BIOMES: u8 = 0x0D; + pub const CLEAR_TITLES: u8 = 0x0E; + pub const COMMAND_SUGGESTIONS_RESPONSE: u8 = 0x0F; + pub const COMMANDS: u8 = 0x10; + pub const CLOSE_CONTAINER: u8 = 0x11; + pub const SET_CONTAINER_CONTENT: u8 = 0x12; + pub const SET_CONTAINER_PROPERTY: u8 = 0x13; + pub const SET_CONTAINER_SLOT: u8 = 0x14; + pub const COOKIE_REQUEST: u8 = 0x15; + pub const SET_COOLDOWN: u8 = 0x16; + pub const CHAT_SUGGESTIONS: u8 = 0x17; + pub const PLUGIN_MESSAGE: u8 = 0x18; + pub const DAMAGE_EVENT: u8 = 0x19; + pub const DEBUG_SAMPLE: u8 = 0x1A; + pub const DELETE_MESSAGE: u8 = 0x1B; + pub const DISCONNECT: u8 = 0x1C; + pub const DISGUISED_CHAT_MESSAGE: u8 = 0x1D; + pub const ENTITY_EVENT: u8 = 0x1E; + pub const TELEPORT_ENTITY: u8 = 0x1F; + pub const EXPLOSION: u8 = 0x20; + pub const UNLOAD_CHUNK: u8 = 0x21; + pub const GAME_EVENT: u8 = 0x22; + pub const OPEN_HORSE_SCREEN: u8 = 0x23; + pub const HURT_ANIMATION: u8 = 0x24; + pub const INITIALIZE_WORLD_BORDER: u8 = 0x25; + pub const KEEP_ALIVE: u8 = 0x26; + pub const CHUNK_DATA_AND_UPDATE_LIGHT: u8 = 0x27; + pub const WORLD_EVENT: u8 = 0x28; + pub const PARTICLE: u8 = 0x29; + pub const UPDATE_LIGHT: u8 = 0x2A; + pub const LOGIN: u8 = 0x2B; + pub const MAP_DATA: u8 = 0x2C; + pub const MERCHANT_OFFERS: u8 = 0x2D; + pub const UPDATE_ENTITY_POSITION: u8 = 0x2E; + pub const UPDATE_ENTITY_POSITION_AND_ROTATION: u8 = 0x2F; + pub const MOVE_MINECART_ALONG_TRACK: u8 = 0x30; + pub const UPDATE_ENTITY_ROTATION: u8 = 0x31; + pub const MOVE_VEHICLE: u8 = 0x32; + pub const OPEN_BOOK: u8 = 0x33; + pub const OPEN_SCREEN: u8 = 0x34; + pub const OPEN_SIGN_EDITOR: u8 = 0x35; + pub const PING: u8 = 0x36; + pub const PING_RESPONSE: u8 = 0x37; + pub const PLACE_GHOST_RECIPE: u8 = 0x38; + pub const PLAYER_ABILITIES: u8 = 0x39; + pub const PLAYER_CHAT_MESSAGE: u8 = 0x3A; + pub const END_COMBAT: u8 = 0x3B; + pub const ENTER_COMBAT: u8 = 0x3C; + pub const COMBAT_DEATH: u8 = 0x3D; + pub const PLAYER_INFO_REMOVE: u8 = 0x3E; + pub const PLAYER_INFO_UPDATE: u8 = 0x3F; + pub const LOOK_AT: u8 = 0x40; + pub const SYNCHRONIZE_PLAYER_POSITION: u8 = 0x41; + pub const PLAYER_ROTATION: u8 = 0x42; + pub const RECIPE_BOOK_ADD: u8 = 0x43; + pub const RECIPE_BOOK_REMOVE: u8 = 0x44; + pub const RECIPE_BOOK_SETTINGS: u8 = 0x45; + pub const REMOVE_ENTITIES: u8 = 0x46; + pub const REMOVE_ENTITY_EFFECT: u8 = 0x47; + pub const RESET_SCORE: u8 = 0x48; + pub const REMOVE_RESOURCE_PACK: u8 = 0x49; + pub const ADD_RESOURCE_PACK: u8 = 0x4A; + pub const RESPAWN: u8 = 0x4B; + pub const SET_HEAD_ROTATION: u8 = 0x4C; + pub const UPDATE_SECTION_BLOCKS: u8 = 0x4D; + pub const SELECT_ADVANCEMENTS_TAB: u8 = 0x4E; + pub const SERVER_DATA: u8 = 0x4F; + pub const SET_ACTION_BAR_TEXT: u8 = 0x50; + pub const SET_BORDER_CENTER: u8 = 0x51; + pub const SET_BORDER_LERP_SIZE: u8 = 0x52; + pub const SET_BORDER_SIZE: u8 = 0x53; + pub const SET_BORDER_WARNING_DELAY: u8 = 0x54; + pub const SET_BORDER_WARNING_DISTANCE: u8 = 0x55; + pub const SET_CAMERA: u8 = 0x56; + pub const SET_CENTER_CHUNK: u8 = 0x57; + pub const SET_RENDER_DISTANCE: u8 = 0x58; + pub const SET_CURSOR_ITEM: u8 = 0x59; + pub const SET_DEFAULT_SPAWN_POSITION: u8 = 0x5A; + pub const DISPLAY_OBJECTIVE: u8 = 0x5B; + pub const SET_ENTITY_METADATA: u8 = 0x5C; + pub const LINK_ENTITIES: u8 = 0x5D; + pub const SET_ENTITY_VELOCITY: u8 = 0x5E; + pub const SET_EQUIPMENT: u8 = 0x5F; + pub const SET_EXPERIENCE: u8 = 0x60; + pub const SET_HEALTH: u8 = 0x61; + pub const SET_HELD_ITEM: u8 = 0x62; + pub const UPDATE_OBJECTIVES: u8 = 0x63; + pub const SET_PASSENGERS: u8 = 0x64; + pub const SET_PLAYER_INVENTORY_SLOT: u8 = 0x65; + pub const UPDATE_TEAMS: u8 = 0x66; + pub const UPDATE_SCORE: u8 = 0x67; + pub const SET_SIMULATION_DISTANCE: u8 = 0x68; + pub const SET_SUBTITLE_TEXT: u8 = 0x69; + pub const UPDATE_TIME: u8 = 0x6A; + pub const SET_TITLE_TEXT: u8 = 0x6B; + pub const SET_TITLE_ANIMATION_TIMES: u8 = 0x6C; + pub const ENTITY_SOUND_EFFECT: u8 = 0x6D; + pub const SOUND_EFFECT: u8 = 0x6E; + pub const START_CONFIGURATION: u8 = 0x6F; + pub const STOP_SOUND: u8 = 0x70; + pub const STORE_COOKIE: u8 = 0x71; + pub const SYSTEM_CHAT_MESSAGE: u8 = 0x72; + pub const SET_TAB_LIST_HEADER_AND_FOOTER: u8 = 0x73; + pub const TAG_QUERY_RESPONSE: u8 = 0x74; + pub const PICKUP_ITEM: u8 = 0x75; + pub const SYNCHRONIZE_VEHICLE_POSITION: u8 = 0x76; + pub const TEST_INSTANCE_BLOCK_STATUS: u8 = 0x77; + pub const SET_TICKING_STATE: u8 = 0x78; + pub const STEP_TICK: u8 = 0x79; + pub const TRANSFER: u8 = 0x7A; + pub const UPDATE_ADVANCEMENTS: u8 = 0x7B; + pub const UPDATE_ATTRIBUTES: u8 = 0x7C; + pub const ENTITY_EFFECT: u8 = 0x7D; + pub const UPDATE_RECIPES: u8 = 0x7E; + pub const UPDATE_TAGS: u8 = 0x7F; + pub const PROJECTILE_POWER: u8 = 0x80; + pub const CUSTOM_REPORT_DETAILS: u8 = 0x81; + pub const SERVER_LINKS: u8 = 0x82; + } } pub mod serverbound { - pub mod handshake { - pub const HANDSHAKE: u8 = 0x00; - } + pub mod handshake { + pub const HANDSHAKE: u8 = 0x00; + } - pub mod status { - pub const REQUEST: u8 = 0x00; - pub const PING_REQUEST: u8 = 0x01; - } + pub mod status { + pub const REQUEST: u8 = 0x00; + pub const PING_REQUEST: u8 = 0x01; + } - pub mod login { - pub const START: u8 = 0x00; - pub const ENCRYPTION_RESPONSE: u8 = 0x01; - pub const PLUGIN_RESPONSE: u8 = 0x02; - pub const ACKNOWLEDGED: u8 = 0x03; - pub const COOKIE_RESPONSE: u8 = 0x04; - } + pub mod login { + pub const START: u8 = 0x00; + pub const ENCRYPTION_RESPONSE: u8 = 0x01; + pub const PLUGIN_RESPONSE: u8 = 0x02; + pub const ACKNOWLEDGED: u8 = 0x03; + pub const COOKIE_RESPONSE: u8 = 0x04; + } - pub mod configuration { - pub const CLIENT_INFORMATION: u8 = 0x00; - pub const COOKIE_RESPONSE: u8 = 0x01; - pub const PLUGIN_MESSAGE: u8 = 0x02; - pub const ACKNOWLEDGE_FINISH: u8 = 0x03; - pub const KEEP_ALIVE: u8 = 0x04; - pub const PONG: u8 = 0x05; - pub const RESOURCE_PACK_RESPONSE: u8 = 0x06; - pub const KNOWN_PACKS: u8 = 0x07; - } + pub mod configuration { + pub const CLIENT_INFORMATION: u8 = 0x00; + pub const COOKIE_RESPONSE: u8 = 0x01; + pub const PLUGIN_MESSAGE: u8 = 0x02; + pub const ACKNOWLEDGE_FINISH: u8 = 0x03; + pub const KEEP_ALIVE: u8 = 0x04; + pub const PONG: u8 = 0x05; + pub const RESOURCE_PACK_RESPONSE: u8 = 0x06; + pub const KNOWN_PACKS: u8 = 0x07; + } - pub mod play { - pub const CONFIRM_TELEPORTATION: u8 = 0x00; - pub const QUERY_BLOCK_ENTITY_TAG: u8 = 0x01; - pub const BUNDLE_ITEM_SELECTED: u8 = 0x02; - pub const CHANGE_DIFFICULTY: u8 = 0x03; - pub const ACKNOWLEDGE_MESSAGE: u8 = 0x04; - pub const CHAT_COMMAND: u8 = 0x05; - pub const SIGNED_CHAT_COMMAND: u8 = 0x06; - pub const CHAT_MESSAGE: u8 = 0x07; - pub const PLAYER_SESSION: u8 = 0x08; - pub const CHUNK_BATCH_RECEIVED: u8 = 0x09; - pub const CLIENT_STATUS: u8 = 0x0A; - pub const CLIENT_TICK_END: u8 = 0x0B; - pub const CLIENT_INFORMATION: u8 = 0x0C; - pub const COMMAND_SUGGESTIONS_REQUEST: u8 = 0x0D; - pub const ACKNOWLEDGE_CONFIGURATION: u8 = 0x0E; - pub const CLICK_CONTAINER_BUTTON: u8 = 0x0F; - pub const CLICK_CONTAINER: u8 = 0x10; - pub const CLOSE_CONTAINER: u8 = 0x11; - pub const CHANGE_CONTAINER_SLOT_STATE: u8 = 0x12; - pub const COOKIE_RESPONSE: u8 = 0x13; - pub const PLUGIN_MESSAGE: u8 = 0x14; - pub const DEBUG_SAMPLE_SUBSCRIPTION: u8 = 0x15; - pub const EDIT_BOOK: u8 = 0x16; - pub const QUERY_ENTITY_TAG: u8 = 0x17; - pub const INTERACT: u8 = 0x18; - pub const JIGSAW_GENERATE: u8 = 0x19; - pub const KEEP_ALIVE: u8 = 0x1A; - pub const LOCK_DIFFICULTY: u8 = 0x1B; - pub const SET_PLAYER_POSITION: u8 = 0x1C; - pub const SET_PLAYER_POSITION_AND_ROTATION: u8 = 0x1D; - pub const SET_PLAYER_ROTATION: u8 = 0x1E; - pub const SET_PLAYER_MOVEMENT_FLAGS: u8 = 0x1F; - pub const MOVE_VEHICLE: u8 = 0x20; - pub const PADDLE_BOAT: u8 = 0x21; - pub const PICK_ITEM_FROM_BLOCK: u8 = 0x22; - pub const PICK_ITEM_FROM_ENTITY: u8 = 0x23; - pub const PING_REQUEST: u8 = 0x24; - pub const PLACE_RECIPE: u8 = 0x25; - pub const PLAYER_ABILITIES: u8 = 0x26; - pub const PLAYER_ACTION: u8 = 0x27; - pub const PLAYER_COMMAND: u8 = 0x28; - pub const PLAYER_INPUT: u8 = 0x29; - pub const PLAYER_LOADED: u8 = 0x2A; - pub const PONG: u8 = 0x2B; - pub const CHANGE_RECIPE_BOOK_SETTINGS: u8 = 0x2C; - pub const SET_SEEN_RECIPE: u8 = 0x2D; - pub const RENAME_ITEM: u8 = 0x2E; - pub const RESOURCE_PACK_RESPONSE: u8 = 0x2F; - pub const SEEN_ADVANCEMENTS: u8 = 0x30; - pub const SELECT_TRADE: u8 = 0x31; - pub const SET_BEACON_EFFECT: u8 = 0x32; - pub const SET_HELD_ITEM: u8 = 0x33; - pub const PROGRAM_COMMAND_BLOCK: u8 = 0x34; - pub const PROGRAM_COMMAND_BLOCK_MINECART: u8 = 0x35; - pub const SET_CREATIVE_MODE_SLOT: u8 = 0x36; - pub const PROGRAM_JIGSAW_BLOCK: u8 = 0x37; - pub const PROGRAM_STRUCTURE_BLOCK: u8 = 0x38; - pub const SET_TEST_BLOCK: u8 = 0x39; - pub const UPDATE_SIGN: u8 = 0x3A; - pub const SWING_ARM: u8 = 0x3B; - pub const TELEPORT_TO_ENTITY: u8 = 0x3C; - pub const TEST_INSTANCE_BLOCK_ACTION: u8 = 0x3D; - pub const USE_ITEM_ON: u8 = 0x3E; - pub const USE_ITEM: u8 = 0x3F; - } + pub mod play { + pub const CONFIRM_TELEPORTATION: u8 = 0x00; + pub const QUERY_BLOCK_ENTITY_TAG: u8 = 0x01; + pub const BUNDLE_ITEM_SELECTED: u8 = 0x02; + pub const CHANGE_DIFFICULTY: u8 = 0x03; + pub const ACKNOWLEDGE_MESSAGE: u8 = 0x04; + pub const CHAT_COMMAND: u8 = 0x05; + pub const SIGNED_CHAT_COMMAND: u8 = 0x06; + pub const CHAT_MESSAGE: u8 = 0x07; + pub const PLAYER_SESSION: u8 = 0x08; + pub const CHUNK_BATCH_RECEIVED: u8 = 0x09; + pub const CLIENT_STATUS: u8 = 0x0A; + pub const CLIENT_TICK_END: u8 = 0x0B; + pub const CLIENT_INFORMATION: u8 = 0x0C; + pub const COMMAND_SUGGESTIONS_REQUEST: u8 = 0x0D; + pub const ACKNOWLEDGE_CONFIGURATION: u8 = 0x0E; + pub const CLICK_CONTAINER_BUTTON: u8 = 0x0F; + pub const CLICK_CONTAINER: u8 = 0x10; + pub const CLOSE_CONTAINER: u8 = 0x11; + pub const CHANGE_CONTAINER_SLOT_STATE: u8 = 0x12; + pub const COOKIE_RESPONSE: u8 = 0x13; + pub const PLUGIN_MESSAGE: u8 = 0x14; + pub const DEBUG_SAMPLE_SUBSCRIPTION: u8 = 0x15; + pub const EDIT_BOOK: u8 = 0x16; + pub const QUERY_ENTITY_TAG: u8 = 0x17; + pub const INTERACT: u8 = 0x18; + pub const JIGSAW_GENERATE: u8 = 0x19; + pub const KEEP_ALIVE: u8 = 0x1A; + pub const LOCK_DIFFICULTY: u8 = 0x1B; + pub const SET_PLAYER_POSITION: u8 = 0x1C; + pub const SET_PLAYER_POSITION_AND_ROTATION: u8 = 0x1D; + pub const SET_PLAYER_ROTATION: u8 = 0x1E; + pub const SET_PLAYER_MOVEMENT_FLAGS: u8 = 0x1F; + pub const MOVE_VEHICLE: u8 = 0x20; + pub const PADDLE_BOAT: u8 = 0x21; + pub const PICK_ITEM_FROM_BLOCK: u8 = 0x22; + pub const PICK_ITEM_FROM_ENTITY: u8 = 0x23; + pub const PING_REQUEST: u8 = 0x24; + pub const PLACE_RECIPE: u8 = 0x25; + pub const PLAYER_ABILITIES: u8 = 0x26; + pub const PLAYER_ACTION: u8 = 0x27; + pub const PLAYER_COMMAND: u8 = 0x28; + pub const PLAYER_INPUT: u8 = 0x29; + pub const PLAYER_LOADED: u8 = 0x2A; + pub const PONG: u8 = 0x2B; + pub const CHANGE_RECIPE_BOOK_SETTINGS: u8 = 0x2C; + pub const SET_SEEN_RECIPE: u8 = 0x2D; + pub const RENAME_ITEM: u8 = 0x2E; + pub const RESOURCE_PACK_RESPONSE: u8 = 0x2F; + pub const SEEN_ADVANCEMENTS: u8 = 0x30; + pub const SELECT_TRADE: u8 = 0x31; + pub const SET_BEACON_EFFECT: u8 = 0x32; + pub const SET_HELD_ITEM: u8 = 0x33; + pub const PROGRAM_COMMAND_BLOCK: u8 = 0x34; + pub const PROGRAM_COMMAND_BLOCK_MINECART: u8 = 0x35; + pub const SET_CREATIVE_MODE_SLOT: u8 = 0x36; + pub const PROGRAM_JIGSAW_BLOCK: u8 = 0x37; + pub const PROGRAM_STRUCTURE_BLOCK: u8 = 0x38; + pub const SET_TEST_BLOCK: u8 = 0x39; + pub const UPDATE_SIGN: u8 = 0x3A; + pub const SWING_ARM: u8 = 0x3B; + pub const TELEPORT_TO_ENTITY: u8 = 0x3C; + pub const TEST_INSTANCE_BLOCK_ACTION: u8 = 0x3D; + pub const USE_ITEM_ON: u8 = 0x3E; + pub const USE_ITEM: u8 = 0x3F; + } } diff --git a/src/server/protocol/mod.rs b/src/server/protocol/mod.rs index 5110371..a1de46a 100644 --- a/src/server/protocol/mod.rs +++ b/src/server/protocol/mod.rs @@ -4,9 +4,9 @@ pub mod play; #[derive(Debug, Clone)] pub enum ConnectionState { - Handshake, - Status, - Login, - Configuration, - Play, + Handshake, + Status, + Login, + Configuration, + Play, } diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 8d1dac0..91db8bb 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -1,215 +1,233 @@ -use std::{io::Cursor, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}}; +use std::{ + io::Cursor, + sync::Arc, + thread, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; -use rust_mc_proto::{read_packet, DataReader, DataWriter, Packet}; +use rust_mc_proto::{DataReader, DataWriter, Packet, read_packet}; -use crate::server::{data::{text_component::TextComponent, ReadWriteNBT}, player::context::ClientContext, ServerError}; +use crate::server::{ + ServerError, + data::{ReadWriteNBT, text_component::TextComponent}, + player::context::ClientContext, +}; use super::id::*; pub fn send_update_tags(client: Arc) -> Result<(), ServerError> { - // TODO: rewrite this hardcode bullshit + // TODO: rewrite this hardcode bullshit - client.write_packet(&Packet::from_bytes( - clientbound::configuration::UPDATE_TAGS, - include_bytes!("update-tags.bin"), - )) + client.write_packet(&Packet::from_bytes( + clientbound::configuration::UPDATE_TAGS, + include_bytes!("update-tags.bin"), + )) } pub fn send_registry_data(client: Arc) -> Result<(), ServerError> { - // TODO: rewrite this hardcode bullshit + // TODO: rewrite this hardcode bullshit - let mut registry_data = Cursor::new(include_bytes!("registry-data.bin")); + let mut registry_data = Cursor::new(include_bytes!("registry-data.bin")); - while let Ok(mut packet) = read_packet(&mut registry_data, None) { - packet.set_id(clientbound::configuration::REGISTRY_DATA); - client.write_packet(&packet)?; - } + while let Ok(mut packet) = read_packet(&mut registry_data, None) { + packet.set_id(clientbound::configuration::REGISTRY_DATA); + client.write_packet(&packet)?; + } - Ok(()) + Ok(()) } // Добавки в Configuration стейт чтобы все работало pub fn handle_configuration_state( - client: Arc, // Контекст клиента + client: Arc, // Контекст клиента ) -> Result<(), ServerError> { - let mut packet = Packet::empty(clientbound::configuration::FEATURE_FLAGS); - packet.write_varint(1)?; - packet.write_string("minecraft:vanilla")?; - client.write_packet(&packet)?; + let mut packet = Packet::empty(clientbound::configuration::FEATURE_FLAGS); + packet.write_varint(1)?; + packet.write_string("minecraft:vanilla")?; + client.write_packet(&packet)?; - let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS); - packet.write_varint(1)?; - packet.write_string("minecraft")?; - packet.write_string("core")?; - packet.write_string("1.21.5")?; - client.write_packet(&packet)?; + let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS); + packet.write_varint(1)?; + packet.write_string("minecraft")?; + packet.write_string("core")?; + packet.write_string("1.21.5")?; + client.write_packet(&packet)?; - client.read_packet(serverbound::configuration::KNOWN_PACKS)?; + client.read_packet(serverbound::configuration::KNOWN_PACKS)?; - send_registry_data(client.clone())?; - send_update_tags(client.clone()) + send_registry_data(client.clone())?; + send_update_tags(client.clone()) } pub fn send_login(client: Arc) -> Result<(), ServerError> { - // Отправка пакета Login - let mut packet = Packet::empty(clientbound::play::LOGIN); + // Отправка пакета Login + let mut packet = Packet::empty(clientbound::play::LOGIN); - packet.write_int(0)?; // Entity ID - packet.write_boolean(false)?; // Is hardcore - packet.write_varint(4)?; // Dimension Names - packet.write_string("minecraft:overworld")?; - packet.write_string("minecraft:nether")?; - packet.write_string("minecraft:the_end")?; - packet.write_string("minecraft:overworld_caves")?; - packet.write_varint(0)?; // Max Players - packet.write_varint(8)?; // View Distance - packet.write_varint(5)?; // Simulation Distance - packet.write_boolean(false)?; // Reduced Debug Info - packet.write_boolean(true)?; // Enable respawn screen - packet.write_boolean(false)?; // Do limited crafting + packet.write_int(0)?; // Entity ID + packet.write_boolean(false)?; // Is hardcore + packet.write_varint(4)?; // Dimension Names + packet.write_string("minecraft:overworld")?; + packet.write_string("minecraft:nether")?; + packet.write_string("minecraft:the_end")?; + packet.write_string("minecraft:overworld_caves")?; + packet.write_varint(0)?; // Max Players + packet.write_varint(8)?; // View Distance + packet.write_varint(5)?; // Simulation Distance + packet.write_boolean(false)?; // Reduced Debug Info + packet.write_boolean(true)?; // Enable respawn screen + packet.write_boolean(false)?; // Do limited crafting - packet.write_varint(0)?; // Dimension Type - packet.write_string("minecraft:overworld")?; // Dimension Name - packet.write_long(0x0f38f26ad09c3e20)?; // Hashed seed - packet.write_byte(0)?; // Game mode - packet.write_signed_byte(-1)?; // Previous Game mode - packet.write_boolean(false)?; // Is Debug - packet.write_boolean(true)?; // Is Flat - packet.write_boolean(false)?; // Has death location - packet.write_varint(20)?; // Portal cooldown - packet.write_varint(60)?; // Sea level + packet.write_varint(0)?; // Dimension Type + packet.write_string("minecraft:overworld")?; // Dimension Name + packet.write_long(0x0f38f26ad09c3e20)?; // Hashed seed + packet.write_byte(0)?; // Game mode + packet.write_signed_byte(-1)?; // Previous Game mode + packet.write_boolean(false)?; // Is Debug + packet.write_boolean(true)?; // Is Flat + packet.write_boolean(false)?; // Has death location + packet.write_varint(20)?; // Portal cooldown + packet.write_varint(60)?; // Sea level - packet.write_boolean(false)?; // Enforces Secure Chat + packet.write_boolean(false)?; // Enforces Secure Chat - client.write_packet(&packet) + client.write_packet(&packet) } -pub fn send_game_event(client: Arc, event: u8, value: f32) -> Result<(), ServerError> { - let mut packet = Packet::empty(clientbound::play::GAME_EVENT); +pub fn send_game_event( + client: Arc, + event: u8, + value: f32, +) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::GAME_EVENT); - packet.write_byte(event)?; - packet.write_float(value)?; + packet.write_byte(event)?; + packet.write_float(value)?; - client.write_packet(&packet) + client.write_packet(&packet) } pub fn sync_player_pos( - client: Arc, - x: f64, - y: f64, - z: f64, - vel_x: f64, - vel_y: f64, - vel_z: f64, - yaw: f32, - pitch: f32, - flags: i32 + client: Arc, + x: f64, + y: f64, + z: f64, + vel_x: f64, + vel_y: f64, + vel_z: f64, + yaw: f32, + pitch: f32, + flags: i32, ) -> Result<(), ServerError> { - let timestamp = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() & 0xFFFFFFFF) as i32; + let timestamp = (SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + & 0xFFFFFFFF) as i32; - let mut packet = Packet::empty(clientbound::play::SYNCHRONIZE_PLAYER_POSITION); + let mut packet = Packet::empty(clientbound::play::SYNCHRONIZE_PLAYER_POSITION); - packet.write_varint(timestamp)?; - packet.write_double(x)?; - packet.write_double(y)?; - packet.write_double(z)?; - packet.write_double(vel_x)?; - packet.write_double(vel_y)?; - packet.write_double(vel_z)?; - packet.write_float(yaw)?; - packet.write_float(pitch)?; - packet.write_int(flags)?; + packet.write_varint(timestamp)?; + packet.write_double(x)?; + packet.write_double(y)?; + packet.write_double(z)?; + packet.write_double(vel_x)?; + packet.write_double(vel_y)?; + packet.write_double(vel_z)?; + packet.write_float(yaw)?; + packet.write_float(pitch)?; + packet.write_int(flags)?; - client.write_packet(&packet)?; + client.write_packet(&packet)?; - Ok(()) + Ok(()) } pub fn set_center_chunk(client: Arc, x: i32, z: i32) -> Result<(), ServerError> { - let mut packet = Packet::empty(clientbound::play::SET_CENTER_CHUNK); + let mut packet = Packet::empty(clientbound::play::SET_CENTER_CHUNK); - packet.write_varint(x)?; - packet.write_varint(z)?; + packet.write_varint(x)?; + packet.write_varint(z)?; - client.write_packet(&packet) + client.write_packet(&packet) } pub fn send_example_chunk(client: Arc, x: i32, z: i32) -> Result<(), ServerError> { - let mut packet = Packet::empty(clientbound::play::CHUNK_DATA_AND_UPDATE_LIGHT); + let mut packet = Packet::empty(clientbound::play::CHUNK_DATA_AND_UPDATE_LIGHT); - packet.write_int(x)?; - packet.write_int(z)?; + packet.write_int(x)?; + packet.write_int(z)?; - // heightmap + // heightmap - packet.write_varint(1)?; // heightmaps count - packet.write_varint(0)?; // MOTION_BLOCKING - packet.write_varint(256)?; // Length of the following long array (16 * 16 = 256) - for _ in 0..256 { - packet.write_long(0)?; // height - 0 - } + packet.write_varint(1)?; // heightmaps count + packet.write_varint(0)?; // MOTION_BLOCKING + packet.write_varint(256)?; // Length of the following long array (16 * 16 = 256) + for _ in 0..256 { + packet.write_long(0)?; // height - 0 + } - // sending chunk data + // sending chunk data - let mut chunk_data = Vec::new(); + let mut chunk_data = Vec::new(); - // we want to fill the area from -64 to 0, so it will be 4 chunk sections + // we want to fill the area from -64 to 0, so it will be 4 chunk sections - for _ in 0..4 { - chunk_data.write_short(4096)?; // non-air blocks count, 16 * 16 * 16 = 4096 stone blocks + for _ in 0..4 { + chunk_data.write_short(4096)?; // non-air blocks count, 16 * 16 * 16 = 4096 stone blocks - // blocks paletted container - chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format - chunk_data.write_varint(1)?; // block state id in the registry (1 for stone) + // blocks paletted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(1)?; // block state id in the registry (1 for stone) - // biomes palleted container - chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format - chunk_data.write_varint(27)?; // biome id in the registry - } + // biomes palleted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(27)?; // biome id in the registry + } - // air chunk sections + // air chunk sections - for _ in 0..20 { - chunk_data.write_short(0)?; // non-air blocks count, 0 + for _ in 0..20 { + chunk_data.write_short(0)?; // non-air blocks count, 0 - // blocks paletted container - chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format - chunk_data.write_varint(0)?; // block state id in the registry (0 for air) + // blocks paletted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(0)?; // block state id in the registry (0 for air) - // biomes palleted container - chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format - chunk_data.write_varint(27)?; // biome id in the registry - } + // biomes palleted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(27)?; // biome id in the registry + } - packet.write_usize_varint(chunk_data.len())?; - packet.write_bytes(&chunk_data)?; + packet.write_usize_varint(chunk_data.len())?; + packet.write_bytes(&chunk_data)?; - packet.write_byte(0)?; + packet.write_byte(0)?; + // light data - // light data + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; - packet.write_byte(0)?; - packet.write_byte(0)?; - packet.write_byte(0)?; - packet.write_byte(0)?; - packet.write_byte(0)?; - packet.write_byte(0)?; + client.write_packet(&packet)?; - - client.write_packet(&packet)?; - - Ok(()) + Ok(()) } pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { - let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64; - let mut packet = Packet::empty(clientbound::play::KEEP_ALIVE); - packet.write_long(timestamp)?; - client.write_packet(&packet)?; + let mut packet = Packet::empty(clientbound::play::KEEP_ALIVE); + packet.write_long(timestamp)?; + client.write_packet(&packet)?; - let mut packet = client.read_packet(serverbound::play::KEEP_ALIVE)?; + let mut packet = client.read_packet(serverbound::play::KEEP_ALIVE)?; let timestamp2 = packet.read_long()?; if timestamp2 != timestamp { // Послать клиента нахуй @@ -218,103 +236,113 @@ pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { println!("KeepAlive Ok") } - Ok(()) + Ok(()) } -pub fn send_system_message(client: Arc, message: TextComponent, is_action_bar: bool) -> Result<(), ServerError> { - let mut packet = Packet::empty(clientbound::play::SYSTEM_CHAT_MESSAGE); - packet.write_nbt(&message)?; - packet.write_boolean(is_action_bar)?; - client.write_packet(&packet) +pub fn send_system_message( + client: Arc, + message: TextComponent, + is_action_bar: bool, +) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::SYSTEM_CHAT_MESSAGE); + packet.write_nbt(&message)?; + packet.write_boolean(is_action_bar)?; + client.write_packet(&packet) } // Отдельная функция для работы с самой игрой pub fn handle_play_state( - client: Arc, // Контекст клиента + client: Arc, // Контекст клиента ) -> Result<(), ServerError> { + thread::spawn({ + let client = client.clone(); - thread::spawn({ - let client = client.clone(); + move || { + let _ = client.run_read_loop(); + client.close(); + } + }); - move || { - let _ = client.run_read_loop(); - client.close(); - } - }); + send_login(client.clone())?; + sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)?; + send_game_event(client.clone(), 13, 0.0)?; // 13 - Start waiting for level chunks + set_center_chunk(client.clone(), 0, 0)?; + send_example_chunk(client.clone(), 0, 0)?; - send_login(client.clone())?; - sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)?; - send_game_event(client.clone(), 13, 0.0)?; // 13 - Start waiting for level chunks - set_center_chunk(client.clone(), 0, 0)?; - send_example_chunk(client.clone(), 0, 0)?; + thread::spawn({ + let client = client.clone(); - thread::spawn({ - let client = client.clone(); + move || -> Result<(), ServerError> { + while client.is_alive() { + let mut packet = client.read_any_packet()?; - move || -> Result<(), ServerError> { - while client.is_alive() { - let mut packet = client.read_any_packet()?; + match packet.id() { + serverbound::play::SET_PLAYER_POSITION => { + let x = packet.read_double()?; + let y = packet.read_double()?; + let z = packet.read_double()?; + let _ = packet.read_byte()?; // flags - match packet.id() { - serverbound::play::SET_PLAYER_POSITION => { - let x = packet.read_double()?; - let y = packet.read_double()?; - let z = packet.read_double()?; - let _ = packet.read_byte()?; // flags + client.set_position((x, y, z)); + } + serverbound::play::SET_PLAYER_POSITION_AND_ROTATION => { + let x = packet.read_double()?; + let y = packet.read_double()?; + let z = packet.read_double()?; + let yaw = packet.read_float()?; + let pitch = packet.read_float()?; + let _ = packet.read_byte()?; // flags - client.set_position((x, y, z)); - }, - serverbound::play::SET_PLAYER_POSITION_AND_ROTATION => { - let x = packet.read_double()?; - let y = packet.read_double()?; - let z = packet.read_double()?; - let yaw = packet.read_float()?; - let pitch = packet.read_float()?; - let _ = packet.read_byte()?; // flags + client.set_position((x, y, z)); + client.set_rotation((yaw, pitch)); + } + serverbound::play::SET_PLAYER_ROTATION => { + let yaw = packet.read_float()?; + let pitch = packet.read_float()?; + let _ = packet.read_byte()?; // flags - client.set_position((x, y, z)); - client.set_rotation((yaw, pitch)); - }, - serverbound::play::SET_PLAYER_ROTATION => { - let yaw = packet.read_float()?; - let pitch = packet.read_float()?; - let _ = packet.read_byte()?; // flags - - client.set_rotation((yaw, pitch)); - }, - _ => { + client.set_rotation((yaw, pitch)); + } + _ => { client.push_back(packet); } - } - } + } + } - Ok(()) - } - }); + Ok(()) + } + }); - let mut ticks_alive = 0u64; + let mut ticks_alive = 0u64; - while client.is_alive() { + while client.is_alive() { println!("{ticks_alive}"); - if ticks_alive % 200 == 0 { // 10 secs timer - send_keep_alive(client.clone())?; - } + if ticks_alive % 200 == 0 { + // 10 secs timer + send_keep_alive(client.clone())?; + } - if ticks_alive % 20 == 0 { // 1 sec timer - let (x, y, z) = client.position(); + if ticks_alive % 20 == 0 { + // 1 sec timer + let (x, y, z) = client.position(); - send_system_message(client.clone(), - TextComponent::rainbow(format!( - "Pos: {} {} {}", x as i64, y as i64, z as i64 - )), false)?; - } + send_system_message( + client.clone(), + TextComponent::rainbow(format!("Pos: {} {} {}", x as i64, y as i64, z as i64)), + false, + )?; + } - send_system_message(client.clone(), TextComponent::rainbow(format!("Ticks alive: {}", ticks_alive)), true)?; + send_system_message( + client.clone(), + TextComponent::rainbow(format!("Ticks alive: {}", ticks_alive)), + true, + )?; - thread::sleep(Duration::from_millis(50)); // 1 tick - ticks_alive += 1; - } + thread::sleep(Duration::from_millis(50)); // 1 tick + ticks_alive += 1; + } println!("Client die"); - Ok(()) + Ok(()) } From 62e7e5bf282da3eb1810b4848eab44e151a30af4 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 04:29:55 +0300 Subject: [PATCH 46/57] read packet with id list --- src/server/player/context.rs | 13 +++++++------ src/server/player/helper.rs | 14 +++++++------- src/server/protocol/handler.rs | 12 ++++++------ src/server/protocol/play.rs | 6 +++--- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/server/player/context.rs b/src/server/player/context.rs index 7e1183d..d2627a3 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -197,13 +197,14 @@ impl ClientContext { Ok(()) } + /// Please avoid using of this bullshit pub fn read_any_packet(self: &Arc) -> Result { if self.read_loop.load(Ordering::SeqCst) { loop { if let Some(packet) = self.packet_buffer.lock().unwrap().pop_front() { return Ok(packet); } - thread::sleep(Duration::from_millis(10)); + thread::sleep(Duration::from_millis(4)); } } else { let state = self.state(); @@ -237,19 +238,19 @@ impl ClientContext { } } - pub fn read_packet(self: &Arc, id: u8) -> Result { + pub fn read_packet(self: &Arc, ids: &[u8]) -> Result { if self.read_loop.load(Ordering::SeqCst) { loop { { let mut locked = self.packet_buffer.lock().unwrap(); for (i, packet) in locked.clone().iter().enumerate() { - if packet.id() == id { + if ids.contains(&packet.id()) { locked.remove(i); return Ok(packet.clone()); } } } - thread::sleep(Duration::from_millis(10)); + thread::sleep(Duration::from_millis(4)); } } else { let packet = match self.read_any_packet() { @@ -260,7 +261,7 @@ impl ClientContext { } }; - if packet.id() != id { + if ids.contains(&packet.id()) { Err(ServerError::UnexpectedPacket(packet.id())) } else { Ok(packet) @@ -268,7 +269,7 @@ impl ClientContext { } } - pub fn push_back(self: &Arc, packet: Packet){ + pub fn push_packet_back(self: &Arc, packet: Packet){ self.packet_buffer.lock().unwrap().push_back(packet) } diff --git a/src/server/player/helper.rs b/src/server/player/helper.rs index 540290a..68a0378 100644 --- a/src/server/player/helper.rs +++ b/src/server/player/helper.rs @@ -70,7 +70,7 @@ impl ProtocolHelper { self.client .write_packet(&Packet::empty(clientbound::configuration::FINISH))?; self.client - .read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; + .read_packet(&[serverbound::configuration::ACKNOWLEDGE_FINISH])?; self.client.set_state(ConnectionState::Play)?; Ok(()) } @@ -85,7 +85,7 @@ impl ProtocolHelper { self.client .write_packet(&Packet::empty(clientbound::play::START_CONFIGURATION))?; self.client - .read_packet(serverbound::play::ACKNOWLEDGE_CONFIGURATION)?; + .read_packet(&[serverbound::play::ACKNOWLEDGE_CONFIGURATION])?; self.client.set_state(ConnectionState::Configuration)?; Ok(()) } @@ -100,14 +100,14 @@ impl ProtocolHelper { let time = SystemTime::now(); self.client .write_packet(&Packet::empty(clientbound::play::PING))?; - self.client.read_packet(serverbound::play::PONG)?; + self.client.read_packet(&[serverbound::play::PONG])?; Ok(SystemTime::now().duration_since(time).unwrap()) } ConnectionState::Configuration => { let time = SystemTime::now(); self.client .write_packet(&Packet::empty(clientbound::configuration::PING))?; - self.client.read_packet(serverbound::configuration::PONG)?; + self.client.read_packet(&[serverbound::configuration::PONG])?; Ok(SystemTime::now().duration_since(time).unwrap()) } _ => Err(ServerError::UnexpectedState), @@ -149,7 +149,7 @@ impl ProtocolHelper { let mut packet = self .client - .read_packet(serverbound::configuration::COOKIE_RESPONSE)?; + .read_packet(&[serverbound::configuration::COOKIE_RESPONSE])?; packet.read_string()?; let data = if packet.read_boolean()? { let n = packet.read_usize_varint()?; @@ -167,7 +167,7 @@ impl ProtocolHelper { let mut packet = self .client - .read_packet(serverbound::play::COOKIE_RESPONSE)?; + .read_packet(&[serverbound::play::COOKIE_RESPONSE])?; packet.read_string()?; let data = if packet.read_boolean()? { let n = packet.read_usize_varint()?; @@ -199,7 +199,7 @@ impl ProtocolHelper { let mut packet = self .client - .read_packet(serverbound::login::PLUGIN_RESPONSE)?; + .read_packet(&[serverbound::login::PLUGIN_RESPONSE])?; let identifier = packet.read_varint()?; let data = if packet.read_boolean()? { let mut data = Vec::new(); diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index d532e33..521977c 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -21,7 +21,7 @@ pub fn handle_connection( // Получение пакетов производится через client.conn(), // ВАЖНО: не помещать сам client.conn() в переменные, // он должен сразу убиваться иначе соединение гдето задедлочится - let mut packet = client.read_packet(serverbound::handshake::HANDSHAKE)?; + let mut packet = client.read_packet(&[serverbound::handshake::HANDSHAKE])?; let protocol_version = packet.read_varint()?; // Получаем версия протокола, может быть отрицательным если наш клиент дэбил let server_address = packet.read_string()?; // Получаем домен/адрес сервера к которому пытается подключиться клиент, например "play.example.com", а не айпи @@ -86,7 +86,7 @@ pub fn handle_connection( client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login // Читаем пакет Login Start - let mut packet = client.read_packet(serverbound::login::START)?; + let mut packet = client.read_packet(&[serverbound::login::START])?; let name = packet.read_string()?; let uuid = packet.read_uuid()?; @@ -115,14 +115,14 @@ pub fn handle_connection( p.write_varint(0) })?)?; - client.read_packet(serverbound::login::ACKNOWLEDGED)?; // Пакет Login Acknowledged + client.read_packet(&[serverbound::login::ACKNOWLEDGED])?; // Пакет Login Acknowledged client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration // Получение бренда клиента из Serverbound Plugin Message // Identifier канала откуда берется бренд: minecraft:brand let brand = loop { - let mut packet = client.read_packet(serverbound::configuration::PLUGIN_MESSAGE)?; // Пакет Serverbound Plugin Message + let mut packet = client.read_packet(&[serverbound::configuration::PLUGIN_MESSAGE])?; // Пакет Serverbound Plugin Message let identifier = packet.read_string()?; @@ -136,7 +136,7 @@ pub fn handle_connection( } }; - let mut packet = client.read_packet(serverbound::configuration::CLIENT_INFORMATION)?; // Пакет Client Information + let mut packet = client.read_packet(&[serverbound::configuration::CLIENT_INFORMATION])?; // Пакет Client Information let locale = packet.read_string()?; // for example: en_us let view_distance = packet.read_signed_byte()?; // client-side render distance in chunks @@ -172,7 +172,7 @@ pub fn handle_connection( handle_configuration_state(client.clone())?; client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; - client.read_packet(serverbound::configuration::ACKNOWLEDGE_FINISH)?; + client.read_packet(&[serverbound::configuration::ACKNOWLEDGE_FINISH])?; client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 8d1dac0..a040847 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -44,7 +44,7 @@ pub fn handle_configuration_state( packet.write_string("1.21.5")?; client.write_packet(&packet)?; - client.read_packet(serverbound::configuration::KNOWN_PACKS)?; + client.read_packet(&[serverbound::configuration::KNOWN_PACKS])?; send_registry_data(client.clone())?; send_update_tags(client.clone()) @@ -209,7 +209,7 @@ pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { packet.write_long(timestamp)?; client.write_packet(&packet)?; - let mut packet = client.read_packet(serverbound::play::KEEP_ALIVE)?; + let mut packet = client.read_packet(&[serverbound::play::KEEP_ALIVE])?; let timestamp2 = packet.read_long()?; if timestamp2 != timestamp { // Послать клиента нахуй @@ -283,7 +283,7 @@ pub fn handle_play_state( client.set_rotation((yaw, pitch)); }, _ => { - client.push_back(packet); + client.push_packet_back(packet); } } } From 8a15d6748f6f03873401cef87ee6e691345396cd Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 04:39:18 +0300 Subject: [PATCH 47/57] fix dump read packet error --- src/server/player/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/player/context.rs b/src/server/player/context.rs index a2935bc..7028458 100644 --- a/src/server/player/context.rs +++ b/src/server/player/context.rs @@ -251,9 +251,9 @@ impl ClientContext { }; if ids.contains(&packet.id()) { - Err(ServerError::UnexpectedPacket(packet.id())) - } else { Ok(packet) + } else { + Err(ServerError::UnexpectedPacket(packet.id())) } } } From e9cc9773e7f3881941b1181aef1523d74f919716 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 04:44:21 +0300 Subject: [PATCH 48/57] remove println logging --- src/server/protocol/play.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index 140537f..ba59269 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -316,7 +316,6 @@ pub fn handle_play_state( let mut ticks_alive = 0u64; while client.is_alive() { - println!("{ticks_alive}"); if ticks_alive % 200 == 0 { // 10 secs timer send_keep_alive(client.clone())?; @@ -342,7 +341,6 @@ pub fn handle_play_state( thread::sleep(Duration::from_millis(50)); // 1 tick ticks_alive += 1; } - println!("Client die"); Ok(()) } From 595940329759ce83d3b232687dbf3ef966a30625 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 04:50:16 +0300 Subject: [PATCH 49/57] remove println again --- src/server/protocol/play.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index ba59269..f3d6217 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -231,12 +231,10 @@ pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { let timestamp2 = packet.read_long()?; if timestamp2 != timestamp { // Послать клиента нахуй - println!("KeepAlive Err") + Err(ServerError::UnexpectedPacket(serverbound::play::KEEP_ALIVE)) } else { - println!("KeepAlive Ok") + Ok(()) } - - Ok(()) } pub fn send_system_message( @@ -267,7 +265,12 @@ pub fn handle_play_state( sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)?; send_game_event(client.clone(), 13, 0.0)?; // 13 - Start waiting for level chunks set_center_chunk(client.clone(), 0, 0)?; - send_example_chunk(client.clone(), 0, 0)?; + + for x in -1..=1 { + for z in -1..=1 { + send_example_chunk(client.clone(), x, z)?; + } + } thread::spawn({ let client = client.clone(); From 28407d271126635326b0e8e5086cba5f8710ab66 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 5 May 2025 23:05:56 +0300 Subject: [PATCH 50/57] move brand to a const --- src/server/protocol/handler.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/protocol/handler.rs b/src/server/protocol/handler.rs index c53e596..01e869a 100644 --- a/src/server/protocol/handler.rs +++ b/src/server/protocol/handler.rs @@ -14,6 +14,8 @@ use super::{ play::{handle_configuration_state, handle_play_state}, }; +pub const BRAND: &str = "rust_mc_serv"; + pub fn handle_connection( client: Arc, // Контекст клиента ) -> Result<(), ServerError> { @@ -165,7 +167,7 @@ pub fn handle_connection( clientbound::configuration::PLUGIN_MESSAGE, |p| { p.write_string("minecraft:brand")?; - p.write_string("rust_minecraft_server") + p.write_string(BRAND) }, )?)?; From 3788bc36dd23a290628d14c999270e3fed99a0b7 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Tue, 6 May 2025 03:22:15 +0300 Subject: [PATCH 51/57] infinity world --- src/server/mod.rs | 1 + src/server/protocol/play.rs | 41 +++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 6166e8d..19988a5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -18,6 +18,7 @@ pub mod protocol; #[derive(Debug)] pub enum ServerError { UnexpectedPacket(u8), // Неожиданный пакет + WrongPacket, // Пакет поломан, неверные данные Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection SerTextComponent, // Ошибка при сериализации текст-компонента diff --git a/src/server/protocol/play.rs b/src/server/protocol/play.rs index f3d6217..8e4cc33 100644 --- a/src/server/protocol/play.rs +++ b/src/server/protocol/play.rs @@ -231,7 +231,7 @@ pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { let timestamp2 = packet.read_long()?; if timestamp2 != timestamp { // Послать клиента нахуй - Err(ServerError::UnexpectedPacket(serverbound::play::KEEP_ALIVE)) + Err(ServerError::WrongPacket) } else { Ok(()) } @@ -248,6 +248,24 @@ pub fn send_system_message( client.write_packet(&packet) } +pub fn send_example_chunks_in_distance( + client: Arc, + chunks: &mut Vec<(i32, i32)>, + distance: i32, + center: (i32, i32), +) -> Result<(), ServerError> { + for x in -distance + center.0..=distance + center.0 { + for z in -distance + center.1..=distance + center.1 { + if !chunks.contains(&(x, z)) { + send_example_chunk(client.clone(), x as i32, z as i32)?; + chunks.push((x, z)); + } + } + } + + Ok(()) +} + // Отдельная функция для работы с самой игрой pub fn handle_play_state( client: Arc, // Контекст клиента @@ -266,11 +284,11 @@ pub fn handle_play_state( send_game_event(client.clone(), 13, 0.0)?; // 13 - Start waiting for level chunks set_center_chunk(client.clone(), 0, 0)?; - for x in -1..=1 { - for z in -1..=1 { - send_example_chunk(client.clone(), x, z)?; - } - } + let mut chunks = Vec::new(); + + let view_distance = client.client_info().unwrap().view_distance as i32; + + send_example_chunks_in_distance(client.clone(), &mut chunks, view_distance, (0, 0))?; thread::spawn({ let client = client.clone(); @@ -328,6 +346,17 @@ pub fn handle_play_state( // 1 sec timer let (x, y, z) = client.position(); + let (chunk_x, chunk_z) = ((x / 16.0) as i64, (z / 16.0) as i64); + let (chunk_x, chunk_z) = (chunk_x as i32, chunk_z as i32); + + set_center_chunk(client.clone(), chunk_x, chunk_z)?; + send_example_chunks_in_distance( + client.clone(), + &mut chunks, + view_distance, + (chunk_x, chunk_z), + )?; + send_system_message( client.clone(), TextComponent::rainbow(format!("Pos: {} {} {}", x as i64, y as i64, z as i64)), From db89af6c07ed83850eac1a89579cad7eaf8fe474 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Tue, 6 May 2025 16:18:13 +0300 Subject: [PATCH 52/57] crate --- src/{server => }/config.rs | 0 src/{server => }/context.rs | 0 src/{server => }/data/mod.rs | 0 src/{server => }/data/text_component.rs | 2 +- src/{server => }/event/mod.rs | 5 ++-- src/{server/mod.rs => lib.rs} | 30 ++++++++++++++++++++ src/main.rs | 4 +-- src/{server => }/player/context.rs | 3 +- src/{server => }/player/helper.rs | 2 +- src/{server => }/player/mod.rs | 0 src/{server => }/protocol/handler.rs | 2 +- src/{server => }/protocol/id.rs | 0 src/{server => }/protocol/mod.rs | 0 src/{server => }/protocol/play.rs | 2 +- src/{server => }/protocol/registry-data.bin | Bin src/{server => }/protocol/update-tags.bin | Bin 16 files changed, 39 insertions(+), 11 deletions(-) rename src/{server => }/config.rs (100%) rename src/{server => }/context.rs (100%) rename src/{server => }/data/mod.rs (100%) rename src/{server => }/data/text_component.rs (99%) rename src/{server => }/event/mod.rs (85%) rename src/{server/mod.rs => lib.rs} (90%) rename src/{server => }/player/context.rs (98%) rename src/{server => }/player/helper.rs (99%) rename src/{server => }/player/mod.rs (100%) rename src/{server => }/protocol/handler.rs (99%) rename src/{server => }/protocol/id.rs (100%) rename src/{server => }/protocol/mod.rs (100%) rename src/{server => }/protocol/play.rs (99%) rename src/{server => }/protocol/registry-data.bin (100%) rename src/{server => }/protocol/update-tags.bin (100%) diff --git a/src/server/config.rs b/src/config.rs similarity index 100% rename from src/server/config.rs rename to src/config.rs diff --git a/src/server/context.rs b/src/context.rs similarity index 100% rename from src/server/context.rs rename to src/context.rs diff --git a/src/server/data/mod.rs b/src/data/mod.rs similarity index 100% rename from src/server/data/mod.rs rename to src/data/mod.rs diff --git a/src/server/data/text_component.rs b/src/data/text_component.rs similarity index 99% rename from src/server/data/text_component.rs rename to src/data/text_component.rs index 3f63dc2..d08264b 100644 --- a/src/server/data/text_component.rs +++ b/src/data/text_component.rs @@ -5,7 +5,7 @@ use rust_mc_proto::Packet; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::server::ServerError; +use crate::ServerError; use super::ReadWriteNBT; diff --git a/src/server/event/mod.rs b/src/event/mod.rs similarity index 85% rename from src/server/event/mod.rs rename to src/event/mod.rs index d7a8f8d..785aa70 100644 --- a/src/server/event/mod.rs +++ b/src/event/mod.rs @@ -1,6 +1,7 @@ use rust_mc_proto::Packet; -use super::protocol::ConnectionState; +use super::{ServerError, player::context::ClientContext, protocol::ConnectionState}; +use std::sync::Arc; #[macro_export] macro_rules! generate_handlers { @@ -10,7 +11,7 @@ macro_rules! generate_handlers { 0 } - fn [](&self, _: std::sync::Arc $(, _: $arg_ty)*) -> Result<(), crate::server::ServerError> { + fn [](&self, _: Arc $(, _: $arg_ty)*) -> Result<(), ServerError> { Ok(()) } } diff --git a/src/server/mod.rs b/src/lib.rs similarity index 90% rename from src/server/mod.rs rename to src/lib.rs index 19988a5..7e0754f 100644 --- a/src/server/mod.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use std::{error::Error, fmt::Display, net::TcpListener, sync::Arc, thread, time::Duration}; +use config::Config; use context::ServerContext; use ignore_result::Ignore; use log::{error, info}; @@ -104,3 +105,32 @@ pub fn start_server(server: Arc) { }); } } + +// server start helper +pub struct Server { + context: Arc, +} + +impl Server { + pub fn new(context: ServerContext) -> Self { + Self { + context: Arc::new(context), + } + } + + pub fn context(&self) -> &ServerContext { + &self.context + } + + pub fn start(&self) { + start_server(self.context.clone()); + } +} + +impl Default for Server { + fn default() -> Self { + Self { + context: Arc::new(ServerContext::new(Arc::new(Config::default()))), + } + } +} diff --git a/src/main.rs b/src/main.rs index 8ee9002..1701297 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::{env::args, path::PathBuf, sync::Arc}; use log::{debug, error, info}; use rust_mc_proto::Packet; -use server::{ +use rust_mc_serv::{ ServerError, config::Config, context::ServerContext, @@ -13,8 +13,6 @@ use server::{ start_server, }; -pub mod server; - struct ExampleListener; impl Listener for ExampleListener { diff --git a/src/server/player/context.rs b/src/player/context.rs similarity index 98% rename from src/server/player/context.rs rename to src/player/context.rs index 7028458..74f6bdb 100644 --- a/src/server/player/context.rs +++ b/src/player/context.rs @@ -13,9 +13,8 @@ use std::{ use rust_mc_proto::{MinecraftConnection, Packet}; use uuid::Uuid; -use crate::server::{ServerError, context::ServerContext, protocol::ConnectionState}; - use super::helper::ProtocolHelper; +use crate::{ServerError, context::ServerContext, protocol::ConnectionState}; // Клиент контекст // Должен быть обернут в Arc для передачи между потоками diff --git a/src/server/player/helper.rs b/src/player/helper.rs similarity index 99% rename from src/server/player/helper.rs rename to src/player/helper.rs index f164ed8..d33cc44 100644 --- a/src/server/player/helper.rs +++ b/src/player/helper.rs @@ -6,7 +6,7 @@ use std::{ use rust_mc_proto::{DataReader, DataWriter, Packet}; -use crate::server::{ +use crate::{ ServerError, data::{ReadWriteNBT, text_component::TextComponent}, protocol::{ diff --git a/src/server/player/mod.rs b/src/player/mod.rs similarity index 100% rename from src/server/player/mod.rs rename to src/player/mod.rs diff --git a/src/server/protocol/handler.rs b/src/protocol/handler.rs similarity index 99% rename from src/server/protocol/handler.rs rename to src/protocol/handler.rs index 01e869a..3967afe 100644 --- a/src/server/protocol/handler.rs +++ b/src/protocol/handler.rs @@ -1,6 +1,6 @@ use std::{io::Read, sync::Arc}; -use crate::server::{ +use crate::{ ServerError, player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, }; diff --git a/src/server/protocol/id.rs b/src/protocol/id.rs similarity index 100% rename from src/server/protocol/id.rs rename to src/protocol/id.rs diff --git a/src/server/protocol/mod.rs b/src/protocol/mod.rs similarity index 100% rename from src/server/protocol/mod.rs rename to src/protocol/mod.rs diff --git a/src/server/protocol/play.rs b/src/protocol/play.rs similarity index 99% rename from src/server/protocol/play.rs rename to src/protocol/play.rs index 8e4cc33..4c95a8f 100644 --- a/src/server/protocol/play.rs +++ b/src/protocol/play.rs @@ -7,7 +7,7 @@ use std::{ use rust_mc_proto::{DataReader, DataWriter, Packet, read_packet}; -use crate::server::{ +use crate::{ ServerError, data::{ReadWriteNBT, text_component::TextComponent}, player::context::ClientContext, diff --git a/src/server/protocol/registry-data.bin b/src/protocol/registry-data.bin similarity index 100% rename from src/server/protocol/registry-data.bin rename to src/protocol/registry-data.bin diff --git a/src/server/protocol/update-tags.bin b/src/protocol/update-tags.bin similarity index 100% rename from src/server/protocol/update-tags.bin rename to src/protocol/update-tags.bin From 37dac2603265e821f93579cb9d21ddba55af311a Mon Sep 17 00:00:00 2001 From: MeexReay Date: Tue, 6 May 2025 16:24:03 +0300 Subject: [PATCH 53/57] add using as library to readme --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index bf73f10..6a8c75a 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,28 @@ cargo run cargo build -r ``` +### Использовать как библиотеку + +Вы можете использовать проект как библиотеку для своих серверов + +Пример добавления в `Cargo.toml`: + +```toml +rust_mc_serv = { git = "https://github.com/GIKExe/rust_minecraft_server.git" } +``` + +Пример запуска сервера: + +```rust +let config = Arc::new(Config::default()); +let mut server = ServerContext::new(config); + +server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера +server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + +start_server(Arc::new(server)); +``` + ## Конфигурация По умолчанию, конфиг будет создан в файле `config.toml` в рабочей директории. Чтобы изменить этот путь, укажите его в первом аргументе к серверу, пример: `./rust_mc_serv /path/to/config.toml` From 78c1404ce817f5ea9088b2ea11fed74a7c803447 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Tue, 6 May 2025 16:24:28 +0300 Subject: [PATCH 54/57] remove server start helper --- src/lib.rs | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7e0754f..ad4e1ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,30 +107,30 @@ pub fn start_server(server: Arc) { } // server start helper -pub struct Server { - context: Arc, -} +// pub struct Server { +// context: Arc, +// } -impl Server { - pub fn new(context: ServerContext) -> Self { - Self { - context: Arc::new(context), - } - } +// impl Server { +// pub fn new(context: ServerContext) -> Self { +// Self { +// context: Arc::new(context), +// } +// } - pub fn context(&self) -> &ServerContext { - &self.context - } +// pub fn context(&self) -> &ServerContext { +// &self.context +// } - pub fn start(&self) { - start_server(self.context.clone()); - } -} +// pub fn start(&self) { +// start_server(self.context.clone()); +// } +// } -impl Default for Server { - fn default() -> Self { - Self { - context: Arc::new(ServerContext::new(Arc::new(Config::default()))), - } - } -} +// impl Default for Server { +// fn default() -> Self { +// Self { +// context: Arc::new(ServerContext::new(Arc::new(Config::default()))), +// } +// } +// } From 2cf3b09d9d50cc5678f3c17f44ce237d0e851c23 Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Tue, 6 May 2025 20:52:46 +0300 Subject: [PATCH 55/57] Player for save player pos --- src/minecraft/world/entity.rs | 6 ++++++ src/minecraft/world/player.rs | 10 ++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/minecraft/world/entity.rs create mode 100644 src/minecraft/world/player.rs diff --git a/src/minecraft/world/entity.rs b/src/minecraft/world/entity.rs new file mode 100644 index 0000000..61dc4aa --- /dev/null +++ b/src/minecraft/world/entity.rs @@ -0,0 +1,6 @@ + + + +struct Entity { + position: Vec3 +} \ No newline at end of file diff --git a/src/minecraft/world/player.rs b/src/minecraft/world/player.rs new file mode 100644 index 0000000..eb8f352 --- /dev/null +++ b/src/minecraft/world/player.rs @@ -0,0 +1,10 @@ + + + +struct Player { + entity_id: i32 +} + +impl Player for Entity { + +} \ No newline at end of file From 8f0f0c6a5e1a9b3826153f09d6b9b7e547bae720 Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Tue, 6 May 2025 22:33:03 +0300 Subject: [PATCH 56/57] =?UTF-8?q?=D0=B7=D0=B5=D0=BC=D0=BB=D1=8F=20=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=BA=D0=B0=D0=BC=D0=BD=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/protocol/play.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/play.rs b/src/protocol/play.rs index 4c95a8f..ac02334 100644 --- a/src/protocol/play.rs +++ b/src/protocol/play.rs @@ -177,7 +177,7 @@ pub fn send_example_chunk(client: Arc, x: i32, z: i32) -> Result< // blocks paletted container chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format - chunk_data.write_varint(1)?; // block state id in the registry (1 for stone) + chunk_data.write_varint(10)?; // block state id in the registry (1 for stone) // biomes palleted container chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format From b86dd46b3682c4fdf4da245d6d101c81073b5ebf Mon Sep 17 00:00:00 2001 From: GIKExe <72767917+GIKExe@users.noreply.github.com> Date: Tue, 6 May 2025 22:49:53 +0300 Subject: [PATCH 57/57] ? --- src/minecraft/world/entity.rs | 6 ------ src/minecraft/world/player.rs | 10 ---------- 2 files changed, 16 deletions(-) delete mode 100644 src/minecraft/world/entity.rs delete mode 100644 src/minecraft/world/player.rs diff --git a/src/minecraft/world/entity.rs b/src/minecraft/world/entity.rs deleted file mode 100644 index 61dc4aa..0000000 --- a/src/minecraft/world/entity.rs +++ /dev/null @@ -1,6 +0,0 @@ - - - -struct Entity { - position: Vec3 -} \ No newline at end of file diff --git a/src/minecraft/world/player.rs b/src/minecraft/world/player.rs deleted file mode 100644 index eb8f352..0000000 --- a/src/minecraft/world/player.rs +++ /dev/null @@ -1,10 +0,0 @@ - - - -struct Player { - entity_id: i32 -} - -impl Player for Entity { - -} \ No newline at end of file