From 1c2cf9bd3f9c24bbdfa2f416fb749b2f134e1d25 Mon Sep 17 00:00:00 2001 From: TriMill Date: Tue, 1 Aug 2023 00:56:38 -0400 Subject: [PATCH] features --- .gitignore | 1 + Cargo.lock | 76 ++++-- Cargo.toml | 4 +- README.md | 12 + plugins/example.lua | 21 +- plugins/mcchat.lua | 39 +++ src/client/event.rs | 6 +- src/client/mod.rs | 25 +- src/main.rs | 28 +++ src/plugin/event.rs | 8 +- src/plugin/mod.rs | 165 ++++++------ src/plugin/plugin.rs | 130 +++++----- src/plugin/server.rs | 165 ++++++++++-- src/protocol/common.rs | 3 +- src/protocol/mod.rs | 17 +- src/protocol/v1_16_5.rs | 138 ++++++++++ src/protocol/v1_17_1.rs | 138 ++++++++++ src/protocol/v1_18_2.rs | 146 +++++++++++ src/protocol/{v1_19_4/mod.rs => v1_19_4.rs} | 104 ++++---- src/protocol/{v1_20_1/mod.rs => v1_20_1.rs} | 120 +++++---- src/resources/dimcodec_1.16.5.nbt | Bin 0 -> 30478 bytes src/resources/dimcodec_1.17.1.nbt | Bin 0 -> 31161 bytes src/resources/dimcodec_1.18.2.nbt | Bin 0 -> 23595 bytes src/resources/dimension_1.16.5.nbt | Bin 0 -> 281 bytes src/resources/dimension_1.17.1.nbt | Bin 0 -> 306 bytes src/resources/dimension_1.18.2.nbt | Bin 0 -> 307 bytes src/resources/heightmap.nbt | Bin 0 -> 322 bytes .../registry_1.19.4.nbt} | Bin 32769 -> 32924 bytes .../registry_1.20.1.nbt} | Bin 39164 -> 39319 bytes src/server.rs | 237 ++++++++++++------ 30 files changed, 1187 insertions(+), 396 deletions(-) create mode 100644 README.md create mode 100644 plugins/mcchat.lua create mode 100644 src/protocol/v1_16_5.rs create mode 100644 src/protocol/v1_17_1.rs create mode 100644 src/protocol/v1_18_2.rs rename src/protocol/{v1_19_4/mod.rs => v1_19_4.rs} (74%) rename src/protocol/{v1_20_1/mod.rs => v1_20_1.rs} (74%) create mode 100644 src/resources/dimcodec_1.16.5.nbt create mode 100644 src/resources/dimcodec_1.17.1.nbt create mode 100644 src/resources/dimcodec_1.18.2.nbt create mode 100644 src/resources/dimension_1.16.5.nbt create mode 100644 src/resources/dimension_1.17.1.nbt create mode 100644 src/resources/dimension_1.18.2.nbt create mode 100644 src/resources/heightmap.nbt rename src/{protocol/v1_19_4/registry.nbt => resources/registry_1.19.4.nbt} (99%) rename src/{protocol/v1_20_1/registry.nbt => resources/registry_1.20.1.nbt} (99%) diff --git a/.gitignore b/.gitignore index ea8c4bf..9026c77 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.vscode diff --git a/Cargo.lock b/Cargo.lock index bd6058c..755d921 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,11 +67,12 @@ checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bstr" -version = "0.2.17" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", + "serde", ] [[package]] @@ -126,6 +127,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "erased-serde" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da96524cc884f6558f1769b6c46686af2fe8e8b4cd253bd5a3cdba8181b8e070" +dependencies = [ + "serde", +] + [[package]] name = "errno" version = "0.3.1" @@ -163,17 +173,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-task" version = "0.3.28" @@ -187,7 +186,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", - "futures-macro", "futures-task", "pin-project-lite", "pin-utils", @@ -297,19 +295,30 @@ dependencies = [ [[package]] name = "mlua" -version = "0.8.9" +version = "0.9.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07366ed2cd22a3b000aed076e2b68896fb46f06f1f5786c5962da73c0af01577" +checksum = "f5369212118d0f115c9adbe7f7905e36fb3ef2994266039c51fc3e96374d705d" dependencies = [ "bstr", - "cc", - "futures-core", - "futures-task", + "erased-serde", "futures-util", + "mlua-sys", "num-traits", "once_cell", - "pkg-config", "rustc-hash", + "serde", + "serde-value", +] + +[[package]] +name = "mlua-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3daecc55a656cae8e54fc599701ab597b67cdde68f780cac8c1c49b9cfff2f5" +dependencies = [ + "cc", + "cfg-if", + "pkg-config", ] [[package]] @@ -346,6 +355,15 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -405,6 +423,8 @@ dependencies = [ "hematite-nbt", "log", "mlua", + "semver", + "serde", "serde_json", "tokio", "uuid", @@ -494,6 +514,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + [[package]] name = "serde" version = "1.0.171" @@ -503,6 +529,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.171" diff --git a/Cargo.toml b/Cargo.toml index 9227ef1..0b16d5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,11 @@ edition = "2021" [dependencies] tokio = { version = "1.29", features = ["full"] } serde_json = "1.0" +serde = "1.0" anyhow = "1.0" uuid = { version = "1.4", features = ["v5"] } hematite-nbt = "0.5.2" log = "0.4" env_logger = "0.10" -mlua = { version = "0.8", features = ["luajit52", "async"] } +mlua = { version = "0.9.0-rc.1", features = ["luajit52", "async", "send", "serialize"] } +semver = "1.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e725ade --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Building + +quectocraft requires that `luajit` is installed. it should be +available via your package manager. + +## Alpine + +install `luajit`, `luajit-dev`, and `pkgconfig` and build with + +```sh +RUSTFLAGS="-C target-feature=-crt-static" cargo build --release +``` diff --git a/plugins/example.lua b/plugins/example.lua index 17d45e8..a956af7 100644 --- a/plugins/example.lua +++ b/plugins/example.lua @@ -1,20 +1,11 @@ return { id = "example", - name = "Example Plugin", - version = "0.1.0", - authors = {"John Doe", "Jane Doe"}, - description = "Example plugin", - license = "MIT", + server_version = "0.2", - init = function(self, srv) - self.info("initializing " .. self.name) - end, - - on_join = function(self, srv, uuid, name) - srv:kick(uuid, "people named " .. name .. " are not allowed here") - end, - - on_leave = function(self, srv, uuid, name) - self.info(name .. " left") + init = function(self) + srv:info("quectocraft version " .. srv.version) + for k, _ in pairs(srv.plugins) do + srv:info("found plugin: " .. k) + end end, } diff --git a/plugins/mcchat.lua b/plugins/mcchat.lua new file mode 100644 index 0000000..0162608 --- /dev/null +++ b/plugins/mcchat.lua @@ -0,0 +1,39 @@ +return { + -- id is required, server_version is recommended, all other fields are optional + id = "mcchat", + name = "MCChat", + version = "0.1.0", + server_version = "0.2", + authors = {"trimill"}, + description = "Adds basic chat and join/leave messages", + license = "MIT", + + on_join = function(self, _, name) + srv:info(name .. " joined the game") + srv:broadcast_msg({ + translate = "multiplayer.player.joined", + with = { { text = name } }, + color = "yellow", + }) + end, + + on_leave = function(self, _, name) + srv:info(name .. " left the game") + srv:broadcast_msg({ + translate = "multiplayer.player.left", + with = { { text = name } }, + color = "yellow", + }) + end, + + on_message = function(self, msg, _, name) + srv:info("<" .. name .. "> " .. msg) + srv:broadcast_chat({ + translate = "chat.type.text", + with = { + { text = name }, + { text = msg }, + }, + }) + end, +} diff --git a/src/client/event.rs b/src/client/event.rs index e79c31b..e7d11e4 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -1,10 +1,12 @@ -use crate::{Player, ClientInfo}; +use crate::{Player, ClientInfo, JsonValue}; #[derive(Debug)] pub enum ClientEvent { Handshake(ClientInfo), Join(Player), KeepAlive(i64), + Command(String), + Message(String), Disconnect, } @@ -12,4 +14,6 @@ pub enum ClientEvent { pub enum ServerEvent { Disconnect(Option), KeepAlive(i64), + SystemMessage(JsonValue, bool), + ChatMessage(JsonValue, JsonValue), } diff --git a/src/client/mod.rs b/src/client/mod.rs index 1ad1eab..256d07f 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -240,6 +240,7 @@ impl ClientState { data: b"\x0bquectocraft".to_vec(), }).await?; + // fabric clients need a 3x3 of chunks to load for x in -1..=1 { for z in -1..=1 { self.write_packet(ServerPacket::ChunkData { x, z }).await?; @@ -251,6 +252,12 @@ impl ClientState { angle: 0.0 }).await?; + self.write_packet(ServerPacket::PositionAndLook { + pos: [0.0, 64.0, 0.0], + look: [0.0, 0.0], + flags: 0, + }).await?; + loop { select! { ev = rx.recv() => match ev { Some(ServerEvent::Disconnect(msg)) => { @@ -262,9 +269,17 @@ impl ClientState { info!("#{} disconnecting: {}", self.id, msg.unwrap_or_default()); return Ok(()) }, + Some(ServerEvent::SystemMessage(msg, overlay)) => { + debug!("#{} sending system message: {msg}", self.id); + self.write_packet(ServerPacket::SystemMessage(msg, overlay)).await?; + }, + Some(ServerEvent::ChatMessage(msg, name)) => { + debug!("#{} sending chat message: {msg}", self.id); + self.write_packet(ServerPacket::ChatMessage(msg, name)).await?; + }, Some(ServerEvent::KeepAlive(data)) => { - self.write_packet(ServerPacket::KeepAlive(data)).await?; debug!("#{} sending keepalive: {data}", self.id); + self.write_packet(ServerPacket::KeepAlive(data)).await?; }, None => (), }, @@ -274,6 +289,14 @@ impl ClientState { debug!("#{} recieved keepalive: {data}", self.id); tx.send((self.id, ClientEvent::KeepAlive(data))).await?; }, + ClientPacket::ChatMessage(msg) => { + debug!("#{} recieved message: {msg}", self.id); + tx.send((self.id, ClientEvent::Message(msg))).await?; + }, + ClientPacket::Command(cmd) => { + debug!("#{} recieved command: {cmd}", self.id); + tx.send((self.id, ClientEvent::Command(cmd))).await?; + }, _ => (), } ReadPacketOutcome::None => (), diff --git a/src/main.rs b/src/main.rs index d9fc201..4b5e42c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,8 @@ mod ser; mod varint; mod client; +pub static QC_VERSION: &str = env!("CARGO_PKG_VERSION"); + pub type ArcMutex = Arc>; pub type JsonValue = serde_json::Value; @@ -32,3 +34,29 @@ pub struct ClientInfo { async fn main() { server::run_server().await.unwrap() } + +// Temporary code used to parse packets collected from vanilla Minecraft servers, +// mostly to collect dimension/registry codecs +#[cfg(no)] +fn main() { + use varint::VarInt; + use ser::Deserializable; + use std::io::{Cursor, Write}; + let packet = include_bytes!("/home/trimill/code/minecraft/joingame/1.16.5_bin"); + let mut r = Cursor::new(packet); + let _packet_id = VarInt::deserialize(&mut r).unwrap(); + let _eid = i32::deserialize(&mut r).unwrap(); + let _hc = bool::deserialize(&mut r).unwrap(); + let _gm = u8::deserialize(&mut r).unwrap(); + let _pgm = i8::deserialize(&mut r).unwrap(); + let dc = VarInt::deserialize(&mut r).unwrap().0; + for _ in 0..dc { + let _dim = String::deserialize(&mut r).unwrap(); + } + let _dim_codec: nbt::Blob = nbt::from_reader(&mut r).unwrap(); + let init = r.position() as usize; + let _dim: nbt::Blob = nbt::from_reader(&mut r).unwrap(); + let end = r.position() as usize; + std::io::stdout().lock().write_all(&packet[init..end]).unwrap(); +} + diff --git a/src/plugin/event.rs b/src/plugin/event.rs index ea71deb..26efea6 100644 --- a/src/plugin/event.rs +++ b/src/plugin/event.rs @@ -1,12 +1,16 @@ use mlua::{Value, FromLua, Table}; use uuid::Uuid; -use crate::{Player, ClientInfo}; +use crate::{Player, ClientInfo, JsonValue}; use super::UuidUD; pub enum PluginEvent { Kick(Uuid, Option), + Chat(Uuid, JsonValue, JsonValue), + BroadcastChat(JsonValue, JsonValue), + System(Uuid, JsonValue, bool), + BroadcastSystem(JsonValue, bool), } impl<'lua> FromLua<'lua> for PluginEvent { @@ -23,4 +27,6 @@ impl<'lua> FromLua<'lua> for PluginEvent { pub enum ServerEvent { Join(Player, ClientInfo), Leave(Player), + Command(Player, String), + Message(Player, String), } diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index b2e8795..b45537c 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -1,12 +1,11 @@ -use std::{io::ErrorKind, fs, cell::RefCell, rc::Rc}; +use std::sync::Arc; -use log::{warn, debug, info}; -use mlua::{Lua, ToLua, Function}; -use tokio::sync::mpsc::Sender; -use std::sync::mpsc::Receiver; +use log::warn; +use mlua::{Lua, Function, IntoLua, FromLua, Value, Table, AnyUserData}; +use tokio::sync::mpsc::{Sender, Receiver}; use uuid::Uuid; -use self::{plugin::Plugin, event::{PluginEvent, ServerEvent}, server::{CellServer, Server}}; +use self::{event::{PluginEvent, ServerEvent}, server::{Server, WrappedServer}, plugin::load_plugins}; pub mod event; mod plugin; @@ -15,57 +14,42 @@ mod server; #[derive(Clone, Copy, Eq, PartialEq)] struct UuidUD(Uuid); +impl<'lua> FromLua<'lua> for UuidUD { + fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result { + match value { + Value::UserData(ud) => Ok(*ud.borrow::()?), + v => Err(mlua::Error::FromLuaConversionError { from: v.type_name(), to: "Uuid", message: Some("Expected Uuid".to_owned()) }) + } + } +} + impl mlua::UserData for UuidUD { fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_meta_method("__tostring", - |lua, this, ()| this.0.to_string().to_lua(lua) + |lua, this, ()| this.0.to_string().into_lua(lua) ); methods.add_meta_method("__eq", - |lua, this, UuidUD(uuid)| (this.0 == uuid).to_lua(lua) + |lua, this, UuidUD(uuid)| (this.0 == uuid).into_lua(lua) ); } } -pub fn load_plugins(lua: &Lua) -> Vec { - debug!("loading plugins"); - let entries = match fs::read_dir("./plugins") { - Ok(n) => n, - Err(e) if e.kind() == ErrorKind::NotFound => { - warn!("plugins directory does not exist, creating"); - if let Err(e) = fs::create_dir("./plugins") { - warn!("failed to create plugins directory: {e}"); +fn broadcast_event<'lua, F>(lua: &'lua Lua, fname: &str, callback: F) -> mlua::Result<()> +where F: Fn(&str, Table<'lua>, AnyUserData, Function) -> mlua::Result<()> { + let srv: AnyUserData = lua.globals().get("srv")?; + let plugins: Table = srv.nth_user_value(1)?; + for (id, table) in plugins.pairs::().filter_map(Result::ok) { + if let Ok(f) = table.get::<_, Function>(fname) { + srv.set_nth_user_value(2, id.clone())?; + if let Err(e) = callback(&id, table.clone(), srv.clone(), f) { + warn!("error in {fname} in plugin {}: {e}", id); } - return Vec::new() - }, - Err(e) => { - warn!("failed to load plugins: {e}"); - return Vec::new() - } - }; - let mut plugins = Vec::new(); - for entry in entries { - let entry = match entry { - Ok(e) => e, - Err(e) => { - warn!("error reading entry: {e}"); - continue - } - }; - let path = entry.path(); - let spath = path.to_str().unwrap_or(""); - debug!("loading plugin at {spath}"); - match Plugin::load(&entry, lua) { - Ok(p) => { - info!("loaded plugin {}", p.id); - plugins.push(p); - } - Err(e) => warn!("error loading plugin at {spath}: {e}"), - } - } - plugins + } + } + Ok(()) } -pub async fn run_plugins(tx: Sender, rx: Receiver) { +pub async fn run_plugins(tx: Sender, mut rx: Receiver) -> mlua::Result<()> { let lua = unsafe { mlua::Lua::unsafe_new_with( mlua::StdLib::ALL_SAFE | mlua::StdLib::DEBUG, @@ -73,64 +57,61 @@ pub async fn run_plugins(tx: Sender, rx: Receiver) { ) }; - let plugins = load_plugins(&lua); - - let server = Rc::new(RefCell::new(Server { + let server = WrappedServer(Arc::new(Server { tx, names: Default::default(), info: Default::default(), })); - for plugin in &plugins { - if let Ok(init) = plugin.table.get::<_, Function>("init") { - if let Err(e) = init.call::<_, ()>((plugin.table.clone(), )) { - warn!("error initializing plugin {}: {e}", plugin.id); - } - } + { + let server_any: AnyUserData = FromLua::from_lua(IntoLua::into_lua(server.clone(), &lua)?, &lua)?; + let plugins = load_plugins(&lua)?; + + server_any.set_nth_user_value(1, plugins)?; + server_any.set_nth_user_value(2, Value::Nil)?; + lua.globals().set("srv", server_any.clone())?; } - while let Ok(ev) = rx.recv() { + broadcast_event(&lua, "init", |_, pl, _, f| { + f.call(pl) + })?; + + while let Some(ev) = rx.recv().await { match ev { ServerEvent::Join(player, info) => { - { - let mut guard = server.borrow_mut(); - guard.names.insert(player.uuid, player.name.clone()); - guard.info.insert(player.uuid, info); - } - for plugin in &plugins { - if let Ok(f) = plugin.table.get::<_, Function>("on_join") { - let res = f.call::<_, ()>(( - plugin.table.clone(), - CellServer(server.clone()), - UuidUD(player.uuid), - player.name.clone() - )); - if let Err(e) = res { - warn!("error responding to join event in plugin {}: {e}", plugin.id); - } - } - } + server.names.write().await.insert(player.uuid, player.name.clone()); + server.info.write().await.insert(player.uuid, info); + broadcast_event(&lua, "on_join", |_, pl, _, f| { + f.call::<_, ()>(( + pl, + UuidUD(player.uuid), + player.name.clone() + )) + })?; }, ServerEvent::Leave(player) => { - for plugin in &plugins { - if let Ok(f) = plugin.table.get::<_, Function>("on_leave") { - let res = f.call::<_, ()>(( - plugin.table.clone(), - CellServer(server.clone()), - UuidUD(player.uuid), - player.name.clone() - )); - if let Err(e) = res { - warn!("error responding to leave event in plugin {}: {e}", plugin.id); - } - } - } - { - let mut guard = server.borrow_mut(); - guard.names.remove(&player.uuid); - guard.info.remove(&player.uuid); - } - } + broadcast_event(&lua, "on_leave", |_, pl, _, f| { + f.call::<_, ()>(( + pl, + UuidUD(player.uuid), + player.name.clone() + )) + })?; + server.names.write().await.remove(&player.uuid); + server.info.write().await.remove(&player.uuid); + }, + ServerEvent::Message(player, msg) => { + broadcast_event(&lua, "on_message", |_, pl, _, f| { + f.call::<_, ()>(( + pl, + msg.clone(), + UuidUD(player.uuid), + player.name.clone() + )) + })?; + }, + ServerEvent::Command(_, _) => () // TODO command registerment } } + Ok(()) } diff --git a/src/plugin/plugin.rs b/src/plugin/plugin.rs index 18d45ec..7a2a721 100644 --- a/src/plugin/plugin.rs +++ b/src/plugin/plugin.rs @@ -1,76 +1,72 @@ -use std::fs::DirEntry; +use std::{fs::{DirEntry, self}, io::ErrorKind}; -use log::{debug, Record}; -use mlua::{Function, FromLua, Lua, Table, Value}; +use anyhow::anyhow; +use log::{debug, warn, info}; +use mlua::{Lua, Table}; -pub struct Plugin<'lua> { - pub table: Table<'lua>, - pub id: String, -} +use crate::QC_VERSION; -impl<'lua> FromLua<'lua> for Plugin<'lua> { - fn from_lua(lua_value: Value<'lua>, lua: &'lua Lua) -> mlua::Result { - let table = Table::from_lua(lua_value, lua)?; - Ok(Plugin { - id: table.get("id")?, - table, - }) +fn load_plugin<'lua>(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result<(String, Table<'lua>)> { + let ty = entry.file_type()?; + let mut path = entry.path(); + if ty.is_dir() { + path.push("main.lua"); } -} + let chunk = lua.load(path); + debug!("evaluating plugin"); + let table: Table = chunk.eval()?; -fn lua_locinfo(lua: &Lua) -> Option<(u32, String)> { - let debug = lua.globals().get::<_, Table>("debug").ok()?; - let getinfo = debug.get::<_, Function>("getinfo").ok()?; - let data = getinfo.call::<_, Table>((2,)).ok()?; - let line = data.get("currentline").ok()?; - let file = data.get("short_src").ok()?; - Some((line, file)) -} + let id = table.get("id") + .map_err(|e| anyhow!("could not get plugin id: {e}"))?; -fn lua_log(level: log::Level, path: &'static str, msg: String, lua: &Lua) { - let mut record = Record::builder(); - record.level(level); - record.target(path); - record.module_path_static(Some(path)); - let locinfo = lua_locinfo(lua); - if let Some((line, file)) = &locinfo { - record.line(Some(*line)); - record.file(Some(file)); - } - log::logger().log(&record.args(format_args!("{}", msg)).build()); - -} - -const LEVELS: [(&str, log::Level); 5] = [ - ("trace", log::Level::Trace), - ("debug", log::Level::Debug), - ("info", log::Level::Info), - ("warn", log::Level::Warn), - ("error", log::Level::Error), -]; - -impl<'lua> Plugin<'lua> { - pub fn load(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result { - let ty = entry.file_type()?; - let mut path = entry.path(); - if ty.is_dir() { - path.push("main.lua"); + if let Ok(qc_ver) = table.get::<_, String>("server_version") { + debug!("checking plugin version"); + let req = semver::VersionReq::parse(&qc_ver)?; + if !req.matches(&semver::Version::parse(QC_VERSION).unwrap()) { + return Err(anyhow!("incompatible with quectocraft version {QC_VERSION}: expected {req}")); } - let chunk = lua.load(&path); - debug!("evaluating plugin"); - let plugin = Self::from_lua(chunk.eval()?, lua)?; - - // leak: plugins are only loaded at the beginning of the program - // and the path needs to live forever anyway, so this is fine - let path: &'static str = Box::leak(format!("qcplugin::{}", plugin.id).into_boxed_str()); - - for (name, level) in LEVELS { - plugin.table.set(name, lua.create_function(move |lua, (msg,): (String,)| { - lua_log(level, path, msg, lua); - Ok(()) - })?)?; - } - - Ok(plugin) + } else { + warn!("plugin '{id}' is missing a version reqirement"); } + + Ok((id, table)) +} + +pub fn load_plugins(lua: &Lua) -> mlua::Result { + let plugins = lua.create_table()?; + debug!("loading plugins"); + let entries = match fs::read_dir("./plugins") { + Ok(n) => n, + Err(e) if e.kind() == ErrorKind::NotFound => { + warn!("plugins directory does not exist, creating"); + if let Err(e) = fs::create_dir("./plugins") { + warn!("failed to create plugins directory: {e}"); + } + return Ok(plugins) + }, + Err(e) => { + warn!("failed to load plugins: {e}"); + return Ok(plugins) + } + }; + for entry in entries { + let entry = match entry { + Ok(e) => e, + Err(e) => { + warn!("error reading entry: {e}"); + continue + } + }; + let path = entry.path(); + let spath = path.to_str().unwrap_or(""); + debug!("loading plugin at {spath}"); + match load_plugin(&entry, lua) { + Ok((id, table)) => { + info!("loaded plugin {}", id); + plugins.set(id, table)?; + } + Err(e) => warn!("error loading plugin at {spath}: {e}"), + } + } + Ok(plugins) } diff --git a/src/plugin/server.rs b/src/plugin/server.rs index 834b76b..0cb0fa8 100644 --- a/src/plugin/server.rs +++ b/src/plugin/server.rs @@ -1,15 +1,17 @@ -use std::{collections::HashMap, cell::RefCell, rc::Rc}; +use std::{collections::HashMap, sync::Arc}; -use mlua::{Value, ToLua}; -use tokio::sync::mpsc::Sender; +use log::{Record, warn}; +use mlua::{Value, IntoLua, Lua, Table, Function, AnyUserData, LuaSerdeExt}; +use serde_json::json; +use tokio::sync::{mpsc::Sender, RwLock}; use uuid::Uuid; -use crate::ClientInfo; +use crate::{ClientInfo, QC_VERSION, JsonValue}; use super::{event::PluginEvent, UuidUD}; -impl<'lua> ToLua<'lua> for ClientInfo { - fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { +impl<'lua> IntoLua<'lua> for ClientInfo { + fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { let table = lua.create_table()?; table.set("addr", self.addr)?; table.set("port", self.port)?; @@ -19,43 +21,164 @@ impl<'lua> ToLua<'lua> for ClientInfo { } } +fn lua_locinfo(lua: &Lua) -> Option<(u32, String)> { + let debug = lua.globals().get::<_, Table>("debug").ok()?; + let getinfo = debug.get::<_, Function>("getinfo").ok()?; + let data = getinfo.call::<_, Table>((2,)).ok()?; + let line = data.get("currentline").ok()?; + let file = data.get("short_src").ok()?; + Some((line, file)) +} + +fn lua_log(level: log::Level, path: &str, msg: String, lua: &Lua) { + let mut record = Record::builder(); + record.level(level); + record.target(path); + record.module_path(Some(path)); + let locinfo = lua_locinfo(lua); + if let Some((line, file)) = &locinfo { + record.line(Some(*line)); + record.file(Some(file)); + } + log::logger().log(&record.args(format_args!("{}", msg)).build()); + +} + +const LEVELS: [(&str, log::Level); 5] = [ + ("trace", log::Level::Trace), + ("debug", log::Level::Debug), + ("info", log::Level::Info), + ("warn", log::Level::Warn), + ("error", log::Level::Error), +]; + +fn value_to_chat<'lua>(lua: &'lua Lua, value: Value<'lua>) -> mlua::Result { + match value { + Value::String(s) => Ok(json!{{"text": s.to_str()?}}), + Value::Nil => Ok(json!{{"text":""}}), + Value::Table(_) => Ok(lua.from_value(value)?), + _ => return Err(mlua::Error::RuntimeError("()".to_owned())), + } +} + + pub struct Server { pub tx: Sender, - pub names: HashMap, - pub info: HashMap, + pub names: RwLock>, + pub info: RwLock>, } #[derive(Clone)] -pub struct CellServer(pub Rc>); +pub struct WrappedServer(pub Arc); -impl mlua::UserData for CellServer { +impl std::ops::Deref for WrappedServer { + type Target = Server; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl mlua::UserData for WrappedServer { fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("players", |lua, this, ()| { + // commands + methods.add_async_method("add_command", |_lua, _this, ()| async { + warn!("TODO add command"); + Ok(()) + }); + + // players + methods.add_async_method("players", |lua, this, ()| async { let table = lua.create_table()?; - for (k, v) in &this.0.borrow().names { + for (k, v) in this.names.read().await.iter() { table.set(UuidUD(*k), v.clone())?; } Ok(table) }); - methods.add_method("get_name", |lua, this, UuidUD(uuid)| { - match this.0.borrow().names.get(&uuid) { - Some(s) => s.clone().to_lua(lua), + methods.add_async_method("get_name", |lua, this, UuidUD(uuid)| async move { + match this.info.read().await.get(&uuid) { + Some(s) => s.clone().into_lua(lua), None => Ok(Value::Nil), } }); - methods.add_method("get_info", |lua, this, UuidUD(uuid)| { - match this.0.borrow().info.get(&uuid) { - Some(i) => i.clone().to_lua(lua), + methods.add_async_method("get_info", |lua, this, UuidUD(uuid)| async move { + match this.info.read().await.get(&uuid) { + Some(i) => i.clone().into_lua(lua), None => Ok(Value::Nil) } }); - methods.add_async_method("kick", |_, CellServer(this), (UuidUD(uuid), msg)| async move { - let tx = this.borrow().tx.clone(); - tx + + // events + methods.add_async_method("kick", |_, this, (UuidUD(uuid), msg)| async move { + this.tx .send(PluginEvent::Kick(uuid, msg)) .await .map_err(|e| mlua::Error::RuntimeError(e.to_string())) }); + methods.add_async_method("send_msg", |lua, this, (UuidUD(uuid), msg, overlay): (_, Value, Option)| async move { + let msg = match msg { + Value::String(s) => json!{{"text": s.to_str()?}}, + Value::Table(_) => lua.from_value(msg)?, + _ => return Err(mlua::Error::RuntimeError("()".to_owned())), + }; + this.tx + .send(PluginEvent::System(uuid, msg, overlay == Some(true))) + .await + .map_err(|e| mlua::Error::RuntimeError(e.to_string())) + }); + methods.add_async_method("broadcast_msg", |lua, this, (msg, overlay): (Value, Option)| async move { + let msg = match msg { + Value::String(s) => json!{{"text": s.to_str()?}}, + Value::Table(_) => lua.from_value(msg)?, + _ => return Err(mlua::Error::RuntimeError("()".to_owned())), + }; + this.tx + .send(PluginEvent::BroadcastSystem(msg, overlay == Some(true))) + .await + .map_err(|e| mlua::Error::RuntimeError(e.to_string())) + }); + methods.add_async_method("send_chat", |lua, this, (UuidUD(uuid), msg, name): (_, Value, Value)| async move { + let msg = value_to_chat(lua, msg)?; + let name = value_to_chat(lua, name)?; + this.tx + .send(PluginEvent::Chat(uuid, msg, name)) + .await + .map_err(|e| mlua::Error::RuntimeError(e.to_string())) + }); + methods.add_async_method("broadcast_chat", |lua, this, (msg, name): (Value, Value)| async move { + let msg = value_to_chat(lua, msg)?; + let name = value_to_chat(lua, name)?; + this.tx + .send(PluginEvent::BroadcastChat(msg, name)) + .await + .map_err(|e| mlua::Error::RuntimeError(e.to_string())) + }); + + // utility + methods.add_function("check_semver", |_, (ver, req): (String, String)| { + let ver = semver::Version::parse(&ver) + .map_err(|_| mlua::Error::runtime(format!("invalid version: {ver}")))?; + let req = semver::VersionReq::parse(&req) + .map_err(|_| mlua::Error::runtime(format!("invalid version request: {req}")))?; + Ok(req.matches(&ver)) + }); + + // logging + for (name, level) in LEVELS { + methods.add_function(name, move |lua, (this, msg): (AnyUserData, String)| { + let mut id: String = this.nth_user_value(2)?; + id.insert_str(0, "plugin::"); + lua_log(level, &id, msg, lua); + Ok(()) + }); + } + } + + fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field("version", QC_VERSION); + fields.add_field_function_get("plugins", |_, this| { + this.nth_user_value::
(1) + }) } } diff --git a/src/protocol/common.rs b/src/protocol/common.rs index 0fddc62..dc09283 100644 --- a/src/protocol/common.rs +++ b/src/protocol/common.rs @@ -1,6 +1,7 @@ use std::io::{Write, Read}; use anyhow::anyhow; +use log::warn; use super::{ServerPacket, ClientPacket}; use crate::{varint::VarInt, ser::{Serializable, Deserializable}}; @@ -53,7 +54,7 @@ fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, _len: i32, id: i32) - Ok(Some(ClientPacket::PingRequest(payload))) }, _ => { - println!("invalid id {id:#x} in state {state:?}"); + warn!("invalid id {id:#x} in state {state:?}"); Ok(None) } } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 798f8a5..3404235 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -5,8 +5,11 @@ use uuid::Uuid; use crate::{JsonValue, ser::Position}; mod common; -mod v1_19_4; mod v1_20_1; +mod v1_19_4; +mod v1_18_2; +mod v1_17_1; +mod v1_16_5; pub use common::COMMON as COMMON; @@ -49,6 +52,8 @@ pub enum ClientPacket { data: Vec }, KeepAlive(i64), + ChatMessage(String), + Command(String), } #[derive(Clone, Debug)] @@ -76,6 +81,13 @@ pub enum ServerPacket { z: i32, }, SetDefaultSpawn { pos: Position, angle: f32 }, + PositionAndLook { + pos: [f64; 3], + look: [f32; 2], + flags: u8, + }, + SystemMessage(JsonValue, bool), + ChatMessage(JsonValue, JsonValue), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -90,6 +102,9 @@ pub const fn get_protocol(version: i32) -> Option { match version { v1_20_1::VERSION => Some(v1_20_1::PROTOCOL), v1_19_4::VERSION => Some(v1_19_4::PROTOCOL), + v1_18_2::VERSION => Some(v1_18_2::PROTOCOL), + v1_17_1::VERSION => Some(v1_17_1::PROTOCOL), + v1_16_5::VERSION => Some(v1_16_5::PROTOCOL), _ => None, } } diff --git a/src/protocol/v1_16_5.rs b/src/protocol/v1_16_5.rs new file mode 100644 index 0000000..7c8a600 --- /dev/null +++ b/src/protocol/v1_16_5.rs @@ -0,0 +1,138 @@ +use std::io::{Write, Read}; + +use log::trace; + +use crate::{ser::{Serializable, Deserializable}, varint::VarInt}; + +use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON}; + +pub const VERSION: i32 = 754; + +pub const PROTOCOL: Protocol = Protocol { + name: "1.16.5", + version: VERSION, + encode, + decode, +}; + +fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()> { + trace!("1.18.2 encoding {ev:?}"); + match ev { + ServerPacket::LoginSuccess { name, uuid } => { + VarInt(0x02).serialize(&mut w)?; + uuid.serialize(&mut w)?; + name.serialize(&mut w)?; + }, + ServerPacket::ChatMessage(msg, _) => { + VarInt(0x0E).serialize(&mut w)?; + msg.serialize(&mut w)?; + 0u8.serialize(&mut w)?; // chat + 0u128.serialize(&mut w)?; // null uuid + }, + ServerPacket::SystemMessage(msg, overlay) => { + VarInt(0x0E).serialize(&mut w)?; + msg.serialize(&mut w)?; + if overlay { 2u8 } else { 1u8 } // system or overlay + .serialize(&mut w)?; + 0u128.serialize(&mut w)?; // null uuid + }, + ServerPacket::PlayDisconnect(msg) => { + VarInt(0x19).serialize(&mut w)?; + msg.serialize(&mut w)?; + }, + ServerPacket::KeepAlive(data) => { + VarInt(0x1F).serialize(&mut w)?; + data.serialize(&mut w)?; + }, + ServerPacket::PluginMessage { channel, data } => { + VarInt(0x17).serialize(&mut w)?; + channel.serialize(&mut w)?; + w.write_all(&data)?; + }, + ServerPacket::ChunkData { x, z } => { + VarInt(0x20).serialize(&mut w)?; + // chunk x + x.serialize(&mut w)?; + // chunk z + z.serialize(&mut w)?; + // full chunk + true.serialize(&mut w)?; + // bit mask + VarInt(0).serialize(&mut w)?; + // heightmap + include_bytes!("../resources/heightmap.nbt").serialize(&mut w)?; + // biomes + VarInt(1024).serialize(&mut w)?; + [VarInt(127); 1024].serialize(&mut w)?; + // chunk data size and chunk data + VarInt(0).serialize(&mut w)?; + // block entities + VarInt(0).serialize(&mut w)?; + }, + ServerPacket::JoinGame { eid, gamemode, hardcode } => { + VarInt(0x24).serialize(&mut w)?; + eid.serialize(&mut w)?; + hardcode.serialize(&mut w)?; + gamemode.serialize(&mut w)?; + (-1i8).serialize(&mut w)?; // prev gamemode undefined + VarInt(1).serialize(&mut w)?; // dimension count + "qc:world".serialize(&mut w)?; // register one dimension + include_bytes!("../resources/dimcodec_1.16.5.nbt").serialize(&mut w)?; // dimension codec + include_bytes!("../resources/dimension_1.16.5.nbt").serialize(&mut w)?; // dimension data + "qc:world".serialize(&mut w)?; // dimension name + 0i64.serialize(&mut w)?; // seed + VarInt(65535).serialize(&mut w)?; // max players + VarInt(8).serialize(&mut w)?; // view dist + false.serialize(&mut w)?; // reduce debug info + false.serialize(&mut w)?; // respawn screen + false.serialize(&mut w)?; // is debug + false.serialize(&mut w)?; // is flat + }, + ServerPacket::PositionAndLook { pos, look, flags } => { + VarInt(0x34).serialize(&mut w)?; + pos.serialize(&mut w)?; + look.serialize(&mut w)?; + flags.serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; // teleport id + } + ServerPacket::SetDefaultSpawn { pos, angle: _ } => { + VarInt(0x42).serialize(&mut w)?; + pos.serialize(&mut w)?; + }, + _ => { (COMMON.encode)(w, state, ev)?; } + } + Ok(()) +} + +fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result> { + trace!("1.18.2 decoding {state:?} {id}"); + type Ps = ProtocolState; + match (state, id) { + (Ps::Login, 0x00) => { + let name = String::deserialize(&mut r)?; + Ok(Some(ClientPacket::LoginStart { name, uuid: None })) + } + (Ps::Login, 0x01 | 0x02) => Ok(None), // unsupported + (Ps::Play, 0x03) => { + let mut msg = String::deserialize(&mut r)?; + if msg.starts_with('/') { + msg.remove(0); + Ok(Some(ClientPacket::Command(msg))) + } else { + Ok(Some(ClientPacket::ChatMessage(msg))) + } + }, + (Ps::Play, 0x0A) => { + let channel = String::deserialize(&mut r)?; + let mut data = Vec::new(); + r.read_to_end(&mut data)?; + Ok(Some(ClientPacket::PluginMessage { channel, data })) + } + (Ps::Play, 0x10) => { + let data = i64::deserialize(&mut r)?; + Ok(Some(ClientPacket::KeepAlive(data))) + } + (Ps::Play, 0x11 | 0x12 | 0x13 | 0x14) => Ok(None), // position & rotation + _ => (COMMON.decode)(r, state, len, id) + } +} diff --git a/src/protocol/v1_17_1.rs b/src/protocol/v1_17_1.rs new file mode 100644 index 0000000..be00d9b --- /dev/null +++ b/src/protocol/v1_17_1.rs @@ -0,0 +1,138 @@ +use std::io::{Write, Read}; + +use log::trace; + +use crate::{ser::{Serializable, Deserializable}, varint::VarInt}; + +use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON}; + +pub const VERSION: i32 = 756; + +pub const PROTOCOL: Protocol = Protocol { + name: "1.17.1", + version: VERSION, + encode, + decode, +}; + +fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()> { + trace!("1.18.2 encoding {ev:?}"); + match ev { + ServerPacket::LoginSuccess { name, uuid } => { + VarInt(0x02).serialize(&mut w)?; + uuid.serialize(&mut w)?; + name.serialize(&mut w)?; + }, + ServerPacket::ChatMessage(msg, _) => { + VarInt(0x0F).serialize(&mut w)?; + msg.serialize(&mut w)?; + 0u8.serialize(&mut w)?; // chat + 0u128.serialize(&mut w)?; // null uuid + }, + ServerPacket::SystemMessage(msg, overlay) => { + VarInt(0x0F).serialize(&mut w)?; + msg.serialize(&mut w)?; + if overlay { 2u8 } else { 1u8 } // system or overlay + .serialize(&mut w)?; + 0u128.serialize(&mut w)?; // null uuid + }, + ServerPacket::PluginMessage { channel, data } => { + VarInt(0x18).serialize(&mut w)?; + channel.serialize(&mut w)?; + w.write_all(&data)?; + }, + ServerPacket::PlayDisconnect(msg) => { + VarInt(0x1A).serialize(&mut w)?; + msg.serialize(&mut w)?; + }, + ServerPacket::KeepAlive(data) => { + VarInt(0x21).serialize(&mut w)?; + data.serialize(&mut w)?; + }, + ServerPacket::ChunkData { x, z } => { + VarInt(0x22).serialize(&mut w)?; + // chunk x + x.serialize(&mut w)?; + // chunk z + z.serialize(&mut w)?; + // bit mask + VarInt(0).serialize(&mut w)?; + // heightmap + include_bytes!("../resources/heightmap.nbt").serialize(&mut w)?; + // biomes + VarInt(1024).serialize(&mut w)?; + [VarInt(127); 1024].serialize(&mut w)?; + // chunk data size and chunk data + VarInt(0).serialize(&mut w)?; + // block entities + VarInt(0).serialize(&mut w)?; + }, + ServerPacket::JoinGame { eid, gamemode, hardcode } => { + VarInt(0x26).serialize(&mut w)?; + eid.serialize(&mut w)?; + hardcode.serialize(&mut w)?; + gamemode.serialize(&mut w)?; + (-1i8).serialize(&mut w)?; // prev gamemode undefined + VarInt(1).serialize(&mut w)?; // dimension count + "qc:world".serialize(&mut w)?; // register one dimension + include_bytes!("../resources/dimcodec_1.17.1.nbt").serialize(&mut w)?; // dimension codec + include_bytes!("../resources/dimension_1.17.1.nbt").serialize(&mut w)?; // dimension data + "qc:world".serialize(&mut w)?; // dimension name + 0i64.serialize(&mut w)?; // seed + VarInt(65535).serialize(&mut w)?; // max players + VarInt(8).serialize(&mut w)?; // view dist + false.serialize(&mut w)?; // reduce debug info + false.serialize(&mut w)?; // respawn screen + false.serialize(&mut w)?; // is debug + false.serialize(&mut w)?; // is flat + }, + ServerPacket::PositionAndLook { pos, look, flags } => { + VarInt(0x38).serialize(&mut w)?; + pos.serialize(&mut w)?; + look.serialize(&mut w)?; + flags.serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; // teleport id + false.serialize(&mut w)?; // dismount + } + ServerPacket::SetDefaultSpawn { pos, angle } => { + VarInt(0x4B).serialize(&mut w)?; + pos.serialize(&mut w)?; + angle.serialize(&mut w)?; + }, + _ => { (COMMON.encode)(w, state, ev)?; } + } + Ok(()) +} + +fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result> { + trace!("1.18.2 decoding {state:?} {id}"); + type Ps = ProtocolState; + match (state, id) { + (Ps::Login, 0x00) => { + let name = String::deserialize(&mut r)?; + Ok(Some(ClientPacket::LoginStart { name, uuid: None })) + } + (Ps::Login, 0x01 | 0x02) => Ok(None), // unsupported + (Ps::Play, 0x03) => { + let mut msg = String::deserialize(&mut r)?; + if msg.starts_with('/') { + msg.remove(0); + Ok(Some(ClientPacket::Command(msg))) + } else { + Ok(Some(ClientPacket::ChatMessage(msg))) + } + }, + (Ps::Play, 0x0A) => { + let channel = String::deserialize(&mut r)?; + let mut data = Vec::new(); + r.read_to_end(&mut data)?; + Ok(Some(ClientPacket::PluginMessage { channel, data })) + } + (Ps::Play, 0x0F) => { + let data = i64::deserialize(&mut r)?; + Ok(Some(ClientPacket::KeepAlive(data))) + } + (Ps::Play, 0x11 | 0x12 | 0x13) => Ok(None), // position & rotation + _ => (COMMON.decode)(r, state, len, id) + } +} diff --git a/src/protocol/v1_18_2.rs b/src/protocol/v1_18_2.rs new file mode 100644 index 0000000..2207619 --- /dev/null +++ b/src/protocol/v1_18_2.rs @@ -0,0 +1,146 @@ +use std::io::{Write, Read}; + +use log::trace; + +use crate::{ser::{Serializable, Deserializable}, varint::VarInt}; + +use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON}; + +pub const VERSION: i32 = 758; + +pub const PROTOCOL: Protocol = Protocol { + name: "1.18.2", + version: VERSION, + encode, + decode, +}; + +fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()> { + trace!("1.18.2 encoding {ev:?}"); + match ev { + ServerPacket::LoginSuccess { name, uuid } => { + VarInt(0x02).serialize(&mut w)?; + uuid.serialize(&mut w)?; + name.serialize(&mut w)?; + }, + ServerPacket::ChatMessage(msg, _) => { + VarInt(0x0F).serialize(&mut w)?; + msg.serialize(&mut w)?; + 0u8.serialize(&mut w)?; // chat + 0u128.serialize(&mut w)?; // null uuid + }, + ServerPacket::SystemMessage(msg, overlay) => { + VarInt(0x0F).serialize(&mut w)?; + msg.serialize(&mut w)?; + if overlay { 2u8 } else { 1u8 } // system or overlay + .serialize(&mut w)?; + 0u128.serialize(&mut w)?; // null uuid + }, + ServerPacket::PluginMessage { channel, data } => { + VarInt(0x18).serialize(&mut w)?; + channel.serialize(&mut w)?; + w.write_all(&data)?; + }, + ServerPacket::PlayDisconnect(msg) => { + VarInt(0x1A).serialize(&mut w)?; + msg.serialize(&mut w)?; + }, + ServerPacket::KeepAlive(data) => { + VarInt(0x21).serialize(&mut w)?; + data.serialize(&mut w)?; + }, + ServerPacket::ChunkData { x, z } => { + VarInt(0x22).serialize(&mut w)?; + // chunk x + x.serialize(&mut w)?; + // chunk z + z.serialize(&mut w)?; + // heightmap + include_bytes!("../resources/heightmap.nbt").serialize(&mut w)?; + // chunk data size and chunk data + VarInt(192).serialize(&mut w)?; + [0u8; 192].serialize(&mut w)?; + // block entities + VarInt(0).serialize(&mut w)?; + // trust edges + true.serialize(&mut w)?; + // light masks + VarInt(0).serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + // sky light array len + VarInt(0).serialize(&mut w)?; + // block light array len + VarInt(0).serialize(&mut w)?; + }, + ServerPacket::JoinGame { eid, gamemode, hardcode } => { + VarInt(0x26).serialize(&mut w)?; + eid.serialize(&mut w)?; + hardcode.serialize(&mut w)?; + gamemode.serialize(&mut w)?; + (-1i8).serialize(&mut w)?; // prev gamemode undefined + VarInt(1).serialize(&mut w)?; // dimension count + "qc:world".serialize(&mut w)?; // register one dimension + include_bytes!("../resources/dimcodec_1.18.2.nbt").serialize(&mut w)?; // dimension codec + include_bytes!("../resources/dimension_1.18.2.nbt").serialize(&mut w)?; // dimension data + "qc:world".serialize(&mut w)?; // dimension name + 0i64.serialize(&mut w)?; // seed + VarInt(65535).serialize(&mut w)?; // max players + VarInt(8).serialize(&mut w)?; // view dist + VarInt(0).serialize(&mut w)?; // sim dist + false.serialize(&mut w)?; // reduce debug info + false.serialize(&mut w)?; // respawn screen + false.serialize(&mut w)?; // is debug + false.serialize(&mut w)?; // is flat + }, + ServerPacket::PositionAndLook { pos, look, flags } => { + VarInt(0x38).serialize(&mut w)?; + pos.serialize(&mut w)?; + look.serialize(&mut w)?; + flags.serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + false.serialize(&mut w)?; + }, + ServerPacket::SetDefaultSpawn { pos, angle } => { + VarInt(0x4B).serialize(&mut w)?; + pos.serialize(&mut w)?; + angle.serialize(&mut w)?; + }, + _ => { (COMMON.encode)(w, state, ev)?; } + } + Ok(()) +} + +fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result> { + trace!("1.18.2 decoding {state:?} {id}"); + type Ps = ProtocolState; + match (state, id) { + (Ps::Login, 0x00) => { + let name = String::deserialize(&mut r)?; + Ok(Some(ClientPacket::LoginStart { name, uuid: None })) + } + (Ps::Login, 0x01 | 0x02) => Ok(None), // unsupported + (Ps::Play, 0x03) => { + let mut msg = String::deserialize(&mut r)?; + if msg.starts_with('/') { + msg.remove(0); + Ok(Some(ClientPacket::Command(msg))) + } else { + Ok(Some(ClientPacket::ChatMessage(msg))) + } + }, + (Ps::Play, 0x0A) => { + let channel = String::deserialize(&mut r)?; + let mut data = Vec::new(); + r.read_to_end(&mut data)?; + Ok(Some(ClientPacket::PluginMessage { channel, data })) + } + (Ps::Play, 0x0F) => { + let data = i64::deserialize(&mut r)?; + Ok(Some(ClientPacket::KeepAlive(data))) + } + (Ps::Play, 0x11 | 0x12 | 0x13) => Ok(None), // position & rotation + _ => (COMMON.decode)(r, state, len, id) + } +} diff --git a/src/protocol/v1_19_4/mod.rs b/src/protocol/v1_19_4.rs similarity index 74% rename from src/protocol/v1_19_4/mod.rs rename to src/protocol/v1_19_4.rs index 0152014..7dae692 100644 --- a/src/protocol/v1_19_4/mod.rs +++ b/src/protocol/v1_19_4.rs @@ -1,5 +1,6 @@ use std::io::{Write, Read}; +use log::trace; use uuid::Uuid; use crate::{ser::{Serializable, Deserializable}, varint::VarInt}; @@ -16,6 +17,7 @@ pub const PROTOCOL: Protocol = Protocol { }; fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()> { + trace!("1.19. encoding {ev:?}"); match ev { ServerPacket::LoginSuccess { name, uuid } => { VarInt(0x02).serialize(&mut w)?; @@ -24,40 +26,26 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> // number of properties (0) VarInt(0x00).serialize(&mut w)?; }, - ServerPacket::JoinGame { eid, gamemode, hardcode } => { - VarInt(0x28).serialize(&mut w)?; - eid.serialize(&mut w)?; - hardcode.serialize(&mut w)?; - gamemode.serialize(&mut w)?; - (-1i8).serialize(&mut w)?; // prev gamemode undefined - VarInt(1).serialize(&mut w)?; // dimension count - "qc:world".serialize(&mut w)?; // register one dimension - include_bytes!("registry.nbt").serialize(&mut w)?; // registry codec - "minecraft:the_end".serialize(&mut w)?; // dimension type - "qc:world".serialize(&mut w)?; // dimension name - 0i64.serialize(&mut w)?; // seed - VarInt(65535).serialize(&mut w)?; // max players - VarInt(8).serialize(&mut w)?; // view dist - VarInt(0).serialize(&mut w)?; // sim dist - false.serialize(&mut w)?; // reduce debug info - false.serialize(&mut w)?; // respawn screen - false.serialize(&mut w)?; // is debug - false.serialize(&mut w)?; // is flat - false.serialize(&mut w)?; // has death location - }, - ServerPacket::KeepAlive(data) => { - VarInt(0x23).serialize(&mut w)?; - data.serialize(&mut w)?; - }, ServerPacket::PlayDisconnect(msg) => { VarInt(0x1A).serialize(&mut w)?; msg.serialize(&mut w)?; }, + ServerPacket::ChatMessage(msg, name) => { + VarInt(0x1B).serialize(&mut w)?; + msg.serialize(&mut w)?; + VarInt(7).serialize(&mut w)?; // chat type from registry codec + name.serialize(&mut w)?; // chat type name + false.serialize(&mut w)?; // no target + }, ServerPacket::PluginMessage { channel, data } => { VarInt(0x17).serialize(&mut w)?; channel.serialize(&mut w)?; w.write_all(&data)?; }, + ServerPacket::KeepAlive(data) => { + VarInt(0x23).serialize(&mut w)?; + data.serialize(&mut w)?; + }, ServerPacket::ChunkData { x, z } => { VarInt(0x24).serialize(&mut w)?; // chunk x @@ -65,26 +53,10 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> // chunk z z.serialize(&mut w)?; // heightmap - let hmdata = [0i64; 37]; - let mut heightmap = nbt::Blob::new(); - heightmap.insert("MOTION_BLOCKING", hmdata.as_ref()).unwrap(); - heightmap.serialize(&mut w)?; - // chunk data - let mut chunk_data: Vec = Vec::new(); - for _ in 0..(384 / 16) { - // number of non-air blocks - (0i16).serialize(&mut chunk_data)?; - // block states - (0u8).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - // biomes - (0u8).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - } - VarInt(chunk_data.len() as i32).serialize(&mut w)?; - w.write_all(&chunk_data)?; + include_bytes!("../resources/heightmap.nbt").serialize(&mut w)?; + // chunk data size and chunk data + VarInt(192).serialize(&mut w)?; + [0u8; 192].serialize(&mut w)?; // block entities VarInt(0).serialize(&mut w)?; // trust edges @@ -99,17 +71,51 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> // block light array len VarInt(0).serialize(&mut w)?; }, + ServerPacket::JoinGame { eid, gamemode, hardcode } => { + VarInt(0x28).serialize(&mut w)?; + eid.serialize(&mut w)?; + hardcode.serialize(&mut w)?; + gamemode.serialize(&mut w)?; + (-1i8).serialize(&mut w)?; // prev gamemode undefined + VarInt(1).serialize(&mut w)?; // dimension count + "qc:world".serialize(&mut w)?; // register one dimension + include_bytes!("../resources/registry_1.19.4.nbt").serialize(&mut w)?; // registry codec + "minecraft:the_end".serialize(&mut w)?; // dimension type + "qc:world".serialize(&mut w)?; // dimension name + 0i64.serialize(&mut w)?; // seed + VarInt(65535).serialize(&mut w)?; // max players + VarInt(8).serialize(&mut w)?; // view dist + VarInt(0).serialize(&mut w)?; // sim dist + false.serialize(&mut w)?; // reduce debug info + false.serialize(&mut w)?; // respawn screen + false.serialize(&mut w)?; // is debug + false.serialize(&mut w)?; // is flat + false.serialize(&mut w)?; // has death location + }, + ServerPacket::PositionAndLook { pos, look, flags } => { + VarInt(0x3C).serialize(&mut w)?; + pos.serialize(&mut w)?; + look.serialize(&mut w)?; + flags.serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + }, ServerPacket::SetDefaultSpawn { pos, angle } => { VarInt(0x50).serialize(&mut w)?; pos.serialize(&mut w)?; angle.serialize(&mut w)?; }, + ServerPacket::SystemMessage(msg, overlay) => { + VarInt(0x64).serialize(&mut w)?; + msg.serialize(&mut w)?; + overlay.serialize(&mut w)?; + }, _ => { (COMMON.encode)(w, state, ev)?; } } Ok(()) } fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result> { + trace!("1.19.4 decoding {state:?} {id}"); type Ps = ProtocolState; match (state, id) { (Ps::Login, 0x00) => { @@ -121,6 +127,14 @@ fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> Ok(Some(ClientPacket::LoginStart { name, uuid })) } (Ps::Login, 0x01 | 0x02) => Ok(None), // unsupported + (Ps::Play, 0x04) => { + let cmd = String::deserialize(&mut r)?; + Ok(Some(ClientPacket::Command(cmd))) + }, + (Ps::Play, 0x05) => { + let msg = String::deserialize(&mut r)?; + Ok(Some(ClientPacket::ChatMessage(msg))) + }, (Ps::Play, 0x0D) => { let channel = String::deserialize(&mut r)?; let mut data = Vec::new(); diff --git a/src/protocol/v1_20_1/mod.rs b/src/protocol/v1_20_1.rs similarity index 74% rename from src/protocol/v1_20_1/mod.rs rename to src/protocol/v1_20_1.rs index 7d6df62..7aa6268 100644 --- a/src/protocol/v1_20_1/mod.rs +++ b/src/protocol/v1_20_1.rs @@ -1,5 +1,6 @@ use std::io::{Write, Read}; +use log::trace; use uuid::Uuid; use crate::{ser::{Serializable, Deserializable}, varint::VarInt}; @@ -16,6 +17,7 @@ pub const PROTOCOL: Protocol = Protocol { }; fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()> { + trace!("1.20.1 encoding {ev:?}"); match ev { ServerPacket::LoginSuccess { name, uuid } => { VarInt(0x02).serialize(&mut w)?; @@ -24,6 +26,49 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> // number of properties (0) VarInt(0x00).serialize(&mut w)?; }, + ServerPacket::PluginMessage { channel, data } => { + VarInt(0x17).serialize(&mut w)?; + channel.serialize(&mut w)?; + w.write_all(&data)?; + }, + ServerPacket::PlayDisconnect(msg) => { + VarInt(0x1A).serialize(&mut w)?; + msg.serialize(&mut w)?; + }, + ServerPacket::ChatMessage(msg, name) => { + VarInt(0x1B).serialize(&mut w)?; + msg.serialize(&mut w)?; + VarInt(7).serialize(&mut w)?; // chat type from registry codec + name.serialize(&mut w)?; + false.serialize(&mut w)?; // no target + }, + ServerPacket::KeepAlive(data) => { + VarInt(0x23).serialize(&mut w)?; + data.serialize(&mut w)?; + }, + ServerPacket::ChunkData { x, z } => { + VarInt(0x24).serialize(&mut w)?; + // chunk x + x.serialize(&mut w)?; + // chunk z + z.serialize(&mut w)?; + // heightmap + include_bytes!("../resources/heightmap.nbt").serialize(&mut w)?; + // chunk data size and chunk data + VarInt(192).serialize(&mut w)?; + [0u8; 192].serialize(&mut w)?; + // block entities + VarInt(0).serialize(&mut w)?; + // light masks + VarInt(0).serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + VarInt(0).serialize(&mut w)?; + // sky light array len + VarInt(0).serialize(&mut w)?; + // block light array len + VarInt(0).serialize(&mut w)?; + }, ServerPacket::JoinGame { eid, gamemode, hardcode } => { VarInt(0x28).serialize(&mut w)?; eid.serialize(&mut w)?; @@ -32,7 +77,7 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> (-1i8).serialize(&mut w)?; // prev gamemode undefined VarInt(1).serialize(&mut w)?; // dimension count "qc:world".serialize(&mut w)?; // register one dimension - include_bytes!("registry.nbt").serialize(&mut w)?; // registry codec + include_bytes!("../resources/registry_1.20.1.nbt").serialize(&mut w)?; // registry codec "minecraft:the_end".serialize(&mut w)?; // dimension type "qc:world".serialize(&mut w)?; // dimension name 0i64.serialize(&mut w)?; // seed @@ -46,56 +91,11 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> false.serialize(&mut w)?; // has death location VarInt(1).serialize(&mut w)?; // portal cooldown }, - ServerPacket::KeepAlive(data) => { - VarInt(0x23).serialize(&mut w)?; - data.serialize(&mut w)?; - }, - ServerPacket::PlayDisconnect(msg) => { - VarInt(0x1A).serialize(&mut w)?; - msg.serialize(&mut w)?; - }, - ServerPacket::PluginMessage { channel, data } => { - VarInt(0x17).serialize(&mut w)?; - channel.serialize(&mut w)?; - w.write_all(&data)?; - }, - ServerPacket::ChunkData { x, z } => { - VarInt(0x24).serialize(&mut w)?; - // chunk x - x.serialize(&mut w)?; - // chunk z - z.serialize(&mut w)?; - // heightmap - let hmdata = [0i64; 37]; - let mut heightmap = nbt::Blob::new(); - heightmap.insert("MOTION_BLOCKING", hmdata.as_ref()).unwrap(); - heightmap.serialize(&mut w)?; - // chunk data - let mut chunk_data: Vec = Vec::new(); - for _ in 0..(384 / 16) { - // number of non-air blocks - (0i16).serialize(&mut chunk_data)?; - // block states - (0u8).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - // biomes - (0u8).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - VarInt(0).serialize(&mut chunk_data)?; - } - VarInt(chunk_data.len() as i32).serialize(&mut w)?; - w.write_all(&chunk_data)?; - // block entities - VarInt(0).serialize(&mut w)?; - // light masks - VarInt(0).serialize(&mut w)?; - VarInt(0).serialize(&mut w)?; - VarInt(0).serialize(&mut w)?; - VarInt(0).serialize(&mut w)?; - // sky light array len - VarInt(0).serialize(&mut w)?; - // block light array len + ServerPacket::PositionAndLook { pos, look, flags } => { + VarInt(0x3C).serialize(&mut w)?; + pos.serialize(&mut w)?; + look.serialize(&mut w)?; + flags.serialize(&mut w)?; VarInt(0).serialize(&mut w)?; }, ServerPacket::SetDefaultSpawn { pos, angle } => { @@ -103,12 +103,18 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> pos.serialize(&mut w)?; angle.serialize(&mut w)?; }, + ServerPacket::SystemMessage(msg, overlay) => { + VarInt(0x64).serialize(&mut w)?; + msg.serialize(&mut w)?; + overlay.serialize(&mut w)?; + }, _ => { (COMMON.encode)(w, state, ev)?; } } Ok(()) } fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result> { + trace!("1.20.1 decoding {state:?} {id}"); type Ps = ProtocolState; match (state, id) { (Ps::Login, 0x00) => { @@ -120,16 +126,24 @@ fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> Ok(Some(ClientPacket::LoginStart { name, uuid })) } (Ps::Login, 0x01 | 0x02) => Ok(None), // unsupported + (Ps::Play, 0x04) => { + let cmd = String::deserialize(&mut r)?; + Ok(Some(ClientPacket::Command(cmd))) + }, + (Ps::Play, 0x05) => { + let msg = String::deserialize(&mut r)?; + Ok(Some(ClientPacket::ChatMessage(msg))) + }, (Ps::Play, 0x0D) => { let channel = String::deserialize(&mut r)?; let mut data = Vec::new(); r.read_to_end(&mut data)?; Ok(Some(ClientPacket::PluginMessage { channel, data })) - } + }, (Ps::Play, 0x12) => { let data = i64::deserialize(&mut r)?; Ok(Some(ClientPacket::KeepAlive(data))) - } + }, (Ps::Play, 0x14 | 0x15 | 0x16) => Ok(None), // position & rotation _ => (COMMON.decode)(r, state, len, id) } diff --git a/src/resources/dimcodec_1.16.5.nbt b/src/resources/dimcodec_1.16.5.nbt new file mode 100644 index 0000000000000000000000000000000000000000..dbecc036a7152dd831a66990105057a54aa7ea0b GIT binary patch literal 30478 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 HR6_m_w%aZ{ literal 0 HcmV?d00001 diff --git a/src/resources/dimcodec_1.17.1.nbt b/src/resources/dimcodec_1.17.1.nbt new file mode 100644 index 0000000000000000000000000000000000000000..eda60e13e313731f841d6b3c1809a4a1f1ab31c2 GIT binary patch literal 31161 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 GA^!*1^rUM5 literal 0 HcmV?d00001 diff --git a/src/resources/dimension_1.16.5.nbt b/src/resources/dimension_1.16.5.nbt new file mode 100644 index 0000000000000000000000000000000000000000..745dbc4937c782edaae430e9a4266bdebdb0e07f GIT binary patch literal 281 zcmYL_Yih(W41~w@vAgMmE)=?F>7O3OD2|+nb>v_>q`h^IP@5E1|D@4;jMM=JPm7E} z)MoNr+k;YB69qirh2~yZiw!bP1+|XoM7^l=Yf@ZhQFe)Z`Mgan>tOJnI4`uRC3RCw zc8lqnEB$$*Z1eSJCmNi39{$B64(vU)!uN4WXR?Y uuOW`&DAQQWS75VFTw^#ETN}`R@+`iCAj-`HL1pWXrcT~Cn literal 0 HcmV?d00001 diff --git a/src/resources/dimension_1.17.1.nbt b/src/resources/dimension_1.17.1.nbt new file mode 100644 index 0000000000000000000000000000000000000000..82392df5396a0fbc8bb9cf7d7b3a9a2e39133374 GIT binary patch literal 306 zcmYL_TW*9f3`H+up7fzss?@G!e!7&%1P81M2}*+K?7B;+5tZ=Y^|g;54N&1}lHN-& znH;M;2P$(Sk0V}aY=uKHUizUxCUWI#Pa6(hg}20cq9qs# zc8JL=G2OD_p9ab%-|ic-!lmWz-#p{Q&ao{S*Bx92UWpHAocw5Qk&)L`fcHt;S6m|L za#iES$6hRXGw`->g(F^VjHwg7icMCNzkj#(8V8+X?yJ%-tRTyxpXgOyW?vF5lt$Dz H?mYYf#ot+l literal 0 HcmV?d00001 diff --git a/src/resources/heightmap.nbt b/src/resources/heightmap.nbt new file mode 100644 index 0000000000000000000000000000000000000000..f2449fcf1f269ce2f161d3e5f79031d91b08091a GIT binary patch literal 322 mcmd;LVBlfk_w^6)^!JN*^6_`}_VjaSU|>)M;!!|M2mk=bWCY0o literal 0 HcmV?d00001 diff --git a/src/protocol/v1_19_4/registry.nbt b/src/resources/registry_1.19.4.nbt similarity index 99% rename from src/protocol/v1_19_4/registry.nbt rename to src/resources/registry_1.19.4.nbt index f4990fff7a9763c82c01e5295ebd4b2973828d90..63d85f6cc1eb318af2aac9b27bbd91f9e3030494 100644 GIT binary patch delta 101 zcmZo{V4Bm&G{KpXaiWVmkmzw{< delta 36 qcmbQ!$kf=tG{KpXVWNvWkmzw{WZ!sUF&h&D*JMKusm)p(MHK+Y_X=kK diff --git a/src/protocol/v1_20_1/registry.nbt b/src/resources/registry_1.20.1.nbt similarity index 99% rename from src/protocol/v1_20_1/registry.nbt rename to src/resources/registry_1.20.1.nbt index 63e4a3a43cf51257bbbcd22d8ec6751b4b7cc3c9..ffad41df59d6a69897c1c17e1be37bd6453a9ece 100644 GIT binary patch delta 93 zcmeyfk!kv7rVVwXj2x363J7nWBszg@vb_WcJ6mD0RZ(L3~5!FfdO3Afbs}X0w>gvl;+&MjO@u delta 25 hcmbQfnd#3)rVVwXjO>#i3J7nWBszg@^In--H2{jp3a|hG diff --git a/src/server.rs b/src/server.rs index 33debcc..03ccafb 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,9 @@ -use std::{collections::HashMap, time::Duration, net::ToSocketAddrs}; +use std::{collections::HashMap, time::Duration, net::{ToSocketAddrs, SocketAddr}}; use log::{info, error}; -use tokio::{net::TcpListener, sync::mpsc::{Sender, self}, select}; +use tokio::{net::{TcpListener, TcpStream}, sync::mpsc::{Sender, self}, select}; +use uuid::Uuid; +use anyhow::anyhow; use crate::{client::run_client, Player, ClientInfo, client::event::{ServerEvent, ClientEvent}, plugin::{run_plugins, event::PluginEvent}}; @@ -14,104 +16,185 @@ pub struct Client { player: Option, } -pub async fn run_server() -> Result<(), Box> { +struct Server { + gc_tx: Sender<(i32, ClientEvent)>, + ps_tx: Sender, + next_id: i32, + clients: HashMap, + ids: HashMap, +} + +pub async fn run_server() -> anyhow::Result<()> { env_logger::init(); - let (ps_tx, ps_rx) = std::sync::mpsc::channel(); + let (ps_tx, ps_rx) = mpsc::channel(256); let (pc_tx, mut pc_rx) = mpsc::channel(256); - tokio::spawn(run_plugins(pc_tx, ps_rx)); + tokio::spawn(async { + if let Err(e) = run_plugins(pc_tx, ps_rx).await { + println!("error in plugins: {e}"); + } + }); - let socket_addr = "0.0.0.0:25565".to_socket_addrs()?.next().expect("invalid server address"); + let socket_addr = "0.0.0.0:25567".to_socket_addrs()? + .next().expect("invalid server address"); let listener = TcpListener::bind(socket_addr).await?; - let mut next_id = 0i32; let (gc_tx, mut gc_rx) = mpsc::channel(16); + + let mut server = Server { + gc_tx, ps_tx, + next_id: 0, + clients: HashMap::new(), + ids: HashMap::new(), + }; + let mut keepalive = tokio::time::interval(Duration::from_secs(15)); - let mut clients = HashMap::new(); info!("listening on {socket_addr}"); loop { select! { conn = listener.accept() => { - let gc_tx = gc_tx.clone(); let (stream, addr) = conn?; - - let id = next_id; - next_id = next_id.wrapping_add(1); - - let (gs_tx, gs_rx) = mpsc::channel(16); - clients.insert(id, Client { - tx: gs_tx, - keepalive: None, - info: None, - player: None, - }); - info!("#{id} connected from {addr}"); - tokio::spawn(async move { - let c_tx2 = gc_tx.clone(); - match run_client(id, stream, gc_tx, gs_rx).await { - Ok(_) => info!("client {id} disconnected"), - Err(e) => error!("client {id} error: {e}"), - } - c_tx2.send((id, ClientEvent::Disconnect)).await.unwrap(); - }); - }, - _ = keepalive.tick() => { - for client in clients.values_mut() { - if client.keepalive.is_some() { - client.tx.send(ServerEvent::Disconnect(Some("Failed to respond to keep alives".to_owned()))).await?; - } else if client.player.is_some() { - let data = 1; - client.keepalive = Some(data); - client.tx.send(ServerEvent::KeepAlive(data)).await?; - } - } + accept_connection(&mut server, stream, addr).await?; }, + _ = keepalive.tick() => handle_keepalive(&mut server).await?, ev = pc_rx.recv() => { let Some(ev) = ev else { - return Err("plugin channel closed".into()); + return Err(anyhow!("plugin channel closed")); }; - match ev { - PluginEvent::Kick(u, msg) => { - for client in clients.values() { - if let Some(Player { uuid, .. }) = client.player { - if uuid == u { - client.tx.send(ServerEvent::Disconnect(msg.clone())).await?; - break; - } - } - } - } - } + handle_plugin_event(&mut server, ev).await?; }, ev = gc_rx.recv() => { let Some(ev) = ev else { - return Err("reciever closed".into()); + return Err(anyhow!("reciever closed")); }; - let id = ev.0; - let client = clients.get_mut(&id).unwrap(); - match ev.1 { - ClientEvent::Handshake(info) => client.info = Some(info), - ClientEvent::Join(player) => { - client.player = Some(player.clone()); - ps_tx.send(PServerEvent::Join(player, client.info.clone().unwrap()))?; - }, - ClientEvent::Disconnect => { - let player = clients.get(&id).unwrap().player.clone(); - clients.remove(&id); - if let Some(player) = player { - ps_tx.send(PServerEvent::Leave(player))?; - } - }, - ClientEvent::KeepAlive(data) => { - if client.keepalive == Some(data) { - client.keepalive = None; - } else { - client.keepalive = Some(0); - } - } - } + handle_client_event(&mut server, ev.0, ev.1).await?; } } } } + +async fn accept_connection(server: &mut Server, stream: TcpStream, addr: SocketAddr) -> anyhow::Result<()> { + let gc_tx = server.gc_tx.clone(); + + let id = server.next_id; + server.next_id = server.next_id.wrapping_add(1); + + let (gs_tx, gs_rx) = mpsc::channel(16); + server.clients.insert(id, Client { + tx: gs_tx, + keepalive: None, + info: None, + player: None, + }); + info!("#{id} connected from {addr}"); + tokio::spawn(async move { + let c_tx2 = gc_tx.clone(); + match run_client(id, stream, gc_tx, gs_rx).await { + Ok(_) => info!("client {id} disconnected"), + Err(e) => error!("client {id} error: {e}"), + } + c_tx2.send((id, ClientEvent::Disconnect)).await.unwrap(); + }); + Ok(()) +} + +async fn handle_keepalive(server: &mut Server) -> anyhow::Result<()> { + for client in server.clients.values_mut() { + if client.keepalive.is_some() { + client.tx.send(ServerEvent::Disconnect(Some("Failed to respond to keep alives".to_owned()))).await?; + } else if client.player.is_some() { + let data = 1; + client.keepalive = Some(data); + client.tx.send(ServerEvent::KeepAlive(data)).await?; + } + } + Ok(()) +} + +async fn handle_plugin_event(server: &mut Server, ev: PluginEvent) -> anyhow::Result<()> { + match ev { + PluginEvent::Kick(uuid, msg) => { + if let Some(id) = server.ids.get(&uuid) { + server.clients[id].tx + .send(ServerEvent::Disconnect(msg)) + .await?; + } + }, + PluginEvent::Chat(uuid, msg, name) => { + if let Some(id) = server.ids.get(&uuid) { + server.clients[id].tx + .send(ServerEvent::ChatMessage(msg, name)) + .await?; + } + }, + PluginEvent::BroadcastChat(msg, name) => { + for client in server.clients.values() { + if client.player.is_some() { + client.tx.send(ServerEvent::ChatMessage(msg.clone(), name.clone())).await?; + } + } + }, + PluginEvent::System(uuid, msg, overlay) => { + if let Some(id) = server.ids.get(&uuid) { + server.clients[id].tx + .send(ServerEvent::SystemMessage(msg, overlay)) + .await?; + } + }, + PluginEvent::BroadcastSystem(msg, overlay) => { + for client in server.clients.values() { + if client.player.is_some() { + client.tx.send(ServerEvent::SystemMessage(msg.clone(), overlay)).await?; + } + } + }, + } + Ok(()) +} + +async fn handle_client_event(server: &mut Server, id: i32, ev: ClientEvent) -> anyhow::Result<()> { + match ev { + ClientEvent::Handshake(info) => { + server.clients.get_mut(&id).unwrap().info = Some(info) + }, + ClientEvent::Join(player) => { + if let Some(other_id) = server.ids.remove(&player.uuid) { + let other_client = server.clients.remove(&other_id).unwrap(); + server.ps_tx + .send(PServerEvent::Leave(other_client.player.unwrap())) + .await?; + other_client.tx + .send(ServerEvent::Disconnect(Some("logged in elsewhere".to_owned()))) + .await?; + } + let client = server.clients.get_mut(&id).unwrap(); + client.player = Some(player.clone()); + server.ids.insert(player.uuid, id); + server.ps_tx.send(PServerEvent::Join(player, client.info.clone().unwrap())).await?; + }, + ClientEvent::Disconnect => if let Some(client) = server.clients.remove(&id) { + if let Some(player) = client.player { + server.ids.remove(&player.uuid); + server.ps_tx.send(PServerEvent::Leave(player)).await?; + } + }, + ClientEvent::KeepAlive(data) => { + let client = server.clients.get_mut(&id).unwrap(); + if client.keepalive == Some(data) { + client.keepalive = None; + } else { + client.keepalive = Some(0); + } + }, + ClientEvent::Command(cmd) => { + let player = server.clients.get(&id).unwrap().player.clone().unwrap(); + server.ps_tx.send(PServerEvent::Command(player, cmd)).await?; + }, + ClientEvent::Message(msg) => { + let player = server.clients.get(&id).unwrap().player.clone().unwrap(); + server.ps_tx.send(PServerEvent::Message(player, msg)).await?; + }, + } + Ok(()) +}