changed things

This commit is contained in:
TriMill 2023-07-26 00:53:51 -04:00
parent 9b1f942636
commit 546a8a83a6
14 changed files with 647 additions and 126 deletions

189
Cargo.lock generated
View file

@ -32,17 +32,6 @@ version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
[[package]]
name = "async-trait"
version = "0.1.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -64,12 +53,27 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"memchr",
]
[[package]]
name = "byteorder"
version = "1.4.3"
@ -153,6 +157,43 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "futures-core"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gimli"
version = "0.27.3"
@ -212,6 +253,16 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.19"
@ -245,10 +296,27 @@ dependencies = [
]
[[package]]
name = "num-traits"
version = "0.2.15"
name = "mlua"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
checksum = "07366ed2cd22a3b000aed076e2b68896fb46f06f1f5786c5962da73c0af01577"
dependencies = [
"bstr",
"cc",
"futures-core",
"futures-task",
"futures-util",
"num-traits",
"once_cell",
"pkg-config",
"rustc-hash",
]
[[package]]
name = "num-traits"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
]
@ -272,12 +340,53 @@ dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "pin-project-lite"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "proc-macro2"
version = "1.0.66"
@ -292,11 +401,10 @@ name = "quectocraft"
version = "0.2.0"
dependencies = [
"anyhow",
"async-trait",
"env_logger",
"hematite-nbt",
"log",
"num-traits",
"mlua",
"serde_json",
"tokio",
"uuid",
@ -311,6 +419,15 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.9.1"
@ -346,13 +463,19 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
dependencies = [
"bitflags",
"bitflags 2.3.3",
"errno",
"libc",
"linux-raw-sys",
@ -365,6 +488,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.171"
@ -402,6 +531,30 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.9"
@ -444,7 +597,9 @@ dependencies = [
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",

View file

@ -4,12 +4,11 @@ version = "0.2.0"
edition = "2021"
[dependencies]
tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "io-util", "net", "sync", "time"] }
async-trait = "0.1"
num-traits = "0.2"
tokio = { version = "1.29", features = ["full"] }
serde_json = "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"] }

20
plugins/example.lua Normal file
View file

@ -0,0 +1,20 @@
return {
id = "example",
name = "Example Plugin",
version = "0.1.0",
authors = {"John Doe", "Jane Doe"},
description = "Example plugin",
license = "MIT",
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")
end,
}

View file

@ -6,9 +6,12 @@ use serde_json::json;
use tokio::{net::{TcpStream, tcp::{OwnedReadHalf, OwnedWriteHalf}}, select, io::{AsyncReadExt, AsyncWriteExt}};
use uuid::Uuid;
use crate::{varint::VarInt, ser::{Deserializable, Position}, protocol::{self, Protocol, ProtocolState, ClientPacket, ServerPacket}, event::{ClientEvent, ServerEvent}, Player, ClientInfo};
use crate::{varint::VarInt, ser::{Deserializable, Position}, protocol::{self, Protocol, ProtocolState, ClientPacket, ServerPacket}, Player, ClientInfo};
use event::{ClientEvent, ServerEvent};
type Sender = tokio::sync::mpsc::Sender<(u64, ClientEvent)>;
pub mod event;
type Sender = tokio::sync::mpsc::Sender<(i32, ClientEvent)>;
type Receiver = tokio::sync::mpsc::Receiver<ServerEvent>;
const OFFLINE_NAMESPACE: Uuid = Uuid::from_bytes([0xc3, 0xe3, 0xe7, 0xa5, 0x58, 0xe5, 0x4c, 0xf4, 0x84, 0xef, 0x27, 0x4b, 0x9e, 0xf1, 0x5c, 0xc3]);
@ -30,7 +33,7 @@ async fn read_varint_async(r: &mut OwnedReadHalf) -> std::io::Result<Option<i32>
return Ok(Some(result as i32))
}
}
return Ok(None)
Ok(None)
}
async fn write_varint_async(w: &mut OwnedWriteHalf, i: i32) -> std::io::Result<()> {
@ -49,7 +52,7 @@ async fn write_varint_async(w: &mut OwnedWriteHalf, i: i32) -> std::io::Result<(
}
struct ClientState {
id: u64,
id: i32,
proto: Protocol,
state: ProtocolState,
r: OwnedReadHalf,
@ -63,7 +66,7 @@ enum ReadPacketOutcome {
Eof,
}
pub async fn run_client(id: u64, stream: TcpStream, tx: Sender, rx: Receiver) -> anyhow::Result<()> {
pub async fn run_client(id: i32, stream: TcpStream, tx: Sender, rx: Receiver) -> anyhow::Result<()> {
debug!("running client #{id}");
let (r, w) = stream.into_split();
let client = ClientState {
@ -92,7 +95,7 @@ impl ClientState {
None => ReadPacketOutcome::None,
})
}
None => return Ok(ReadPacketOutcome::Eof),
None => Ok(ReadPacketOutcome::Eof),
}
}
@ -138,12 +141,11 @@ impl ClientState {
async fn slp(mut self, _tx: Sender, mut rx: Receiver) -> anyhow::Result<()> {
debug!("#{} entering slp", self.id);
loop { select! {
ev = rx.recv() => match ev {
Some(ServerEvent::Disconnect(msg)) => {
ev = rx.recv() => {
if let Some(ServerEvent::Disconnect(msg)) = ev {
debug!("#{} disconnecting: {}", self.id, msg.unwrap_or_default());
return Ok(())
},
_ => (),
}
},
pk = self.read_packet() => match pk? {
ReadPacketOutcome::Packet(ClientPacket::PingRequest(data)) => {
@ -196,8 +198,8 @@ impl ClientState {
return Ok(())
}
loop { select! {
ev = rx.recv() => match ev {
Some(ServerEvent::Disconnect(msg)) => {
ev = rx.recv() => {
if let Some(ServerEvent::Disconnect(msg)) = ev {
self.write_packet(ServerPacket::LoginDisconnect(json!(
{
"text": msg
@ -205,8 +207,7 @@ impl ClientState {
))).await?;
info!("#{} disconnecting: {}", self.id, msg.unwrap_or_default());
return Ok(())
},
_ => (),
}
},
pk = self.read_packet() => match pk? {
ReadPacketOutcome::Packet(ClientPacket::LoginStart { name, uuid }) => {
@ -229,7 +230,7 @@ impl ClientState {
async fn play(mut self, tx: Sender, mut rx: Receiver) -> anyhow::Result<()> {
debug!("#{} entering play", self.id);
self.write_packet(ServerPacket::JoinGame {
eid: self.id as i32,
eid: self.id,
gamemode: 3,
hardcode: false,
}).await?;

View file

@ -1,20 +1,24 @@
use std::{collections::HashMap, time::Duration, net::ToSocketAddrs};
use std::sync::Arc;
use event::ServerEvent;
use log::{info, error};
use tokio::{net::TcpListener, sync::mpsc::{Sender, self}, select};
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::{client::run_client, event::ClientEvent};
mod event;
mod protocol;
mod server;
mod plugin;
mod ser;
mod varint;
mod client;
pub type ArcMutex<T> = Arc<Mutex<T>>;
pub type JsonValue = serde_json::Value;
#[derive(Clone, Debug)]
pub struct Player {
name: String,
uuid: Uuid,
}
#[derive(Clone, Debug)]
pub struct ClientInfo {
addr: String,
@ -23,82 +27,8 @@ pub struct ClientInfo {
proto_name: &'static str,
}
#[derive(Debug)]
pub struct Player {
name: String,
uuid: Uuid,
}
struct Client {
tx: Sender<ServerEvent>,
keepalive: Option<i64>,
info: Option<ClientInfo>,
player: Option<Player>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let socket_addr = "0.0.0.0:25565".to_socket_addrs()?.next().expect("invalid server address");
let listener = TcpListener::bind(socket_addr).await?;
let mut clients = HashMap::new();
let mut next_id = 0;
let (c_tx, mut c_rx) = mpsc::channel(16);
let mut keepalive = tokio::time::interval(Duration::from_secs(15));
info!("listening on {socket_addr}");
loop { select! {
conn = listener.accept() => {
let c_tx = c_tx.clone();
let (stream, addr) = conn?;
let id = next_id;
next_id += 1;
let (s_tx, s_rx) = mpsc::channel(16);
clients.insert(id, Client {
tx: s_tx,
keepalive: None,
info: None,
player: None,
});
info!("#{id} connected from {addr}");
tokio::spawn(async move {
let c_tx2 = c_tx.clone();
match run_client(id, stream, c_tx, s_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 &mut clients {
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?;
}
}
},
ev = c_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),
ClientEvent::Join(player) => clients.get_mut(&id).unwrap().player = Some(player),
ClientEvent::Disconnect => { clients.remove(&id); },
ClientEvent::KeepAlive(data) => {
let client = clients.get_mut(&id).unwrap();
if client.keepalive == Some(data) {
client.keepalive = None;
} else {
client.keepalive = Some(0);
}
}
}
}
} }
async fn main() {
server::run_server().await.unwrap()
}

26
src/plugin/event.rs Normal file
View file

@ -0,0 +1,26 @@
use mlua::{Value, FromLua, Table};
use uuid::Uuid;
use crate::{Player, ClientInfo};
use super::UuidUD;
pub enum PluginEvent {
Kick(Uuid, Option<String>),
}
impl<'lua> FromLua<'lua> for PluginEvent {
fn from_lua(lua_value: Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
let table = Table::from_lua(lua_value, lua)?;
let ty: Box<str> = table.get("type")?;
match ty.as_ref() {
"kick" => Ok(Self::Kick(table.get::<_, UuidUD>("uuid")?.0, table.get("msg")?)),
_ => Err(mlua::Error::RuntimeError(format!("unknown event type {ty}"))),
}
}
}
pub enum ServerEvent {
Join(Player, ClientInfo),
Leave(Player),
}

136
src/plugin/mod.rs Normal file
View file

@ -0,0 +1,136 @@
use std::{io::ErrorKind, fs, cell::RefCell, rc::Rc};
use log::{warn, debug, info};
use mlua::{Lua, ToLua, Function};
use tokio::sync::mpsc::Sender;
use std::sync::mpsc::Receiver;
use uuid::Uuid;
use self::{plugin::Plugin, event::{PluginEvent, ServerEvent}, server::{CellServer, Server}};
pub mod event;
mod plugin;
mod server;
#[derive(Clone, Copy, Eq, PartialEq)]
struct UuidUD(Uuid);
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)
);
methods.add_meta_method("__eq",
|lua, this, UuidUD(uuid)| (this.0 == uuid).to_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}"),
}
}
plugins
}
pub async fn run_plugins(tx: Sender<PluginEvent>, rx: Receiver<ServerEvent>) {
let lua = unsafe {
mlua::Lua::unsafe_new_with(
mlua::StdLib::ALL_SAFE | mlua::StdLib::DEBUG,
mlua::LuaOptions::default()
)
};
let plugins = load_plugins(&lua);
let server = Rc::new(RefCell::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);
}
}
}
while let Ok(ev) = rx.recv() {
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);
}
}
}
},
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);
}
}
}
}
}

76
src/plugin/plugin.rs Normal file
View file

@ -0,0 +1,76 @@
use std::fs::DirEntry;
use log::{debug, Record};
use mlua::{Function, FromLua, Lua, Table, Value};
pub struct Plugin<'lua> {
pub table: Table<'lua>,
pub id: String,
}
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> {
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 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)
}
}

61
src/plugin/server.rs Normal file
View file

@ -0,0 +1,61 @@
use std::{collections::HashMap, cell::RefCell, rc::Rc};
use mlua::{Value, ToLua};
use tokio::sync::mpsc::Sender;
use uuid::Uuid;
use crate::ClientInfo;
use super::{event::PluginEvent, UuidUD};
impl<'lua> ToLua<'lua> for ClientInfo {
fn to_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)?;
table.set("proto_name", self.proto_name)?;
table.set("proto_version", self.proto_version)?;
Ok(Value::Table(table))
}
}
pub struct Server {
pub tx: Sender<PluginEvent>,
pub names: HashMap<Uuid, String>,
pub info: HashMap<Uuid, ClientInfo>,
}
#[derive(Clone)]
pub struct CellServer(pub Rc<RefCell<Server>>);
impl mlua::UserData for CellServer {
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("players", |lua, this, ()| {
let table = lua.create_table()?;
for (k, v) in &this.0.borrow().names {
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),
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),
None => Ok(Value::Nil)
}
});
methods.add_async_method("kick", |_, CellServer(this), (UuidUD(uuid), msg)| async move {
let tx = this.borrow().tx.clone();
tx
.send(PluginEvent::Kick(uuid, msg))
.await
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))
});
}
}

View file

@ -53,10 +53,10 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
VarInt(0x1A).serialize(&mut w)?;
msg.serialize(&mut w)?;
},
ServerPacket::PluginMessage { channel, mut data } => {
ServerPacket::PluginMessage { channel, data } => {
VarInt(0x17).serialize(&mut w)?;
channel.serialize(&mut w)?;
w.write_all(&mut data)?;
w.write_all(&data)?;
},
ServerPacket::ChunkData { x, z } => {
VarInt(0x24).serialize(&mut w)?;

View file

@ -54,10 +54,10 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
VarInt(0x1A).serialize(&mut w)?;
msg.serialize(&mut w)?;
},
ServerPacket::PluginMessage { channel, mut data } => {
ServerPacket::PluginMessage { channel, data } => {
VarInt(0x17).serialize(&mut w)?;
channel.serialize(&mut w)?;
w.write_all(&mut data)?;
w.write_all(&data)?;
},
ServerPacket::ChunkData { x, z } => {
VarInt(0x24).serialize(&mut w)?;

View file

@ -103,7 +103,7 @@ impl Deserializable for VarInt {
return Ok(VarInt(result as i32))
}
}
return Err(anyhow!("VarInt too long"))
Err(anyhow!("VarInt too long"))
}
}
@ -134,7 +134,7 @@ impl Deserializable for VarLong {
return Ok(VarLong(result as i64))
}
}
return Err(anyhow!("VarLong too long"))
Err(anyhow!("VarLong too long"))
}
}
@ -191,8 +191,8 @@ where T: Deserializable {
let mut arr: [MaybeUninit<T>; N] = unsafe {
MaybeUninit::uninit().assume_init()
};
for i in 0..N {
arr[i] = MaybeUninit::new(T::deserialize(r)?);
for item in arr.iter_mut().take(N) {
*item = MaybeUninit::new(T::deserialize(r)?);
}
// Safety: since MaybeUninit<T> and T have the same memory
// layout, so will [MaybeUninit<T>; N] and [T; N].

117
src/server.rs Normal file
View file

@ -0,0 +1,117 @@
use std::{collections::HashMap, time::Duration, net::ToSocketAddrs};
use log::{info, error};
use tokio::{net::TcpListener, sync::mpsc::{Sender, self}, select};
use crate::{client::run_client, Player, ClientInfo, client::event::{ServerEvent, ClientEvent}, plugin::{run_plugins, event::PluginEvent}};
type PServerEvent = crate::plugin::event::ServerEvent;
pub struct Client {
tx: Sender<ServerEvent>,
keepalive: Option<i64>,
info: Option<ClientInfo>,
player: Option<Player>,
}
pub async fn run_server() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let (ps_tx, ps_rx) = std::sync::mpsc::channel();
let (pc_tx, mut pc_rx) = mpsc::channel(256);
tokio::spawn(run_plugins(pc_tx, ps_rx));
let socket_addr = "0.0.0.0:25565".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 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?;
}
}
},
ev = pc_rx.recv() => {
let Some(ev) = ev else {
return Err("plugin channel closed".into());
};
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;
}
}
}
}
}
},
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),
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);
}
}
}
}
} }
}