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