features
This commit is contained in:
parent
546a8a83a6
commit
1c2cf9bd3f
|
@ -1 +1,2 @@
|
|||
/target
|
||||
.vscode
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
```
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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<String>),
|
||||
KeepAlive(i64),
|
||||
SystemMessage(JsonValue, bool),
|
||||
ChatMessage(JsonValue, JsonValue),
|
||||
}
|
||||
|
|
|
@ -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 => (),
|
||||
|
|
28
src/main.rs
28
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<T> = Arc<Mutex<T>>;
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String>),
|
||||
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),
|
||||
}
|
||||
|
|
|
@ -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<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
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<Plugin> {
|
||||
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::<String, Table>().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("<non-unicode path>");
|
||||
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<PluginEvent>, rx: Receiver<ServerEvent>) {
|
||||
pub async fn run_plugins(tx: Sender<PluginEvent>, mut rx: Receiver<ServerEvent>) -> 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<PluginEvent>, rx: Receiver<ServerEvent>) {
|
|||
)
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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<Self> {
|
||||
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<Self> {
|
||||
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<Table> {
|
||||
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("<non-unicode path>");
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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<Value<'lua>> {
|
||||
impl<'lua> IntoLua<'lua> for ClientInfo {
|
||||
fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<Value<'lua>> {
|
||||
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<JsonValue> {
|
||||
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<PluginEvent>,
|
||||
pub names: HashMap<Uuid, String>,
|
||||
pub info: HashMap<Uuid, ClientInfo>,
|
||||
pub names: RwLock<HashMap<Uuid, String>>,
|
||||
pub info: RwLock<HashMap<Uuid, ClientInfo>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CellServer(pub Rc<RefCell<Server>>);
|
||||
pub struct WrappedServer(pub Arc<Server>);
|
||||
|
||||
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<bool>)| 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<bool>)| 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::<Table>(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<u8>
|
||||
},
|
||||
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<Protocol> {
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Option<ClientPacket>> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<Option<ClientPacket>> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<Option<ClientPacket>> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<u8> = 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<Option<ClientPacket>> {
|
||||
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();
|
|
@ -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<u8> = 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<Option<ClientPacket>> {
|
||||
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)
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
237
src/server.rs
237
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<Player>,
|
||||
}
|
||||
|
||||
pub async fn run_server() -> Result<(), Box<dyn std::error::Error>> {
|
||||
struct Server {
|
||||
gc_tx: Sender<(i32, ClientEvent)>,
|
||||
ps_tx: Sender<PServerEvent>,
|
||||
next_id: i32,
|
||||
clients: HashMap<i32, Client>,
|
||||
ids: HashMap<Uuid, i32>,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue