Start implementing Play state up to chunk data

This commit is contained in:
Garen Tyler 2022-04-19 18:07:10 -06:00
parent 4849a7903d
commit b12f612c62
No known key found for this signature in database
GPG Key ID: E3BF83D66394FD92
10 changed files with 293 additions and 58 deletions

90
Cargo.lock generated
View File

@ -2,6 +2,18 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anyhow"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
[[package]]
name = "arrayref"
version = "0.3.6"
@ -109,11 +121,11 @@ dependencies = [
"async-trait",
"chrono",
"ctrlc",
"fastnbt",
"fern",
"futures",
"lazy_static",
"log",
"quartz_nbt",
"radix64",
"serde",
"serde_json",
@ -123,6 +135,15 @@ dependencies = [
"uuid",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "ctrlc"
version = "3.2.1"
@ -133,18 +154,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "fastnbt"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adbc536576ecb712740a53d5bf4c64f38ee0ceb221f3f90980919e7506e38cb7"
dependencies = [
"byteorder",
"cesu8",
"serde",
"serde_bytes",
]
[[package]]
name = "fern"
version = "0.6.1"
@ -155,6 +164,18 @@ dependencies = [
"log",
]
[[package]]
name = "flate2"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
dependencies = [
"cfg-if 1.0.0",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]]
name = "futures"
version = "0.3.21"
@ -305,6 +326,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.2"
@ -429,6 +459,31 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "quartz_nbt"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348031720b71761481d77969dcb3c89ab06f04132ee1503aca1bd9313eef5e67"
dependencies = [
"anyhow",
"byteorder",
"cesu8",
"flate2",
"quartz_nbt_macros",
"serde",
]
[[package]]
name = "quartz_nbt_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "289baa0c8a4d1f840d2de528a7f8c29e0e9af48b3018172b3edad4f716e8daed"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.18"
@ -478,15 +533,6 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde_bytes"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.136"

View File

@ -11,7 +11,7 @@ build = "build.rs"
async-trait = "0.1.48"
chrono = "0.4.13"
ctrlc = "3.1.8"
fastnbt = "*"
# fastnbt = "*"
fern = {version = "0.6", features = ["colored"]}
futures = "0.3.13"
lazy_static = "1.4.0"
@ -23,6 +23,7 @@ tokio = {version = "1", features = ["full"]}
toml = "0.5"
uuid = "0.8.2"
substring = "1.4.5"
quartz_nbt = {version = "0.2.6", features = ["serde"]}
# colorful = "0.2.1"
# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2)

View File

@ -1,5 +1,5 @@
favicon = "server-icon.png"
max_players = 20
motd = "Hello world!"
max_players = 4294967295
motd = "Hell world!"
port = 25565
log_level = "debug"

View File

@ -63,7 +63,7 @@ pub mod prelude {
pub use serde_json::json;
pub use uuid::Uuid;
pub type JSON = serde_json::Value;
pub type NBT = fastnbt::Value;
pub type NBT = quartz_nbt::NbtCompound;
pub use std::collections::VecDeque;
pub use substring::Substring;
pub use tokio::io::{AsyncReadExt, AsyncWriteExt};

View File

@ -5,7 +5,11 @@ use std::time::Duration;
#[tokio::main]
pub async fn main() {
let ctrlc_rx = composition::init();
info!("Starting {}", composition::CONFIG.server_version);
info!(
"Starting {} on port {}",
composition::CONFIG.server_version,
composition::CONFIG.port
);
let mut server = composition::start_server().await;
info!("Done! Start took {:?}", composition::START_TIME.elapsed());

View File

@ -8,8 +8,8 @@ pub fn parse_byte(data: &[u8]) -> ParseResult<i8> {
Ok((value, 1))
}
}
pub fn serialize_byte(num: i8) -> [u8; 1] {
num.to_be_bytes()
pub fn serialize_byte(value: i8) -> [u8; 1] {
value.to_be_bytes()
}
pub fn parse_short(data: &[u8]) -> ParseResult<i16> {
@ -20,8 +20,8 @@ pub fn parse_short(data: &[u8]) -> ParseResult<i16> {
Ok((value, 2))
}
}
pub fn serialize_short(num: i16) -> [u8; 2] {
num.to_be_bytes()
pub fn serialize_short(value: i16) -> [u8; 2] {
value.to_be_bytes()
}
pub fn parse_int(data: &[u8]) -> ParseResult<i32> {
@ -32,8 +32,8 @@ pub fn parse_int(data: &[u8]) -> ParseResult<i32> {
Ok((value, 4))
}
}
pub fn serialize_int(num: i32) -> [u8; 4] {
num.to_be_bytes()
pub fn serialize_int(value: i32) -> [u8; 4] {
value.to_be_bytes()
}
pub fn parse_long(data: &[u8]) -> ParseResult<i64> {
@ -46,8 +46,8 @@ pub fn parse_long(data: &[u8]) -> ParseResult<i64> {
Ok((value, 8))
}
}
pub fn serialize_long(num: i64) -> [u8; 8] {
num.to_be_bytes()
pub fn serialize_long(value: i64) -> [u8; 8] {
value.to_be_bytes()
}
pub fn parse_varint(data: &[u8]) -> ParseResult<i32> {
@ -90,6 +90,32 @@ pub fn serialize_varint(value: i32) -> Vec<u8> {
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)
@ -98,8 +124,8 @@ pub fn parse_unsigned_byte(data: &[u8]) -> ParseResult<u8> {
Ok((value, 1))
}
}
pub fn serialize_unsigned_byte(num: u8) -> [u8; 1] {
num.to_be_bytes()
pub fn serialize_unsigned_byte(value: u8) -> [u8; 1] {
value.to_be_bytes()
}
pub fn parse_unsigned_short(data: &[u8]) -> ParseResult<u16> {
@ -110,8 +136,8 @@ pub fn parse_unsigned_short(data: &[u8]) -> ParseResult<u16> {
Ok((value, 2))
}
}
pub fn serialize_unsigned_short(num: u16) -> [u8; 2] {
num.to_be_bytes()
pub fn serialize_unsigned_short(value: u16) -> [u8; 2] {
value.to_be_bytes()
}
pub fn parse_unsigned_int(data: &[u8]) -> ParseResult<u32> {
@ -122,8 +148,8 @@ pub fn parse_unsigned_int(data: &[u8]) -> ParseResult<u32> {
Ok((value, 4))
}
}
pub fn serialize_unsigned_int(num: u32) -> [u8; 4] {
num.to_be_bytes()
pub fn serialize_unsigned_int(value: u32) -> [u8; 4] {
value.to_be_bytes()
}
pub fn parse_unsigned_long(data: &[u8]) -> ParseResult<u64> {
@ -136,8 +162,8 @@ pub fn parse_unsigned_long(data: &[u8]) -> ParseResult<u64> {
Ok((value, 8))
}
}
pub fn serialize_unsigned_long(num: u64) -> [u8; 8] {
num.to_be_bytes()
pub fn serialize_unsigned_long(value: u64) -> [u8; 8] {
value.to_be_bytes()
}
#[cfg(test)]

View File

@ -46,3 +46,47 @@ pub fn parse_json(data: &[u8]) -> ParseResult<JSON> {
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()
}
}

View File

@ -36,7 +36,7 @@ impl NetworkClient {
}
}
pub async fn read_data(&mut self) -> Result<(), tokio::io::Error> {
trace!("NetworkClient.read_data()");
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];
@ -45,6 +45,7 @@ impl NetworkClient {
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);
@ -58,7 +59,7 @@ impl NetworkClient {
Ok(())
}
pub fn read_packet(&mut self) -> Result<(), ParseError> {
trace!("NetworkClient.read_packet()");
trace!("NetworkClient.read_packet() id {}", self.id);
self.buffer.make_contiguous();
if let (data, &[]) = self.buffer.as_slices() {
let mut offset = 0;
@ -85,15 +86,6 @@ impl NetworkClient {
self.stream.write(&bytes).await?;
Ok(())
}
pub async fn update(&mut self) {
// if self.state == NetworkClientState::Disconnected {
// return Err(tokio::io::Error::from(tokio::io::ErrorKind::BrokenPipe));
// } else if self.last_data_time.elapsed() > Duration::from_secs(10) {
// return self.disconnect(tokio::io::ErrorKind::TimedOut);
// }
let _ = self.read_data().await;
let _ = self.read_packet();
}
pub async fn disconnect(&mut self, reason: Option<JSON>) {
if let Some(reason) = reason {
if self.state == NetworkClientState::Login {

View File

@ -60,7 +60,42 @@ pub enum Packet {
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(
@ -193,15 +228,69 @@ impl Packet {
),
CS01Pong { payload } => (0x01, serialize_long(*payload).to_vec()),
CL00Disconnect { reason } => (0x00, serialize_json(reason.clone())),
// CL01EncryptionRequest
CL02LoginSuccess { uuid, username } => (0x02, {
let mut out = vec![];
out.extend(uuid.to_be_bytes());
out.extend(serialize_string(username));
out
}),
// CL03SetCompression
// CL04LoginPluginRequest
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);

View File

@ -1,4 +1,8 @@
use crate::{net::*, prelude::*};
use crate::{
net::{mctypes::Position, *},
prelude::*,
};
use std::time::Duration;
use tokio::{
net::{TcpListener, ToSocketAddrs},
sync::mpsc::{self, error::TryRecvError, UnboundedReceiver},
@ -52,6 +56,9 @@ impl Server {
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;
}
@ -149,6 +156,32 @@ impl Server {
})
.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"),
}