initial commit

This commit is contained in:
TriMill 2023-07-24 00:50:20 -04:00
commit 9b1f942636
14 changed files with 1709 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

580
Cargo.lock generated Normal file
View file

@ -0,0 +1,580 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
[[package]]
name = "async-trait"
version = "0.1.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "gimli"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
[[package]]
name = "hematite-nbt"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670d0784ee67cfb57393dc1837867d2951f9a59ca7db99a653499c854f745739"
dependencies = [
"byteorder",
"cesu8",
"flate2",
"serde",
]
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "linux-raw-sys"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
dependencies = [
"memchr",
]
[[package]]
name = "pin-project-lite"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quectocraft"
version = "0.2.0"
dependencies = [
"anyhow",
"async-trait",
"env_logger",
"hematite-nbt",
"log",
"num-traits",
"serde_json",
"tokio",
"uuid",
]
[[package]]
name = "quote"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "serde"
version = "1.0.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "2.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "tokio"
version = "1.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
dependencies = [
"autocfg",
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "uuid"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"sha1_smol",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

15
Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "quectocraft"
version = "0.2.0"
edition = "2021"
[dependencies]
tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "io-util", "net", "sync", "time"] }
async-trait = "0.1"
num-traits = "0.2"
serde_json = "1.0"
anyhow = "1.0"
uuid = { version = "1.4", features = ["v5"] }
hematite-nbt = "0.5.2"
log = "0.4"
env_logger = "0.10"

284
src/client.rs Normal file
View file

@ -0,0 +1,284 @@
use std::io::Cursor;
use anyhow::anyhow;
use log::{info, debug};
use serde_json::json;
use tokio::{net::{TcpStream, tcp::{OwnedReadHalf, OwnedWriteHalf}}, select, io::{AsyncReadExt, AsyncWriteExt}};
use uuid::Uuid;
use crate::{varint::VarInt, ser::{Deserializable, Position}, protocol::{self, Protocol, ProtocolState, ClientPacket, ServerPacket}, event::{ClientEvent, ServerEvent}, Player, ClientInfo};
type Sender = tokio::sync::mpsc::Sender<(u64, ClientEvent)>;
type Receiver = tokio::sync::mpsc::Receiver<ServerEvent>;
const OFFLINE_NAMESPACE: Uuid = Uuid::from_bytes([0xc3, 0xe3, 0xe7, 0xa5, 0x58, 0xe5, 0x4c, 0xf4, 0x84, 0xef, 0x27, 0x4b, 0x9e, 0xf1, 0x5c, 0xc3]);
fn offline_uuid(name: &str) -> Uuid {
Uuid::new_v5(&OFFLINE_NAMESPACE, name.as_bytes())
}
async fn read_varint_async(r: &mut OwnedReadHalf) -> std::io::Result<Option<i32>> {
let mut buf = [0];
let mut result = 0u32;
for count in 0..5 {
if r.read(&mut buf).await? != 1 {
return Ok(None)
}
let byte = buf[0];
result |= ((byte & 0x7f) as u32) << (7 * count);
if byte & 0x80 == 0 {
return Ok(Some(result as i32))
}
}
return Ok(None)
}
async fn write_varint_async(w: &mut OwnedWriteHalf, i: i32) -> std::io::Result<()> {
let mut data = i as u32;
loop {
let mut byte = (data & 0x7F) as u8;
data >>= 7;
if data != 0 {
byte |= 0x80;
}
w.write_u8(byte).await?;
if data == 0 {
return Ok(())
}
}
}
struct ClientState {
id: u64,
proto: Protocol,
state: ProtocolState,
r: OwnedReadHalf,
w: OwnedWriteHalf,
buf: Vec<u8>
}
enum ReadPacketOutcome {
Packet(ClientPacket),
None,
Eof,
}
pub async fn run_client(id: u64, stream: TcpStream, tx: Sender, rx: Receiver) -> anyhow::Result<()> {
debug!("running client #{id}");
let (r, w) = stream.into_split();
let client = ClientState {
id,
proto: protocol::COMMON,
r, w,
buf: Vec::new(),
state: ProtocolState::Handshake,
};
client.handshake(tx, rx).await
}
impl ClientState {
async fn read_packet(&mut self) -> anyhow::Result<ReadPacketOutcome> {
match read_varint_async(&mut self.r).await? {
Some(len) => {
if len as usize > self.buf.len() {
self.buf.resize(len as usize, 0);
}
self.r.read_exact(&mut self.buf[0..(len as usize)]).await?;
let mut cursor = Cursor::new(&mut self.buf);
let id = VarInt::deserialize(&mut cursor)?.0;
let decoded = (self.proto.decode)(Box::new(&mut cursor), self.state, len, id)?;
Ok(match decoded {
Some(pk) => ReadPacketOutcome::Packet(pk),
None => ReadPacketOutcome::None,
})
}
None => return Ok(ReadPacketOutcome::Eof),
}
}
async fn write_packet(&mut self, pk: ServerPacket) -> anyhow::Result<()> {
self.buf.clear();
let mut cursor = Cursor::new(&mut self.buf);
(self.proto.encode)(Box::new(&mut cursor), self.state, pk)?;
write_varint_async(&mut self.w, self.buf.len() as i32).await?;
self.w.write_all(&self.buf).await?;
Ok(())
}
async fn handshake(mut self, tx: Sender, rx: Receiver) -> anyhow::Result<()> {
let ReadPacketOutcome::Packet(ClientPacket::Handshake { version, addr, port, next_state }) = self.read_packet().await? else {
return Err(anyhow!("Did not recieve handshake"))
};
self.state = ProtocolState::try_from(next_state).unwrap_or(ProtocolState::Handshake);
if let Some(proto) = protocol::get_protocol(version) {
tx.send((self.id, ClientEvent::Handshake(ClientInfo {
addr,
port,
proto_version: proto.version,
proto_name: proto.name,
}))).await?;
self.proto = proto;
info!("#{} using protocol {} ({})", self.id, proto.name, proto.version);
} else {
info!("#{} using unsupported protocol {}", self.id, version);
}
match self.state {
ProtocolState::Status => self.slp(tx, rx).await,
ProtocolState::Login => self.login(tx, rx).await,
ProtocolState::Handshake
| ProtocolState::Play => Err(anyhow!("invalid next state")),
}
}
async fn slp(mut self, _tx: Sender, mut rx: Receiver) -> anyhow::Result<()> {
debug!("#{} entering slp", self.id);
loop { select! {
ev = rx.recv() => match ev {
Some(ServerEvent::Disconnect(msg)) => {
debug!("#{} disconnecting: {}", self.id, msg.unwrap_or_default());
return Ok(())
},
_ => (),
},
pk = self.read_packet() => match pk? {
ReadPacketOutcome::Packet(ClientPacket::PingRequest(data)) => {
debug!("#{} ping request: {}", self.id, data);
self.write_packet(ServerPacket::PingResponse(data)).await?;
},
ReadPacketOutcome::Packet(ClientPacket::StatusRequest) => {
debug!("#{} status request", self.id);
let desc = if self.proto == protocol::COMMON {
"Unsupported client version"
} else {
"server list ping"
};
self.write_packet(ServerPacket::StatusResponse(json!(
{
"version": {
"name": self.proto.name,
"protocol": self.proto.version,
},
"players": {
"max": 1337,
"online": 0,
},
"description": {
"text": desc,
},
"enforcesSecureChat": false,
"previewsChat": false,
}
))).await?;
},
ReadPacketOutcome::Eof => {
debug!("#{} got eof, closing", self.id);
return Ok(())
},
_ => (),
},
} }
}
async fn login(mut self, tx: Sender, mut rx: Receiver) -> anyhow::Result<()> {
debug!("#{} entering login", self.id);
if self.proto == protocol::COMMON {
self.write_packet(ServerPacket::LoginDisconnect(json!(
{
"text": "Unsupported client version"
}
))).await?;
debug!("#{} disconnecting due to unsupported version", self.id);
return Ok(())
}
loop { select! {
ev = rx.recv() => match ev {
Some(ServerEvent::Disconnect(msg)) => {
self.write_packet(ServerPacket::LoginDisconnect(json!(
{
"text": msg
}
))).await?;
info!("#{} disconnecting: {}", self.id, msg.unwrap_or_default());
return Ok(())
},
_ => (),
},
pk = self.read_packet() => match pk? {
ReadPacketOutcome::Packet(ClientPacket::LoginStart { name, uuid }) => {
let uuid = uuid.unwrap_or_else(|| offline_uuid(&name));
info!("#{} logged in as {name} ({uuid})", self.id);
self.write_packet(ServerPacket::LoginSuccess { name: name.clone(), uuid }).await?;
self.state = ProtocolState::Play;
tx.send((self.id, ClientEvent::Join(Player { name, uuid }))).await?;
return self.play(tx, rx).await
}
ReadPacketOutcome::Eof => {
debug!("#{} got eof, closing", self.id);
return Ok(())
},
_ => (),
},
} }
}
async fn play(mut self, tx: Sender, mut rx: Receiver) -> anyhow::Result<()> {
debug!("#{} entering play", self.id);
self.write_packet(ServerPacket::JoinGame {
eid: self.id as i32,
gamemode: 3,
hardcode: false,
}).await?;
self.write_packet(ServerPacket::PluginMessage {
channel: "minecraft:brand".to_owned(),
data: b"\x0bquectocraft".to_vec(),
}).await?;
for x in -1..=1 {
for z in -1..=1 {
self.write_packet(ServerPacket::ChunkData { x, z }).await?;
}
}
self.write_packet(ServerPacket::SetDefaultSpawn {
pos: Position { x: 0, y: 0, z: 0 },
angle: 0.0
}).await?;
loop { select! {
ev = rx.recv() => match ev {
Some(ServerEvent::Disconnect(msg)) => {
self.write_packet(ServerPacket::PlayDisconnect(json!(
{
"text": msg
}
))).await?;
info!("#{} disconnecting: {}", self.id, msg.unwrap_or_default());
return Ok(())
},
Some(ServerEvent::KeepAlive(data)) => {
self.write_packet(ServerPacket::KeepAlive(data)).await?;
debug!("#{} sending keepalive: {data}", self.id);
},
None => (),
},
pk = self.read_packet() => match pk? {
ReadPacketOutcome::Packet(pk) => match pk {
ClientPacket::KeepAlive(data) => {
debug!("#{} recieved keepalive: {data}", self.id);
tx.send((self.id, ClientEvent::KeepAlive(data))).await?;
},
_ => (),
}
ReadPacketOutcome::None => (),
ReadPacketOutcome::Eof => return Ok(()),
},
} }
}
}

15
src/event.rs Normal file
View file

@ -0,0 +1,15 @@
use crate::{Player, ClientInfo};
#[derive(Debug)]
pub enum ClientEvent {
Handshake(ClientInfo),
Join(Player),
KeepAlive(i64),
Disconnect,
}
#[derive(Debug)]
pub enum ServerEvent {
Disconnect(Option<String>),
KeepAlive(i64),
}

104
src/main.rs Normal file
View file

@ -0,0 +1,104 @@
use std::{collections::HashMap, time::Duration, net::ToSocketAddrs};
use event::ServerEvent;
use log::{info, error};
use tokio::{net::TcpListener, sync::mpsc::{Sender, self}, select};
use uuid::Uuid;
use crate::{client::run_client, event::ClientEvent};
mod event;
mod protocol;
mod ser;
mod varint;
mod client;
pub type JsonValue = serde_json::Value;
#[derive(Clone, Debug)]
pub struct ClientInfo {
addr: String,
port: u16,
proto_version: i32,
proto_name: &'static str,
}
#[derive(Debug)]
pub struct Player {
name: String,
uuid: Uuid,
}
struct Client {
tx: Sender<ServerEvent>,
keepalive: Option<i64>,
info: Option<ClientInfo>,
player: Option<Player>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let socket_addr = "0.0.0.0:25565".to_socket_addrs()?.next().expect("invalid server address");
let listener = TcpListener::bind(socket_addr).await?;
let mut clients = HashMap::new();
let mut next_id = 0;
let (c_tx, mut c_rx) = mpsc::channel(16);
let mut keepalive = tokio::time::interval(Duration::from_secs(15));
info!("listening on {socket_addr}");
loop { select! {
conn = listener.accept() => {
let c_tx = c_tx.clone();
let (stream, addr) = conn?;
let id = next_id;
next_id += 1;
let (s_tx, s_rx) = mpsc::channel(16);
clients.insert(id, Client {
tx: s_tx,
keepalive: None,
info: None,
player: None,
});
info!("#{id} connected from {addr}");
tokio::spawn(async move {
let c_tx2 = c_tx.clone();
match run_client(id, stream, c_tx, s_rx).await {
Ok(_) => info!("client {id} disconnected"),
Err(e) => error!("client {id} error: {e}"),
}
c_tx2.send((id, ClientEvent::Disconnect)).await.unwrap();
});
},
_ = keepalive.tick() => {
for (_, client) in &mut clients {
if client.keepalive.is_some() {
client.tx.send(ServerEvent::Disconnect(Some("Failed to respond to keep alives".to_owned()))).await?;
} else if client.player.is_some() {
let data = 1;
client.keepalive = Some(data);
client.tx.send(ServerEvent::KeepAlive(data)).await?;
}
}
},
ev = c_rx.recv() => {
let Some(ev) = ev else {
return Err("reciever closed".into());
};
let id = ev.0;
let client = clients.get_mut(&id).unwrap();
match ev.1 {
ClientEvent::Handshake(info) => client.info = Some(info),
ClientEvent::Join(player) => clients.get_mut(&id).unwrap().player = Some(player),
ClientEvent::Disconnect => { clients.remove(&id); },
ClientEvent::KeepAlive(data) => {
let client = clients.get_mut(&id).unwrap();
if client.keepalive == Some(data) {
client.keepalive = None;
} else {
client.keepalive = Some(0);
}
}
}
}
} }
}

60
src/protocol/common.rs Normal file
View file

@ -0,0 +1,60 @@
use std::io::{Write, Read};
use anyhow::anyhow;
use super::{ServerPacket, ClientPacket};
use crate::{varint::VarInt, ser::{Serializable, Deserializable}};
use super::{Protocol, ProtocolState};
pub const COMMON: Protocol = Protocol {
name: "common",
version: i32::MAX,
encode,
decode,
};
fn encode(mut w: Box<&mut dyn Write>, _state: ProtocolState, pk: ServerPacket) -> anyhow::Result<()> {
match pk {
ServerPacket::StatusResponse(response) => {
VarInt(0x00).serialize(&mut w)?;
response.serialize(&mut w)?;
},
ServerPacket::PingResponse(payload) => {
VarInt(0x01).serialize(&mut w)?;
payload.serialize(&mut w)?;
},
ServerPacket::LoginDisconnect(response) => {
VarInt(0x00).serialize(&mut w)?;
response.serialize(&mut w)?;
},
_ => {
return Err(anyhow!("packet {pk:?} unhandled"))
}
}
Ok(())
}
fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, _len: i32, id: i32) -> anyhow::Result<Option<ClientPacket>> {
type Ps = ProtocolState;
match (state, id) {
(Ps::Handshake, 0x00) => {
let version = VarInt::deserialize(&mut r)?.0;
let addr = String::deserialize(&mut r)?;
let port = u16::deserialize(&mut r)?;
let next_state = VarInt::deserialize(&mut r)?.0;
Ok(Some(ClientPacket::Handshake { version, addr, port, next_state }))
},
(Ps::Status, 0x00) => {
Ok(Some(ClientPacket::StatusRequest))
},
(Ps::Status, 0x01) => {
let payload = i64::deserialize(&mut r)?;
Ok(Some(ClientPacket::PingRequest(payload)))
},
_ => {
println!("invalid id {id:#x} in state {state:?}");
Ok(None)
}
}
}

96
src/protocol/mod.rs Normal file
View file

@ -0,0 +1,96 @@
use std::io::{Read, Write};
use uuid::Uuid;
use crate::{JsonValue, ser::Position};
mod common;
mod v1_19_4;
mod v1_20_1;
pub use common::COMMON as COMMON;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProtocolState {
Handshake = 0,
Status = 1,
Login = 2,
Play = 3,
}
impl TryFrom<i32> for ProtocolState {
type Error = ();
fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
match value {
0 => Ok(Self::Handshake),
1 => Ok(Self::Status),
2 => Ok(Self::Login),
_ => Err(())
}
}
}
#[derive(Clone, Debug)]
pub enum ClientPacket {
Handshake {
version: i32,
addr: String,
port: u16,
next_state: i32
},
StatusRequest,
PingRequest(i64),
LoginStart {
name: String,
uuid: Option<Uuid>
},
PluginMessage {
channel: String,
data: Vec<u8>
},
KeepAlive(i64),
}
#[derive(Clone, Debug)]
pub enum ServerPacket {
StatusResponse(JsonValue),
PingResponse(i64),
LoginDisconnect(JsonValue),
LoginSuccess {
name: String,
uuid: Uuid
},
JoinGame {
eid: i32,
gamemode: u8,
hardcode: bool,
},
PlayDisconnect(JsonValue),
KeepAlive(i64),
PluginMessage {
channel: String,
data: Vec<u8>
},
ChunkData {
x: i32,
z: i32,
},
SetDefaultSpawn { pos: Position, angle: f32 },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Protocol {
pub name: &'static str,
pub version: i32,
pub encode: fn(w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()>,
pub decode: fn(r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result<Option<ClientPacket>>,
}
pub const fn get_protocol(version: i32) -> Option<Protocol> {
match version {
v1_20_1::VERSION => Some(v1_20_1::PROTOCOL),
v1_19_4::VERSION => Some(v1_19_4::PROTOCOL),
_ => None,
}
}

137
src/protocol/v1_19_4/mod.rs Normal file
View file

@ -0,0 +1,137 @@
use std::io::{Write, Read};
use uuid::Uuid;
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
pub const VERSION: i32 = 762;
pub const PROTOCOL: Protocol = Protocol {
name: "1.19.4",
version: VERSION,
encode,
decode,
};
fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()> {
match ev {
ServerPacket::LoginSuccess { name, uuid } => {
VarInt(0x02).serialize(&mut w)?;
uuid.serialize(&mut w)?;
name.serialize(&mut w)?;
// number of properties (0)
VarInt(0x00).serialize(&mut w)?;
},
ServerPacket::JoinGame { eid, gamemode, hardcode } => {
VarInt(0x28).serialize(&mut w)?;
eid.serialize(&mut w)?;
hardcode.serialize(&mut w)?;
gamemode.serialize(&mut w)?;
(-1i8).serialize(&mut w)?; // prev gamemode undefined
VarInt(1).serialize(&mut w)?; // dimension count
"qc:world".serialize(&mut w)?; // register one dimension
include_bytes!("registry.nbt").serialize(&mut w)?; // registry codec
"minecraft:the_end".serialize(&mut w)?; // dimension type
"qc:world".serialize(&mut w)?; // dimension name
0i64.serialize(&mut w)?; // seed
VarInt(65535).serialize(&mut w)?; // max players
VarInt(8).serialize(&mut w)?; // view dist
VarInt(0).serialize(&mut w)?; // sim dist
false.serialize(&mut w)?; // reduce debug info
false.serialize(&mut w)?; // respawn screen
false.serialize(&mut w)?; // is debug
false.serialize(&mut w)?; // is flat
false.serialize(&mut w)?; // has death location
},
ServerPacket::KeepAlive(data) => {
VarInt(0x23).serialize(&mut w)?;
data.serialize(&mut w)?;
},
ServerPacket::PlayDisconnect(msg) => {
VarInt(0x1A).serialize(&mut w)?;
msg.serialize(&mut w)?;
},
ServerPacket::PluginMessage { channel, mut data } => {
VarInt(0x17).serialize(&mut w)?;
channel.serialize(&mut w)?;
w.write_all(&mut data)?;
},
ServerPacket::ChunkData { x, z } => {
VarInt(0x24).serialize(&mut w)?;
// chunk x
x.serialize(&mut w)?;
// chunk z
z.serialize(&mut w)?;
// heightmap
let hmdata = [0i64; 37];
let mut heightmap = nbt::Blob::new();
heightmap.insert("MOTION_BLOCKING", hmdata.as_ref()).unwrap();
heightmap.serialize(&mut w)?;
// chunk data
let mut chunk_data: Vec<u8> = Vec::new();
for _ in 0..(384 / 16) {
// number of non-air blocks
(0i16).serialize(&mut chunk_data)?;
// block states
(0u8).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
// biomes
(0u8).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
}
VarInt(chunk_data.len() as i32).serialize(&mut w)?;
w.write_all(&chunk_data)?;
// block entities
VarInt(0).serialize(&mut w)?;
// trust edges
true.serialize(&mut w)?;
// light masks
VarInt(0).serialize(&mut w)?;
VarInt(0).serialize(&mut w)?;
VarInt(0).serialize(&mut w)?;
VarInt(0).serialize(&mut w)?;
// sky light array len
VarInt(0).serialize(&mut w)?;
// block light array len
VarInt(0).serialize(&mut w)?;
},
ServerPacket::SetDefaultSpawn { pos, angle } => {
VarInt(0x50).serialize(&mut w)?;
pos.serialize(&mut w)?;
angle.serialize(&mut w)?;
},
_ => { (COMMON.encode)(w, state, ev)?; }
}
Ok(())
}
fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result<Option<ClientPacket>> {
type Ps = ProtocolState;
match (state, id) {
(Ps::Login, 0x00) => {
let name = String::deserialize(&mut r)?;
let has_uuid = bool::deserialize(&mut r)?;
let uuid = if has_uuid {
Some(Uuid::deserialize(&mut r)?)
} else { None };
Ok(Some(ClientPacket::LoginStart { name, uuid }))
}
(Ps::Login, 0x01 | 0x02) => Ok(None), // unsupported
(Ps::Play, 0x0D) => {
let channel = String::deserialize(&mut r)?;
let mut data = Vec::new();
r.read_to_end(&mut data)?;
Ok(Some(ClientPacket::PluginMessage { channel, data }))
}
(Ps::Play, 0x12) => {
let data = i64::deserialize(&mut r)?;
Ok(Some(ClientPacket::KeepAlive(data)))
}
(Ps::Play, 0x14 | 0x15 | 0x16) => Ok(None), // position & rotation
_ => (COMMON.decode)(r, state, len, id)
}
}

Binary file not shown.

136
src/protocol/v1_20_1/mod.rs Normal file
View file

@ -0,0 +1,136 @@
use std::io::{Write, Read};
use uuid::Uuid;
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
pub const VERSION: i32 = 763;
pub const PROTOCOL: Protocol = Protocol {
name: "1.20.1",
version: VERSION,
encode,
decode,
};
fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) -> anyhow::Result<()> {
match ev {
ServerPacket::LoginSuccess { name, uuid } => {
VarInt(0x02).serialize(&mut w)?;
uuid.serialize(&mut w)?;
name.serialize(&mut w)?;
// number of properties (0)
VarInt(0x00).serialize(&mut w)?;
},
ServerPacket::JoinGame { eid, gamemode, hardcode } => {
VarInt(0x28).serialize(&mut w)?;
eid.serialize(&mut w)?;
hardcode.serialize(&mut w)?;
gamemode.serialize(&mut w)?;
(-1i8).serialize(&mut w)?; // prev gamemode undefined
VarInt(1).serialize(&mut w)?; // dimension count
"qc:world".serialize(&mut w)?; // register one dimension
include_bytes!("registry.nbt").serialize(&mut w)?; // registry codec
"minecraft:the_end".serialize(&mut w)?; // dimension type
"qc:world".serialize(&mut w)?; // dimension name
0i64.serialize(&mut w)?; // seed
VarInt(65535).serialize(&mut w)?; // max players
VarInt(8).serialize(&mut w)?; // view dist
VarInt(0).serialize(&mut w)?; // sim dist
false.serialize(&mut w)?; // reduce debug info
false.serialize(&mut w)?; // respawn screen
false.serialize(&mut w)?; // is debug
false.serialize(&mut w)?; // is flat
false.serialize(&mut w)?; // has death location
VarInt(1).serialize(&mut w)?; // portal cooldown
},
ServerPacket::KeepAlive(data) => {
VarInt(0x23).serialize(&mut w)?;
data.serialize(&mut w)?;
},
ServerPacket::PlayDisconnect(msg) => {
VarInt(0x1A).serialize(&mut w)?;
msg.serialize(&mut w)?;
},
ServerPacket::PluginMessage { channel, mut data } => {
VarInt(0x17).serialize(&mut w)?;
channel.serialize(&mut w)?;
w.write_all(&mut data)?;
},
ServerPacket::ChunkData { x, z } => {
VarInt(0x24).serialize(&mut w)?;
// chunk x
x.serialize(&mut w)?;
// chunk z
z.serialize(&mut w)?;
// heightmap
let hmdata = [0i64; 37];
let mut heightmap = nbt::Blob::new();
heightmap.insert("MOTION_BLOCKING", hmdata.as_ref()).unwrap();
heightmap.serialize(&mut w)?;
// chunk data
let mut chunk_data: Vec<u8> = Vec::new();
for _ in 0..(384 / 16) {
// number of non-air blocks
(0i16).serialize(&mut chunk_data)?;
// block states
(0u8).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
// biomes
(0u8).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
VarInt(0).serialize(&mut chunk_data)?;
}
VarInt(chunk_data.len() as i32).serialize(&mut w)?;
w.write_all(&chunk_data)?;
// block entities
VarInt(0).serialize(&mut w)?;
// light masks
VarInt(0).serialize(&mut w)?;
VarInt(0).serialize(&mut w)?;
VarInt(0).serialize(&mut w)?;
VarInt(0).serialize(&mut w)?;
// sky light array len
VarInt(0).serialize(&mut w)?;
// block light array len
VarInt(0).serialize(&mut w)?;
},
ServerPacket::SetDefaultSpawn { pos, angle } => {
VarInt(0x50).serialize(&mut w)?;
pos.serialize(&mut w)?;
angle.serialize(&mut w)?;
},
_ => { (COMMON.encode)(w, state, ev)?; }
}
Ok(())
}
fn decode(mut r: Box<&mut dyn Read>, state: ProtocolState, len: i32, id: i32) -> anyhow::Result<Option<ClientPacket>> {
type Ps = ProtocolState;
match (state, id) {
(Ps::Login, 0x00) => {
let name = String::deserialize(&mut r)?;
let has_uuid = bool::deserialize(&mut r)?;
let uuid = if has_uuid {
Some(Uuid::deserialize(&mut r)?)
} else { None };
Ok(Some(ClientPacket::LoginStart { name, uuid }))
}
(Ps::Login, 0x01 | 0x02) => Ok(None), // unsupported
(Ps::Play, 0x0D) => {
let channel = String::deserialize(&mut r)?;
let mut data = Vec::new();
r.read_to_end(&mut data)?;
Ok(Some(ClientPacket::PluginMessage { channel, data }))
}
(Ps::Play, 0x12) => {
let data = i64::deserialize(&mut r)?;
Ok(Some(ClientPacket::KeepAlive(data)))
}
(Ps::Play, 0x14 | 0x15 | 0x16) => Ok(None), // position & rotation
_ => (COMMON.decode)(r, state, len, id)
}
}

Binary file not shown.

244
src/ser.rs Normal file
View file

@ -0,0 +1,244 @@
use std::{io::{self, Read, Write}, mem::MaybeUninit};
use anyhow::anyhow;
use uuid::Uuid;
use crate::JsonValue;
use super::varint::{VarInt, VarLong};
pub trait ReadExt: Read {
fn readb(&mut self) -> io::Result<u8> {
Ok(self.readn::<1>()?[0])
}
fn readn<const N: usize>(&mut self) -> io::Result<[u8; N]> {
let mut buf = [0; N];
self.read_exact(&mut buf)?;
Ok(buf)
}
}
impl<T: Read> ReadExt for T {}
pub trait Serializable: Sized {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()>;
}
pub trait Deserializable: Sized {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self>;
}
impl Serializable for bool {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_all(&[*self as u8])?;
Ok(())
}
}
impl Deserializable for bool {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
match r.readb()? {
0 => Ok(false),
1 => Ok(true),
b => Err(anyhow!("value {b} out of range for bool"))
}
}
}
macro_rules! serialize_primitive {
($ty:ty) => {
impl Serializable for $ty {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
w.write_all(&self.to_be_bytes())?;
Ok(())
}
}
impl Deserializable for $ty {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
Ok(Self::from_be_bytes(r.readn()?))
}
}
};
}
serialize_primitive!(i8);
serialize_primitive!(i16);
serialize_primitive!(i32);
serialize_primitive!(i64);
serialize_primitive!(i128);
serialize_primitive!(u8);
serialize_primitive!(u16);
serialize_primitive!(u32);
serialize_primitive!(u64);
serialize_primitive!(u128);
serialize_primitive!(f32);
serialize_primitive!(f64);
impl Serializable for VarInt {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
let mut data = self.0 as u32;
loop {
let mut byte = (data & 0x7F) as u8;
data >>= 7;
if data != 0 {
byte |= 0x80;
}
w.write_all(&[byte])?;
if data == 0 {
return Ok(())
}
}
}
}
impl Deserializable for VarInt {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
let mut result = 0u32;
for count in 0..5 {
let byte = r.readb()?;
result |= ((byte & 0x7f) as u32) << (7 * count);
if byte & 0x80 == 0 {
return Ok(VarInt(result as i32))
}
}
return Err(anyhow!("VarInt too long"))
}
}
impl Serializable for VarLong {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
let mut data = self.0 as u64;
loop {
let mut byte = (data & 0x7F) as u8;
data >>= 7;
if data != 0 {
byte |= 0x80;
}
w.write_all(&[byte])?;
if data == 0 {
return Ok(())
}
}
}
}
impl Deserializable for VarLong {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
let mut result = 0u64;
for count in 0..10 {
let byte = r.readb()?;
result |= ((byte & 0x7f) as u64) << (7 * count);
if byte & 0x80 == 0 {
return Ok(VarLong(result as i64))
}
}
return Err(anyhow!("VarLong too long"))
}
}
impl Serializable for String {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.len() as i32).serialize(w)?;
w.write_all(self.as_bytes())?;
Ok(())
}
}
impl Deserializable for String {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
let len = VarInt::deserialize(r)?.0 as u64;
let mut buf = String::new();
r.take(len).read_to_string(&mut buf)?;
Ok(buf)
}
}
impl Serializable for &str {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(self.len() as i32).serialize(w)?;
w.write_all(self.as_bytes())?;
Ok(())
}
}
impl Serializable for JsonValue {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
serde_json::to_string(self)?.serialize(w)
}
}
impl Deserializable for JsonValue {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
let v = serde_json::from_str(&String::deserialize(r)?)?;
Ok(v)
}
}
impl<T, const N: usize> Serializable for [T; N]
where T: Serializable {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
for item in self {
item.serialize(w)?;
}
Ok(())
}
}
impl<T, const N: usize> Deserializable for [T; N]
where T: Deserializable {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
let mut arr: [MaybeUninit<T>; N] = unsafe {
MaybeUninit::uninit().assume_init()
};
for i in 0..N {
arr[i] = MaybeUninit::new(T::deserialize(r)?);
}
// Safety: since MaybeUninit<T> and T have the same memory
// layout, so will [MaybeUninit<T>; N] and [T; N].
// std::mem::transmute cannot yet handle arrays of
// generic size.
Ok(unsafe { arr.as_ptr().cast::<Self>().read() })
}
}
impl Serializable for Uuid {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.as_u128().serialize(w)
}
}
impl Deserializable for Uuid {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
Ok(Uuid::from_u128(u128::deserialize(r)?))
}
}
impl Serializable for nbt::Blob {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.to_writer(w)?;
Ok(())
}
}
impl Deserializable for nbt::Blob {
fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
Ok(nbt::Blob::from_reader(r)?)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Position {
pub x: i32,
pub y: i16,
pub z: i32,
}
impl Serializable for Position {
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
let value = (((self.x & 0x3FFFFFF) as u64) << 38) |
(((self.z & 0x3FFFFFF) as u64) << 12) |
(((self.y & 0xFFF) as u64) << 38);
value.serialize(w)
}
}

37
src/varint.rs Normal file
View file

@ -0,0 +1,37 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct VarInt(pub i32);
impl AsRef<i32> for VarInt {
fn as_ref(&self) -> &i32 { &self.0 }
}
impl From<i32> for VarInt {
fn from(value: i32) -> Self {
Self(value)
}
}
impl From<VarInt> for i32 {
fn from(value: VarInt) -> Self {
value.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct VarLong(pub i64);
impl AsRef<i64> for VarLong {
fn as_ref(&self) -> &i64 { &self.0 }
}
impl From<i64> for VarLong {
fn from(value: i64) -> Self {
Self(value)
}
}
impl From<VarLong> for i64 {
fn from(value: VarLong) -> Self {
value.0
}
}