changed things
This commit is contained in:
parent
9b1f942636
commit
546a8a83a6
14 changed files with 647 additions and 126 deletions
189
Cargo.lock
generated
189
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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
20
plugins/example.lua
Normal 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,
|
||||
}
|
|
@ -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?;
|
96
src/main.rs
96
src/main.rs
|
@ -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
26
src/plugin/event.rs
Normal 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
136
src/plugin/mod.rs
Normal 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
76
src/plugin/plugin.rs
Normal 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
61
src/plugin/server.rs
Normal 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()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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)?;
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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
117
src/server.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} }
|
||||
}
|
Loading…
Reference in a new issue