features
This commit is contained in:
parent
546a8a83a6
commit
1c2cf9bd3f
30 changed files with 1187 additions and 396 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/target
|
||||
.vscode
|
||||
|
|
76
Cargo.lock
generated
76
Cargo.lock
generated
|
@ -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"
|
||||
|
|
12
README.md
Normal file
12
README.md
Normal file
|
@ -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,
|
||||
}
|
||||
|
|
39
plugins/mcchat.lua
Normal file
39
plugins/mcchat.lua
Normal file
|
@ -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}");
|
||||
}
|
||||
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}"),
|
||||
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);
|
||||
}
|
||||
}
|
||||
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()),
|
||||
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()
|
||||
));
|
||||
if let Err(e) = res {
|
||||
warn!("error responding to join event in plugin {}: {e}", plugin.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
})?;
|
||||
},
|
||||
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()),
|
||||
broadcast_event(&lua, "on_leave", |_, pl, _, f| {
|
||||
f.call::<_, ()>((
|
||||
pl,
|
||||
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);
|
||||
}
|
||||
}
|
||||
))
|
||||
})?;
|
||||
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 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: &'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> {
|
||||
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);
|
||||
let chunk = lua.load(path);
|
||||
debug!("evaluating plugin");
|
||||
let plugin = Self::from_lua(chunk.eval()?, lua)?;
|
||||
let table: Table = chunk.eval()?;
|
||||
|
||||
// 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());
|
||||
let id = table.get("id")
|
||||
.map_err(|e| anyhow!("could not get plugin id: {e}"))?;
|
||||
|
||||
for (name, level) in LEVELS {
|
||||
plugin.table.set(name, lua.create_function(move |lua, (msg,): (String,)| {
|
||||
lua_log(level, path, msg, lua);
|
||||
Ok(())
|
||||
})?)?;
|
||||
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}"));
|
||||
}
|
||||
} else {
|
||||
warn!("plugin '{id}' is missing a version reqirement");
|
||||
}
|
||||
|
||||
Ok(plugin)
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
138
src/protocol/v1_16_5.rs
Normal file
138
src/protocol/v1_16_5.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
138
src/protocol/v1_17_1.rs
Normal file
138
src/protocol/v1_17_1.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
146
src/protocol/v1_18_2.rs
Normal file
146
src/protocol/v1_18_2.rs
Normal file
|
@ -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)
|
||||
}
|
BIN
src/resources/dimcodec_1.16.5.nbt
Normal file
BIN
src/resources/dimcodec_1.16.5.nbt
Normal file
Binary file not shown.
BIN
src/resources/dimcodec_1.17.1.nbt
Normal file
BIN
src/resources/dimcodec_1.17.1.nbt
Normal file
Binary file not shown.
BIN
src/resources/dimcodec_1.18.2.nbt
Normal file
BIN
src/resources/dimcodec_1.18.2.nbt
Normal file
Binary file not shown.
BIN
src/resources/dimension_1.16.5.nbt
Normal file
BIN
src/resources/dimension_1.16.5.nbt
Normal file
Binary file not shown.
BIN
src/resources/dimension_1.17.1.nbt
Normal file
BIN
src/resources/dimension_1.17.1.nbt
Normal file
Binary file not shown.
BIN
src/resources/dimension_1.18.2.nbt
Normal file
BIN
src/resources/dimension_1.18.2.nbt
Normal file
Binary file not shown.
BIN
src/resources/heightmap.nbt
Normal file
BIN
src/resources/heightmap.nbt
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
171
src/server.rs
171
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,34 +16,72 @@ 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?;
|
||||
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(anyhow!("plugin channel closed"));
|
||||
};
|
||||
handle_plugin_event(&mut server, ev).await?;
|
||||
},
|
||||
ev = gc_rx.recv() => {
|
||||
let Some(ev) = ev else {
|
||||
return Err(anyhow!("reciever closed"));
|
||||
};
|
||||
handle_client_event(&mut server, ev.0, ev.1).await?;
|
||||
}
|
||||
} }
|
||||
}
|
||||
|
||||
let id = next_id;
|
||||
next_id = next_id.wrapping_add(1);
|
||||
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);
|
||||
clients.insert(id, Client {
|
||||
server.clients.insert(id, Client {
|
||||
tx: gs_tx,
|
||||
keepalive: None,
|
||||
info: None,
|
||||
|
@ -56,9 +96,11 @@ pub async fn run_server() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
c_tx2.send((id, ClientEvent::Disconnect)).await.unwrap();
|
||||
});
|
||||
},
|
||||
_ = keepalive.tick() => {
|
||||
for client in clients.values_mut() {
|
||||
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() {
|
||||
|
@ -67,51 +109,92 @@ pub async fn run_server() -> Result<(), Box<dyn std::error::Error>> {
|
|||
client.tx.send(ServerEvent::KeepAlive(data)).await?;
|
||||
}
|
||||
}
|
||||
},
|
||||
ev = pc_rx.recv() => {
|
||||
let Some(ev) = ev else {
|
||||
return Err("plugin channel closed".into());
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_plugin_event(server: &mut Server, ev: PluginEvent) -> anyhow::Result<()> {
|
||||
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;
|
||||
}
|
||||
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?;
|
||||
}
|
||||
}
|
||||
},
|
||||
ev = gc_rx.recv() => {
|
||||
let Some(ev) = ev else {
|
||||
return Err("reciever closed".into());
|
||||
};
|
||||
let id = ev.0;
|
||||
let client = clients.get_mut(&id).unwrap();
|
||||
match ev.1 {
|
||||
ClientEvent::Handshake(info) => client.info = Some(info),
|
||||
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());
|
||||
ps_tx.send(PServerEvent::Join(player, client.info.clone().unwrap()))?;
|
||||
server.ids.insert(player.uuid, id);
|
||||
server.ps_tx.send(PServerEvent::Join(player, client.info.clone().unwrap())).await?;
|
||||
},
|
||||
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::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 a new issue