added velocity support

This commit is contained in:
TriMill 2022-12-17 15:39:56 -05:00
parent 8434884747
commit d4cf718214
10 changed files with 221 additions and 35 deletions

83
Cargo.lock generated
View file

@ -38,6 +38,15 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 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]] [[package]]
name = "bstr" name = "bstr"
version = "0.2.17" version = "0.2.17"
@ -108,6 +117,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.3.2" version = "1.3.2"
@ -117,6 +135,16 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.83" version = "1.0.83"
@ -161,6 +189,17 @@ dependencies = [
"syn", "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]] [[package]]
name = "either" name = "either"
version = "1.8.0" version = "1.8.0"
@ -220,6 +259,16 @@ dependencies = [
"miniz_oxide", "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]] [[package]]
name = "hematite-nbt" name = "hematite-nbt"
version = "0.5.2" version = "0.5.2"
@ -241,6 +290,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -465,10 +523,12 @@ dependencies = [
"chrono", "chrono",
"env_logger", "env_logger",
"hematite-nbt", "hematite-nbt",
"hmac",
"log", "log",
"mlua", "mlua",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"uuid", "uuid",
] ]
@ -561,6 +621,23 @@ dependencies = [
"serde", "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]] [[package]]
name = "syn" name = "syn"
version = "1.0.105" version = "1.0.105"
@ -592,6 +669,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.5" version = "1.0.5"

View file

@ -14,3 +14,5 @@ uuid = "1.2"
log = "0.4.0" log = "0.4.0"
env_logger = "0.10.0" env_logger = "0.10.0"
chrono = "0.4" chrono = "0.4"
hmac = "0.12"
sha2 = "0.10"

View file

@ -2,13 +2,25 @@ use std::{net::IpAddr, fs::OpenOptions};
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize, PartialEq, Eq)]
pub enum LoginMode {
Offline,
Velocity,
// TODO online, bungeecord
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
pub addr: IpAddr, pub addr: IpAddr,
pub port: u16, pub port: u16,
pub login: LoginMode,
pub velocity_secret: Option<String>,
} }
pub fn load_config() -> Result<Config, Box<dyn std::error::Error>> { 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) Ok(config)
} }

View file

@ -1,5 +1,4 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::net::SocketAddr;
use std::time::Duration; use std::time::Duration;
use std::io::Write; 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()) writeln!(buf, "\x1b[90m[\x1b[37m{} {color}{}\x1b[37m {}\x1b[90m]\x1b[0m {}", now, record.level(), target, record.args())
}).init(); }).init();
let config = load_config().expect("Failed to load config");
let addr = SocketAddr::new(config.addr, config.port);
info!("Starting Quectocraft version {}", VERSION); info!("Starting Quectocraft version {}", VERSION);
let config = load_config().expect("Failed to load config");
let lua = Lua::new(); let lua = Lua::new();
let mut plugins = Plugins::new(&lua).expect("Error initializing lua environment"); let mut plugins = Plugins::new(&lua).expect("Error initializing lua environment");
std::fs::create_dir_all("plugins").expect("Couldn't create the plugins directory"); std::fs::create_dir_all("plugins").expect("Couldn't create the plugins directory");
plugins.load_plugins(); 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 sleep_dur = Duration::from_millis(5);
let mut i = 0; let mut i = 0;
loop { loop {

View file

@ -8,7 +8,7 @@ use super::Player;
pub struct NetworkClient { pub struct NetworkClient {
pub id: i32, pub id: i32,
pub play: bool, pub verified: bool,
pub closed: bool, pub closed: bool,
pub stream: TcpStream, pub stream: TcpStream,
pub serverbound: Receiver<ServerBoundPacket>, pub serverbound: Receiver<ServerBoundPacket>,

View file

@ -1,9 +1,11 @@
use std::{net::{TcpListener, SocketAddr}, thread, sync::mpsc::{Receiver, Sender, channel}, collections::HashSet}; 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 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::plugins::{Plugins, Response};
use crate::VERSION; use crate::VERSION;
@ -14,18 +16,20 @@ pub struct NetworkServer<'lua> {
commands: Commands, commands: Commands,
new_clients: Receiver<NetworkClient>, new_clients: Receiver<NetworkClient>,
clients: Vec<NetworkClient>, clients: Vec<NetworkClient>,
config: Config,
} }
impl <'lua> NetworkServer<'lua> { 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(); let (send, recv) = channel();
info!("Initializing plugins"); info!("Initializing plugins");
plugins.init(); plugins.init();
let mut commands = Commands::new(); let mut commands = Commands::new();
commands.create_simple_cmd("qc"); commands.create_simple_cmd("qc");
let commands = plugins.register_commands(commands).unwrap(); 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 { Self {
config,
plugins, plugins,
commands, commands,
new_clients: recv, new_clients: recv,
@ -44,7 +48,7 @@ impl <'lua> NetworkServer<'lua> {
thread::spawn(|| NetworkClient::listen(stream_2, send)); thread::spawn(|| NetworkClient::listen(stream_2, send));
let client = NetworkClient { let client = NetworkClient {
id: id as i32, id: id as i32,
play: false, verified: false,
closed: false, closed: false,
stream, stream,
serverbound: recv, serverbound: recv,
@ -63,13 +67,11 @@ impl <'lua> NetworkServer<'lua> {
pub fn send_keep_alive(&mut self) { pub fn send_keep_alive(&mut self) {
let mut closed = Vec::new(); let mut closed = Vec::new();
for client in self.clients.iter_mut() { for client in self.clients.iter_mut() {
if client.play { if client.player.is_some() {
let result = client.send_packet(ClientBoundPacket::KeepAlive(0)); let result = client.send_packet(ClientBoundPacket::KeepAlive(0));
if result.is_err() { if result.is_err() {
client.close(); client.close();
if let Some(pl) = &client.player { self.plugins.player_leave(client.player.as_ref().unwrap());
self.plugins.player_leave(pl);
}
closed.push(client.id); closed.push(client.id);
} }
} }
@ -87,6 +89,7 @@ impl <'lua> NetworkServer<'lua> {
while let Some(packet) = client.recv_packet(&mut alive) { while let Some(packet) = client.recv_packet(&mut alive) {
let result = self.handle_packet(client, packet); let result = self.handle_packet(client, packet);
if result.is_err() { if result.is_err() {
warn!("error: {}", result.unwrap_err());
alive = false; alive = false;
break break
} }
@ -102,7 +105,7 @@ impl <'lua> NetworkServer<'lua> {
for response in self.plugins.get_responses() { for response in self.plugins.get_responses() {
let _ = self.handle_plugin_response(response); 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<()> { fn handle_plugin_response(&mut self, response: Response) -> std::io::Result<()> {
@ -137,7 +140,8 @@ impl <'lua> NetworkServer<'lua> {
Ok(()) 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 { match packet {
ServerBoundPacket::Ignored(_) => (), ServerBoundPacket::Ignored(_) => (),
ServerBoundPacket::Unknown(id) => warn!("Unknown packet: {}", id), ServerBoundPacket::Unknown(id) => warn!("Unknown packet: {}", id),
@ -152,21 +156,80 @@ impl <'lua> NetworkServer<'lua> {
} }
ServerBoundPacket::LoginStart(login_start) => { ServerBoundPacket::LoginStart(login_start) => {
if self.clients.iter().filter_map(|x| x.player.as_ref()).any(|x| x.uuid == login_start.uuid) { 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(); client.close();
} else { return Ok(())
client.player = Some(Player {
name: login_start.name.clone(),
uuid: login_start.uuid,
});
client.play = true;
client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess {
name: login_start.name,
uuid: login_start.uuid,
}))?;
self.plugins.player_join(client.player.as_ref().unwrap());
self.post_login(client)?;
} }
client.player = Some(Player {
name: login_start.name.clone(),
uuid: login_start.uuid,
});
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: 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) => { ServerBoundPacket::ChatMessage(msg) => {
self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message); self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message);

View file

@ -142,6 +142,7 @@ pub enum ClientBoundPacket {
StatusResponse(String), StatusResponse(String),
PingResponse(i64), PingResponse(i64),
// login // login
LoginPluginRequest { id: i32, channel: String, data: Vec<u8> },
LoginSuccess(LoginSuccess), LoginSuccess(LoginSuccess),
LoginDisconnect(serde_json::Value), LoginDisconnect(serde_json::Value),
// play // play
@ -175,6 +176,12 @@ impl ClientBoundPacket {
packet.write_string(262144, &message.to_string()); packet.write_string(262144, &message.to_string());
finalize_packet(packet, 0) 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) => { Self::LoginSuccess(login_success) => {
login_success.encode(&mut packet); login_success.encode(&mut packet);
finalize_packet(packet, 2) finalize_packet(packet, 2)

View file

@ -50,7 +50,8 @@ pub trait PacketEncoder: Write {
self.write_all(&data.to_be_bytes()).unwrap(); 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 { loop {
let mut byte = (data & 0b11111111) as u8; let mut byte = (data & 0b11111111) as u8;
data >>= 7; 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 { loop {
let mut byte = (data & 0b11111111) as u8; let mut byte = (data & 0b11111111) as u8;
data >>= 7; data >>= 7;
@ -161,6 +163,12 @@ impl PacketDecoder {
ret 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 { pub fn read_bool(&mut self) -> bool {
self.idx += 1; self.idx += 1;
self.data[self.idx-1] != 0 self.data[self.idx-1] != 0

View file

@ -71,6 +71,7 @@ pub enum ServerBoundPacket {
PingRequest(i64), PingRequest(i64),
// login // login
LoginStart(LoginStart), LoginStart(LoginStart),
LoginPluginResponse { id: i32, data: Option<Vec<u8>> },
// play // play
ChatMessage(ChatMessage), ChatMessage(ChatMessage),
ChatCommand(ChatMessage), ChatCommand(ChatMessage),
@ -94,9 +95,21 @@ impl ServerBoundPacket {
(NS::Status, 1) (NS::Status, 1)
=> ServerBoundPacket::PingRequest(decoder.read_long()), => ServerBoundPacket::PingRequest(decoder.read_long()),
(NS::Login, 0) => { (NS::Login, 0) => {
*state = NS::Play;
ServerBoundPacket::LoginStart(LoginStart::decode(decoder)) 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, 4) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)),
(NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)), (NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),
(NS::Play, id @ (17 | 19 | 20 | 21 | 29)) => ServerBoundPacket::Ignored(id), (NS::Play, id @ (17 | 19 | 20 | 21 | 29)) => ServerBoundPacket::Ignored(id),

Binary file not shown.