initial commit
This commit is contained in:
commit
9b1f942636
14 changed files with 1709 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
580
Cargo.lock
generated
Normal file
580
Cargo.lock
generated
Normal 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
15
Cargo.toml
Normal 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
284
src/client.rs
Normal 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
15
src/event.rs
Normal 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
104
src/main.rs
Normal 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
60
src/protocol/common.rs
Normal 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
96
src/protocol/mod.rs
Normal 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
137
src/protocol/v1_19_4/mod.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
BIN
src/protocol/v1_19_4/registry.nbt
Normal file
BIN
src/protocol/v1_19_4/registry.nbt
Normal file
Binary file not shown.
136
src/protocol/v1_20_1/mod.rs
Normal file
136
src/protocol/v1_20_1/mod.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
BIN
src/protocol/v1_20_1/registry.nbt
Normal file
BIN
src/protocol/v1_20_1/registry.nbt
Normal file
Binary file not shown.
244
src/ser.rs
Normal file
244
src/ser.rs
Normal 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
37
src/varint.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue