From e1ccb6ec7d52656c34931722cde008dedf6c18d0 Mon Sep 17 00:00:00 2001 From: TriMill Date: Sun, 4 Dec 2022 01:06:21 -0500 Subject: [PATCH] improvements --- Cargo.lock | 309 ++++++++++++++++++++++++++++++++ Cargo.toml | 4 +- plugins/chat/main.lua | 45 +++++ plugins/example_plugin/main.lua | 23 --- src/main.rs | 22 ++- src/network/client.rs | 56 ++++++ src/network/mod.rs | 237 +----------------------- src/network/server.rs | 230 ++++++++++++++++++++++++ src/plugins.rs | 128 ------------- src/plugins/mod.rs | 177 ++++++++++++++++++ src/plugins/plugin.rs | 36 ++++ src/protocol/data.rs | 10 +- 12 files changed, 880 insertions(+), 397 deletions(-) create mode 100644 plugins/chat/main.lua delete mode 100644 plugins/example_plugin/main.lua create mode 100644 src/network/client.rs create mode 100644 src/network/server.rs delete mode 100644 src/plugins.rs create mode 100644 src/plugins/mod.rs create mode 100644 src/plugins/plugin.rs diff --git a/Cargo.lock b/Cargo.lock index 3d154c0..e795d96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bstr" version = "0.2.17" @@ -56,6 +71,55 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "erased-serde" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11" +dependencies = [ + "serde", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.25" @@ -78,12 +142,79 @@ dependencies = [ "serde", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.5.0" @@ -107,10 +238,28 @@ checksum = "4351dbcc863fb6249c81b3bd0c8001214e9d4d44d22cabda17026353a77fe612" dependencies = [ "bstr", "cc", + "erased-serde", + "mlua_derive", "num-traits", "once_cell", "pkg-config", "rustc-hash", + "serde", +] + +[[package]] +name = "mlua_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9214e60d3cf1643013b107330fcd374ccec1e4ba1eef76e7e5da5e8202e71c0" +dependencies = [ + "itertools", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn", ] [[package]] @@ -134,6 +283,30 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.47" @@ -147,7 +320,9 @@ dependencies = [ name = "quectocraft" version = "0.1.0" dependencies = [ + "env_logger", "hematite-nbt", + "log", "mlua", "serde", "serde_json", @@ -163,12 +338,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustix" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.11" @@ -217,6 +423,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "unicode-ident" version = "1.0.5" @@ -228,3 +443,97 @@ name = "uuid" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index a34df41..299883d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,7 @@ edition = "2021" hematite-nbt = "0.5" serde_json = "1.0" serde = "1.0" -mlua = { version = "0.8", features = ["lua54"] } +mlua = { version = "0.8", features = ["lua54", "macros", "serialize"] } uuid = "1.2" +log = "0.4.0" +env_logger = "0.10.0" diff --git a/plugins/chat/main.lua b/plugins/chat/main.lua new file mode 100644 index 0000000..8cd727e --- /dev/null +++ b/plugins/chat/main.lua @@ -0,0 +1,45 @@ +local plugin = { + id = "chat", + name = "Chat", + description = "Provides Minecraft-style chat. Messages sent by one client will be broadcasted to every client.", + authors = { "trimill" }, + version = "0.1.0", +} + +local logger = nil + +function plugin.init() + logger = server.initLogger(plugin) + logger.info("Loaded chat") +end + +function plugin.playerJoin(name) + logger.info(name .. " joined the game") + server.broadcast({ + translate = "multiplayer.player.joined", + with = { {text = name} }, + color = "yellow" + }) +end + +function plugin.playerLeave(name) + logger.info(name .. " left the game") + server.broadcast({ + translate = "multiplayer.player.left", + with = { {text = name} }, + color = "yellow" + }) +end + +function plugin.chatMessage(message, author) + logger.info("<" .. author .. "> " .. message) + server.broadcast({ + translate = "chat.type.text", + with = { + {text = author}, + {text = message} + } + }) +end + +return plugin diff --git a/plugins/example_plugin/main.lua b/plugins/example_plugin/main.lua deleted file mode 100644 index 5097c82..0000000 --- a/plugins/example_plugin/main.lua +++ /dev/null @@ -1,23 +0,0 @@ -local plugin = { - id = "example_plugin", - name = "Example Plugin", - version = "0.1.0", -} - -function plugin.init() - print("PLUGIN init") -end - -function plugin.playerJoin(name, uuid) - print("PLUGIN player joined: " .. name .. " uuid " .. uuid) -end - -function plugin.playerLeave(name, uuid) - print("PLUGIN player left: " .. name .. " uuid " .. uuid) -end - -function plugin.chatMessage(message, author, authorUuid) - print("PLUGIN message from " .. author .. ": " .. message) -end - -return plugin diff --git a/src/main.rs b/src/main.rs index 069ff2a..92a757a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,28 @@ -use std::time::Duration; +use std::time::{Duration, SystemTime}; +use env_logger::Env; +use log::info; use mlua::Lua; use network::NetworkServer; -use plugins::{Plugin, Plugins}; +use plugins::Plugins; mod plugins; mod protocol; mod network; +pub const VERSION: &'static str = std::env!("CARGO_PKG_VERSION"); + fn main() { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + + info!("quectocraft version {}", VERSION); + let lua = Lua::new(); - let plugin = Plugin::load("plugins/example_plugin/main.lua".into(), &lua).unwrap(); - let mut plugins = Plugins::new(&lua).unwrap(); - plugins.add_plugin(plugin); + let mut plugins = Plugins::new(&lua).expect("Error initializing lua environment"); + std::fs::create_dir_all("plugins").expect("couldn't create the plugins directory"); + plugins.load_plugins(); + + info!("{} plugins loaded", plugins.count()); let mut server = NetworkServer::new("127.0.0.1:25565".to_owned(), plugins); let sleep_dur = Duration::from_millis(5); @@ -20,11 +30,11 @@ fn main() { loop { server.get_new_clients(); server.handle_connections(); - std::thread::sleep(sleep_dur); if i % 1024 == 0 { server.send_keep_alive(); i = 0; } i += 1; + std::thread::sleep(sleep_dur); } } diff --git a/src/network/client.rs b/src/network/client.rs new file mode 100644 index 0000000..79ce48f --- /dev/null +++ b/src/network/client.rs @@ -0,0 +1,56 @@ +use std::{net::{TcpStream, Shutdown}, thread, io::Write, sync::mpsc::{Receiver, Sender, TryRecvError}, time::Duration}; + +use log::info; + +use crate::{protocol::{data::{PacketDecoder}, serverbound::*, clientbound::*, NetworkState}}; + +use super::Player; + +pub struct NetworkClient { + pub id: i32, + pub play: bool, + pub closed: bool, + pub stream: TcpStream, + pub serverbound: Receiver, + pub player: Option, +} + +impl NetworkClient { + pub fn listen(mut stream: TcpStream, send: Sender) { + let mut state = NetworkState::Handshake; + let dur = Duration::from_millis(5); + loop { + match PacketDecoder::decode(&mut stream) { + Ok(decoder) => { + let packet = ServerBoundPacket::decode(&mut state, decoder); + send.send(packet).unwrap(); + } + Err(_) => break + } + thread::sleep(dur) + } + let _ = stream.shutdown(Shutdown::Both); + } + + pub fn recv_packet(&mut self, alive: &mut bool) -> Option { + match self.serverbound.try_recv() { + Ok(packet) => Some(packet), + Err(TryRecvError::Empty) => None, + Err(TryRecvError::Disconnected) => { + *alive = false; + None + } + } + } + + pub fn send_packet(&mut self, packet: ClientBoundPacket) -> std::io::Result<()> { + self.stream.write_all(&packet.encode())?; + Ok(()) + } + + pub fn close(&mut self) { + info!("closed connection id {}", self.id); + let _ = self.stream.shutdown(Shutdown::Both); + self.closed = true; + } +} diff --git a/src/network/mod.rs b/src/network/mod.rs index ed6533e..952c710 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,243 +1,12 @@ -use std::{net::{TcpStream, TcpListener, Shutdown}, thread, io::Write, sync::mpsc::{Receiver, Sender, channel, TryRecvError}, time::Duration}; - -use serde_json::json; use uuid::Uuid; -use crate::{protocol::{data::{PacketDecoder, PacketEncoder}, serverbound::*, clientbound::*, NetworkState}, plugins::Plugins}; +mod client; +mod server; -pub struct NetworkServer<'lua> { - plugins: Plugins<'lua>, - new_clients: Receiver, - clients: Vec, -} - -impl <'lua> NetworkServer<'lua> { - pub fn new(addr: String, plugins: Plugins<'lua>) -> Self { - let (send, recv) = channel(); - thread::spawn(move || Self::listen(&addr, send)); - plugins.init(); - Self { - plugins, - new_clients: recv, - clients: Vec::new(), - } - } - - fn listen(addr: &str, send_clients: Sender) { - println!("listening for connections"); - let listener = TcpListener::bind(addr).unwrap(); - for (id, stream) in listener.incoming().enumerate() { - let stream = stream.unwrap(); - println!("got connection from {} (id {})", stream.peer_addr().unwrap(), id); - let stream_2 = stream.try_clone().unwrap(); - let (send, recv) = channel(); - thread::spawn(|| NetworkClient::listen(stream_2, send)); - let client = NetworkClient { - id: id as i32, - play: false, - closed: false, - stream, - serverbound: recv, - player: None, - }; - send_clients.send(client).unwrap(); - } - } - - pub fn get_new_clients(&mut self) { - while let Ok(client) = self.new_clients.try_recv() { - self.clients.push(client); - } - } - - pub fn send_keep_alive(&mut self) { - let mut closed = Vec::new(); - for client in self.clients.iter_mut() { - if client.play { - if let Err(_) = client.send_packet(ClientBoundPacket::KeepAlive(0)) { - client.close(); - if let Some(pl) = &client.player { - self.plugins.player_leave(pl); - } - closed.push(client.id); - } - } - } - self.clients.retain(|x| !closed.contains(&x.id)); - } - - pub fn handle_connections(&mut self) { - let mut closed = Vec::new(); - for i in 0..self.clients.len() { - let client: &mut NetworkClient = unsafe { - &mut *(self.clients.get_unchecked_mut(i) as *mut _) - }; - let mut alive = true; - while let Some(packet) = client.recv_packet(&mut alive) { - if let Err(_) = self.handle_packet(client, packet) { - alive = false; - break; - } - } - if !alive && !client.closed { - closed.push(client.id); - if let Some(pl) = &client.player { - self.plugins.player_leave(pl); - } - client.close(); - } - } - self.clients.retain(|x| !closed.contains(&x.id)); - } - - fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> std::io::Result<()> { - match packet { - ServerBoundPacket::Ignored(_) => (), - ServerBoundPacket::Unknown(id) => println!("unknown: {}", id), - ServerBoundPacket::Handshake(_) => (), - ServerBoundPacket::StatusRequest() - => client.send_packet(ClientBoundPacket::StatusResponse( - r#"{"version":{"name":"1.19.2","protocol":760}}"#.to_owned() - ))?, - ServerBoundPacket::PingRequest(n) => { - client.send_packet(ClientBoundPacket::PingResponse(n))?; - client.close(); - } - ServerBoundPacket::LoginStart(login_start) => { - client.player = Some(Player { - name: login_start.name.clone(), - uuid: login_start.uuid.unwrap(), - }); - client.play = true; - client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess { - name: login_start.name, - uuid: login_start.uuid.unwrap(), - }))?; - self.plugins.player_join(client.player.as_ref().unwrap()); - self.post_login(client)?; - } - ServerBoundPacket::ChatMessage(msg) => { - self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message); - } - } - Ok(()) - } - - fn post_login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> { - client.send_packet(ClientBoundPacket::LoginPlay(LoginPlay { - eid: client.id, - is_hardcore: false, - gamemode: 1, - prev_gamemode: 1, - dimensions: vec![ - "minecraft:world".to_owned(), - ], - registry_codec: include_bytes!("../resources/registry_codec.nbt").to_vec(), - dimension_type: "minecraft:the_end".to_owned(), - dimension_name: "minecraft:world".to_owned(), - seed_hash: 0, - max_players: 0, - view_distance: 8, - sim_distance: 8, - reduced_debug_info: false, - respawn_screen: false, - is_debug: false, - is_flat: false, - death_location: None, - }))?; - client.send_packet(ClientBoundPacket::PluginMessage(PluginMessage { - channel: "minecraft:brand".to_owned(), - data: { - let mut data = Vec::new(); - data.write_string(32767, "QuectoCraft"); - data - } - }))?; - client.send_packet(ClientBoundPacket::PlayerAbilities(0x0d, 0.05, 0.1))?; - let mut chunk_data: Vec = Vec::new(); - for _ in 0..(384 / 16) { - // number of non-air blocks - chunk_data.write_short(0); - // block states - chunk_data.write_ubyte(0); - chunk_data.write_varint(0); - chunk_data.write_varint(0); - // biomes - chunk_data.write_ubyte(0); - chunk_data.write_varint(0); - chunk_data.write_varint(0); - } - let hmdata = vec![0i64; 37]; - let mut heightmap = nbt::Blob::new(); - heightmap.insert("MOTION_BLOCKING", hmdata).unwrap(); - client.send_packet(ClientBoundPacket::ChunkData(ChunkData { - x: 0, - z: 0, - heightmap, - chunk_data, - }))?; - client.send_packet(ClientBoundPacket::SyncPlayerPosition(SyncPlayerPosition { - x: 0.0, - y: 64.0, - z: 0.0, - yaw: 0.0, - pitch: 0.0, - flags: 0, - teleport_id: 0, - dismount: false - }))?; - Ok(()) - } - -} +pub use server::NetworkServer; #[derive(Debug)] pub struct Player { pub name: String, pub uuid: Uuid, } - -struct NetworkClient { - pub id: i32, - pub play: bool, - pub closed: bool, - stream: TcpStream, - serverbound: Receiver, - player: Option, -} - -impl NetworkClient { - pub fn listen(mut stream: TcpStream, send: Sender) { - let mut state = NetworkState::Handshake; - let dur = Duration::from_millis(5); - loop { - if let Some(decoder) = PacketDecoder::decode(&mut stream) { - let packet = ServerBoundPacket::decode(&mut state, decoder); - send.send(packet).unwrap(); - } - thread::sleep(dur) - } - } - - pub fn recv_packet(&mut self, alive: &mut bool) -> Option { - match self.serverbound.try_recv() { - Ok(packet) => Some(packet), - Err(TryRecvError::Empty) => None, - Err(TryRecvError::Disconnected) => { - *alive = false; - None - } - } - } - - pub fn send_packet(&mut self, packet: ClientBoundPacket) -> std::io::Result<()> { - self.stream.write_all(&packet.encode())?; - Ok(()) - } - - pub fn close(&mut self) { - println!("closed connection with {}", self.id); - let _ = self.stream.shutdown(Shutdown::Both); - self.closed = true; - } -} diff --git a/src/network/server.rs b/src/network/server.rs new file mode 100644 index 0000000..88f26dd --- /dev/null +++ b/src/network/server.rs @@ -0,0 +1,230 @@ +use std::{net::TcpListener, thread, sync::mpsc::{Receiver, Sender, channel}, collections::HashSet}; + +use log::{info, warn, trace, debug, error}; + +use crate::{protocol::{data::PacketEncoder, serverbound::*, clientbound::*}, plugins::{Plugins, Response}, VERSION}; + +use super::{client::NetworkClient, Player}; + +pub struct NetworkServer<'lua> { + plugins: Plugins<'lua>, + new_clients: Receiver, + clients: Vec, +} + +impl <'lua> NetworkServer<'lua> { + pub fn new(addr: String, plugins: Plugins<'lua>) -> Self { + let (send, recv) = channel(); + info!("initializing plugins"); + plugins.init(); + thread::spawn(move || Self::listen(&addr, send)); + Self { + plugins, + new_clients: recv, + clients: Vec::new(), + } + } + + fn listen(addr: &str, send_clients: Sender) { + info!("listening on {}", addr); + let listener = TcpListener::bind(addr).unwrap(); + for (id, stream) in listener.incoming().enumerate() { + let stream = stream.unwrap(); + info!("connection from {} (id {})", stream.peer_addr().unwrap(), id); + let stream_2 = stream.try_clone().unwrap(); + let (send, recv) = channel(); + thread::spawn(|| NetworkClient::listen(stream_2, send)); + let client = NetworkClient { + id: id as i32, + play: false, + closed: false, + stream, + serverbound: recv, + player: None, + }; + send_clients.send(client).unwrap(); + } + } + + pub fn get_new_clients(&mut self) { + while let Ok(client) = self.new_clients.try_recv() { + self.clients.push(client); + } + } + + pub fn send_keep_alive(&mut self) { + let mut closed = Vec::new(); + for client in self.clients.iter_mut() { + if client.play { + if let Err(_) = client.send_packet(ClientBoundPacket::KeepAlive(0)) { + client.close(); + if let Some(pl) = &client.player { + self.plugins.player_leave(pl); + } + closed.push(client.id); + } + } + } + self.clients.retain(|x| !closed.contains(&x.id)); + } + + pub fn handle_connections(&mut self) { + let mut closed = HashSet::new(); + for i in 0..self.clients.len() { + let client: &mut NetworkClient = unsafe { + &mut *(self.clients.get_unchecked_mut(i) as *mut _) + }; + let mut alive = true; + while let Some(packet) = client.recv_packet(&mut alive) { + if let Err(_) = self.handle_packet(client, packet) { + alive = false; + break + } + } + if !alive && !client.closed { + closed.insert(client.id); + if let Some(pl) = &client.player { + self.plugins.player_leave(pl); + } + client.close(); + } + } + for response in self.plugins.get_responses() { + let _ = self.handle_plugin_response(response); + } + self.clients.retain(|x| !closed.contains(&x.id)); + } + + fn handle_plugin_response(&mut self, response: Response) -> std::io::Result<()> { + match response { + Response::Log { level, origin, message } => { + match level { + 0 => trace!(target: &origin, "{}", message), + 1 => debug!(target: &origin, "{}", message), + 2 => info!(target: &origin, "{}", message), + 3 => warn!(target: &origin, "{}", message), + 4 => error!(target: &origin, "{}", message), + _ => warn!("unknown log level: {}", level) + } + }, + Response::Message { player, message } => { + for client in self.clients.iter_mut() { + if let Some(p) = &client.player { + if p.name == player || p.uuid.to_string() == player { + client.send_packet(ClientBoundPacket::SystemChatMessage(message, false))?; + break + } + } + } + }, + Response::Broadcast { message } => { + for client in self.clients.iter_mut() { + if client.player.is_some() { + client.send_packet(ClientBoundPacket::SystemChatMessage(message.clone(), false))?; + } + } + }, + } + Ok(()) + } + + fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> std::io::Result<()> { + match packet { + ServerBoundPacket::Ignored(_) => (), + ServerBoundPacket::Unknown(id) => warn!("unknown: {}", id), + ServerBoundPacket::Handshake(_) => (), + ServerBoundPacket::StatusRequest() + => client.send_packet(ClientBoundPacket::StatusResponse( + r#"{"version":{"name":"1.19.2","protocol":760}}"#.to_owned() + ))?, + ServerBoundPacket::PingRequest(n) => { + client.send_packet(ClientBoundPacket::PingResponse(n))?; + client.close(); + } + ServerBoundPacket::LoginStart(login_start) => { + client.player = Some(Player { + name: login_start.name.clone(), + uuid: login_start.uuid.unwrap(), + }); + client.play = true; + client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess { + name: login_start.name, + uuid: login_start.uuid.unwrap(), + }))?; + self.plugins.player_join(client.player.as_ref().unwrap()); + self.post_login(client)?; + } + ServerBoundPacket::ChatMessage(msg) => { + self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message); + } + } + Ok(()) + } + + fn post_login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> { + client.send_packet(ClientBoundPacket::LoginPlay(LoginPlay { + eid: client.id, + is_hardcore: false, + gamemode: 1, + prev_gamemode: 1, + dimensions: vec![ + "minecraft:world".to_owned(), + ], + registry_codec: include_bytes!("../resources/registry_codec.nbt").to_vec(), + dimension_type: "minecraft:the_end".to_owned(), + dimension_name: "minecraft:world".to_owned(), + seed_hash: 0, + max_players: 0, + view_distance: 8, + sim_distance: 8, + reduced_debug_info: false, + respawn_screen: false, + is_debug: false, + is_flat: false, + death_location: None, + }))?; + client.send_packet(ClientBoundPacket::PluginMessage(PluginMessage { + channel: "minecraft:brand".to_owned(), + data: { + let mut data = Vec::new(); + data.write_string(32767, &format!("quectocraft {}", VERSION)); + data + } + }))?; + client.send_packet(ClientBoundPacket::PlayerAbilities(0x0d, 0.05, 0.1))?; + let mut chunk_data: Vec = Vec::new(); + for _ in 0..(384 / 16) { + // number of non-air blocks + chunk_data.write_short(0); + // block states + chunk_data.write_ubyte(0); + chunk_data.write_varint(0); + chunk_data.write_varint(0); + // biomes + chunk_data.write_ubyte(0); + chunk_data.write_varint(0); + chunk_data.write_varint(0); + } + let hmdata = vec![0i64; 37]; + let mut heightmap = nbt::Blob::new(); + heightmap.insert("MOTION_BLOCKING", hmdata).unwrap(); + client.send_packet(ClientBoundPacket::ChunkData(ChunkData { + x: 0, + z: 0, + heightmap, + chunk_data, + }))?; + client.send_packet(ClientBoundPacket::SyncPlayerPosition(SyncPlayerPosition { + x: 0.0, + y: 64.0, + z: 0.0, + yaw: 0.0, + pitch: 0.0, + flags: 0, + teleport_id: 0, + dismount: false + }))?; + Ok(()) + } + +} diff --git a/src/plugins.rs b/src/plugins.rs deleted file mode 100644 index 0e383fe..0000000 --- a/src/plugins.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::{path::{Path, PathBuf}, str::FromStr}; - -use mlua::{Function, Lua, Error, Table}; -use uuid::Uuid; - -use crate::network::Player; - -pub struct EventHandlers<'lua> { - init: Option>, - player_join: Option>, - player_leave: Option>, - chat_message: Option>, -} - -pub struct Plugin<'lua> { - pub id: String, - pub name: String, - pub version: String, - pub event_handlers: EventHandlers<'lua>, -} - -impl <'lua> Plugin<'lua> { - pub fn load(path: &str, lua: &'lua Lua) -> Result> { - let path = PathBuf::from_str(path).unwrap(); - let chunk = lua.load(&path); - let module: Table = chunk.eval()?; - - let id: String = module.get("id")?; - let name: String = module.get("name").unwrap_or_else(|_| id.clone()); - let version: String = module.get("version").unwrap_or_else(|_| "?".to_owned()); - - let init: Option> = module.get("init").ok(); - let player_join: Option> = module.get("playerJoin").ok(); - let player_leave: Option> = module.get("playerLeave").ok(); - let chat_message: Option> = module.get("chatMessage").ok(); - - let event_handlers = EventHandlers { init, player_join, player_leave, chat_message }; - Ok(Plugin { id, name, version, event_handlers }) - } -} - -pub struct Plugins<'lua> { - lua: &'lua Lua, - plugins: Vec> -} - -impl <'lua> Plugins<'lua> { - pub fn new(lua: &'lua Lua) -> Result { - let server = lua.create_table()?; - let players = lua.create_table()?; - server.set("players", players)?; - let fn_send = lua.create_function(|_, (uuid, message): (String, String)| { - Ok(()) - })?; - server.set("send", fn_send)?; - lua.globals().set("server", server)?; - Ok(Self { - lua, - plugins: Vec::new(), - }) - } - - pub fn add_plugin(&mut self, pl: Plugin<'lua>) { - self.plugins.push(pl); - } - - pub fn init(&self) { - for pl in &self.plugins { - if let Some(init) = &pl.event_handlers.init { - if let Err(e) = init.call::<_, ()>(()) { - println!("Error in plugin {}: {}", pl.name, e); - } - } - } - } - - pub fn player_join(&self, player: &Player) { - if let Err(e) = self.add_player(player) { - println!("Error adding player: {}", e); - return - } - for pl in &self.plugins { - if let Some(init) = &pl.event_handlers.player_join { - if let Err(e) = init.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) { - println!("Error in plugin {}: {}", pl.name, e); - } - } - } - } - - fn add_player(&self, player: &Player) -> Result<(), mlua::Error> { - let server: Table = self.lua.globals().get("server")?; - let players: Table = server.get("players")?; - players.set(player.uuid.to_string(), player.name.as_str())?; - Ok(()) - } - - pub fn player_leave(&self, player: &Player) { - if let Err(e) = self.remove_player(player.uuid) { - println!("Error removing player: {}", e); - return - } - for pl in &self.plugins { - if let Some(func) = &pl.event_handlers.player_leave { - if let Err(e) = func.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) { - println!("Error in plugin {}: {}", pl.name, e); - } - } - } - } - - fn remove_player(&self, uuid: Uuid) -> Result<(), mlua::Error> { - let server: Table = self.lua.globals().get("server")?; - let players: Table = server.get("players")?; - players.set(uuid.to_string(), mlua::Nil)?; - Ok(()) - } - - pub fn chat_message(&self, player: &Player, message: &str) { - for pl in &self.plugins { - if let Some(func) = &pl.event_handlers.chat_message { - if let Err(e) = func.call::<_, ()>((message, player.name.as_str(), player.uuid.to_string())) { - println!("Error in plugin {}: {}", pl.name, e); - } - } - } - } -} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs new file mode 100644 index 0000000..f092a71 --- /dev/null +++ b/src/plugins/mod.rs @@ -0,0 +1,177 @@ +use std::{path::Path, fs::{self, read_dir}}; + +use log::warn; +use mlua::{Lua, Table, chunk, LuaSerdeExt}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::network::Player; + +use self::plugin::Plugin; + +mod plugin; + +pub struct Plugins<'lua> { + lua: &'lua Lua, + plugins: Vec> +} + +impl <'lua> Plugins<'lua> { + pub fn new(lua: &'lua Lua) -> Result { + lua.load(chunk!{ + server = { players = {} } + _qc = { responses = {} } + function server.sendMessage(player, message) + if type(player) ~= "string" then + error("player must be a string") + end + if type(message) == "table" then + table.insert(_qc.responses, {type = "message", player = player, message = message}) + elseif type(message) == "string" then + table.insert(_qc.responses, {type = "message", player = player, message = { text = message}}) + else + error("message must be a string or table") + end + end + function server.broadcast(message) + if type(message) == "table" then + table.insert(_qc.responses, { type = "broadcast", message = message }) + elseif type(message) == "string" then + table.insert(_qc.responses, { type = "broadcast", message = { text = message } }) + else + error("message must be a string or table") + end + end + function server.initLogger(plugin) + local function log_for(level) + return function(message) + table.insert(_qc.responses, { + type = "log", origin = plugin.id, message = message, level = level + }) + end + end + return { + trace = log_for(0), + debug = log_for(1), + info = log_for(2), + warn = log_for(3), + error = log_for(4), + } + end + }).exec()?; + Ok(Self { + lua, + plugins: Vec::new(), + }) + } + + pub fn load_plugins(&mut self) { + let files = read_dir("plugins").expect("couldn't read plugins directory"); + for file in files { + let file = file.expect("couldn't read contents of plugins directory"); + let path = if file.file_type().expect("couldn't get type of plugin file").is_dir() { + let mut main = file.path(); + main.push("main.lua"); + main + } else { + file.path() + }; + let pl = Plugin::load(&path, &self.lua).expect("error loading plugin"); + self.plugins.push(pl); + } + } + + pub fn count(&self) -> usize { + self.plugins.len() + } + + pub fn get_responses(&self) -> Vec { + match self.get_responses_inner() { + Ok(x) => x, + Err(e) => { + warn!("error getting responses: {}", e); + Vec::new() + } + } + } + + fn get_responses_inner(&self) -> Result, Box> { + let qc: Table = self.lua.globals().get("_qc")?; + let responses: Vec = self.lua.from_value(qc.get("responses")?)?; + qc.set("responses", self.lua.create_table()?)?; + Ok(responses) + } + + pub fn init(&self) { + for pl in &self.plugins { + if let Some(init) = &pl.event_handlers.init { + if let Err(e) = init.call::<_, ()>(()) { + warn!("Error in plugin {}: {}", pl.name, e); + } + } + } + } + + pub fn player_join(&self, player: &Player) { + if let Err(e) = self.add_player(player) { + warn!("Error adding player: {}", e); + return + } + for pl in &self.plugins { + if let Some(init) = &pl.event_handlers.player_join { + if let Err(e) = init.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) { + warn!("Error in plugin {}: {}", pl.name, e); + } + } + } + } + + fn add_player(&self, player: &Player) -> Result<(), mlua::Error> { + let server: Table = self.lua.globals().get("server")?; + let players: Table = server.get("players")?; + players.set(player.uuid.to_string(), player.name.as_str())?; + Ok(()) + } + + pub fn player_leave(&self, player: &Player) { + if let Err(e) = self.remove_player(player.uuid) { + warn!("Error removing player: {}", e); + return + } + for pl in &self.plugins { + if let Some(func) = &pl.event_handlers.player_leave { + if let Err(e) = func.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) { + warn!("Error in plugin {}: {}", pl.name, e); + } + } + } + } + + fn remove_player(&self, uuid: Uuid) -> Result<(), mlua::Error> { + let server: Table = self.lua.globals().get("server")?; + let players: Table = server.get("players")?; + players.set(uuid.to_string(), mlua::Nil)?; + Ok(()) + } + + pub fn chat_message(&self, player: &Player, message: &str) { + for pl in &self.plugins { + if let Some(func) = &pl.event_handlers.chat_message { + if let Err(e) = func.call::<_, ()>((message, player.name.as_str(), player.uuid.to_string())) { + warn!("Error in plugin {}: {}", pl.name, e); + } + } + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(tag="type")] +pub enum Response { + #[serde(rename = "log")] + Log { level: i32, origin: String, message: String }, + #[serde(rename = "message")] + Message { player: String, message: serde_json::Value }, + #[serde(rename = "broadcast")] + Broadcast { message: serde_json::Value }, +} diff --git a/src/plugins/plugin.rs b/src/plugins/plugin.rs new file mode 100644 index 0000000..81779f1 --- /dev/null +++ b/src/plugins/plugin.rs @@ -0,0 +1,36 @@ +use std::{path::{PathBuf, Path}, str::FromStr}; + +use mlua::{Function, Table, Lua}; + +pub struct EventHandlers<'lua> { + pub init: Option>, + pub player_join: Option>, + pub player_leave: Option>, + pub chat_message: Option>, +} + +pub struct Plugin<'lua> { + pub id: String, + pub name: String, + pub version: String, + pub event_handlers: EventHandlers<'lua>, +} + +impl <'lua> Plugin<'lua> { + pub fn load(path: &Path, lua: &'lua Lua) -> Result> { + let chunk = lua.load(path); + let module: Table = chunk.eval()?; + + let id: String = module.get("id")?; + let name: String = module.get("name").unwrap_or_else(|_| id.clone()); + let version: String = module.get("version").unwrap_or_else(|_| "?".to_owned()); + + let init: Option> = module.get("init").ok(); + let player_join: Option> = module.get("playerJoin").ok(); + let player_leave: Option> = module.get("playerLeave").ok(); + let chat_message: Option> = module.get("chatMessage").ok(); + + let event_handlers = EventHandlers { init, player_join, player_leave, chat_message }; + Ok(Plugin { id, name, version, event_handlers }) + } +} diff --git a/src/protocol/data.rs b/src/protocol/data.rs index be14c5c..4e13084 100644 --- a/src/protocol/data.rs +++ b/src/protocol/data.rs @@ -137,7 +137,7 @@ pub struct PacketDecoder { } impl PacketDecoder { - pub fn decode(read: &mut impl Read) -> Option { + pub fn decode(read: &mut impl Read) -> Result { let size = read_varint(read)? as usize; let mut data = vec![0; size]; read.read_exact(&mut data).unwrap(); @@ -147,7 +147,7 @@ impl PacketDecoder { packet_id: 0 }; decoder.packet_id = decoder.read_varint(); - return Some(decoder); + return Ok(decoder); } pub fn packet_id(&self) -> i32 { @@ -264,12 +264,12 @@ impl PacketDecoder { } } -fn read_varint(read: &mut impl Read) -> Option { +fn read_varint(read: &mut impl Read) -> Result { let mut result = 0; let mut count = 0; loop { let mut byte = [0]; - read.read_exact(&mut byte).ok()?; + read.read_exact(&mut byte)?; let byte = byte[0]; result |= ((byte & 0x7f) as i32) << (7 * count); count += 1; @@ -280,5 +280,5 @@ fn read_varint(read: &mut impl Read) -> Option { break } } - Some(result) + Ok(result) }