Restructure project
This commit is contained in:
parent
6589eb2836
commit
866f39a356
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
target
|
||||
/output.log
|
||||
/*.log
|
||||
/logs
|
||||
/world
|
||||
/server-icon.png
|
||||
/composition.toml
|
||||
|
819
Cargo.lock
generated
819
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
@ -1,5 +1,9 @@
|
||||
[workspace]
|
||||
members = [ "crates/*", "crates/*/examples/*" ]
|
||||
members = [ "crates/*" ]
|
||||
|
||||
[workspace.dependencies]
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
composition-protocol = { path = "./crates/composition-protocol" }
|
||||
|
||||
[package]
|
||||
name = "composition"
|
||||
@ -8,8 +12,32 @@ edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
description = "An extremely fast Minecraft server"
|
||||
license = "MIT"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
composition-core = { path = "./crates/composition-core" }
|
||||
log = "0.4"
|
||||
tokio = "1"
|
||||
ctrlc = "3.1.8"
|
||||
clap = { version = "4.2.1", features = ["derive"] }
|
||||
composition-protocol = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "*", features = ["tracing-log"] }
|
||||
tracing-appender = "0.2"
|
||||
toml = "0.5"
|
||||
once_cell = "1.17"
|
||||
serde = { version = "1.0.114", features = ["serde_derive"] }
|
||||
serde_json = "1.0.59"
|
||||
quartz_nbt = { version = "0.2.6", features = ["serde"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
uuid = "0.8.2"
|
||||
tokio-util = "0.7.7"
|
||||
futures = "0.3.28"
|
||||
|
||||
# Unused but possibly useful dependencies:
|
||||
# async-trait = "0.1.48"
|
||||
# backtrace = "0.3.50"
|
||||
# base64 = "0.12.3"
|
||||
# colorful = "0.2.1"
|
||||
# fastnbt = "*"
|
||||
# mojang-api = "0.6.1"
|
||||
# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2)
|
||||
# radix64 = "0.6.2"
|
||||
# toml = "0.5.6"
|
||||
|
@ -11,6 +11,7 @@ Composition is broken up into multiple crates to speed up build times and improv
|
||||
- `composition-core` implements the main server logic, such as handling clients and loading world chunks.
|
||||
- `composition-protocol` handles the types and packets needed for network communication.
|
||||
The library was designed to be able to used by anyone looking to implement a Minecraft server.
|
||||
- `composition-config` handles the server configuration files and command line argument parsing.
|
||||
|
||||
## Useful Resources
|
||||
- [Protocol Specification](https://wiki.vg/Protocol)
|
||||
|
24
build.rs
Normal file
24
build.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use std::process::Command;
|
||||
fn main() {
|
||||
if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
|
||||
let git_hash = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
|
||||
} else {
|
||||
println!("cargo:rustc-env=GIT_HASH=000000000");
|
||||
}
|
||||
|
||||
if let Ok(output) = Command::new("git")
|
||||
.args(["log", "-1", "--format=%cI"])
|
||||
.output()
|
||||
{
|
||||
let iso_date = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
println!("cargo:rustc-env=GIT_DATE={}", iso_date);
|
||||
} else {
|
||||
println!("cargo:rustc-env=GIT_DATE=1970-01-01");
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-env=BUILD_TARGET={}",
|
||||
std::env::var("TARGET").unwrap()
|
||||
);
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
[package]
|
||||
name = "composition-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.13"
|
||||
ctrlc = "3.1.8"
|
||||
fern = { version = "0.6", features = ["colored"] }
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
quartz_nbt = { version = "0.2.6", features = ["serde"] }
|
||||
radix64 = "0.6.2"
|
||||
serde = { version = "1.0.114", features = ["serde_derive"] }
|
||||
serde_json = "1.0.59"
|
||||
substring = "1.4.5"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
toml = "0.5"
|
||||
uuid = "0.8.2"
|
||||
|
||||
# async-trait = "0.1.48"
|
||||
# backtrace = "0.3.50"
|
||||
# base64 = "0.12.3"
|
||||
# colorful = "0.2.1"
|
||||
# composition-protocol = { path = "../composition-protocol" }
|
||||
# fastnbt = "*"
|
||||
# futures = "0.3.13"
|
||||
# mojang-api = "0.6.1"
|
||||
# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2)
|
||||
# toml = "0.5.6"
|
@ -1,9 +0,0 @@
|
||||
use std::process::Command;
|
||||
fn main() {
|
||||
if let Ok(output) = Command::new("git").args(&["rev-parse", "HEAD"]).output() {
|
||||
let git_hash = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
|
||||
} else {
|
||||
println!("cargo:rustc-env=GIT_HASH=00000000");
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use std::{fs::File, path::Path};
|
||||
|
||||
fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
|
||||
let mut data = vec![];
|
||||
let mut file = File::open(path)?;
|
||||
file.read_to_end(&mut data)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub port: u16,
|
||||
pub max_players: usize,
|
||||
pub motd: String,
|
||||
pub server_icon: String,
|
||||
pub server_icon_bytes: Vec<u8>,
|
||||
pub server_string: String,
|
||||
pub log_level: log::LevelFilter,
|
||||
pub protocol_version: i32,
|
||||
pub game_version: String,
|
||||
pub server_version: String,
|
||||
}
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let server_version = format!(
|
||||
"composition/{} ({})",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("GIT_HASH").substring(0, 8)
|
||||
);
|
||||
Config {
|
||||
port: 25565,
|
||||
max_players: 20,
|
||||
motd: "Hello world!".to_owned(),
|
||||
server_icon: "server-icon.png".to_owned(),
|
||||
server_icon_bytes: include_bytes!("./server-icon.png").to_vec(),
|
||||
server_string: server_version.clone(),
|
||||
log_level: if cfg!(debug_assertions) {
|
||||
log::LevelFilter::Debug
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
},
|
||||
protocol_version: 761,
|
||||
game_version: "1.19.3".to_owned(),
|
||||
server_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Config {
|
||||
pub fn from_toml(cfg: toml::Value) -> Config {
|
||||
let mut config = Config::default();
|
||||
|
||||
let get_string = |cfg: &toml::Value, field: &str, default: &str, error: &str| -> String {
|
||||
if let Some(s) = cfg.get(field) {
|
||||
if let Some(s) = s.as_str() {
|
||||
return s.to_owned();
|
||||
} else {
|
||||
warn!("{}", error);
|
||||
}
|
||||
} else {
|
||||
warn!("{}", error);
|
||||
}
|
||||
default.to_owned()
|
||||
};
|
||||
|
||||
if let Some(&toml::Value::Integer(port)) = cfg.get("port") {
|
||||
if port < u16::MIN as i64 || port > u16::MAX as i64 {
|
||||
warn!(
|
||||
"Config port must be an integer in the range of {}-{}, using default port: {}",
|
||||
u16::MIN,
|
||||
u16::MAX,
|
||||
config.port
|
||||
);
|
||||
} else {
|
||||
config.port = port as u16;
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Config port must be an integer in the range of {}-{}, using default port: {}",
|
||||
u16::MIN,
|
||||
u16::MAX,
|
||||
config.port
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(&toml::Value::Integer(max_players)) = cfg.get("max_players") {
|
||||
if max_players < 0 {
|
||||
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
|
||||
} else {
|
||||
config.max_players = max_players as usize;
|
||||
}
|
||||
} else {
|
||||
warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players);
|
||||
}
|
||||
|
||||
config.motd = get_string(
|
||||
&cfg,
|
||||
"motd",
|
||||
&config.motd,
|
||||
&format!(
|
||||
"Config motd must be a string, using default motd: \"{}\"",
|
||||
config.motd
|
||||
),
|
||||
);
|
||||
config.game_version = get_string(
|
||||
&cfg,
|
||||
"ping_game_version",
|
||||
&config.game_version,
|
||||
&format!(
|
||||
"Config ping_game_version must be a string, using default ping_game_version: \"{}\"",
|
||||
config.game_version
|
||||
),
|
||||
);
|
||||
config.server_icon = get_string(
|
||||
&cfg,
|
||||
"server_icon",
|
||||
&config.server_icon,
|
||||
&format!(
|
||||
"Config server_icon must be a string, using default server_icon: \"{}\"",
|
||||
config.server_icon
|
||||
),
|
||||
);
|
||||
let default_log_level = format!("{}", config.log_level).to_ascii_lowercase();
|
||||
config.log_level = match &get_string(
|
||||
&cfg,
|
||||
"log_level",
|
||||
&default_log_level,
|
||||
&format!(
|
||||
"Config log_level must be a string, using default log_level: {}",
|
||||
default_log_level
|
||||
),
|
||||
)[..]
|
||||
{
|
||||
"off" => log::LevelFilter::Off,
|
||||
"error" => log::LevelFilter::Error,
|
||||
"warn" => log::LevelFilter::Warn,
|
||||
"info" => log::LevelFilter::Info,
|
||||
"debug" => log::LevelFilter::Debug,
|
||||
"trace" => log::LevelFilter::Trace,
|
||||
_ => {
|
||||
warn!("Config log_level must be one of the predefined levels: off, error, warn, info, debug, trace");
|
||||
config.log_level
|
||||
}
|
||||
};
|
||||
config
|
||||
}
|
||||
pub fn load() -> Config {
|
||||
let mut config = Config::default();
|
||||
|
||||
// Load the config
|
||||
let config_path = Path::new("composition.toml");
|
||||
if config_path.exists() {
|
||||
if let Ok(cfg) = read_file(config_path) {
|
||||
let cfg = String::from_utf8_lossy(&cfg);
|
||||
if let Ok(cfg) = cfg.parse::<toml::Value>() {
|
||||
config = Config::from_toml(cfg);
|
||||
} else {
|
||||
error!("Could not parse configuration file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Could not read configuration file, creating {}",
|
||||
config_path.to_str().unwrap_or("")
|
||||
);
|
||||
if config.write(config_path).is_err() {
|
||||
error!("Could not write configuration file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Configuration file does not exist, creating {}",
|
||||
config_path.to_str().unwrap_or("")
|
||||
);
|
||||
if config.write(config_path).is_err() {
|
||||
error!("Could not write configuration file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the server icon
|
||||
let server_icon_path = Path::new(&config.server_icon);
|
||||
if server_icon_path.exists() {
|
||||
if let Ok(server_icon_bytes) = read_file(server_icon_path) {
|
||||
config.server_icon_bytes = server_icon_bytes;
|
||||
} else {
|
||||
warn!(
|
||||
"Could not read server icon file, creating {}",
|
||||
server_icon_path.to_str().unwrap_or("")
|
||||
);
|
||||
if config.write_server_icon(server_icon_path).is_err() {
|
||||
error!("Could not write server icon file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Server icon file does not exist, creating {}",
|
||||
server_icon_path.to_str().unwrap_or("")
|
||||
);
|
||||
if config.write_server_icon(server_icon_path).is_err() {
|
||||
error!("Could not write server icon file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
pub fn write(&self, path: &Path) -> std::io::Result<()> {
|
||||
use toml::{map::Map, Value};
|
||||
|
||||
let config = Value::Table({
|
||||
let mut m = Map::new();
|
||||
m.insert(
|
||||
"server_icon".to_owned(),
|
||||
Value::String(self.server_icon.clone()),
|
||||
);
|
||||
m.insert(
|
||||
"log_level".to_owned(),
|
||||
Value::String(format!("{}", self.log_level).to_ascii_lowercase()),
|
||||
);
|
||||
m.insert("max_players".to_owned(), Value::Integer(20));
|
||||
m.insert("motd".to_owned(), Value::String(self.motd.clone()));
|
||||
m.insert(
|
||||
"ping_game_version".to_owned(),
|
||||
Value::String(self.game_version.clone()),
|
||||
);
|
||||
m.insert("port".to_owned(), Value::Integer(25565));
|
||||
m
|
||||
});
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path)?;
|
||||
}
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(&toml::ser::to_vec(&config).unwrap())?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn write_server_icon(&self, path: &Path) -> std::io::Result<()> {
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path)?;
|
||||
}
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(&self.server_icon_bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod config;
|
||||
pub mod net;
|
||||
pub mod server;
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::sync::mpsc::{self, Receiver};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Config = Config::load();
|
||||
pub static ref START_TIME: std::time::Instant = std::time::Instant::now();
|
||||
}
|
||||
|
||||
/// Set up logging, read the config file, etc.
|
||||
pub fn init() -> Receiver<()> {
|
||||
let _ = START_TIME.elapsed();
|
||||
// Set up fern logging.
|
||||
fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"[{date}][{target}][{level}] {message}",
|
||||
date = chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||
target = record.target(),
|
||||
level = record.level(),
|
||||
message = message,
|
||||
))
|
||||
})
|
||||
.level(log::LevelFilter::Trace)
|
||||
.chain(std::io::stdout())
|
||||
.chain(fern::log_file("output.log").unwrap())
|
||||
.apply()
|
||||
.unwrap();
|
||||
log::set_max_level(CONFIG.log_level);
|
||||
// Set up the ctrl-c handler.
|
||||
let (ctrlc_tx, ctrlc_rx) = mpsc::channel();
|
||||
ctrlc::set_handler(move || {
|
||||
ctrlc_tx.send(()).expect("Ctrl-C receiver disconnected");
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
ctrlc_rx
|
||||
}
|
||||
|
||||
/// Start the server.
|
||||
pub async fn start_server() -> server::Server {
|
||||
server::Server::new(format!("0.0.0.0:{}", CONFIG.port)).await
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{config::Config, CONFIG, START_TIME};
|
||||
pub use log::*;
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use serde_json::json;
|
||||
pub use uuid::Uuid;
|
||||
pub type JSON = serde_json::Value;
|
||||
pub type NBT = quartz_nbt::NbtCompound;
|
||||
pub use std::collections::VecDeque;
|
||||
pub use std::io::{Read, Write};
|
||||
pub use substring::Substring;
|
||||
pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ParseError {
|
||||
NotEnoughData,
|
||||
InvalidData,
|
||||
VarIntTooBig,
|
||||
}
|
||||
pub type ParseResult<T> = Result<(T, usize), ParseError>;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
pub mod numbers;
|
||||
pub mod other;
|
||||
|
||||
pub use numbers::*;
|
||||
pub use other::*;
|
@ -1,211 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn parse_byte(data: &[u8]) -> ParseResult<i8> {
|
||||
if data.is_empty() {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = i8::from_be_bytes([data[0]]);
|
||||
Ok((value, 1))
|
||||
}
|
||||
}
|
||||
pub fn serialize_byte(value: i8) -> [u8; 1] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_short(data: &[u8]) -> ParseResult<i16> {
|
||||
if data.len() < 2 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = i16::from_be_bytes([data[0], data[1]]);
|
||||
Ok((value, 2))
|
||||
}
|
||||
}
|
||||
pub fn serialize_short(value: i16) -> [u8; 2] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_int(data: &[u8]) -> ParseResult<i32> {
|
||||
if data.len() < 4 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = i32::from_be_bytes([data[0], data[1], data[2], data[3]]);
|
||||
Ok((value, 4))
|
||||
}
|
||||
}
|
||||
pub fn serialize_int(value: i32) -> [u8; 4] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_long(data: &[u8]) -> ParseResult<i64> {
|
||||
if data.len() < 8 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = i64::from_be_bytes([
|
||||
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]);
|
||||
Ok((value, 8))
|
||||
}
|
||||
}
|
||||
pub fn serialize_long(value: i64) -> [u8; 8] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_varint(data: &[u8]) -> ParseResult<i32> {
|
||||
let mut offset = 0;
|
||||
let mut output = 0i32;
|
||||
let mut bytes_read = 0i32;
|
||||
|
||||
loop {
|
||||
if data.len() <= offset {
|
||||
return Err(ParseError::NotEnoughData);
|
||||
}
|
||||
|
||||
output |= (((data[offset] & 0x7f) as i32) << (bytes_read * 7)) as i32;
|
||||
bytes_read += 1;
|
||||
if data[offset] & 0x80 != 0x80 {
|
||||
break;
|
||||
}
|
||||
offset += 1;
|
||||
if bytes_read >= 5 {
|
||||
return Err(ParseError::VarIntTooBig);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((output, bytes_read as usize))
|
||||
}
|
||||
pub fn serialize_varint(value: i32) -> Vec<u8> {
|
||||
let mut value = value as u32;
|
||||
let mut output = vec![];
|
||||
loop {
|
||||
let data = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
|
||||
if value == 0 {
|
||||
output.push(data);
|
||||
break;
|
||||
} else {
|
||||
output.push(data | 0x80);
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn parse_float(data: &[u8]) -> ParseResult<f32> {
|
||||
if data.len() < 4 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = f32::from_be_bytes([data[0], data[1], data[2], data[3]]);
|
||||
Ok((value, 4))
|
||||
}
|
||||
}
|
||||
pub fn serialize_float(value: f32) -> [u8; 4] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_double(data: &[u8]) -> ParseResult<f64> {
|
||||
if data.len() < 8 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = f64::from_be_bytes([
|
||||
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]);
|
||||
Ok((value, 4))
|
||||
}
|
||||
}
|
||||
pub fn serialize_double(value: f64) -> [u8; 8] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_unsigned_byte(data: &[u8]) -> ParseResult<u8> {
|
||||
if data.is_empty() {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = u8::from_be_bytes([data[0]]);
|
||||
Ok((value, 1))
|
||||
}
|
||||
}
|
||||
pub fn serialize_unsigned_byte(value: u8) -> [u8; 1] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_unsigned_short(data: &[u8]) -> ParseResult<u16> {
|
||||
if data.len() < 2 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = u16::from_be_bytes([data[0], data[1]]);
|
||||
Ok((value, 2))
|
||||
}
|
||||
}
|
||||
pub fn serialize_unsigned_short(value: u16) -> [u8; 2] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_unsigned_int(data: &[u8]) -> ParseResult<u32> {
|
||||
if data.len() < 4 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
|
||||
Ok((value, 4))
|
||||
}
|
||||
}
|
||||
pub fn serialize_unsigned_int(value: u32) -> [u8; 4] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
pub fn parse_unsigned_long(data: &[u8]) -> ParseResult<u64> {
|
||||
if data.len() < 8 {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
let value = u64::from_be_bytes([
|
||||
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]);
|
||||
Ok((value, 8))
|
||||
}
|
||||
}
|
||||
pub fn serialize_unsigned_long(value: u64) -> [u8; 8] {
|
||||
value.to_be_bytes()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_varint_works() {
|
||||
let tests = vec![
|
||||
(Ok((0, 1)), vec![0x00]),
|
||||
(Ok((1, 1)), vec![0x01]),
|
||||
(Ok((2, 1)), vec![0x02]),
|
||||
(Ok((127, 1)), vec![0x7f]),
|
||||
(Ok((128, 2)), vec![0x80, 0x01]),
|
||||
(Ok((255, 2)), vec![0xff, 0x01]),
|
||||
(Ok((25565, 3)), vec![0xdd, 0xc7, 0x01]),
|
||||
(Ok((2097151, 3)), vec![0xff, 0xff, 0x7f]),
|
||||
(Ok((2147483647, 5)), vec![0xff, 0xff, 0xff, 0xff, 0x07]),
|
||||
(Ok((-1, 5)), vec![0xff, 0xff, 0xff, 0xff, 0x0f]),
|
||||
(Ok((-2147483648, 5)), vec![0x80, 0x80, 0x80, 0x80, 0x08]),
|
||||
];
|
||||
for test in &tests {
|
||||
assert_eq!(test.0, parse_varint(&test.1));
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn serialize_varint_works() {
|
||||
let tests = vec![
|
||||
(0, vec![0x00]),
|
||||
(1, vec![0x01]),
|
||||
(2, vec![0x02]),
|
||||
(127, vec![0x7f]),
|
||||
(128, vec![0x80, 0x01]),
|
||||
(255, vec![0xff, 0x01]),
|
||||
(25565, vec![0xdd, 0xc7, 0x01]),
|
||||
(2097151, vec![0xff, 0xff, 0x7f]),
|
||||
(2147483647, vec![0xff, 0xff, 0xff, 0xff, 0x07]),
|
||||
(-1, vec![0xff, 0xff, 0xff, 0xff, 0x0f]),
|
||||
(-2147483648, vec![0x80, 0x80, 0x80, 0x80, 0x08]),
|
||||
];
|
||||
for test in &tests {
|
||||
assert_eq!(serialize_varint(test.0), test.1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn parse_bool(data: &[u8]) -> ParseResult<bool> {
|
||||
if data.is_empty() {
|
||||
Err(ParseError::NotEnoughData)
|
||||
} else {
|
||||
Ok((data[0] == 1, 1))
|
||||
}
|
||||
}
|
||||
pub fn serialize_bool(value: bool) -> [u8; 1] {
|
||||
if value {
|
||||
[0x01]
|
||||
} else {
|
||||
[0x00]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_string(data: &[u8]) -> ParseResult<String> {
|
||||
let mut offset = 0;
|
||||
let (length, offset_delta) = parse_varint(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
let length = length as usize;
|
||||
if data.len() < offset + length {
|
||||
return Err(ParseError::NotEnoughData);
|
||||
}
|
||||
let output = String::from_utf8_lossy(&data[offset..offset + length]).to_string();
|
||||
offset += length;
|
||||
Ok((output, offset))
|
||||
}
|
||||
pub fn serialize_string(value: &str) -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(value.len() as i32));
|
||||
output.extend_from_slice(value.as_bytes());
|
||||
output
|
||||
}
|
||||
|
||||
pub fn parse_json(data: &[u8]) -> ParseResult<JSON> {
|
||||
let (value_string, offset) = parse_string(data)?;
|
||||
if let Ok(value) = serde_json::from_str(&value_string) {
|
||||
Ok((value, offset))
|
||||
} else {
|
||||
Err(ParseError::InvalidData)
|
||||
}
|
||||
}
|
||||
pub fn serialize_json(value: JSON) -> Vec<u8> {
|
||||
serialize_string(&serde_json::to_string(&value).expect("Could not serialize JSON"))
|
||||
}
|
||||
|
||||
pub fn parse_nbt(data: &[u8]) -> ParseResult<NBT> {
|
||||
use quartz_nbt::io::{read_nbt, Flavor};
|
||||
use std::io::Cursor;
|
||||
let mut data = Cursor::new(data);
|
||||
// let (value_string, offset) = parse_string(data)?;
|
||||
if let Ok(value) = read_nbt(&mut data, Flavor::Uncompressed) {
|
||||
Ok((value.0, data.position() as usize))
|
||||
} else {
|
||||
Err(ParseError::InvalidData)
|
||||
}
|
||||
}
|
||||
pub fn serialize_nbt(value: NBT) -> Vec<u8> {
|
||||
use quartz_nbt::io::{write_nbt, Flavor};
|
||||
// serialize_string(&fastnbt::to_string(&value).expect("Could not serialize JSON"))
|
||||
let mut out = vec![];
|
||||
write_nbt(&mut out, None, &value, Flavor::Uncompressed).expect("Could not serialize NBT");
|
||||
out
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Position {
|
||||
pub x: i32,
|
||||
pub y: i16,
|
||||
pub z: i32,
|
||||
}
|
||||
impl Position {
|
||||
pub fn new(x: i32, y: i16, z: i32) -> Position {
|
||||
Position { x, y, z }
|
||||
}
|
||||
pub fn parse(data: &[u8]) -> ParseResult<Position> {
|
||||
let (value, offset) = parse_unsigned_long(data)?;
|
||||
let x = (value >> 38) as i32;
|
||||
let y = (value & 0xFFF) as i16;
|
||||
let z = ((value >> 12) & 0x3FFFFFF) as i32;
|
||||
Ok((Position::new(x, y, z), offset))
|
||||
}
|
||||
pub fn serialize(&self) -> [u8; 8] {
|
||||
(((self.x as u64 & 0x3FFFFFF) << 38)
|
||||
| ((self.z as u64 & 0x3FFFFFF) << 12)
|
||||
| (self.y as u64 & 0xFFF))
|
||||
.to_be_bytes()
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
pub mod mctypes;
|
||||
pub mod packets;
|
||||
|
||||
use crate::prelude::*;
|
||||
use mctypes::*;
|
||||
pub use packets::Packet;
|
||||
use std::time::Instant;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug)]
|
||||
pub enum NetworkClientState {
|
||||
Disconnected,
|
||||
Handshake,
|
||||
Status,
|
||||
Login,
|
||||
Play,
|
||||
}
|
||||
|
||||
pub struct NetworkClient {
|
||||
pub id: u128,
|
||||
pub state: NetworkClientState,
|
||||
pub stream: TcpStream,
|
||||
pub buffer: VecDeque<u8>,
|
||||
pub packet_queue: VecDeque<Packet>,
|
||||
pub last_data_time: Instant,
|
||||
}
|
||||
impl NetworkClient {
|
||||
pub fn new(id: u128, stream: TcpStream) -> NetworkClient {
|
||||
NetworkClient {
|
||||
id,
|
||||
state: NetworkClientState::Handshake,
|
||||
stream,
|
||||
buffer: VecDeque::new(),
|
||||
packet_queue: VecDeque::new(),
|
||||
last_data_time: Instant::now(),
|
||||
}
|
||||
}
|
||||
pub async fn read_data(&mut self) -> Result<(), tokio::io::Error> {
|
||||
trace!("NetworkClient.read_data() id {}", self.id);
|
||||
// Try to read 4kb at a time until there is no more data.
|
||||
loop {
|
||||
let mut buf = [0; 4096];
|
||||
match self.stream.try_read(&mut buf) {
|
||||
// There is no data available.
|
||||
Ok(0) => break,
|
||||
// Data was read.
|
||||
Ok(n) => {
|
||||
trace!("Setting last_data_time for client {}", self.id);
|
||||
self.last_data_time = Instant::now();
|
||||
self.buffer.extend(&buf[0..n]);
|
||||
debug!("Read {} bytes from client {}", n, self.id);
|
||||
}
|
||||
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => break,
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn read_packet(&mut self) -> Result<(), ParseError> {
|
||||
trace!("NetworkClient.read_packet() id {}", self.id);
|
||||
self.buffer.make_contiguous();
|
||||
if let (data, &[]) = self.buffer.as_slices() {
|
||||
let mut offset = 0;
|
||||
let (packet_length, offset_delta) = parse_varint(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
let packet_length = packet_length as usize;
|
||||
let (packet_id, offset_delta) = parse_varint(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
let packet_id = packet_id as usize;
|
||||
let (packet, offset_delta) =
|
||||
Packet::parse_body(&data[offset..], packet_length, packet_id, self.state, true)?;
|
||||
debug!("Got packet {:?} from client {}", packet, self.id);
|
||||
offset += offset_delta;
|
||||
self.packet_queue.push_back(packet);
|
||||
let remaining_data = self.buffer.split_off(offset);
|
||||
self.buffer = remaining_data;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send_packet(&mut self, packet: Packet) -> Result<(), tokio::io::Error> {
|
||||
trace!("NetworkClient.send_packet()");
|
||||
debug!("Sending packet {:?} to client {}", packet, self.id);
|
||||
let bytes = packet.serialize();
|
||||
self.stream.write(&bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn disconnect(&mut self, reason: Option<JSON>) {
|
||||
if let Some(reason) = reason {
|
||||
if self.state == NetworkClientState::Login {
|
||||
let _ = self.send_packet(Packet::CL00Disconnect { reason }).await;
|
||||
}
|
||||
}
|
||||
self.state = NetworkClientState::Disconnected;
|
||||
}
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
use super::{mctypes::*, NetworkClientState};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Packet {
|
||||
// Handshake
|
||||
SH00Handshake {
|
||||
protocol_version: i32,
|
||||
server_address: String,
|
||||
server_port: u16,
|
||||
next_state: NetworkClientState,
|
||||
},
|
||||
|
||||
// Status
|
||||
CS00Response {
|
||||
version_name: String,
|
||||
protocol_version: i32,
|
||||
max_players: usize,
|
||||
current_players: usize,
|
||||
description: JSON,
|
||||
},
|
||||
CS01Pong {
|
||||
payload: i64,
|
||||
},
|
||||
SS00Request,
|
||||
SS01Ping {
|
||||
payload: i64,
|
||||
},
|
||||
|
||||
// Login
|
||||
CL00Disconnect {
|
||||
reason: JSON,
|
||||
},
|
||||
CL01EncryptionRequest {
|
||||
server_id: String,
|
||||
public_key: Vec<u8>,
|
||||
verify_token: Vec<u8>,
|
||||
},
|
||||
CL02LoginSuccess {
|
||||
uuid: u128,
|
||||
username: String,
|
||||
},
|
||||
CL03SetCompression {
|
||||
threshold: usize,
|
||||
},
|
||||
CL04LoginPluginRequest {
|
||||
message_id: i32,
|
||||
channel: String,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
SL00LoginStart {
|
||||
username: String,
|
||||
},
|
||||
SL01EncryptionResponse {
|
||||
shared_secret: Vec<u8>,
|
||||
verify_token: Vec<u8>,
|
||||
},
|
||||
SL02LoginPluginResponse {
|
||||
message_id: i32,
|
||||
successful: bool,
|
||||
data: Option<Vec<u8>>,
|
||||
},
|
||||
|
||||
// Play
|
||||
CP14WindowItems {
|
||||
window_id: u8,
|
||||
state_id: i32,
|
||||
slots: Vec<NBT>,
|
||||
carried_item: NBT,
|
||||
},
|
||||
CP26JoinGame,
|
||||
CP48HeldItemChange,
|
||||
CP66DeclareRecipes,
|
||||
CP67Tags,
|
||||
CP1BEntityStatus,
|
||||
CP12DeclareCommands,
|
||||
CP39UnlockRecipes,
|
||||
CP22ChunkDataAndUpdateLight,
|
||||
CP38PlayerPositionAndLook {
|
||||
x: (f64, bool),
|
||||
y: (f64, bool),
|
||||
z: (f64, bool),
|
||||
yaw: (f32, bool),
|
||||
pitch: (f32, bool),
|
||||
teleport_id: i32,
|
||||
dismount_vehicle: bool,
|
||||
},
|
||||
CP36PlayerInfo,
|
||||
CP49UpdateViewPosition,
|
||||
CP25UpdateLight,
|
||||
CP4BSpawnPosition {
|
||||
location: Position,
|
||||
angle: f32,
|
||||
},
|
||||
CP00TeleportConfirm,
|
||||
|
||||
SP05ClientSettings,
|
||||
SP04ClientStatus,
|
||||
}
|
||||
impl Packet {
|
||||
pub fn parse_body(
|
||||
data: &[u8],
|
||||
_length: usize,
|
||||
id: usize,
|
||||
state: NetworkClientState,
|
||||
serverbound: bool,
|
||||
) -> ParseResult<Packet> {
|
||||
use NetworkClientState::*;
|
||||
use Packet::*;
|
||||
|
||||
let mut offset = 0;
|
||||
match state {
|
||||
Disconnected => Err(ParseError::InvalidData),
|
||||
Handshake => {
|
||||
if id == 0x00 && serverbound {
|
||||
let (protocol_version, offset_delta) = parse_varint(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
let (server_address, offset_delta) = parse_string(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
let (server_port, offset_delta) = parse_unsigned_short(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
let (next_state, offset_delta) = parse_varint(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
let next_state = match next_state {
|
||||
1 => NetworkClientState::Status,
|
||||
2 => NetworkClientState::Login,
|
||||
_ => return Err(ParseError::InvalidData),
|
||||
};
|
||||
Ok((
|
||||
SH00Handshake {
|
||||
protocol_version,
|
||||
server_address,
|
||||
server_port,
|
||||
next_state,
|
||||
},
|
||||
offset,
|
||||
))
|
||||
} else {
|
||||
Err(ParseError::InvalidData)
|
||||
}
|
||||
}
|
||||
Status => match id {
|
||||
0x00 => {
|
||||
if serverbound {
|
||||
Ok((SS00Request, offset))
|
||||
} else {
|
||||
unimplemented!("Parse CS00Response")
|
||||
}
|
||||
}
|
||||
0x01 => {
|
||||
if serverbound {
|
||||
let (payload, offset_delta) = parse_long(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
Ok((SS01Ping { payload }, offset))
|
||||
} else {
|
||||
unimplemented!("Parse CS01Pong")
|
||||
}
|
||||
}
|
||||
_ => Err(ParseError::InvalidData),
|
||||
},
|
||||
Login => match id {
|
||||
0x00 => {
|
||||
if serverbound {
|
||||
let (username, offset_delta) = parse_string(&data[offset..])?;
|
||||
offset += offset_delta;
|
||||
Ok((SL00LoginStart { username }, offset))
|
||||
} else {
|
||||
unimplemented!("Parse CL00Disconnect")
|
||||
}
|
||||
}
|
||||
0x01 => {
|
||||
if serverbound {
|
||||
unimplemented!("Parse SL01EncryptionResponse")
|
||||
} else {
|
||||
unimplemented!("Parse CL01EncryptionRequest")
|
||||
}
|
||||
}
|
||||
0x02 => {
|
||||
if serverbound {
|
||||
unimplemented!("Parse SL02LoginPluginResponse")
|
||||
} else {
|
||||
unimplemented!("Parse CL02LoginSuccess")
|
||||
}
|
||||
}
|
||||
0x03 => {
|
||||
if serverbound {
|
||||
Err(ParseError::InvalidData)
|
||||
} else {
|
||||
unimplemented!("Parse CL03SetCompression")
|
||||
}
|
||||
}
|
||||
0x04 => {
|
||||
if serverbound {
|
||||
Err(ParseError::InvalidData)
|
||||
} else {
|
||||
unimplemented!("Parse CL04LoginPluginRequest")
|
||||
}
|
||||
}
|
||||
_ => Err(ParseError::InvalidData),
|
||||
},
|
||||
Play => unimplemented!("Parse Play packet"),
|
||||
}
|
||||
}
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
use Packet::*;
|
||||
let (id, mut body): (usize, Vec<u8>) = match self {
|
||||
CS00Response {
|
||||
version_name,
|
||||
protocol_version,
|
||||
max_players,
|
||||
current_players,
|
||||
description,
|
||||
} => (
|
||||
0x00,
|
||||
serialize_json(json!({
|
||||
"version": {
|
||||
"name": version_name,
|
||||
"protocol": protocol_version,
|
||||
},
|
||||
"players": {
|
||||
"max": max_players,
|
||||
"online": current_players,
|
||||
},
|
||||
"description": description,
|
||||
"favicon": format!("data:image/png;base64,{}", radix64::STD_NO_PAD.encode(&CONFIG.server_icon_bytes)),
|
||||
})),
|
||||
),
|
||||
CS01Pong { payload } => (0x01, serialize_long(*payload).to_vec()),
|
||||
CL00Disconnect { reason } => (0x00, serialize_json(reason.clone())),
|
||||
CL02LoginSuccess { uuid, username } => (0x02, {
|
||||
let mut out = vec![];
|
||||
out.extend(uuid.to_be_bytes());
|
||||
out.extend(serialize_string(username));
|
||||
out
|
||||
}),
|
||||
CP14WindowItems {
|
||||
window_id,
|
||||
state_id,
|
||||
slots,
|
||||
carried_item,
|
||||
} => (0x14, {
|
||||
let mut out = vec![*window_id];
|
||||
out.extend(serialize_varint(*state_id));
|
||||
out.extend(serialize_varint(slots.len() as i32));
|
||||
for slot in slots {
|
||||
out.extend(serialize_nbt(slot.clone()));
|
||||
}
|
||||
out.extend(serialize_nbt(carried_item.clone()));
|
||||
out
|
||||
}),
|
||||
CP38PlayerPositionAndLook {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
yaw,
|
||||
pitch,
|
||||
teleport_id,
|
||||
dismount_vehicle,
|
||||
} => (0x38, {
|
||||
let mut out = vec![];
|
||||
out.extend(serialize_double(x.0));
|
||||
out.extend(serialize_double(y.0));
|
||||
out.extend(serialize_double(z.0));
|
||||
out.extend(serialize_float(yaw.0));
|
||||
out.extend(serialize_float(pitch.0));
|
||||
let mut flags = 0x00;
|
||||
if x.1 {
|
||||
flags |= 0x01;
|
||||
}
|
||||
if y.1 {
|
||||
flags |= 0x02;
|
||||
}
|
||||
if z.1 {
|
||||
flags |= 0x04;
|
||||
}
|
||||
if yaw.1 {
|
||||
flags |= 0x10;
|
||||
}
|
||||
if pitch.1 {
|
||||
flags |= 0x08;
|
||||
}
|
||||
out.push(flags);
|
||||
out.extend(serialize_varint(*teleport_id));
|
||||
out.extend(serialize_bool(*dismount_vehicle));
|
||||
out
|
||||
}),
|
||||
CP4BSpawnPosition { location, angle } => (0x4b, {
|
||||
let mut out = vec![];
|
||||
out.extend(location.serialize());
|
||||
out.extend(serialize_float(*angle));
|
||||
out
|
||||
}),
|
||||
_ => unimplemented!("Serializing unknown packet"),
|
||||
};
|
||||
let mut id_and_body = serialize_varint(id as i32);
|
||||
id_and_body.append(&mut body);
|
||||
let mut output = serialize_varint(id_and_body.len() as i32);
|
||||
output.append(&mut id_and_body);
|
||||
output
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
use crate::{
|
||||
net::{mctypes::Position, *},
|
||||
prelude::*,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use tokio::{
|
||||
net::{TcpListener, ToSocketAddrs},
|
||||
sync::mpsc::{self, error::TryRecvError, UnboundedReceiver},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ServerError {}
|
||||
|
||||
pub struct Server {
|
||||
network_client_receiver: UnboundedReceiver<NetworkClient>,
|
||||
clients: Vec<NetworkClient>,
|
||||
}
|
||||
impl Server {
|
||||
pub async fn new<A: 'static + ToSocketAddrs + Send>(bind_address: A) -> Server {
|
||||
let (client_tx, client_rx) = mpsc::unbounded_channel();
|
||||
tokio::task::spawn(async move {
|
||||
let listener = TcpListener::bind(bind_address)
|
||||
.await
|
||||
.expect("Could not bind to given address");
|
||||
let mut id = 0u128;
|
||||
loop {
|
||||
trace!("Server accepting new client");
|
||||
match listener.accept().await {
|
||||
Ok((socket, addr)) => {
|
||||
let _ = client_tx.send(NetworkClient::new(id, socket));
|
||||
debug!("Connected client {} at {:?}", id, addr);
|
||||
id += 1;
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
Server {
|
||||
network_client_receiver: client_rx,
|
||||
clients: vec![],
|
||||
}
|
||||
}
|
||||
pub async fn update(&mut self) -> Result<(), ServerError> {
|
||||
trace!("Server.update()");
|
||||
// Read new clients from the receiver
|
||||
loop {
|
||||
match self.network_client_receiver.try_recv() {
|
||||
Ok(client) => self.clients.push(client),
|
||||
Err(TryRecvError::Empty) => break,
|
||||
Err(TryRecvError::Disconnected) => panic!("Client sender disconnected"),
|
||||
}
|
||||
}
|
||||
// Remove disconnected clients.
|
||||
let mut i = 0;
|
||||
while i < self.clients.len() {
|
||||
if self.clients[i].state == NetworkClientState::Disconnected {
|
||||
debug!("Removed client {}", self.clients[i].id);
|
||||
self.clients.remove(i);
|
||||
} else if self.clients[i].last_data_time.elapsed() > Duration::from_secs(10) {
|
||||
debug!("Client {} timed out", self.clients[i].id);
|
||||
self.clients[i].disconnect(None).await;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
// Read data and try to parse packets from each client.
|
||||
for client in self.clients.iter_mut() {
|
||||
if client.state == NetworkClientState::Disconnected {
|
||||
continue;
|
||||
}
|
||||
let _ = client.read_data().await;
|
||||
'packet: loop {
|
||||
match client.read_packet() {
|
||||
Ok(_) => {}
|
||||
Err(ParseError::NotEnoughData) => break 'packet,
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle each packet for each player.
|
||||
'client: for i in 0..self.clients.len() {
|
||||
while let Some(packet) = self.clients[i].packet_queue.pop_front() {
|
||||
if self.handle_packet(i, packet).await.is_err() {
|
||||
continue 'client;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle general world updates.
|
||||
// Send out packets to each client.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn handle_packet(&mut self, client_index: usize, packet: Packet) -> Result<(), ()> {
|
||||
use Packet::*;
|
||||
trace!("Server.handle_packet()");
|
||||
debug!("Handling packet {:?}", packet);
|
||||
let mut current_players = 0;
|
||||
for client in &self.clients {
|
||||
if client.state == NetworkClientState::Play {
|
||||
current_players += 1;
|
||||
}
|
||||
}
|
||||
let client = &mut self.clients[client_index];
|
||||
match packet {
|
||||
SH00Handshake {
|
||||
protocol_version,
|
||||
server_address: _,
|
||||
server_port: _,
|
||||
next_state,
|
||||
} => {
|
||||
if protocol_version != CONFIG.protocol_version
|
||||
&& next_state == NetworkClientState::Login
|
||||
{
|
||||
debug!(
|
||||
"Disconnecting client {} for mismatched protocols: {} (expected {})",
|
||||
client.id, protocol_version, CONFIG.protocol_version
|
||||
);
|
||||
client.disconnect(None).await;
|
||||
return Err(());
|
||||
}
|
||||
client.state = next_state;
|
||||
}
|
||||
SS00Request => {
|
||||
let _ = client
|
||||
.send_packet(CS00Response {
|
||||
version_name: CONFIG.game_version.clone(),
|
||||
protocol_version: CONFIG.protocol_version,
|
||||
max_players: CONFIG.max_players,
|
||||
current_players,
|
||||
description: json!({
|
||||
"text": CONFIG.motd
|
||||
}),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
SS01Ping { payload } => {
|
||||
let _ = client.send_packet(CS01Pong { payload }).await;
|
||||
debug!("Disconnecting client {}, SLP completed", client.id);
|
||||
client.disconnect(None).await;
|
||||
}
|
||||
SL00LoginStart { username } => {
|
||||
debug!(
|
||||
"Client {} is connecting with username {}",
|
||||
client.id, username
|
||||
);
|
||||
if current_players >= CONFIG.max_players {
|
||||
client
|
||||
.disconnect(Some(json!({ "text": "Server full!" })))
|
||||
.await;
|
||||
}
|
||||
// TODO: Authentication
|
||||
// TODO: Encryption
|
||||
// TODO: Compression
|
||||
let _ = client
|
||||
.send_packet(CL02LoginSuccess {
|
||||
uuid: client.id,
|
||||
username,
|
||||
})
|
||||
.await;
|
||||
client.state = NetworkClientState::Play;
|
||||
// Log them in.
|
||||
let _ = client
|
||||
.send_packet(CP4BSpawnPosition {
|
||||
location: Position::new(0, 0, 0),
|
||||
angle: 0.0,
|
||||
})
|
||||
.await;
|
||||
let _ = client
|
||||
.send_packet(CP14WindowItems {
|
||||
window_id: 0,
|
||||
state_id: 0,
|
||||
slots: vec![quartz_nbt::compound! {}; 44],
|
||||
carried_item: quartz_nbt::compound! {},
|
||||
})
|
||||
.await;
|
||||
let _ = client
|
||||
.send_packet(CP38PlayerPositionAndLook {
|
||||
x: (0.0, false),
|
||||
y: (0.0, false),
|
||||
z: (0.0, false),
|
||||
yaw: (0.0, false),
|
||||
pitch: (0.0, false),
|
||||
teleport_id: 0,
|
||||
dismount_vehicle: false,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
_ => unimplemented!("Handling unknown packet"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn shutdown(self) -> Result<(), ServerError> {
|
||||
trace!("Server.shutdown()");
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -2,13 +2,13 @@
|
||||
name = "composition-protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
description = "The Minecraft protocol implemented in a network-agnostic way"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
nom = "7"
|
||||
serde_json = "1.0.94"
|
||||
anyhow = "1.0.69"
|
||||
tracing = { workspace = true }
|
||||
byteorder = "1"
|
||||
serde_json = "1.0.94"
|
||||
thiserror = "1.0.39"
|
||||
|
||||
[[example]]
|
||||
name = "packet_logger"
|
||||
path = "examples/packet_logger/src/main.rs"
|
||||
|
@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "packet_logger"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.69"
|
||||
composition-protocol = { path = "../../" }
|
||||
tokio = { version = "1.26.0", features = [ "full" ] }
|
@ -1,117 +0,0 @@
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind("0.0.0.0:25566").await?;
|
||||
|
||||
println!("Proxy listening on port 25566, expecting server at 25565");
|
||||
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
process_client(stream).await?;
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_client(client: TcpStream) -> anyhow::Result<()> {
|
||||
use composition_protocol::packet::GenericPacket;
|
||||
client.readable().await?;
|
||||
println!("Client connected");
|
||||
let server = TcpStream::connect("localhost:25565").await?;
|
||||
server.writable().await?;
|
||||
println!("Server connected");
|
||||
|
||||
let mut client_state = composition_protocol::ClientState::Handshake;
|
||||
let mut last_client_data_time = std::time::Instant::now();
|
||||
let mut last_client_data = vec![];
|
||||
let mut last_server_data_time = std::time::Instant::now();
|
||||
let mut last_server_data = vec![];
|
||||
|
||||
loop {
|
||||
let bytes = copy_bytes(&client, &server).await?;
|
||||
if !bytes.is_empty() {
|
||||
last_client_data_time = std::time::Instant::now();
|
||||
last_client_data.extend_from_slice(&bytes);
|
||||
|
||||
if let Ok((d, packet)) =
|
||||
GenericPacket::parse_uncompressed(&client_state, true, &last_client_data)
|
||||
{
|
||||
last_client_data = d.to_vec();
|
||||
println!("C -> S: {:?}", packet);
|
||||
match packet {
|
||||
GenericPacket::SH00Handshake(handshake) => {
|
||||
client_state = handshake.next_state;
|
||||
}
|
||||
GenericPacket::CP17Disconnect(_) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = copy_bytes(&server, &client).await?;
|
||||
if !bytes.is_empty() {
|
||||
last_server_data_time = std::time::Instant::now();
|
||||
last_server_data.extend_from_slice(&bytes);
|
||||
|
||||
if let Ok((d, packet)) =
|
||||
GenericPacket::parse_uncompressed(&client_state, false, &last_server_data)
|
||||
{
|
||||
last_server_data = d.to_vec();
|
||||
println!("S -> C: {:?}", packet);
|
||||
match packet {
|
||||
GenericPacket::CS01PingResponse(_) | GenericPacket::CL00Disconnect(_) => {
|
||||
break;
|
||||
}
|
||||
GenericPacket::CL02LoginSuccess(_) => {
|
||||
client_state = composition_protocol::ClientState::Play;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if last_client_data_time.elapsed() > std::time::Duration::from_secs(10)
|
||||
|| last_server_data_time.elapsed() > std::time::Duration::from_secs(10)
|
||||
{
|
||||
println!("timed out");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn copy_bytes(from: &TcpStream, to: &TcpStream) -> anyhow::Result<Vec<u8>> {
|
||||
let mut bytes = vec![];
|
||||
|
||||
loop {
|
||||
// Read 8kb at a time
|
||||
let mut buf = vec![0u8; 8192];
|
||||
|
||||
let num_bytes = match from.try_read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
bytes.extend_from_slice(&buf[0..num_bytes]);
|
||||
|
||||
match to.try_write(&buf[0..num_bytes]) {
|
||||
Ok(_n) => {}
|
||||
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
@ -38,12 +38,10 @@ impl TryFrom<u8> for Difficulty {
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ProtocolError {
|
||||
#[error("invalid packet data")]
|
||||
InvalidPacket,
|
||||
#[error("invalid data")]
|
||||
InvalidData,
|
||||
#[error("not enough data")]
|
||||
NotEnoughData,
|
||||
#[error("unexpected end of file")]
|
||||
UnexpectedEOF,
|
||||
#[error("stream timed out")]
|
||||
Timeout,
|
||||
#[error("communicating to disconnected client")]
|
||||
|
@ -1,74 +1,38 @@
|
||||
use crate::{
|
||||
packet::{GenericPacket, Packet, PacketId},
|
||||
util::{
|
||||
parse_json, parse_string, parse_uuid, parse_varint, serialize_json, serialize_string,
|
||||
serialize_uuid, serialize_varint,
|
||||
},
|
||||
Chat, Uuid,
|
||||
};
|
||||
use crate::{util::*, Chat, Uuid};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL00Disconnect {
|
||||
reason: Chat,
|
||||
pub reason: Chat,
|
||||
}
|
||||
impl Packet for CL00Disconnect {
|
||||
fn id() -> PacketId {
|
||||
0x00
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CL00Disconnect,
|
||||
0x00,
|
||||
crate::ClientState::Login,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CL00Disconnect> {
|
||||
let (data, reason) = parse_json(data)?;
|
||||
Ok((data, CL00Disconnect { reason }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
serialize_json(&self.reason)
|
||||
}
|
||||
}
|
||||
impl From<CL00Disconnect> for GenericPacket {
|
||||
fn from(value: CL00Disconnect) -> Self {
|
||||
GenericPacket::CL00Disconnect(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CL00Disconnect {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CL00Disconnect(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &CL00Disconnect| -> Vec<u8> { serialize_json(&packet.reason) }
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL01EncryptionRequest {
|
||||
server_id: String,
|
||||
public_key: Vec<u8>,
|
||||
verify_token: Vec<u8>,
|
||||
pub server_id: String,
|
||||
pub public_key: Vec<u8>,
|
||||
pub verify_token: Vec<u8>,
|
||||
}
|
||||
impl Packet for CL01EncryptionRequest {
|
||||
fn id() -> PacketId {
|
||||
0x01
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CL01EncryptionRequest,
|
||||
0x01,
|
||||
crate::ClientState::Login,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CL01EncryptionRequest> {
|
||||
let (data, server_id) = parse_string(data)?;
|
||||
let (data, public_key_len) = parse_varint(data)?;
|
||||
let (data, public_key) = nom::bytes::streaming::take(public_key_len as usize)(data)?;
|
||||
let (data, public_key) = take_bytes(public_key_len as usize)(data)?;
|
||||
let (data, verify_token_len) = parse_varint(data)?;
|
||||
let (data, verify_token) = nom::bytes::streaming::take(verify_token_len as usize)(data)?;
|
||||
let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?;
|
||||
|
||||
Ok((
|
||||
data,
|
||||
@ -78,50 +42,35 @@ impl Packet for CL01EncryptionRequest {
|
||||
verify_token: verify_token.to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CL01EncryptionRequest| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_string(&self.server_id));
|
||||
output.extend_from_slice(&serialize_varint(self.public_key.len() as i32));
|
||||
output.extend_from_slice(&self.public_key);
|
||||
output.extend_from_slice(&serialize_varint(self.verify_token.len() as i32));
|
||||
output.extend_from_slice(&self.verify_token);
|
||||
output.extend_from_slice(&serialize_string(&packet.server_id));
|
||||
output.extend_from_slice(&serialize_varint(packet.public_key.len() as i32));
|
||||
output.extend_from_slice(&packet.public_key);
|
||||
output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32));
|
||||
output.extend_from_slice(&packet.verify_token);
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CL01EncryptionRequest> for GenericPacket {
|
||||
fn from(value: CL01EncryptionRequest) -> Self {
|
||||
GenericPacket::CL01EncryptionRequest(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CL01EncryptionRequest {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CL01EncryptionRequest(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL02LoginSuccess {
|
||||
uuid: Uuid,
|
||||
username: String,
|
||||
properties: Vec<CL02LoginSuccessProperty>,
|
||||
pub uuid: Uuid,
|
||||
pub username: String,
|
||||
pub properties: Vec<CL02LoginSuccessProperty>,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL02LoginSuccessProperty {
|
||||
name: String,
|
||||
value: String,
|
||||
signature: Option<String>,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
impl CL02LoginSuccessProperty {
|
||||
pub fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
pub fn parse(data: &[u8]) -> ParseResult<'_, Self> {
|
||||
let (data, name) = parse_string(data)?;
|
||||
let (data, value) = parse_string(data)?;
|
||||
let (data, is_signed) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
let (data, is_signed) = take_bytes(1usize)(data)?;
|
||||
if is_signed == [0x01] {
|
||||
let (data, signature) = parse_string(data)?;
|
||||
Ok((
|
||||
@ -157,18 +106,12 @@ impl CL02LoginSuccessProperty {
|
||||
output
|
||||
}
|
||||
}
|
||||
impl Packet for CL02LoginSuccess {
|
||||
fn id() -> PacketId {
|
||||
0x02
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CL02LoginSuccess,
|
||||
0x02,
|
||||
crate::ClientState::Login,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CL02LoginSuccess> {
|
||||
let (data, uuid) = parse_uuid(data)?;
|
||||
let (data, username) = parse_string(data)?;
|
||||
let (mut data, properties_len) = parse_varint(data)?;
|
||||
@ -187,91 +130,47 @@ impl Packet for CL02LoginSuccess {
|
||||
properties,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CL02LoginSuccess| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_uuid(&self.uuid));
|
||||
output.extend_from_slice(&serialize_string(&self.username));
|
||||
output.extend_from_slice(&serialize_varint(self.properties.len() as i32));
|
||||
for property in &self.properties {
|
||||
output.extend_from_slice(&serialize_uuid(&packet.uuid));
|
||||
output.extend_from_slice(&serialize_string(&packet.username));
|
||||
output.extend_from_slice(&serialize_varint(packet.properties.len() as i32));
|
||||
for property in &packet.properties {
|
||||
output.extend_from_slice(&property.serialize());
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CL02LoginSuccess> for GenericPacket {
|
||||
fn from(value: CL02LoginSuccess) -> Self {
|
||||
GenericPacket::CL02LoginSuccess(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CL02LoginSuccess {
|
||||
type Error = ();
|
||||
);
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CL02LoginSuccess(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CL03SetCompression {
|
||||
threshold: i32,
|
||||
pub threshold: i32,
|
||||
}
|
||||
impl Packet for CL03SetCompression {
|
||||
fn id() -> PacketId {
|
||||
0x03
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CL03SetCompression,
|
||||
0x03,
|
||||
crate::ClientState::Login,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CL03SetCompression> {
|
||||
let (data, threshold) = parse_varint(data)?;
|
||||
Ok((data, CL03SetCompression { threshold }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
serialize_varint(self.threshold)
|
||||
}
|
||||
}
|
||||
impl From<CL03SetCompression> for GenericPacket {
|
||||
fn from(value: CL03SetCompression) -> Self {
|
||||
GenericPacket::CL03SetCompression(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CL03SetCompression {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CL03SetCompression(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &CL03SetCompression| -> Vec<u8> { serialize_varint(packet.threshold) }
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL04LoginPluginRequest {
|
||||
message_id: i32,
|
||||
channel: String,
|
||||
data: Vec<u8>,
|
||||
pub message_id: i32,
|
||||
pub channel: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
impl Packet for CL04LoginPluginRequest {
|
||||
fn id() -> PacketId {
|
||||
0x04
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CL04LoginPluginRequest,
|
||||
0x04,
|
||||
crate::ClientState::Login,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CL04LoginPluginRequest> {
|
||||
let (data, message_id) = parse_varint(data)?;
|
||||
let (data, channel) = parse_string(data)?;
|
||||
Ok((
|
||||
@ -282,27 +181,12 @@ impl Packet for CL04LoginPluginRequest {
|
||||
data: data.to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CL04LoginPluginRequest| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(self.message_id));
|
||||
output.extend_from_slice(&serialize_string(&self.channel));
|
||||
output.extend_from_slice(&self.data);
|
||||
output.extend_from_slice(&serialize_varint(packet.message_id));
|
||||
output.extend_from_slice(&serialize_string(&packet.channel));
|
||||
output.extend_from_slice(&packet.data);
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CL04LoginPluginRequest> for GenericPacket {
|
||||
fn from(value: CL04LoginPluginRequest) -> Self {
|
||||
GenericPacket::CL04LoginPluginRequest(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CL04LoginPluginRequest {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CL04LoginPluginRequest(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,51 +1,58 @@
|
||||
use crate::{
|
||||
packet::{GenericPacket, Packet, PacketId},
|
||||
util::{
|
||||
parse_json, parse_uuid, parse_varint, serialize_json, serialize_uuid, serialize_varint,
|
||||
Position,
|
||||
},
|
||||
Chat, Difficulty, Uuid,
|
||||
};
|
||||
use crate::{util::*, Chat, Difficulty, ProtocolError, Uuid};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CP00SpawnEntity {
|
||||
entity_id: i32,
|
||||
entity_uuid: Uuid,
|
||||
kind: i32,
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
pitch: u8,
|
||||
yaw: u8,
|
||||
head_yaw: u8,
|
||||
data: i32,
|
||||
velocity_x: i16,
|
||||
velocity_y: i16,
|
||||
velocity_z: i16,
|
||||
pub entity_id: i32,
|
||||
pub entity_uuid: Uuid,
|
||||
pub kind: i32,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub z: f64,
|
||||
pub pitch: u8,
|
||||
pub yaw: u8,
|
||||
pub head_yaw: u8,
|
||||
pub data: i32,
|
||||
pub velocity_x: i16,
|
||||
pub velocity_y: i16,
|
||||
pub velocity_z: i16,
|
||||
}
|
||||
impl Packet for CP00SpawnEntity {
|
||||
fn id() -> PacketId {
|
||||
0x00
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CP00SpawnEntity,
|
||||
0x00,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP00SpawnEntity> {
|
||||
let (data, entity_id) = parse_varint(data)?;
|
||||
let (data, entity_uuid) = parse_uuid(data)?;
|
||||
let (data, kind) = parse_varint(data)?;
|
||||
let (data, x) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, y) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, z) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, t) = nom::bytes::streaming::take(3usize)(data)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let x = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let y = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let z = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, t) = take_bytes(3usize)(data)?;
|
||||
let (data, d) = parse_varint(data)?;
|
||||
let (data, velocity_x) = nom::number::streaming::be_i16(data)?;
|
||||
let (data, velocity_y) = nom::number::streaming::be_i16(data)?;
|
||||
let (data, velocity_z) = nom::number::streaming::be_i16(data)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_x = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_y = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_z = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
|
||||
Ok((
|
||||
data,
|
||||
CP00SpawnEntity {
|
||||
@ -64,64 +71,43 @@ impl Packet for CP00SpawnEntity {
|
||||
velocity_z,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CP00SpawnEntity| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(self.entity_id));
|
||||
output.extend_from_slice(&serialize_uuid(&self.entity_uuid));
|
||||
output.extend_from_slice(&serialize_varint(self.kind));
|
||||
output.extend_from_slice(&self.x.to_be_bytes());
|
||||
output.extend_from_slice(&self.y.to_be_bytes());
|
||||
output.extend_from_slice(&self.z.to_be_bytes());
|
||||
output.push(self.pitch);
|
||||
output.push(self.yaw);
|
||||
output.push(self.head_yaw);
|
||||
output.extend_from_slice(&serialize_varint(self.data));
|
||||
output.extend_from_slice(&self.velocity_x.to_be_bytes());
|
||||
output.extend_from_slice(&self.velocity_y.to_be_bytes());
|
||||
output.extend_from_slice(&self.velocity_z.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_varint(packet.entity_id));
|
||||
output.extend_from_slice(&serialize_uuid(&packet.entity_uuid));
|
||||
output.extend_from_slice(&serialize_varint(packet.kind));
|
||||
output.extend_from_slice(&packet.x.to_be_bytes());
|
||||
output.extend_from_slice(&packet.y.to_be_bytes());
|
||||
output.extend_from_slice(&packet.z.to_be_bytes());
|
||||
output.push(packet.pitch);
|
||||
output.push(packet.yaw);
|
||||
output.push(packet.head_yaw);
|
||||
output.extend_from_slice(&serialize_varint(packet.data));
|
||||
output.extend_from_slice(&packet.velocity_x.to_be_bytes());
|
||||
output.extend_from_slice(&packet.velocity_y.to_be_bytes());
|
||||
output.extend_from_slice(&packet.velocity_z.to_be_bytes());
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CP00SpawnEntity> for GenericPacket {
|
||||
fn from(value: CP00SpawnEntity) -> Self {
|
||||
GenericPacket::CP00SpawnEntity(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP00SpawnEntity {
|
||||
type Error = ();
|
||||
);
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP00SpawnEntity(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CP0BChangeDifficulty {
|
||||
difficulty: Difficulty,
|
||||
is_locked: bool,
|
||||
pub difficulty: Difficulty,
|
||||
pub is_locked: bool,
|
||||
}
|
||||
impl Packet for CP0BChangeDifficulty {
|
||||
fn id() -> PacketId {
|
||||
0x0b
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, difficulty) = nom::number::streaming::be_u8(data)?;
|
||||
let difficulty: Difficulty = difficulty
|
||||
crate::packet::packet!(
|
||||
CP0BChangeDifficulty,
|
||||
0x0b,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP0BChangeDifficulty> {
|
||||
let (data, difficulty) = take_bytes(1)(data)?;
|
||||
let difficulty: Difficulty = difficulty[0]
|
||||
.try_into()
|
||||
.expect("TODO: handle incorrect difficulty");
|
||||
let (data, is_locked) = nom::number::streaming::be_u8(data)?;
|
||||
let is_locked = is_locked > 0;
|
||||
let (data, is_locked) = take_bytes(1)(data)?;
|
||||
let is_locked = is_locked[0] > 0;
|
||||
Ok((
|
||||
data,
|
||||
CP0BChangeDifficulty {
|
||||
@ -129,131 +115,73 @@ impl Packet for CP0BChangeDifficulty {
|
||||
is_locked,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CP0BChangeDifficulty| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.push(self.difficulty as u8);
|
||||
output.push(if self.is_locked { 0x01 } else { 0x00 });
|
||||
output.push(packet.difficulty as u8);
|
||||
output.push(if packet.is_locked { 0x01 } else { 0x00 });
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CP0BChangeDifficulty> for GenericPacket {
|
||||
fn from(value: CP0BChangeDifficulty) -> Self {
|
||||
GenericPacket::CP0BChangeDifficulty(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP0BChangeDifficulty {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP0BChangeDifficulty(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CP17Disconnect {
|
||||
reason: Chat,
|
||||
pub reason: Chat,
|
||||
}
|
||||
impl Packet for CP17Disconnect {
|
||||
fn id() -> PacketId {
|
||||
0x17
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CP17Disconnect,
|
||||
0x17,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP17Disconnect> {
|
||||
let (data, reason) = parse_json(data)?;
|
||||
Ok((data, CP17Disconnect { reason }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
serialize_json(&self.reason)
|
||||
}
|
||||
}
|
||||
impl From<CP17Disconnect> for GenericPacket {
|
||||
fn from(value: CP17Disconnect) -> Self {
|
||||
GenericPacket::CP17Disconnect(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP17Disconnect {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP17Disconnect(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &CP17Disconnect| -> Vec<u8> { serialize_json(&packet.reason) }
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CP1FKeepAlive {
|
||||
payload: i64,
|
||||
pub payload: i64,
|
||||
}
|
||||
impl Packet for CP1FKeepAlive {
|
||||
fn id() -> PacketId {
|
||||
0x1f
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, payload) = nom::number::streaming::be_i64(data)?;
|
||||
crate::packet::packet!(
|
||||
CP1FKeepAlive,
|
||||
0x1f,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP1FKeepAlive> {
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let payload = bytes
|
||||
.read_i64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, CP1FKeepAlive { payload }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
self.payload.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
impl From<CP1FKeepAlive> for GenericPacket {
|
||||
fn from(value: CP1FKeepAlive) -> Self {
|
||||
GenericPacket::CP1FKeepAlive(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP1FKeepAlive {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP1FKeepAlive(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &CP1FKeepAlive| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CP21WorldEvent {
|
||||
event: i32,
|
||||
location: Position,
|
||||
data: i32,
|
||||
disable_relative_volume: bool,
|
||||
pub event: i32,
|
||||
pub location: Position,
|
||||
pub data: i32,
|
||||
pub disable_relative_volume: bool,
|
||||
}
|
||||
impl Packet for CP21WorldEvent {
|
||||
fn id() -> PacketId {
|
||||
0x21
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, event) = nom::number::streaming::be_i32(data)?;
|
||||
crate::packet::packet!(
|
||||
CP21WorldEvent,
|
||||
0x21,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP21WorldEvent> {
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let event = bytes
|
||||
.read_i32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, location) = Position::parse(data)?;
|
||||
let (data, d) = nom::number::streaming::be_i32(data)?;
|
||||
let (data, disable_relative_volume) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let d = bytes
|
||||
.read_i32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, disable_relative_volume) = take_bytes(1usize)(data)?;
|
||||
let disable_relative_volume = disable_relative_volume == [0x01];
|
||||
Ok((
|
||||
data,
|
||||
@ -264,59 +192,47 @@ impl Packet for CP21WorldEvent {
|
||||
disable_relative_volume,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CP21WorldEvent| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&self.event.to_be_bytes());
|
||||
output.extend_from_slice(&self.location.serialize());
|
||||
output.extend_from_slice(&self.data.to_be_bytes());
|
||||
output.push(if self.disable_relative_volume {
|
||||
output.extend_from_slice(&packet.event.to_be_bytes());
|
||||
output.extend_from_slice(&packet.location.serialize());
|
||||
output.extend_from_slice(&packet.data.to_be_bytes());
|
||||
output.push(if packet.disable_relative_volume {
|
||||
0x01
|
||||
} else {
|
||||
0x00
|
||||
});
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CP21WorldEvent> for GenericPacket {
|
||||
fn from(value: CP21WorldEvent) -> Self {
|
||||
GenericPacket::CP21WorldEvent(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP21WorldEvent {
|
||||
type Error = ();
|
||||
);
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP21WorldEvent(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CP50SetEntityVelocity {
|
||||
entity_id: i32,
|
||||
velocity_x: i16,
|
||||
velocity_y: i16,
|
||||
velocity_z: i16,
|
||||
pub entity_id: i32,
|
||||
pub velocity_x: i16,
|
||||
pub velocity_y: i16,
|
||||
pub velocity_z: i16,
|
||||
}
|
||||
impl Packet for CP50SetEntityVelocity {
|
||||
fn id() -> PacketId {
|
||||
0x50
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CP50SetEntityVelocity,
|
||||
0x50,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP50SetEntityVelocity> {
|
||||
let (data, entity_id) = parse_varint(data)?;
|
||||
let (data, velocity_x) = nom::number::streaming::be_i16(data)?;
|
||||
let (data, velocity_y) = nom::number::streaming::be_i16(data)?;
|
||||
let (data, velocity_z) = nom::number::streaming::be_i16(data)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_x = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_y = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_z = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((
|
||||
data,
|
||||
CP50SetEntityVelocity {
|
||||
@ -326,51 +242,33 @@ impl Packet for CP50SetEntityVelocity {
|
||||
velocity_z,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CP50SetEntityVelocity| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(self.entity_id));
|
||||
output.extend_from_slice(&self.velocity_x.to_be_bytes());
|
||||
output.extend_from_slice(&self.velocity_y.to_be_bytes());
|
||||
output.extend_from_slice(&self.velocity_z.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_varint(packet.entity_id));
|
||||
output.extend_from_slice(&packet.velocity_x.to_be_bytes());
|
||||
output.extend_from_slice(&packet.velocity_y.to_be_bytes());
|
||||
output.extend_from_slice(&packet.velocity_z.to_be_bytes());
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CP50SetEntityVelocity> for GenericPacket {
|
||||
fn from(value: CP50SetEntityVelocity) -> Self {
|
||||
GenericPacket::CP50SetEntityVelocity(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP50SetEntityVelocity {
|
||||
type Error = ();
|
||||
);
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP50SetEntityVelocity(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CP52SetExperience {
|
||||
experience_bar: f32,
|
||||
total_experience: i32,
|
||||
level: i32,
|
||||
pub experience_bar: f32,
|
||||
pub total_experience: i32,
|
||||
pub level: i32,
|
||||
}
|
||||
impl Packet for CP52SetExperience {
|
||||
fn id() -> PacketId {
|
||||
0x52
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, experience_bar) = nom::number::streaming::be_f32(data)?;
|
||||
crate::packet::packet!(
|
||||
CP52SetExperience,
|
||||
0x52,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP52SetExperience> {
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let experience_bar = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, total_experience) = parse_varint(data)?;
|
||||
let (data, level) = parse_varint(data)?;
|
||||
Ok((
|
||||
@ -381,65 +279,46 @@ impl Packet for CP52SetExperience {
|
||||
level,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CP52SetExperience| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&self.experience_bar.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_varint(self.total_experience));
|
||||
output.extend_from_slice(&serialize_varint(self.level));
|
||||
output.extend_from_slice(&packet.experience_bar.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_varint(packet.total_experience));
|
||||
output.extend_from_slice(&serialize_varint(packet.level));
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CP52SetExperience> for GenericPacket {
|
||||
fn from(value: CP52SetExperience) -> Self {
|
||||
GenericPacket::CP52SetExperience(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP52SetExperience {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP52SetExperience(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CP68EntityEffect {
|
||||
entity_id: i32,
|
||||
effect_id: i32,
|
||||
amplifier: i8,
|
||||
duration: i32,
|
||||
is_ambient: bool,
|
||||
show_particles: bool,
|
||||
show_icon: bool,
|
||||
has_factor_data: bool,
|
||||
// TODO: factor_codec: NBT
|
||||
pub entity_id: i32,
|
||||
pub effect_id: i32,
|
||||
pub amplifier: i8,
|
||||
pub duration: i32,
|
||||
pub is_ambient: bool,
|
||||
pub show_particles: bool,
|
||||
pub show_icon: bool,
|
||||
pub has_factor_data: bool,
|
||||
// TODO: pub factor_codec: NBT
|
||||
}
|
||||
impl Packet for CP68EntityEffect {
|
||||
fn id() -> PacketId {
|
||||
0x68
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CP68EntityEffect,
|
||||
0x68,
|
||||
crate::ClientState::Play,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CP68EntityEffect> {
|
||||
let (data, entity_id) = parse_varint(data)?;
|
||||
let (data, effect_id) = parse_varint(data)?;
|
||||
let (data, amplifier) = nom::number::streaming::be_i8(data)?;
|
||||
let (data, amplifier) = take_bytes(1)(data)?;
|
||||
let amplifier = amplifier[0] as i8;
|
||||
let (data, duration) = parse_varint(data)?;
|
||||
let (data, flags) = nom::number::streaming::be_i8(data)?;
|
||||
let (data, flags) = take_bytes(1)(data)?;
|
||||
let flags = flags[0] as i8;
|
||||
let is_ambient = flags & 0x01 > 0;
|
||||
let show_particles = flags & 0x02 > 0;
|
||||
let show_icon = flags & 0x04 > 0;
|
||||
let (data, has_factor_data) = nom::number::streaming::be_u8(data)?;
|
||||
let has_factor_data = has_factor_data > 0;
|
||||
let (data, has_factor_data) = take_bytes(1)(data)?;
|
||||
let has_factor_data = has_factor_data[0] > 0;
|
||||
// TODO: factor_codec
|
||||
|
||||
Ok((
|
||||
@ -455,40 +334,25 @@ impl Packet for CP68EntityEffect {
|
||||
has_factor_data,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &CP68EntityEffect| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(self.entity_id));
|
||||
output.extend_from_slice(&serialize_varint(self.effect_id));
|
||||
output.push(self.amplifier as u8);
|
||||
output.extend_from_slice(&serialize_varint(self.duration));
|
||||
output.extend_from_slice(&serialize_varint(packet.entity_id));
|
||||
output.extend_from_slice(&serialize_varint(packet.effect_id));
|
||||
output.push(packet.amplifier as u8);
|
||||
output.extend_from_slice(&serialize_varint(packet.duration));
|
||||
let mut flags = 0x00i8;
|
||||
if self.is_ambient {
|
||||
if packet.is_ambient {
|
||||
flags |= 0x01;
|
||||
}
|
||||
if self.show_particles {
|
||||
if packet.show_particles {
|
||||
flags |= 0x02;
|
||||
}
|
||||
if self.show_icon {
|
||||
if packet.show_icon {
|
||||
flags |= 0x04;
|
||||
}
|
||||
output.push(flags as u8);
|
||||
// TODO: factor_codec
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<CP68EntityEffect> for GenericPacket {
|
||||
fn from(value: CP68EntityEffect) -> Self {
|
||||
GenericPacket::CP68EntityEffect(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CP68EntityEffect {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CP68EntityEffect(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,83 +1,37 @@
|
||||
use crate::{
|
||||
packet::{GenericPacket, Packet, PacketId},
|
||||
util::{parse_json, serialize_json},
|
||||
Json,
|
||||
};
|
||||
use crate::{util::*, Json, ProtocolError};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CS00StatusResponse {
|
||||
response: Json,
|
||||
pub response: Json,
|
||||
}
|
||||
impl Packet for CS00StatusResponse {
|
||||
fn id() -> PacketId {
|
||||
0x00
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Status
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
CS00StatusResponse,
|
||||
0x00,
|
||||
crate::ClientState::Status,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CS00StatusResponse> {
|
||||
let (data, response) = parse_json(data)?;
|
||||
Ok((data, CS00StatusResponse { response }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
serialize_json(&self.response)
|
||||
}
|
||||
}
|
||||
impl From<CS00StatusResponse> for GenericPacket {
|
||||
fn from(value: CS00StatusResponse) -> Self {
|
||||
GenericPacket::CS00StatusResponse(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CS00StatusResponse {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CS00StatusResponse(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &CS00StatusResponse| -> Vec<u8> { serialize_json(&packet.response) }
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct CS01PingResponse {
|
||||
payload: i64,
|
||||
pub payload: i64,
|
||||
}
|
||||
impl Packet for CS01PingResponse {
|
||||
fn id() -> PacketId {
|
||||
0x01
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Status
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, payload) = nom::number::streaming::be_i64(data)?;
|
||||
crate::packet::packet!(
|
||||
CS01PingResponse,
|
||||
0x01,
|
||||
crate::ClientState::Status,
|
||||
false,
|
||||
|data: &'data [u8]| -> ParseResult<'data, CS01PingResponse> {
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let payload = bytes
|
||||
.read_i64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, CS01PingResponse { payload }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
self.payload.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
impl From<CS01PingResponse> for GenericPacket {
|
||||
fn from(value: CS01PingResponse) -> Self {
|
||||
GenericPacket::CS01PingResponse(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for CS01PingResponse {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::CS01PingResponse(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &CS01PingResponse| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
|
||||
);
|
||||
|
@ -1,288 +1,168 @@
|
||||
pub mod clientbound;
|
||||
pub mod serverbound;
|
||||
|
||||
use crate::ClientState;
|
||||
|
||||
pub type PacketId = i32;
|
||||
|
||||
pub trait Packet: TryFrom<GenericPacket> + Into<GenericPacket> + std::fmt::Debug {
|
||||
fn id() -> PacketId;
|
||||
fn client_state() -> crate::ClientState;
|
||||
fn serverbound() -> bool;
|
||||
pub trait Packet: std::fmt::Debug + Clone + TryFrom<GenericPacket> + Into<GenericPacket> {
|
||||
const ID: PacketId;
|
||||
const CLIENT_STATE: crate::ClientState;
|
||||
const IS_SERVERBOUND: bool;
|
||||
|
||||
// The slice given should only be the exact amount of data in the body.
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self>
|
||||
fn parse_body(data: &[u8]) -> crate::util::ParseResult<'_, Self>
|
||||
where
|
||||
Self: Sized;
|
||||
fn serialize_body(&self) -> Vec<u8>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GenericPacket {
|
||||
macro_rules! generic_packet {
|
||||
($($packet_type: ident),*) => {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GenericPacket {
|
||||
$(
|
||||
$packet_type($packet_type),
|
||||
)*
|
||||
}
|
||||
impl GenericPacket {
|
||||
#[tracing::instrument]
|
||||
pub fn parse_uncompressed<'data>(
|
||||
client_state: crate::ClientState,
|
||||
is_serverbound: bool,
|
||||
data: &'data [u8]
|
||||
) -> crate::util::ParseResult<'data, Self> {
|
||||
tracing::trace!(
|
||||
"GenericPacket::parse_uncompressed: {:?} {} {:?}",
|
||||
client_state,
|
||||
is_serverbound,
|
||||
data
|
||||
);
|
||||
|
||||
let (data, packet_length) = crate::util::parse_varint(data)?;
|
||||
let (data, packet_data) = crate::util::take_bytes(packet_length as usize)(data)?;
|
||||
|
||||
let (packet_data, packet_id) = crate::util::parse_varint(packet_data)?;
|
||||
let (_packet_data, packet_body) =
|
||||
Self::parse_body(client_state, packet_id, is_serverbound, packet_data)?;
|
||||
|
||||
// if !packet_data.is_empty() {
|
||||
// println!("Packet data not empty after parsing!");
|
||||
// }
|
||||
|
||||
Ok((data, packet_body))
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn parse_body<'data>(
|
||||
client_state: crate::ClientState,
|
||||
packet_id: crate::packet::PacketId,
|
||||
is_serverbound: bool,
|
||||
data: &'data [u8],
|
||||
) -> crate::util::ParseResult<'data, Self> {
|
||||
tracing::trace!(
|
||||
"GenericPacket::parse_body: {:?} {} {}",
|
||||
client_state,
|
||||
packet_id,
|
||||
is_serverbound
|
||||
);
|
||||
match (client_state, packet_id, is_serverbound) {
|
||||
$(
|
||||
($packet_type::CLIENT_STATE, $packet_type::ID, $packet_type::IS_SERVERBOUND) => $packet_type::parse_body(data).map(|(data, packet)| (data, Into::<GenericPacket>::into(packet))),
|
||||
)*
|
||||
_ => Ok((data, Self::UnimplementedPacket(UnimplementedPacket(packet_id)))),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn serialize(&self) -> (crate::packet::PacketId, Vec<u8>) {
|
||||
tracing::trace!("GenericPacket::serialize: {:?}", self);
|
||||
match self {
|
||||
$(
|
||||
Self::$packet_type(packet) => ($packet_type::ID, packet.serialize_body()),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct UnimplementedPacket(i32);
|
||||
packet!(
|
||||
UnimplementedPacket,
|
||||
0x00,
|
||||
crate::ClientState::Disconnected,
|
||||
false,
|
||||
|data: &'data [u8]| -> crate::util::ParseResult<'data, UnimplementedPacket> {
|
||||
Ok((data, UnimplementedPacket(0i32)))
|
||||
},
|
||||
|_packet: &UnimplementedPacket| -> Vec<u8> { vec![] }
|
||||
);
|
||||
|
||||
use clientbound::*;
|
||||
use serverbound::*;
|
||||
generic_packet!(
|
||||
UnimplementedPacket,
|
||||
// Handshake
|
||||
SH00Handshake(serverbound::SH00Handshake),
|
||||
|
||||
SH00Handshake,
|
||||
// Status
|
||||
SS00StatusRequest(serverbound::SS00StatusRequest),
|
||||
SS01PingRequest(serverbound::SS01PingRequest),
|
||||
|
||||
CS00StatusResponse(clientbound::CS00StatusResponse),
|
||||
CS01PingResponse(clientbound::CS01PingResponse),
|
||||
|
||||
SS00StatusRequest,
|
||||
SS01PingRequest,
|
||||
CS00StatusResponse,
|
||||
CS01PingResponse,
|
||||
// Login
|
||||
SL00LoginStart(serverbound::SL00LoginStart),
|
||||
SL01EncryptionResponse(serverbound::SL01EncryptionResponse),
|
||||
SL02LoginPluginResponse(serverbound::SL02LoginPluginResponse),
|
||||
|
||||
CL00Disconnect(clientbound::CL00Disconnect),
|
||||
CL01EncryptionRequest(clientbound::CL01EncryptionRequest),
|
||||
CL02LoginSuccess(clientbound::CL02LoginSuccess),
|
||||
CL03SetCompression(clientbound::CL03SetCompression),
|
||||
CL04LoginPluginRequest(clientbound::CL04LoginPluginRequest),
|
||||
|
||||
SL00LoginStart,
|
||||
SL01EncryptionResponse,
|
||||
SL02LoginPluginResponse,
|
||||
CL00Disconnect,
|
||||
CL01EncryptionRequest,
|
||||
CL02LoginSuccess,
|
||||
CL03SetCompression,
|
||||
CL04LoginPluginRequest,
|
||||
// Play
|
||||
SP08CommandSuggestionsRequest(serverbound::SP08CommandSuggestionsRequest),
|
||||
SP11KeepAlive(serverbound::SP11KeepAlive),
|
||||
SP13SetPlayerPosition(serverbound::SP13SetPlayerPosition),
|
||||
SP14SetPlayerPositionAndRotation(serverbound::SP14SetPlayerPositionAndRotation),
|
||||
SP15SetPlayerRotation(serverbound::SP15SetPlayerRotation),
|
||||
SP08CommandSuggestionsRequest,
|
||||
SP11KeepAlive,
|
||||
SP13SetPlayerPosition,
|
||||
SP14SetPlayerPositionAndRotation,
|
||||
SP15SetPlayerRotation,
|
||||
CP00SpawnEntity,
|
||||
CP0BChangeDifficulty,
|
||||
CP17Disconnect,
|
||||
CP1FKeepAlive,
|
||||
CP21WorldEvent,
|
||||
CP50SetEntityVelocity,
|
||||
CP52SetExperience,
|
||||
CP68EntityEffect
|
||||
);
|
||||
|
||||
CP00SpawnEntity(clientbound::CP00SpawnEntity),
|
||||
CP0BChangeDifficulty(clientbound::CP0BChangeDifficulty),
|
||||
CP17Disconnect(clientbound::CP17Disconnect),
|
||||
CP1FKeepAlive(clientbound::CP1FKeepAlive),
|
||||
CP21WorldEvent(clientbound::CP21WorldEvent),
|
||||
CP50SetEntityVelocity(clientbound::CP50SetEntityVelocity),
|
||||
CP52SetExperience(clientbound::CP52SetExperience),
|
||||
CP68EntityEffect(clientbound::CP68EntityEffect),
|
||||
macro_rules! packet {
|
||||
($packet_type: ident, $id: literal, $client_state: expr, $serverbound: literal, $parse_body: expr, $serialize_body: expr) => {
|
||||
impl crate::packet::Packet for $packet_type {
|
||||
const ID: crate::packet::PacketId = $id;
|
||||
const CLIENT_STATE: crate::ClientState = $client_state;
|
||||
const IS_SERVERBOUND: bool = $serverbound;
|
||||
|
||||
// Until we implement all the packets this will stay.
|
||||
UnimplementedPacket(PacketId),
|
||||
}
|
||||
impl GenericPacket {
|
||||
pub fn parse_uncompressed<'data>(
|
||||
client_state: &ClientState,
|
||||
serverbound: bool,
|
||||
data: &'data [u8],
|
||||
) -> nom::IResult<&'data [u8], Self> {
|
||||
let (data, packet_length) = crate::util::parse_varint(data)?;
|
||||
let (data, packet_data) = nom::bytes::streaming::take(packet_length as usize)(data)?;
|
||||
|
||||
let (packet_data, packet_id) = crate::util::parse_varint(packet_data)?;
|
||||
let (_packet_data, packet_body) =
|
||||
Self::parse_body(client_state, packet_id, serverbound, packet_data)?;
|
||||
|
||||
// if !packet_data.is_empty() {
|
||||
// println!("Packet data not empty after parsing!");
|
||||
// }
|
||||
|
||||
Ok((data, packet_body))
|
||||
}
|
||||
pub fn parse_body<'data>(
|
||||
client_state: &ClientState,
|
||||
packet_id: PacketId,
|
||||
serverbound: bool,
|
||||
data: &'data [u8],
|
||||
) -> nom::IResult<&'data [u8], Self> {
|
||||
fn mapper<P: Into<GenericPacket> + Sized>(
|
||||
(data, packet): (&[u8], P),
|
||||
) -> (&[u8], GenericPacket) {
|
||||
(data, Into::<GenericPacket>::into(packet))
|
||||
}
|
||||
|
||||
match (client_state, packet_id, serverbound) {
|
||||
// Handshake
|
||||
(ClientState::Handshake, 0x00, true) => {
|
||||
serverbound::SH00Handshake::parse_body(data).map(mapper)
|
||||
}
|
||||
|
||||
// Status
|
||||
(ClientState::Status, 0x00, true) => {
|
||||
serverbound::SS00StatusRequest::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Status, 0x01, true) => {
|
||||
serverbound::SS00StatusRequest::parse_body(data).map(mapper)
|
||||
}
|
||||
|
||||
(ClientState::Status, 0x00, false) => {
|
||||
clientbound::CS00StatusResponse::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Status, 0x01, false) => {
|
||||
clientbound::CS01PingResponse::parse_body(data).map(mapper)
|
||||
}
|
||||
|
||||
// Login
|
||||
(ClientState::Login, 0x00, true) => {
|
||||
serverbound::SL00LoginStart::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Login, 0x01, true) => {
|
||||
serverbound::SL01EncryptionResponse::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Login, 0x02, true) => {
|
||||
serverbound::SL02LoginPluginResponse::parse_body(data).map(mapper)
|
||||
}
|
||||
|
||||
(ClientState::Login, 0x00, false) => {
|
||||
clientbound::CL00Disconnect::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Login, 0x01, false) => {
|
||||
clientbound::CL01EncryptionRequest::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Login, 0x02, false) => {
|
||||
clientbound::CL02LoginSuccess::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Login, 0x03, false) => {
|
||||
clientbound::CL03SetCompression::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Login, 0x04, false) => {
|
||||
clientbound::CL04LoginPluginRequest::parse_body(data).map(mapper)
|
||||
}
|
||||
|
||||
// Play
|
||||
(ClientState::Play, 0x08, true) => {
|
||||
serverbound::SP08CommandSuggestionsRequest::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x11, true) => {
|
||||
serverbound::SP11KeepAlive::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x13, true) => {
|
||||
serverbound::SP13SetPlayerPosition::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x14, true) => {
|
||||
serverbound::SP14SetPlayerPositionAndRotation::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x15, true) => {
|
||||
serverbound::SP15SetPlayerRotation::parse_body(data).map(mapper)
|
||||
}
|
||||
|
||||
(ClientState::Play, 0x00, false) => {
|
||||
clientbound::CP00SpawnEntity::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x0b, false) => {
|
||||
clientbound::CP0BChangeDifficulty::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x17, false) => {
|
||||
clientbound::CP17Disconnect::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x1f, false) => {
|
||||
clientbound::CP1FKeepAlive::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x21, false) => {
|
||||
clientbound::CP21WorldEvent::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x50, false) => {
|
||||
clientbound::CP50SetEntityVelocity::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x52, false) => {
|
||||
clientbound::CP52SetExperience::parse_body(data).map(mapper)
|
||||
}
|
||||
(ClientState::Play, 0x68, false) => {
|
||||
clientbound::CP68EntityEffect::parse_body(data).map(mapper)
|
||||
}
|
||||
|
||||
_ => Ok((&data[0..0], GenericPacket::UnimplementedPacket(packet_id))),
|
||||
// Invalid packet
|
||||
// _ => Err(nom::Err::Failure(nom::error::Error::new(
|
||||
// data,
|
||||
// nom::error::ErrorKind::Verify,
|
||||
// ))),
|
||||
}
|
||||
}
|
||||
pub fn serialize(&self) -> (PacketId, Vec<u8>) {
|
||||
use GenericPacket::*;
|
||||
|
||||
match self {
|
||||
// Handshake
|
||||
SH00Handshake(packet) => (serverbound::SH00Handshake::id(), packet.serialize_body()),
|
||||
|
||||
// Status
|
||||
SS00StatusRequest(packet) => (
|
||||
serverbound::SS00StatusRequest::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
SS01PingRequest(packet) => {
|
||||
(serverbound::SS01PingRequest::id(), packet.serialize_body())
|
||||
}
|
||||
|
||||
CS00StatusResponse(packet) => (
|
||||
clientbound::CS00StatusResponse::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
CS01PingResponse(packet) => {
|
||||
(clientbound::CS01PingResponse::id(), packet.serialize_body())
|
||||
}
|
||||
|
||||
// Login
|
||||
SL00LoginStart(packet) => (serverbound::SL00LoginStart::id(), packet.serialize_body()),
|
||||
SL01EncryptionResponse(packet) => (
|
||||
serverbound::SL01EncryptionResponse::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
SL02LoginPluginResponse(packet) => (
|
||||
serverbound::SL02LoginPluginResponse::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
|
||||
CL00Disconnect(packet) => (clientbound::CL00Disconnect::id(), packet.serialize_body()),
|
||||
CL01EncryptionRequest(packet) => (
|
||||
clientbound::CL01EncryptionRequest::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
CL02LoginSuccess(packet) => {
|
||||
(clientbound::CL02LoginSuccess::id(), packet.serialize_body())
|
||||
}
|
||||
CL03SetCompression(packet) => (
|
||||
clientbound::CL03SetCompression::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
CL04LoginPluginRequest(packet) => (
|
||||
clientbound::CL04LoginPluginRequest::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
|
||||
// Play
|
||||
SP08CommandSuggestionsRequest(packet) => (
|
||||
serverbound::SP08CommandSuggestionsRequest::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
SP11KeepAlive(packet) => (serverbound::SP11KeepAlive::id(), packet.serialize_body()),
|
||||
SP13SetPlayerPosition(packet) => (
|
||||
serverbound::SP13SetPlayerPosition::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
SP14SetPlayerPositionAndRotation(packet) => (
|
||||
serverbound::SP14SetPlayerPositionAndRotation::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
SP15SetPlayerRotation(packet) => (
|
||||
serverbound::SP15SetPlayerRotation::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
|
||||
CP00SpawnEntity(packet) => {
|
||||
(clientbound::CP00SpawnEntity::id(), packet.serialize_body())
|
||||
}
|
||||
CP0BChangeDifficulty(packet) => (
|
||||
clientbound::CP0BChangeDifficulty::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
CP17Disconnect(packet) => (clientbound::CP17Disconnect::id(), packet.serialize_body()),
|
||||
CP1FKeepAlive(packet) => (clientbound::CP1FKeepAlive::id(), packet.serialize_body()),
|
||||
CP21WorldEvent(packet) => (clientbound::CP21WorldEvent::id(), packet.serialize_body()),
|
||||
CP50SetEntityVelocity(packet) => (
|
||||
clientbound::CP50SetEntityVelocity::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
CP52SetExperience(packet) => (
|
||||
clientbound::CP52SetExperience::id(),
|
||||
packet.serialize_body(),
|
||||
),
|
||||
CP68EntityEffect(packet) => {
|
||||
(clientbound::CP68EntityEffect::id(), packet.serialize_body())
|
||||
}
|
||||
|
||||
// Unimplemented packets get no body.
|
||||
UnimplementedPacket(packet_id) => (*packet_id, vec![]),
|
||||
}
|
||||
}
|
||||
fn parse_body<'data>(data: &'data [u8]) -> crate::util::ParseResult<'_, $packet_type> {
|
||||
$parse_body(data)
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
$serialize_body(self)
|
||||
}
|
||||
}
|
||||
impl From<$packet_type> for crate::packet::GenericPacket {
|
||||
fn from(value: $packet_type) -> Self {
|
||||
crate::packet::GenericPacket::$packet_type(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<crate::packet::GenericPacket> for $packet_type {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: crate::packet::GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
crate::packet::GenericPacket::$packet_type(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use packet;
|
||||
|
@ -1,8 +1,5 @@
|
||||
use crate::{
|
||||
packet::{GenericPacket, Packet, PacketId},
|
||||
util::{parse_string, parse_varint, serialize_string, serialize_varint},
|
||||
ClientState,
|
||||
};
|
||||
use crate::{util::*, ClientState, ProtocolError};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SH00Handshake {
|
||||
@ -11,21 +8,18 @@ pub struct SH00Handshake {
|
||||
pub server_port: u16,
|
||||
pub next_state: ClientState,
|
||||
}
|
||||
impl Packet for SH00Handshake {
|
||||
fn id() -> PacketId {
|
||||
0x00
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Handshake
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
SH00Handshake,
|
||||
0x00,
|
||||
ClientState::Handshake,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SH00Handshake> {
|
||||
let (data, protocol_version) = parse_varint(data)?;
|
||||
let (data, server_address) = parse_string(data)?;
|
||||
let (data, server_port) = nom::number::streaming::be_u16(data)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let server_port = bytes
|
||||
.read_u16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, next_state) = parse_varint(data)?;
|
||||
|
||||
Ok((
|
||||
@ -41,32 +35,17 @@ impl Packet for SH00Handshake {
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SH00Handshake| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&self.protocol_version.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_string(&self.server_address));
|
||||
output.extend_from_slice(&self.server_port.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_varint(match self.next_state {
|
||||
output.extend_from_slice(&packet.protocol_version.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_string(&packet.server_address));
|
||||
output.extend_from_slice(&packet.server_port.to_be_bytes());
|
||||
output.extend_from_slice(&serialize_varint(match packet.next_state {
|
||||
ClientState::Status => 0x01,
|
||||
ClientState::Login => 0x02,
|
||||
_ => panic!("invalid SH00Handshake next_state"),
|
||||
}));
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SH00Handshake> for GenericPacket {
|
||||
fn from(value: SH00Handshake) -> Self {
|
||||
GenericPacket::SH00Handshake(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SH00Handshake {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SH00Handshake(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,30 +1,18 @@
|
||||
use crate::{
|
||||
packet::{GenericPacket, Packet, PacketId},
|
||||
util::{
|
||||
parse_string, parse_uuid, parse_varint, serialize_string, serialize_uuid, serialize_varint,
|
||||
},
|
||||
Uuid,
|
||||
};
|
||||
use crate::{util::*, Uuid};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SL00LoginStart {
|
||||
name: String,
|
||||
uuid: Option<Uuid>,
|
||||
pub name: String,
|
||||
pub uuid: Option<Uuid>,
|
||||
}
|
||||
impl Packet for SL00LoginStart {
|
||||
fn id() -> PacketId {
|
||||
0x00
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
SL00LoginStart,
|
||||
0x00,
|
||||
crate::ClientState::Login,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SL00LoginStart> {
|
||||
let (data, name) = parse_string(data)?;
|
||||
let (data, has_uuid) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
let (data, has_uuid) = take_bytes(1usize)(data)?;
|
||||
if has_uuid == [0x01] {
|
||||
let (data, uuid) = parse_uuid(data)?;
|
||||
Ok((
|
||||
@ -37,11 +25,11 @@ impl Packet for SL00LoginStart {
|
||||
} else {
|
||||
Ok((data, SL00LoginStart { name, uuid: None }))
|
||||
}
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SL00LoginStart| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_string(&self.name));
|
||||
match self.uuid {
|
||||
output.extend_from_slice(&serialize_string(&packet.name));
|
||||
match packet.uuid {
|
||||
Some(uuid) => {
|
||||
output.push(0x01);
|
||||
output.extend_from_slice(&serialize_uuid(&uuid));
|
||||
@ -50,44 +38,23 @@ impl Packet for SL00LoginStart {
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SL00LoginStart> for GenericPacket {
|
||||
fn from(value: SL00LoginStart) -> Self {
|
||||
GenericPacket::SL00LoginStart(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SL00LoginStart {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SL00LoginStart(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SL01EncryptionResponse {
|
||||
shared_secret: Vec<u8>,
|
||||
verify_token: Vec<u8>,
|
||||
pub shared_secret: Vec<u8>,
|
||||
pub verify_token: Vec<u8>,
|
||||
}
|
||||
impl Packet for SL01EncryptionResponse {
|
||||
fn id() -> PacketId {
|
||||
0x01
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
SL01EncryptionResponse,
|
||||
0x01,
|
||||
crate::ClientState::Login,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SL01EncryptionResponse> {
|
||||
let (data, shared_secret_len) = parse_varint(data)?;
|
||||
let (data, shared_secret) = nom::bytes::streaming::take(shared_secret_len as usize)(data)?;
|
||||
let (data, shared_secret) = take_bytes(shared_secret_len as usize)(data)?;
|
||||
let (data, verify_token_len) = parse_varint(data)?;
|
||||
let (data, verify_token) = nom::bytes::streaming::take(verify_token_len as usize)(data)?;
|
||||
let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?;
|
||||
|
||||
Ok((
|
||||
data,
|
||||
@ -96,52 +63,31 @@ impl Packet for SL01EncryptionResponse {
|
||||
verify_token: verify_token.to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SL01EncryptionResponse| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(self.shared_secret.len() as i32));
|
||||
output.extend_from_slice(&self.shared_secret);
|
||||
output.extend_from_slice(&serialize_varint(self.verify_token.len() as i32));
|
||||
output.extend_from_slice(&self.verify_token);
|
||||
output.extend_from_slice(&serialize_varint(packet.shared_secret.len() as i32));
|
||||
output.extend_from_slice(&packet.shared_secret);
|
||||
output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32));
|
||||
output.extend_from_slice(&packet.verify_token);
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SL01EncryptionResponse> for GenericPacket {
|
||||
fn from(value: SL01EncryptionResponse) -> Self {
|
||||
GenericPacket::SL01EncryptionResponse(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SL01EncryptionResponse {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SL01EncryptionResponse(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SL02LoginPluginResponse {
|
||||
message_id: i32,
|
||||
successful: bool,
|
||||
data: Vec<u8>,
|
||||
pub message_id: i32,
|
||||
pub successful: bool,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
impl Packet for SL02LoginPluginResponse {
|
||||
fn id() -> PacketId {
|
||||
0x02
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Login
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
SL02LoginPluginResponse,
|
||||
0x02,
|
||||
crate::ClientState::Login,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SL02LoginPluginResponse> {
|
||||
let (data, message_id) = parse_varint(data)?;
|
||||
let (data, successful) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
let (data, successful) = take_bytes(1usize)(data)?;
|
||||
let successful = successful == [0x01];
|
||||
Ok((
|
||||
data,
|
||||
@ -154,31 +100,16 @@ impl Packet for SL02LoginPluginResponse {
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SL02LoginPluginResponse| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(self.message_id));
|
||||
if self.successful {
|
||||
output.extend_from_slice(&serialize_varint(packet.message_id));
|
||||
if packet.successful {
|
||||
output.push(0x01);
|
||||
output.extend_from_slice(&self.data);
|
||||
output.extend_from_slice(&packet.data);
|
||||
} else {
|
||||
output.push(0x00);
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SL02LoginPluginResponse> for GenericPacket {
|
||||
fn from(value: SL02LoginPluginResponse) -> Self {
|
||||
GenericPacket::SL02LoginPluginResponse(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SL02LoginPluginResponse {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SL02LoginPluginResponse(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,25 +1,17 @@
|
||||
use crate::{
|
||||
packet::{GenericPacket, Packet, PacketId},
|
||||
util::{parse_string, parse_varint, serialize_string, serialize_varint},
|
||||
};
|
||||
use crate::{util::*, ProtocolError};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SP08CommandSuggestionsRequest {
|
||||
transaction_id: i32,
|
||||
text: String,
|
||||
pub transaction_id: i32,
|
||||
pub text: String,
|
||||
}
|
||||
impl Packet for SP08CommandSuggestionsRequest {
|
||||
fn id() -> PacketId {
|
||||
0x08
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
crate::packet::packet!(
|
||||
SP08CommandSuggestionsRequest,
|
||||
0x08,
|
||||
crate::ClientState::Play,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SP08CommandSuggestionsRequest> {
|
||||
let (data, transaction_id) = parse_varint(data)?;
|
||||
let (data, text) = parse_string(data)?;
|
||||
Ok((
|
||||
@ -29,147 +21,109 @@ impl Packet for SP08CommandSuggestionsRequest {
|
||||
text,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SP08CommandSuggestionsRequest| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(self.transaction_id));
|
||||
output.extend_from_slice(&serialize_string(&self.text));
|
||||
output.extend_from_slice(&serialize_varint(packet.transaction_id));
|
||||
output.extend_from_slice(&serialize_string(&packet.text));
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SP08CommandSuggestionsRequest> for GenericPacket {
|
||||
fn from(value: SP08CommandSuggestionsRequest) -> Self {
|
||||
GenericPacket::SP08CommandSuggestionsRequest(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SP08CommandSuggestionsRequest {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SP08CommandSuggestionsRequest(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct SP11KeepAlive {
|
||||
payload: i64,
|
||||
pub payload: i64,
|
||||
}
|
||||
impl Packet for SP11KeepAlive {
|
||||
fn id() -> PacketId {
|
||||
0x11
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, payload) = nom::number::streaming::be_i64(data)?;
|
||||
crate::packet::packet!(
|
||||
SP11KeepAlive,
|
||||
0x11,
|
||||
crate::ClientState::Play,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SP11KeepAlive> {
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let payload = bytes
|
||||
.read_i64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, SP11KeepAlive { payload }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
self.payload.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
impl From<SP11KeepAlive> for GenericPacket {
|
||||
fn from(value: SP11KeepAlive) -> Self {
|
||||
GenericPacket::SP11KeepAlive(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SP11KeepAlive {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SP11KeepAlive(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &SP11KeepAlive| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct SP13SetPlayerPosition {
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
on_ground: bool,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub z: f64,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
impl Packet for SP13SetPlayerPosition {
|
||||
fn id() -> PacketId {
|
||||
0x13
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, x) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, y) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, z) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
crate::packet::packet!(
|
||||
SP13SetPlayerPosition,
|
||||
0x13,
|
||||
crate::ClientState::Play,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SP13SetPlayerPosition> {
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let x = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let y = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let z = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, on_ground) = take_bytes(1usize)(data)?;
|
||||
let on_ground = on_ground == [0x01];
|
||||
Ok((data, SP13SetPlayerPosition { x, y, z, on_ground }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SP13SetPlayerPosition| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&self.x.to_be_bytes());
|
||||
output.extend_from_slice(&self.y.to_be_bytes());
|
||||
output.extend_from_slice(&self.z.to_be_bytes());
|
||||
output.push(if self.on_ground { 0x01 } else { 0x00 });
|
||||
output.extend_from_slice(&packet.x.to_be_bytes());
|
||||
output.extend_from_slice(&packet.y.to_be_bytes());
|
||||
output.extend_from_slice(&packet.z.to_be_bytes());
|
||||
output.push(if packet.on_ground { 0x01 } else { 0x00 });
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SP13SetPlayerPosition> for GenericPacket {
|
||||
fn from(value: SP13SetPlayerPosition) -> Self {
|
||||
GenericPacket::SP13SetPlayerPosition(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SP13SetPlayerPosition {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SP13SetPlayerPosition(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct SP14SetPlayerPositionAndRotation {
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
yaw: f32,
|
||||
pitch: f32,
|
||||
on_ground: bool,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub z: f64,
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
impl Packet for SP14SetPlayerPositionAndRotation {
|
||||
fn id() -> PacketId {
|
||||
0x14
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, x) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, y) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, z) = nom::number::streaming::be_f64(data)?;
|
||||
let (data, yaw) = nom::number::streaming::be_f32(data)?;
|
||||
let (data, pitch) = nom::number::streaming::be_f32(data)?;
|
||||
let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
crate::packet::packet!(
|
||||
SP14SetPlayerPositionAndRotation,
|
||||
0x14,
|
||||
crate::ClientState::Play,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SP14SetPlayerPositionAndRotation> {
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let x = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let y = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let z = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let yaw = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let pitch = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, on_ground) = take_bytes(1usize)(data)?;
|
||||
let on_ground = on_ground == [0x01];
|
||||
Ok((
|
||||
data,
|
||||
@ -182,55 +136,40 @@ impl Packet for SP14SetPlayerPositionAndRotation {
|
||||
on_ground,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SP14SetPlayerPositionAndRotation| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&self.x.to_be_bytes());
|
||||
output.extend_from_slice(&self.y.to_be_bytes());
|
||||
output.extend_from_slice(&self.z.to_be_bytes());
|
||||
output.extend_from_slice(&self.yaw.to_be_bytes());
|
||||
output.extend_from_slice(&self.pitch.to_be_bytes());
|
||||
output.push(if self.on_ground { 0x01 } else { 0x00 });
|
||||
output.extend_from_slice(&packet.x.to_be_bytes());
|
||||
output.extend_from_slice(&packet.y.to_be_bytes());
|
||||
output.extend_from_slice(&packet.z.to_be_bytes());
|
||||
output.extend_from_slice(&packet.yaw.to_be_bytes());
|
||||
output.extend_from_slice(&packet.pitch.to_be_bytes());
|
||||
output.push(if packet.on_ground { 0x01 } else { 0x00 });
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SP14SetPlayerPositionAndRotation> for GenericPacket {
|
||||
fn from(value: SP14SetPlayerPositionAndRotation) -> Self {
|
||||
GenericPacket::SP14SetPlayerPositionAndRotation(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SP14SetPlayerPositionAndRotation {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SP14SetPlayerPositionAndRotation(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct SP15SetPlayerRotation {
|
||||
yaw: f32,
|
||||
pitch: f32,
|
||||
on_ground: bool,
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
impl Packet for SP15SetPlayerRotation {
|
||||
fn id() -> PacketId {
|
||||
0x15
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Play
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, yaw) = nom::number::streaming::be_f32(data)?;
|
||||
let (data, pitch) = nom::number::streaming::be_f32(data)?;
|
||||
let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
crate::packet::packet!(
|
||||
SP15SetPlayerRotation,
|
||||
0x15,
|
||||
crate::ClientState::Play,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SP15SetPlayerRotation> {
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let yaw = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let pitch = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, on_ground) = take_bytes(1usize)(data)?;
|
||||
let on_ground = on_ground == [0x01];
|
||||
Ok((
|
||||
data,
|
||||
@ -240,27 +179,12 @@ impl Packet for SP15SetPlayerRotation {
|
||||
on_ground,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
},
|
||||
|packet: &SP15SetPlayerRotation| -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&self.yaw.to_be_bytes());
|
||||
output.extend_from_slice(&self.pitch.to_be_bytes());
|
||||
output.push(if self.on_ground { 0x01 } else { 0x00 });
|
||||
output.extend_from_slice(&packet.yaw.to_be_bytes());
|
||||
output.extend_from_slice(&packet.pitch.to_be_bytes());
|
||||
output.push(if packet.on_ground { 0x01 } else { 0x00 });
|
||||
output
|
||||
}
|
||||
}
|
||||
impl From<SP15SetPlayerRotation> for GenericPacket {
|
||||
fn from(value: SP15SetPlayerRotation) -> Self {
|
||||
GenericPacket::SP15SetPlayerRotation(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SP15SetPlayerRotation {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SP15SetPlayerRotation(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,76 +1,32 @@
|
||||
use crate::packet::{GenericPacket, Packet, PacketId};
|
||||
use crate::{util::*, ProtocolError};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct SS00StatusRequest;
|
||||
impl Packet for SS00StatusRequest {
|
||||
fn id() -> PacketId {
|
||||
0x00
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Status
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
Ok((data, SS00StatusRequest))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
impl From<SS00StatusRequest> for GenericPacket {
|
||||
fn from(value: SS00StatusRequest) -> Self {
|
||||
GenericPacket::SS00StatusRequest(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SS00StatusRequest {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SS00StatusRequest(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::packet::packet!(
|
||||
SS00StatusRequest,
|
||||
0x00,
|
||||
crate::ClientState::Status,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SS00StatusRequest> { Ok((data, SS00StatusRequest)) },
|
||||
|_packet: &SS00StatusRequest| -> Vec<u8> { vec![] }
|
||||
);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct SS01PingRequest {
|
||||
payload: i64,
|
||||
pub payload: i64,
|
||||
}
|
||||
impl Packet for SS01PingRequest {
|
||||
fn id() -> PacketId {
|
||||
0x01
|
||||
}
|
||||
fn client_state() -> crate::ClientState {
|
||||
crate::ClientState::Status
|
||||
}
|
||||
fn serverbound() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, payload) = nom::number::streaming::be_i64(data)?;
|
||||
crate::packet::packet!(
|
||||
SS01PingRequest,
|
||||
0x01,
|
||||
crate::ClientState::Status,
|
||||
true,
|
||||
|data: &'data [u8]| -> ParseResult<'data, SS01PingRequest> {
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let payload = bytes
|
||||
.read_i64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, SS01PingRequest { payload }))
|
||||
}
|
||||
fn serialize_body(&self) -> Vec<u8> {
|
||||
self.payload.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
impl From<SS01PingRequest> for GenericPacket {
|
||||
fn from(value: SS01PingRequest) -> Self {
|
||||
GenericPacket::SS01PingRequest(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<GenericPacket> for SS01PingRequest {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GenericPacket::SS01PingRequest(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|packet: &SS01PingRequest| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
|
||||
);
|
||||
|
@ -1,13 +1,33 @@
|
||||
use nom::error::FromExternalError;
|
||||
use crate::ProtocolError;
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use tracing::trace;
|
||||
|
||||
pub fn parse_varint(mut data: &[u8]) -> nom::IResult<&[u8], i32> {
|
||||
pub type ParseResult<'data, T> = crate::Result<(&'data [u8], T)>;
|
||||
|
||||
pub fn take_bytes(num: usize) -> impl Fn(&'_ [u8]) -> ParseResult<'_, &'_ [u8]> {
|
||||
move |data| {
|
||||
if data.len() < num {
|
||||
Err(ProtocolError::NotEnoughData)
|
||||
} else {
|
||||
Ok(data.split_at(num))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn parse_varint(mut data: &[u8]) -> ParseResult<'_, i32> {
|
||||
trace!("{:?}", data);
|
||||
let mut output = 0i32;
|
||||
let mut bytes_read = 0i32;
|
||||
|
||||
loop {
|
||||
let (d, next_byte) = nom::bytes::streaming::take(1usize)(data)?;
|
||||
let (d, next_byte) = take_bytes(1usize)(data)?;
|
||||
data = d;
|
||||
|
||||
if next_byte.is_empty() {
|
||||
return Err(ProtocolError::NotEnoughData);
|
||||
}
|
||||
|
||||
output |= ((next_byte[0] & 0x7f) as i32) << (bytes_read * 7);
|
||||
bytes_read += 1;
|
||||
if next_byte[0] & 0x80 != 0x80 {
|
||||
@ -17,8 +37,9 @@ pub fn parse_varint(mut data: &[u8]) -> nom::IResult<&[u8], i32> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
nom::IResult::Ok((data, output))
|
||||
Ok((data, output))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn serialize_varint(value: i32) -> Vec<u8> {
|
||||
let mut value = value as u32;
|
||||
let mut output = vec![];
|
||||
@ -36,12 +57,14 @@ pub fn serialize_varint(value: i32) -> Vec<u8> {
|
||||
output
|
||||
}
|
||||
|
||||
pub fn parse_string(data: &[u8]) -> nom::IResult<&[u8], String> {
|
||||
#[tracing::instrument]
|
||||
pub fn parse_string(data: &[u8]) -> ParseResult<'_, String> {
|
||||
let (data, len) = parse_varint(data)?;
|
||||
let (data, str_bytes) = nom::bytes::streaming::take(len as usize)(data)?;
|
||||
let (data, str_bytes) = take_bytes(len as usize)(data)?;
|
||||
let s = String::from_utf8_lossy(str_bytes).to_string();
|
||||
nom::IResult::Ok((data, s))
|
||||
Ok((data, s))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn serialize_string(value: &str) -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend_from_slice(&serialize_varint(value.len() as i32));
|
||||
@ -49,43 +72,63 @@ pub fn serialize_string(value: &str) -> Vec<u8> {
|
||||
output
|
||||
}
|
||||
|
||||
pub fn parse_json(data: &[u8]) -> nom::IResult<&[u8], crate::Json> {
|
||||
use nom::error::{Error, ErrorKind};
|
||||
#[tracing::instrument]
|
||||
pub fn parse_json(data: &[u8]) -> ParseResult<'_, crate::Json> {
|
||||
trace!("parse_json: {:?}", data);
|
||||
let (data, json) = parse_string(data)?;
|
||||
let json = serde_json::from_str(&json)
|
||||
.map_err(|e| nom::Err::Error(Error::from_external_error(data, ErrorKind::Verify, e)))?;
|
||||
let json = serde_json::from_str(&json)?;
|
||||
Ok((data, json))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn serialize_json(value: &crate::Json) -> Vec<u8> {
|
||||
trace!("serialize_json: {:?}", value);
|
||||
serialize_string(&serde_json::to_string(value).expect("valid json"))
|
||||
}
|
||||
|
||||
pub fn parse_chat(data: &[u8]) -> nom::IResult<&[u8], crate::Chat> {
|
||||
#[tracing::instrument]
|
||||
pub fn parse_chat(data: &[u8]) -> ParseResult<'_, crate::Chat> {
|
||||
trace!("parse_chat: {:?}", data);
|
||||
parse_json(data)
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn serialize_chat(value: &crate::Chat) -> Vec<u8> {
|
||||
trace!("serialize_chat: {:?}", value);
|
||||
serialize_json(value)
|
||||
}
|
||||
|
||||
pub fn parse_uuid(data: &[u8]) -> nom::IResult<&[u8], crate::Uuid> {
|
||||
nom::number::streaming::be_u128(data)
|
||||
#[tracing::instrument]
|
||||
pub fn parse_uuid(data: &[u8]) -> ParseResult<'_, crate::Uuid> {
|
||||
trace!("parse_uuid: {:?}", data);
|
||||
let (data, mut bytes) = take_bytes(16)(data)?;
|
||||
let uuid = bytes
|
||||
.read_u128::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, uuid))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn serialize_uuid(value: &crate::Uuid) -> Vec<u8> {
|
||||
trace!("serialize_uuid: {:?}", value);
|
||||
value.to_be_bytes().to_vec()
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Position {
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
impl Position {
|
||||
#[tracing::instrument]
|
||||
pub fn new(x: i32, y: i32, z: i32) -> Self {
|
||||
Position { x, y, z }
|
||||
}
|
||||
pub fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> {
|
||||
let (data, i) = nom::number::streaming::be_i64(data)?;
|
||||
#[tracing::instrument]
|
||||
pub fn parse(data: &[u8]) -> ParseResult<'_, Self> {
|
||||
trace!("Position::parse: {:?}", data);
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let i = bytes
|
||||
.read_i64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
|
||||
// x: i26, z: i26, y: i12
|
||||
let x = i >> 38;
|
||||
@ -97,7 +140,9 @@ impl Position {
|
||||
|
||||
Ok((data, Position::new(x as i32, y as i32, z as i32)))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
trace!("Position::serialize: {:?}", self);
|
||||
let i: i64 = ((self.x as i64 & 0x3FF_FFFF) << 38)
|
||||
| ((self.z as i64 & 0x3FF_FFFF) << 12)
|
||||
| (self.y as i64 & 0xFFF);
|
||||
|
263
src/config.rs
Normal file
263
src/config.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use clap::Arg;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Write};
|
||||
use std::{fs::File, path::Path, path::PathBuf};
|
||||
use tracing::{error, trace, warn};
|
||||
|
||||
/// The globally-accessible static instance of Config.
|
||||
/// On program startup, Config::load() should be called to initialize it.
|
||||
pub static CONFIG: OnceCell<Config> = OnceCell::new();
|
||||
|
||||
/// The globablly-accessible static instance of Args.
|
||||
/// On program startup, Args::load() should be called to initialize it.
|
||||
pub static ARGS: OnceCell<Args> = OnceCell::new();
|
||||
static DEFAULT_ARGS: Lazy<Args> = Lazy::new(Args::default);
|
||||
|
||||
#[tracing::instrument]
|
||||
fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
|
||||
trace!("{:?}", path);
|
||||
let mut data = vec![];
|
||||
let mut file = File::open(path)?;
|
||||
file.read_to_end(&mut data)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub port: u16,
|
||||
pub max_players: usize,
|
||||
pub motd: String,
|
||||
pub server_icon: PathBuf,
|
||||
#[serde(skip)]
|
||||
pub server_icon_bytes: Vec<u8>,
|
||||
pub protocol_version: i32,
|
||||
pub game_version: String,
|
||||
#[serde(skip)]
|
||||
pub server_version: String,
|
||||
pub server_threads: Option<usize>,
|
||||
}
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let server_version = format!(
|
||||
"composition {} ({} {})",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
&env!("GIT_HASH")[0..9],
|
||||
&env!("GIT_DATE")[0..10]
|
||||
);
|
||||
Config {
|
||||
port: 25565,
|
||||
max_players: 20,
|
||||
motd: "Hello world!".to_owned(),
|
||||
server_icon: PathBuf::from("server-icon.png"),
|
||||
server_icon_bytes: include_bytes!("./server-icon.png").to_vec(),
|
||||
protocol_version: 761,
|
||||
game_version: "1.19.3".to_owned(),
|
||||
server_version,
|
||||
server_threads: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Config {
|
||||
pub fn instance() -> &'static Self {
|
||||
match CONFIG.get() {
|
||||
Some(a) => a,
|
||||
None => Self::load(),
|
||||
}
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn load() -> &'static Self {
|
||||
trace!("Config::load()");
|
||||
let args = Args::instance();
|
||||
let mut config = Config::default();
|
||||
let config_path = Path::new(&args.config_file);
|
||||
|
||||
if !config_path.exists() {
|
||||
warn!(
|
||||
"Configuration file does not exist, creating {}",
|
||||
config_path.to_str().unwrap_or("")
|
||||
);
|
||||
config.write(config_path);
|
||||
}
|
||||
|
||||
if let Ok(cfg) = read_file(config_path) {
|
||||
let cfg: Result<Config, _> = toml::from_slice(&cfg);
|
||||
if let Ok(cfg) = cfg {
|
||||
config = cfg;
|
||||
} else {
|
||||
error!("Could not parse configuration file, using default");
|
||||
}
|
||||
} else {
|
||||
error!("Could not read configuration file, using default");
|
||||
}
|
||||
|
||||
// Load the server icon
|
||||
config.server_icon = args.server_icon.clone();
|
||||
let server_icon_path = Path::new(&config.server_icon);
|
||||
|
||||
if server_icon_path.exists() {
|
||||
if let Ok(server_icon_bytes) = read_file(server_icon_path) {
|
||||
config.server_icon_bytes = server_icon_bytes;
|
||||
} else {
|
||||
warn!("Could not read server icon file, using default");
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Server icon file does not exist, creating {}",
|
||||
server_icon_path.to_str().unwrap_or("")
|
||||
);
|
||||
config.write_server_icon(server_icon_path);
|
||||
}
|
||||
|
||||
CONFIG.set(config).expect("could not set CONFIG");
|
||||
Self::instance()
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn write(&self, path: &Path) {
|
||||
trace!("Config.write()");
|
||||
if let Ok(mut file) = File::options().write(true).create(true).open(path) {
|
||||
if file
|
||||
.write_all(toml::to_string(&self).unwrap().as_bytes())
|
||||
.is_ok()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
error!("Could not write configuration file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn write_server_icon(&self, path: &Path) {
|
||||
trace!("Config.write_server_icon()");
|
||||
if let Ok(mut file) = File::options().write(true).create(true).open(path) {
|
||||
if file.write_all(&self.server_icon_bytes).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
error!("Could not write server icon file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Args {
|
||||
pub config_file: PathBuf,
|
||||
pub server_icon: PathBuf,
|
||||
pub log_level: Option<tracing::Level>,
|
||||
pub log_dir: PathBuf,
|
||||
}
|
||||
impl Default for Args {
|
||||
fn default() -> Self {
|
||||
let config = Config::default();
|
||||
Args {
|
||||
config_file: PathBuf::from("composition.toml"),
|
||||
server_icon: config.server_icon,
|
||||
log_level: None,
|
||||
log_dir: PathBuf::from("logs"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Args {
|
||||
pub fn instance() -> &'static Self {
|
||||
match ARGS.get() {
|
||||
Some(a) => a,
|
||||
None => Self::load(),
|
||||
}
|
||||
}
|
||||
pub fn load() -> &'static Self {
|
||||
ARGS.set(Self::parse()).expect("could not set ARGS");
|
||||
Self::instance()
|
||||
}
|
||||
fn parse() -> Self {
|
||||
use std::ffi::OsStr;
|
||||
|
||||
let m = clap::Command::new("composition")
|
||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||
.disable_version_flag(true)
|
||||
.arg(
|
||||
Arg::new("version")
|
||||
.short('V')
|
||||
.long("version")
|
||||
.help("Print version")
|
||||
.global(true)
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("Set log level to debug")
|
||||
.global(true)
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("config-file")
|
||||
.short('c')
|
||||
.long("config-file")
|
||||
.help("Configuration file path")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.default_value(OsStr::new(&DEFAULT_ARGS.config_file)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("server-icon")
|
||||
.long("server-icon")
|
||||
.help("Server icon file path")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.default_value(OsStr::new(&DEFAULT_ARGS.server_icon)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("log-level")
|
||||
.short('l')
|
||||
.long("log-level")
|
||||
.help("Set the log level")
|
||||
.conflicts_with("verbose")
|
||||
.value_name("level")
|
||||
.value_parser(["trace", "debug", "info", "warn", "error"]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("log-dir")
|
||||
.long("log-dir")
|
||||
.help("Set the log output directory")
|
||||
.value_name("dir")
|
||||
.value_hint(clap::ValueHint::DirPath)
|
||||
.default_value(OsStr::new(&DEFAULT_ARGS.log_dir)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let mut args = Self::default();
|
||||
args.config_file = m
|
||||
.get_one::<String>("config-file")
|
||||
.map_or(args.config_file, PathBuf::from);
|
||||
args.server_icon = m
|
||||
.get_one::<String>("server-icon")
|
||||
.map_or(args.server_icon, PathBuf::from);
|
||||
args.log_dir = m
|
||||
.get_one::<String>("log-dir")
|
||||
.map_or(args.log_dir, PathBuf::from);
|
||||
|
||||
if m.get_flag("verbose") {
|
||||
args.log_level = Some(tracing::Level::DEBUG);
|
||||
} else {
|
||||
args.log_level = m.get_one("log-level").map_or(args.log_level, |s: &String| {
|
||||
Some(s.parse::<tracing::Level>().unwrap())
|
||||
});
|
||||
}
|
||||
|
||||
if m.get_flag("version") {
|
||||
println!("{}", Config::default().server_version);
|
||||
if m.get_flag("verbose") {
|
||||
println!("release: {}", env!("CARGO_PKG_VERSION"));
|
||||
println!("commit-hash: {}", env!("GIT_HASH"));
|
||||
println!("commit-date: {}", &env!("GIT_DATE")[0..10]);
|
||||
println!("license: {}", env!("CARGO_PKG_LICENSE"));
|
||||
println!("authors: {}", env!("CARGO_PKG_AUTHORS"));
|
||||
println!("build-target: {}", env!("BUILD_TARGET"));
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
}
|
39
src/lib.rs
Normal file
39
src/lib.rs
Normal file
@ -0,0 +1,39 @@
|
||||
pub mod config;
|
||||
pub mod net;
|
||||
pub mod server;
|
||||
|
||||
use crate::config::Config;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::time::Instant;
|
||||
|
||||
pub static START_TIME: OnceCell<Instant> = OnceCell::new();
|
||||
|
||||
/// Start the server.
|
||||
#[tracing::instrument]
|
||||
pub async fn start_server(
|
||||
start_time: Instant,
|
||||
) -> (server::Server, tokio_util::sync::CancellationToken) {
|
||||
START_TIME
|
||||
.set(start_time)
|
||||
.expect("could not set START_TIME");
|
||||
server::Server::new(format!("0.0.0.0:{}", Config::instance().port)).await
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::config::Config;
|
||||
pub use crate::START_TIME;
|
||||
pub use composition_protocol::{Chat, Json, Uuid};
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use serde_json::json;
|
||||
pub use std::collections::VecDeque;
|
||||
pub use std::io::{Read, Write};
|
||||
pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
pub use tracing::{debug, error, info, trace, warn};
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ParseError {
|
||||
NotEnoughData,
|
||||
InvalidData,
|
||||
VarIntTooBig,
|
||||
}
|
||||
pub type ParseResult<T> = Result<(T, usize), ParseError>;
|
||||
}
|
98
src/main.rs
98
src/main.rs
@ -1,32 +1,76 @@
|
||||
use log::info;
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use tracing::{info, instrument, warn};
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let ctrlc_rx = composition_core::init();
|
||||
info!(
|
||||
"Starting {} on port {}",
|
||||
composition_core::CONFIG.server_version,
|
||||
composition_core::CONFIG.port
|
||||
);
|
||||
let mut server = composition_core::start_server().await;
|
||||
info!(
|
||||
"Done! Start took {:?}",
|
||||
composition_core::START_TIME.elapsed()
|
||||
);
|
||||
#[instrument]
|
||||
pub fn main() {
|
||||
let start_time = Instant::now();
|
||||
|
||||
// The main server loop.
|
||||
loop {
|
||||
match ctrlc_rx.try_recv() {
|
||||
Ok(_) => {
|
||||
let _ = server.shutdown().await;
|
||||
break; // Exit the loop.
|
||||
}
|
||||
Err(TryRecvError::Empty) => {} // Doesn't matter if there's nothing for us.
|
||||
Err(TryRecvError::Disconnected) => panic!("Ctrl-C sender disconnected"),
|
||||
// Set up logging.
|
||||
let file_writer =
|
||||
tracing_appender::rolling::daily(&composition::config::Args::instance().log_dir, "log");
|
||||
let (file_writer, _guard) = tracing_appender::non_blocking(file_writer);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::filter::LevelFilter::from_level(
|
||||
composition::config::Args::instance()
|
||||
.log_level
|
||||
.unwrap_or(if cfg!(debug_assertions) {
|
||||
tracing::Level::DEBUG
|
||||
} else {
|
||||
tracing::Level::INFO
|
||||
}),
|
||||
))
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_ansi(false)
|
||||
.with_writer(file_writer),
|
||||
)
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_writer(std::io::stdout),
|
||||
)
|
||||
.init();
|
||||
|
||||
// Load the config.
|
||||
let config = composition::config::Config::load();
|
||||
|
||||
match config.server_threads {
|
||||
Some(1) => {
|
||||
warn!("Running on only one thread");
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
}
|
||||
server.update().await.unwrap();
|
||||
std::thread::sleep(Duration::from_millis(2));
|
||||
Some(n) => {
|
||||
info!("Running on {} threads", n);
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.worker_threads(n)
|
||||
.build()
|
||||
}
|
||||
None => tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build(),
|
||||
}
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
info!("Starting {} on port {}", config.server_version, config.port);
|
||||
let (mut server, running) = composition::start_server(start_time).await;
|
||||
info!("Done! Start took {:?}", start_time.elapsed());
|
||||
|
||||
// The main server loop.
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = running.cancelled() => {
|
||||
break;
|
||||
}
|
||||
_ = server.update() => {}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tokio::time::timeout(std::time::Duration::from_secs(10), server.shutdown()).await;
|
||||
});
|
||||
}
|
||||
|
212
src/net.rs
Normal file
212
src/net.rs
Normal file
@ -0,0 +1,212 @@
|
||||
use crate::prelude::*;
|
||||
use composition_protocol::{packet::GenericPacket, ClientState, ProtocolError};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum NetworkClientState {
|
||||
Handshake,
|
||||
Status {
|
||||
received_request: bool,
|
||||
received_ping: bool,
|
||||
},
|
||||
Login,
|
||||
Play,
|
||||
Disconnected,
|
||||
}
|
||||
impl From<NetworkClientState> for ClientState {
|
||||
fn from(value: NetworkClientState) -> Self {
|
||||
match value {
|
||||
NetworkClientState::Handshake => ClientState::Handshake,
|
||||
NetworkClientState::Status {
|
||||
received_request: _,
|
||||
received_ping: _,
|
||||
} => ClientState::Status,
|
||||
NetworkClientState::Login => ClientState::Login,
|
||||
NetworkClientState::Play => ClientState::Play,
|
||||
NetworkClientState::Disconnected => ClientState::Disconnected,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AsRef<ClientState> for NetworkClientState {
|
||||
fn as_ref(&self) -> &ClientState {
|
||||
match self {
|
||||
NetworkClientState::Handshake => &ClientState::Handshake,
|
||||
NetworkClientState::Status {
|
||||
received_request: _,
|
||||
received_ping: _,
|
||||
} => &ClientState::Status,
|
||||
NetworkClientState::Login => &ClientState::Login,
|
||||
NetworkClientState::Play => &ClientState::Play,
|
||||
NetworkClientState::Disconnected => &ClientState::Disconnected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NetworkClient {
|
||||
pub id: u128,
|
||||
pub state: NetworkClientState,
|
||||
stream: Arc<RwLock<TcpStream>>,
|
||||
incoming_data: VecDeque<u8>,
|
||||
pub incoming_packet_queue: VecDeque<GenericPacket>,
|
||||
pub last_received_data_time: Instant,
|
||||
pub outgoing_packet_queue: VecDeque<GenericPacket>,
|
||||
}
|
||||
impl NetworkClient {
|
||||
#[tracing::instrument]
|
||||
pub fn new(id: u128, stream: TcpStream) -> NetworkClient {
|
||||
NetworkClient {
|
||||
id,
|
||||
state: NetworkClientState::Handshake,
|
||||
stream: Arc::new(RwLock::new(stream)),
|
||||
incoming_data: VecDeque::new(),
|
||||
incoming_packet_queue: VecDeque::new(),
|
||||
last_received_data_time: Instant::now(),
|
||||
outgoing_packet_queue: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
#[tracing::instrument]
|
||||
async fn read_data(&mut self) -> tokio::io::Result<()> {
|
||||
trace!("NetworkClient.read_data() id {}", self.id);
|
||||
let stream = self.stream.read().await;
|
||||
|
||||
// Try to read 4kb at a time until there is no more data.
|
||||
loop {
|
||||
let mut buf = [0; 4096];
|
||||
|
||||
let num_bytes = match stream.try_read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => {
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
debug!("Read {} bytes from client {}", num_bytes, self.id);
|
||||
|
||||
self.last_received_data_time = Instant::now();
|
||||
self.incoming_data.extend(&buf[..num_bytes]);
|
||||
}
|
||||
|
||||
trace!("NetworkClient.read_data() end id {}", self.id);
|
||||
Ok(())
|
||||
}
|
||||
// TODO: Stream compression/encryption.
|
||||
#[tracing::instrument]
|
||||
pub async fn read_packets(&mut self) -> composition_protocol::Result<()> {
|
||||
trace!("NetworkClient.read_packet() id {}", self.id);
|
||||
|
||||
if self.read_data().await.is_err() {
|
||||
self.disconnect(None).await;
|
||||
return Err(ProtocolError::Disconnected);
|
||||
}
|
||||
|
||||
self.incoming_data.make_contiguous();
|
||||
let (mut data, &[..]) = self.incoming_data.as_slices();
|
||||
|
||||
let mut bytes_consumed = 0;
|
||||
while !data.is_empty() {
|
||||
match GenericPacket::parse_uncompressed(self.state.into(), true, data) {
|
||||
Ok((d, packet)) => {
|
||||
debug!("Got packet {:?} from client {}", packet, self.id);
|
||||
bytes_consumed += data.len() - d.len();
|
||||
data = d;
|
||||
self.incoming_packet_queue.push_back(packet);
|
||||
}
|
||||
Err(ProtocolError::NotEnoughData) => break,
|
||||
Err(e) => {
|
||||
// Remove the valid bytes before this packet.
|
||||
self.incoming_data = self.incoming_data.split_off(bytes_consumed);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the bytes we just read.
|
||||
self.incoming_data = self.incoming_data.split_off(bytes_consumed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// None: There was no packet to read.
|
||||
// Some(Err(())): The packet was the wrong type.
|
||||
// Some(Ok(_)): The packet was successfully read.
|
||||
#[tracing::instrument]
|
||||
pub fn read_packet<P: std::fmt::Debug + TryFrom<GenericPacket>>(
|
||||
&mut self,
|
||||
) -> Option<Result<P, GenericPacket>> {
|
||||
if let Some(generic_packet) = self.incoming_packet_queue.pop_back() {
|
||||
if let Ok(packet) = TryInto::<P>::try_into(generic_packet.clone()) {
|
||||
Some(Ok(packet))
|
||||
} else {
|
||||
self.incoming_packet_queue.push_back(generic_packet.clone());
|
||||
Some(Err(generic_packet))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn queue_packet<P: std::fmt::Debug + Into<GenericPacket>>(&mut self, packet: P) {
|
||||
self.outgoing_packet_queue.push_back(packet.into());
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub async fn send_queued_packets(&mut self) -> composition_protocol::Result<()> {
|
||||
let packets: Vec<_> = self.outgoing_packet_queue.drain(..).collect();
|
||||
for packet in packets {
|
||||
self.send_packet(packet)
|
||||
.await
|
||||
.map_err(|_| ProtocolError::Disconnected)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub async fn send_packet<P: std::fmt::Debug + Into<GenericPacket>>(
|
||||
&self,
|
||||
packet: P,
|
||||
) -> tokio::io::Result<()> {
|
||||
use composition_protocol::util::serialize_varint;
|
||||
let packet: GenericPacket = packet.into();
|
||||
|
||||
debug!("Sending packet {:?} to client {}", packet, self.id);
|
||||
let (packet_id, mut packet_body) = packet.serialize();
|
||||
let mut packet_id = serialize_varint(packet_id);
|
||||
|
||||
// TODO: Stream compression/encryption.
|
||||
|
||||
let mut b = vec![];
|
||||
b.append(&mut packet_id);
|
||||
b.append(&mut packet_body);
|
||||
|
||||
// bytes: packet length as varint, packet id as varint, packet body
|
||||
let mut bytes = serialize_varint(b.len() as i32);
|
||||
bytes.append(&mut b);
|
||||
|
||||
self.stream.write().await.write_all(&bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub async fn disconnect(&mut self, reason: Option<composition_protocol::Chat>) {
|
||||
use composition_protocol::packet::clientbound::{CL00Disconnect, CP17Disconnect};
|
||||
let reason = reason.unwrap_or(json!({
|
||||
"text": "You have been disconnected!"
|
||||
}));
|
||||
|
||||
match self.state.as_ref() {
|
||||
ClientState::Disconnected | ClientState::Handshake | ClientState::Status => {
|
||||
// Impossible to send a disconnect in these states.
|
||||
}
|
||||
ClientState::Login => {
|
||||
let _ = self.send_packet(CL00Disconnect { reason }).await;
|
||||
}
|
||||
ClientState::Play => {
|
||||
let _ = self.send_packet(CP17Disconnect { reason }).await;
|
||||
}
|
||||
}
|
||||
|
||||
self.state = NetworkClientState::Disconnected;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
270
src/server/mod.rs
Normal file
270
src/server/mod.rs
Normal file
@ -0,0 +1,270 @@
|
||||
use crate::net::{NetworkClient, NetworkClientState};
|
||||
use crate::prelude::*;
|
||||
use composition_protocol::ClientState;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::{TcpListener, ToSocketAddrs};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ServerError {
|
||||
NotRunning,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ServerError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Server {
|
||||
clients: Arc<RwLock<Vec<NetworkClient>>>,
|
||||
net_tasks_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
#[tracing::instrument]
|
||||
pub async fn new<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
|
||||
bind_address: A,
|
||||
) -> (Server, CancellationToken) {
|
||||
trace!("Server::new()");
|
||||
|
||||
let running = CancellationToken::new();
|
||||
let clients = Arc::new(RwLock::new(vec![]));
|
||||
let net_tasks_handle = tokio::spawn(Self::create_network_tasks(
|
||||
bind_address,
|
||||
clients.clone(),
|
||||
running.clone(),
|
||||
));
|
||||
|
||||
let server = Server {
|
||||
clients,
|
||||
net_tasks_handle,
|
||||
};
|
||||
|
||||
// let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
let r = running.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
info!("Ctrl-C received, shutting down");
|
||||
r.cancel();
|
||||
// shutdown_tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
(server, running)
|
||||
}
|
||||
#[tracing::instrument]
|
||||
async fn create_network_tasks<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
|
||||
bind_address: A,
|
||||
network_clients: Arc<RwLock<Vec<NetworkClient>>>,
|
||||
running: CancellationToken,
|
||||
) {
|
||||
// Start a task to receive new clients.
|
||||
trace!("Creating listener task");
|
||||
let nc = network_clients.clone();
|
||||
let r = running.clone();
|
||||
let listener_task = tokio::spawn(async move {
|
||||
trace!("Listener task created");
|
||||
let Ok(listener) = TcpListener::bind(bind_address).await else {
|
||||
error!("Could not bind to given address, shutting down.");
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
let mut client_id = 0u128;
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = r.cancelled() => {
|
||||
trace!("Listener task received shutdown");
|
||||
break;
|
||||
}
|
||||
result = listener.accept() => {
|
||||
if let Ok((stream, _)) = result {
|
||||
trace!("Listener task got client (id {})", client_id);
|
||||
nc.write().await.push(NetworkClient::new(client_id, stream));
|
||||
client_id += 1;
|
||||
} else {
|
||||
trace!("Listener task failed to accept client");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start a task to update existing clients' packet queues.
|
||||
trace!("Creating network task");
|
||||
let nc = network_clients.clone();
|
||||
let r = running.clone();
|
||||
let packet_task = tokio::spawn(async move {
|
||||
trace!("Network task created");
|
||||
loop {
|
||||
// Start tasks to read/write to clients concurrently.
|
||||
tokio::select! {
|
||||
_ = r.cancelled() => {
|
||||
trace!("Network task received shutdown");
|
||||
break;
|
||||
}
|
||||
mut nc = nc.write() => {
|
||||
trace!("Network task updating clients");
|
||||
let tasks: Vec<JoinHandle<NetworkClient>> = nc
|
||||
.drain(..)
|
||||
.map(|mut client: NetworkClient| {
|
||||
tokio::spawn(async move {
|
||||
let _ = client.read_packets().await;
|
||||
if client.send_queued_packets().await.is_err() {
|
||||
client
|
||||
.disconnect(Some(json!({ "text": "Error writing packets." })))
|
||||
.await;
|
||||
}
|
||||
client
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
*nc = Vec::with_capacity(tasks.len());
|
||||
for task in tasks {
|
||||
nc.push(task.await.unwrap());
|
||||
}
|
||||
trace!("Network task updated clients");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start a task to remove disconnected clients.
|
||||
trace!("Creating disconnection task");
|
||||
let nc = network_clients.clone();
|
||||
let r = running.clone();
|
||||
let disconnection_task = tokio::spawn(async move {
|
||||
trace!("Disconnection task created");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = r.cancelled() => {
|
||||
trace!("Disconnection task received shutdown");
|
||||
break;
|
||||
}
|
||||
mut nc = nc.write() => {
|
||||
let before = nc.len();
|
||||
nc.retain(|client| client.state != NetworkClientState::Disconnected);
|
||||
let after = nc.len();
|
||||
trace!("Disconnection task removed {} clients", before - after);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Join the tasks on shutdown.
|
||||
listener_task.await.expect("Listener task crashed");
|
||||
packet_task.await.expect("Packet task crashed");
|
||||
disconnection_task
|
||||
.await
|
||||
.expect("Disconnection task crashed");
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub async fn update(&mut self) -> Result<()> {
|
||||
trace!("Server.update()");
|
||||
|
||||
let mut clients = self.clients.write().await;
|
||||
|
||||
// Handle packets from the clients.
|
||||
let online_players = clients
|
||||
.iter()
|
||||
.filter(|client| matches!(client.state, NetworkClientState::Play))
|
||||
.count();
|
||||
'clients: for client in clients.iter_mut() {
|
||||
use composition_protocol::packet::{clientbound::*, serverbound::*};
|
||||
'packets: while !client.incoming_packet_queue.is_empty() {
|
||||
// client.read_packet()
|
||||
// None: The client doesn't have any more packets.
|
||||
// Some(Err(_)): The client read an unexpected packet. TODO: Handle this error.
|
||||
// Some(Ok(_)): The client read the expected packet.
|
||||
match client.state {
|
||||
NetworkClientState::Handshake => {
|
||||
let handshake = match client.read_packet::<SH00Handshake>() {
|
||||
None => continue 'packets,
|
||||
Some(Err(_)) => continue 'clients,
|
||||
Some(Ok(handshake)) => handshake,
|
||||
};
|
||||
|
||||
if handshake.next_state == ClientState::Status {
|
||||
client.state = NetworkClientState::Status {
|
||||
received_request: false,
|
||||
received_ping: false,
|
||||
};
|
||||
} else if handshake.next_state == ClientState::Login {
|
||||
client.state = NetworkClientState::Login;
|
||||
} else {
|
||||
client
|
||||
.disconnect(Some(
|
||||
json!({ "text": "Received invalid SH00Handshake packet" }),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
// Status !received_request: Read SS00StatusRequest and respond with CS00StatusResponse
|
||||
NetworkClientState::Status {
|
||||
received_request,
|
||||
received_ping,
|
||||
} if !received_request => {
|
||||
let _status_request = match client.read_packet::<SS00StatusRequest>() {
|
||||
None => continue 'packets,
|
||||
Some(Err(_)) => continue 'clients,
|
||||
Some(Ok(p)) => p,
|
||||
};
|
||||
client.state = NetworkClientState::Status {
|
||||
received_request: true,
|
||||
received_ping,
|
||||
};
|
||||
let config = Config::instance();
|
||||
client.queue_packet(CS00StatusResponse {
|
||||
response: json!({
|
||||
"version": {
|
||||
"name": config.game_version,
|
||||
"protocol": config.protocol_version
|
||||
},
|
||||
"players": {
|
||||
"max": config.max_players,
|
||||
"online": online_players,
|
||||
"sample": []
|
||||
},
|
||||
"description": {
|
||||
"text": config.motd
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
// Status !received_ping: Read SS00StatusRequest and respond with CS00StatusResponse
|
||||
NetworkClientState::Status { received_ping, .. } if !received_ping => {
|
||||
let ping = match client.read_packet::<SS01PingRequest>() {
|
||||
None => continue 'packets,
|
||||
Some(Err(_)) => continue 'clients,
|
||||
Some(Ok(p)) => p,
|
||||
};
|
||||
client.queue_packet(CS01PingResponse {
|
||||
payload: ping.payload,
|
||||
});
|
||||
client.state = NetworkClientState::Disconnected;
|
||||
}
|
||||
NetworkClientState::Status { .. } => unreachable!(),
|
||||
NetworkClientState::Login => unimplemented!(),
|
||||
NetworkClientState::Play => unimplemented!(),
|
||||
NetworkClientState::Disconnected => unimplemented!(),
|
||||
}
|
||||
// If continue was not
|
||||
break 'packets;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub async fn shutdown(self) {
|
||||
trace!("Server.shutdown()");
|
||||
|
||||
// Close the concurrent tasks.
|
||||
let _ = self.net_tasks_handle.await;
|
||||
|
||||
// Send disconnect messages to the clients.
|
||||
for client in self.clients.write().await.iter_mut() {
|
||||
client
|
||||
.disconnect(Some(json!({ "text": "The server is shutting down." })))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user