added velocity support
This commit is contained in:
parent
8434884747
commit
d4cf718214
10 changed files with 221 additions and 35 deletions
83
Cargo.lock
generated
83
Cargo.lock
generated
|
@ -38,6 +38,15 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
|
@ -108,6 +117,15 @@ version = "0.8.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
|
@ -117,6 +135,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.83"
|
||||
|
@ -161,6 +189,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
|
@ -220,6 +259,16 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hematite-nbt"
|
||||
version = "0.5.2"
|
||||
|
@ -241,6 +290,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
|
@ -465,10 +523,12 @@ dependencies = [
|
|||
"chrono",
|
||||
"env_logger",
|
||||
"hematite-nbt",
|
||||
"hmac",
|
||||
"log",
|
||||
"mlua",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -561,6 +621,23 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.105"
|
||||
|
@ -592,6 +669,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
|
|
|
@ -14,3 +14,5 @@ uuid = "1.2"
|
|||
log = "0.4.0"
|
||||
env_logger = "0.10.0"
|
||||
chrono = "0.4"
|
||||
hmac = "0.12"
|
||||
sha2 = "0.10"
|
||||
|
|
|
@ -2,13 +2,25 @@ use std::{net::IpAddr, fs::OpenOptions};
|
|||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, PartialEq, Eq)]
|
||||
pub enum LoginMode {
|
||||
Offline,
|
||||
Velocity,
|
||||
// TODO online, bungeecord
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub addr: IpAddr,
|
||||
pub port: u16,
|
||||
pub login: LoginMode,
|
||||
pub velocity_secret: Option<String>,
|
||||
}
|
||||
|
||||
pub fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
|
||||
let config = serde_json::from_reader(OpenOptions::new().read(true).open("./config.json")?)?;
|
||||
let config: Config = serde_json::from_reader(OpenOptions::new().read(true).open("./config.json")?)?;
|
||||
if config.login == LoginMode::Velocity && config.velocity_secret.is_none() {
|
||||
Err("Velocity is enabled but no secret is configured")?
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use std::io::Write;
|
||||
|
||||
|
@ -41,17 +40,16 @@ fn main() {
|
|||
writeln!(buf, "\x1b[90m[\x1b[37m{} {color}{}\x1b[37m {}\x1b[90m]\x1b[0m {}", now, record.level(), target, record.args())
|
||||
}).init();
|
||||
|
||||
let config = load_config().expect("Failed to load config");
|
||||
let addr = SocketAddr::new(config.addr, config.port);
|
||||
|
||||
info!("Starting Quectocraft version {}", VERSION);
|
||||
|
||||
let config = load_config().expect("Failed to load config");
|
||||
|
||||
let lua = Lua::new();
|
||||
let mut plugins = Plugins::new(&lua).expect("Error initializing lua environment");
|
||||
std::fs::create_dir_all("plugins").expect("Couldn't create the plugins directory");
|
||||
plugins.load_plugins();
|
||||
|
||||
let mut server = NetworkServer::new(addr, plugins);
|
||||
let mut server = NetworkServer::new(config, plugins);
|
||||
let sleep_dur = Duration::from_millis(5);
|
||||
let mut i = 0;
|
||||
loop {
|
||||
|
|
|
@ -8,7 +8,7 @@ use super::Player;
|
|||
|
||||
pub struct NetworkClient {
|
||||
pub id: i32,
|
||||
pub play: bool,
|
||||
pub verified: bool,
|
||||
pub closed: bool,
|
||||
pub stream: TcpStream,
|
||||
pub serverbound: Receiver<ServerBoundPacket>,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::{net::{TcpListener, SocketAddr}, thread, sync::mpsc::{Receiver, Sender, channel}, collections::HashSet};
|
||||
|
||||
use log::{info, warn, debug};
|
||||
use hmac::{Hmac, Mac};
|
||||
use log::{info, warn, debug, trace};
|
||||
use serde_json::json;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::protocol::{data::PacketEncoder, serverbound::*, clientbound::*, command::Commands, Position};
|
||||
use crate::{protocol::{data::PacketEncoder, serverbound::*, clientbound::*, command::Commands, Position}, config::{Config, LoginMode}};
|
||||
use crate::plugins::{Plugins, Response};
|
||||
use crate::VERSION;
|
||||
|
||||
|
@ -14,18 +16,20 @@ pub struct NetworkServer<'lua> {
|
|||
commands: Commands,
|
||||
new_clients: Receiver<NetworkClient>,
|
||||
clients: Vec<NetworkClient>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl <'lua> NetworkServer<'lua> {
|
||||
pub fn new(addr: SocketAddr, mut plugins: Plugins<'lua>) -> Self {
|
||||
pub fn new(config: Config, mut plugins: Plugins<'lua>) -> Self {
|
||||
let (send, recv) = channel();
|
||||
info!("Initializing plugins");
|
||||
plugins.init();
|
||||
let mut commands = Commands::new();
|
||||
commands.create_simple_cmd("qc");
|
||||
let commands = plugins.register_commands(commands).unwrap();
|
||||
thread::spawn(move || Self::listen(&addr, send));
|
||||
thread::spawn(move || Self::listen(&SocketAddr::new(config.addr, config.port), send));
|
||||
Self {
|
||||
config,
|
||||
plugins,
|
||||
commands,
|
||||
new_clients: recv,
|
||||
|
@ -44,7 +48,7 @@ impl <'lua> NetworkServer<'lua> {
|
|||
thread::spawn(|| NetworkClient::listen(stream_2, send));
|
||||
let client = NetworkClient {
|
||||
id: id as i32,
|
||||
play: false,
|
||||
verified: false,
|
||||
closed: false,
|
||||
stream,
|
||||
serverbound: recv,
|
||||
|
@ -63,13 +67,11 @@ impl <'lua> NetworkServer<'lua> {
|
|||
pub fn send_keep_alive(&mut self) {
|
||||
let mut closed = Vec::new();
|
||||
for client in self.clients.iter_mut() {
|
||||
if client.play {
|
||||
if client.player.is_some() {
|
||||
let result = client.send_packet(ClientBoundPacket::KeepAlive(0));
|
||||
if result.is_err() {
|
||||
client.close();
|
||||
if let Some(pl) = &client.player {
|
||||
self.plugins.player_leave(pl);
|
||||
}
|
||||
self.plugins.player_leave(client.player.as_ref().unwrap());
|
||||
closed.push(client.id);
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +89,7 @@ impl <'lua> NetworkServer<'lua> {
|
|||
while let Some(packet) = client.recv_packet(&mut alive) {
|
||||
let result = self.handle_packet(client, packet);
|
||||
if result.is_err() {
|
||||
warn!("error: {}", result.unwrap_err());
|
||||
alive = false;
|
||||
break
|
||||
}
|
||||
|
@ -102,7 +105,7 @@ impl <'lua> NetworkServer<'lua> {
|
|||
for response in self.plugins.get_responses() {
|
||||
let _ = self.handle_plugin_response(response);
|
||||
}
|
||||
self.clients.retain(|x| !closed.contains(&x.id));
|
||||
self.clients.retain(|x| !closed.contains(&x.id) && !x.closed);
|
||||
}
|
||||
|
||||
fn handle_plugin_response(&mut self, response: Response) -> std::io::Result<()> {
|
||||
|
@ -137,7 +140,8 @@ impl <'lua> NetworkServer<'lua> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> std::io::Result<()> {
|
||||
fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> Result<(), Box<dyn std::error::Error>> {
|
||||
trace!("Recieved packet from client {}:", client.id);
|
||||
match packet {
|
||||
ServerBoundPacket::Ignored(_) => (),
|
||||
ServerBoundPacket::Unknown(id) => warn!("Unknown packet: {}", id),
|
||||
|
@ -152,21 +156,80 @@ impl <'lua> NetworkServer<'lua> {
|
|||
}
|
||||
ServerBoundPacket::LoginStart(login_start) => {
|
||||
if self.clients.iter().filter_map(|x| x.player.as_ref()).any(|x| x.uuid == login_start.uuid) {
|
||||
client.send_packet(ClientBoundPacket::LoginDisconnect(json!({"translate": "multiplayer.disconnect.duplicate_login"})))?;
|
||||
client.send_packet(ClientBoundPacket::LoginDisconnect(json!({
|
||||
"translate": "multiplayer.disconnect.duplicate_login"
|
||||
})))?;
|
||||
client.close();
|
||||
} else {
|
||||
return Ok(())
|
||||
}
|
||||
client.player = Some(Player {
|
||||
name: login_start.name.clone(),
|
||||
uuid: login_start.uuid,
|
||||
});
|
||||
client.play = true;
|
||||
match self.config.login {
|
||||
LoginMode::Offline => {
|
||||
client.verified = true;
|
||||
client.send_packet(ClientBoundPacket::LoginPluginRequest{
|
||||
id: -1,
|
||||
channel: "qc:init".to_owned(),
|
||||
data: Vec::new()
|
||||
})?;
|
||||
},
|
||||
LoginMode::Velocity => {
|
||||
client.send_packet(ClientBoundPacket::LoginPluginRequest{
|
||||
id: 10,
|
||||
channel: "velocity:player_info".to_owned(),
|
||||
data: vec![1],
|
||||
})?
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finalize login
|
||||
ServerBoundPacket::LoginPluginResponse { id: -1, .. } => {
|
||||
if !client.verified {
|
||||
client.send_packet(ClientBoundPacket::Disconnect(json!({
|
||||
"text": "Failed to verify your connection",
|
||||
"color": "red",
|
||||
})))?;
|
||||
client.close();
|
||||
}
|
||||
client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess {
|
||||
name: login_start.name,
|
||||
uuid: login_start.uuid,
|
||||
name: client.player.as_ref().unwrap().name.to_owned(),
|
||||
uuid: client.player.as_ref().unwrap().uuid,
|
||||
}))?;
|
||||
self.plugins.player_join(client.player.as_ref().unwrap());
|
||||
self.post_login(client)?;
|
||||
}
|
||||
// Velocity login
|
||||
ServerBoundPacket::LoginPluginResponse { id: 10, data } => {
|
||||
let Some(data) = data else {
|
||||
client.send_packet(ClientBoundPacket::LoginDisconnect(json!({
|
||||
"text": "This server can only be connected to via a Velocity proxy",
|
||||
"color": "red"
|
||||
})))?;
|
||||
client.close();
|
||||
return Ok(());
|
||||
};
|
||||
let (sig, data) = data.split_at(32);
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(self.config.velocity_secret.clone().unwrap().as_bytes())?;
|
||||
mac.update(data);
|
||||
if let Err(_) = mac.verify_slice(sig) {
|
||||
client.send_packet(ClientBoundPacket::Disconnect(json!({
|
||||
"text": "Could not verify secret. Ensure that the secrets configured for Velocity and Quectocraft match."
|
||||
})))?;
|
||||
client.close();
|
||||
return Ok(())
|
||||
}
|
||||
client.verified = true;
|
||||
client.send_packet(ClientBoundPacket::LoginPluginRequest{
|
||||
id: -1,
|
||||
channel: "qc:init".to_owned(),
|
||||
data: Vec::new()
|
||||
})?;
|
||||
}
|
||||
ServerBoundPacket::LoginPluginResponse { .. } => {
|
||||
client.send_packet(ClientBoundPacket::LoginDisconnect(json!({"text": "bad plugin response"})))?;
|
||||
client.close();
|
||||
}
|
||||
ServerBoundPacket::ChatMessage(msg) => {
|
||||
self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message);
|
||||
|
|
|
@ -142,6 +142,7 @@ pub enum ClientBoundPacket {
|
|||
StatusResponse(String),
|
||||
PingResponse(i64),
|
||||
// login
|
||||
LoginPluginRequest { id: i32, channel: String, data: Vec<u8> },
|
||||
LoginSuccess(LoginSuccess),
|
||||
LoginDisconnect(serde_json::Value),
|
||||
// play
|
||||
|
@ -175,6 +176,12 @@ impl ClientBoundPacket {
|
|||
packet.write_string(262144, &message.to_string());
|
||||
finalize_packet(packet, 0)
|
||||
}
|
||||
Self::LoginPluginRequest { id, channel, data } => {
|
||||
packet.write_varint(id);
|
||||
packet.write_string(32767, &channel);
|
||||
packet.write_bytes(&data);
|
||||
finalize_packet(packet, 4)
|
||||
}
|
||||
Self::LoginSuccess(login_success) => {
|
||||
login_success.encode(&mut packet);
|
||||
finalize_packet(packet, 2)
|
||||
|
|
|
@ -50,7 +50,8 @@ pub trait PacketEncoder: Write {
|
|||
self.write_all(&data.to_be_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn write_varint(&mut self, mut data: i32) {
|
||||
fn write_varint(&mut self, data: i32) {
|
||||
let mut data = data as u32;
|
||||
loop {
|
||||
let mut byte = (data & 0b11111111) as u8;
|
||||
data >>= 7;
|
||||
|
@ -64,7 +65,8 @@ pub trait PacketEncoder: Write {
|
|||
}
|
||||
}
|
||||
|
||||
fn write_varlong(&mut self, mut data: i64) {
|
||||
fn write_varlong(&mut self, data: i64) {
|
||||
let mut data = data as u64;
|
||||
loop {
|
||||
let mut byte = (data & 0b11111111) as u8;
|
||||
data >>= 7;
|
||||
|
@ -161,6 +163,12 @@ impl PacketDecoder {
|
|||
ret
|
||||
}
|
||||
|
||||
pub fn read_to_end(&mut self) -> &[u8] {
|
||||
let ret = &self.data[self.idx..];
|
||||
self.idx = self.data.len();
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn read_bool(&mut self) -> bool {
|
||||
self.idx += 1;
|
||||
self.data[self.idx-1] != 0
|
||||
|
|
|
@ -71,6 +71,7 @@ pub enum ServerBoundPacket {
|
|||
PingRequest(i64),
|
||||
// login
|
||||
LoginStart(LoginStart),
|
||||
LoginPluginResponse { id: i32, data: Option<Vec<u8>> },
|
||||
// play
|
||||
ChatMessage(ChatMessage),
|
||||
ChatCommand(ChatMessage),
|
||||
|
@ -94,9 +95,21 @@ impl ServerBoundPacket {
|
|||
(NS::Status, 1)
|
||||
=> ServerBoundPacket::PingRequest(decoder.read_long()),
|
||||
(NS::Login, 0) => {
|
||||
*state = NS::Play;
|
||||
ServerBoundPacket::LoginStart(LoginStart::decode(decoder))
|
||||
},
|
||||
(NS::Login, 2) => {
|
||||
let id = decoder.read_varint();
|
||||
let success = decoder.read_bool();
|
||||
let data = if success {
|
||||
Some(decoder.read_to_end().to_vec())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if id == -1 {
|
||||
*state = NetworkState::Play;
|
||||
}
|
||||
ServerBoundPacket::LoginPluginResponse { id, data }
|
||||
},
|
||||
(NS::Play, 4) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)),
|
||||
(NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),
|
||||
(NS::Play, id @ (17 | 19 | 20 | 21 | 29)) => ServerBoundPacket::Ignored(id),
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue