Compare commits
10 Commits
0cdc71ab8a
...
beac4bdb42
Author | SHA1 | Date |
---|---|---|
TriMill | beac4bdb42 | |
TriMill | 459245bf8b | |
TriMill | 12677b2068 | |
TriMill | 278e28ad18 | |
TriMill | a3375c53a4 | |
TriMill | d4cf718214 | |
TriMill | 8434884747 | |
TriMill | 0f067d6114 | |
TriMill | 162d337769 | |
TriMill | e1ccb6ec7d |
|
@ -1 +1,2 @@
|
|||
/target
|
||||
config.json
|
||||
|
|
|
@ -8,12 +8,45 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
|
@ -23,6 +56,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
|
@ -47,6 +86,46 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
|
@ -56,6 +135,120 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[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 = "erased-serde"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[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.25"
|
||||
|
@ -66,6 +259,16 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hematite-nbt"
|
||||
version = "0.5.2"
|
||||
|
@ -78,12 +281,130 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
|
@ -107,10 +428,38 @@ checksum = "4351dbcc863fb6249c81b3bd0c8001214e9d4d44d22cabda17026353a77fe612"
|
|||
dependencies = [
|
||||
"bstr",
|
||||
"cc",
|
||||
"erased-serde",
|
||||
"mlua_derive",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"pkg-config",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua_derive"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9214e60d3cf1643013b107330fcd374ccec1e4ba1eef76e7e5da5e8202e71c0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"once_cell",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -134,6 +483,30 @@ version = "0.3.26"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
|
@ -147,10 +520,15 @@ dependencies = [
|
|||
name = "quectocraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"env_logger",
|
||||
"hematite-nbt",
|
||||
"hmac",
|
||||
"log",
|
||||
"mlua",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -163,18 +541,55 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.148"
|
||||
|
@ -206,6 +621,23 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.105"
|
||||
|
@ -217,14 +649,200 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[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.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
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.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
|
|
|
@ -9,5 +9,10 @@ edition = "2021"
|
|||
hematite-nbt = "0.5"
|
||||
serde_json = "1.0"
|
||||
serde = "1.0"
|
||||
mlua = { version = "0.8", features = ["lua54"] }
|
||||
mlua = { version = "0.8", features = ["lua54", "macros", "serialize"] }
|
||||
uuid = "1.2"
|
||||
log = "0.4.0"
|
||||
env_logger = "0.10.0"
|
||||
chrono = "0.4"
|
||||
hmac = "0.12"
|
||||
sha2 = "0.10"
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# Quectocraft
|
||||
|
||||
Quectocraft is a minimal, extensible, efficient Minecraft server implementation written in Rust and Lua.
|
||||
|
||||
## Goals
|
||||
|
||||
- Minimal: By default, Quectocraft does very little by itself. It accepts connections, encodes and decodes packets, and handles the login sequence for you, but everything else must be done via plugins.
|
||||
- Extensible: Via its Lua plugin system, Quectocraft can be configured to do a variety of things.
|
||||
- Efficient: The vanilla Minecraft server, and even more efficient servers like Spigot and Paper, all use significant amounts of CPU even while idling with no players connected. Due to its low CPU and memory usage, Quectocraft is suitable for running on lower-end systems, or alongside another server without causing additional lag.
|
||||
|
||||
## Plugin API
|
||||
|
||||
See [Plugin API](docs/plugins.md)
|
|
@ -0,0 +1,50 @@
|
|||
# Quectocraft Plugin API
|
||||
|
||||
Quectocraft plugins are written in Lua. Examples can be seen in the [plugins directory](plugins). Plugins can either be a single Lua file or a directory containing a file named `main.lua`.
|
||||
|
||||
## Plugin table
|
||||
|
||||
All information about a plugin is stored in a table, which must be returned at the end of the plugin. This table contains both information about the plugin and functions that act as event handlers.
|
||||
|
||||
| Field | Description |
|
||||
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `id` | The plugin's ID. This should consist of lowercase letters and underscores only. |
|
||||
| `name` | The plugin's human-readable name. |
|
||||
| `description` | The plugin's description. |
|
||||
| `authors` | A list of the plugin's authors. |
|
||||
| `version` | The plugin's version (semantic versioning encouraged). |
|
||||
| `init` | Called when the plugin is initialized. The `server` table is available at this point. |
|
||||
| `registerCommands` | Called to register the plugin's commands. Arguments: a `registry` table. |
|
||||
| `playerJoin` | Called when a player joins. Arguments: the player's name, the player's UUID. |
|
||||
| `playerLeave` | Called when a player leaves. Arguments: the player's name, the player's UUID. |
|
||||
| `chatMessage` | Called when a player sends a chat message. Arguments: the message, the player's name and UUID. |
|
||||
| `pluginMessage` | Called when a client sends a [plugin message](https://wiki.vg/Plugin_channels). Arguments: the channel, the message, the player's name and UUID. |
|
||||
| `command` | Called when a player runs a command. Arguments: the command, the arguments, the player's name and UUID. |
|
||||
|
||||
## The `server` table
|
||||
|
||||
The `server` table is used to interact with the server. It has the following fields:
|
||||
|
||||
| Field | Description |
|
||||
|---------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `players` | A map from UUIDs to player names. |
|
||||
| `sendPluginMessage` | Send a player a [plugin message](https://wiki.vg/Plugin_channels). Arguments: the player (name or UUID), the channel, the message. |
|
||||
| `sendMessage` | Send a player a message. Arguments: the player (name or UUID), the message. |
|
||||
| `broadcast` | Broadcast a message to all online players. Arguments: the message. |
|
||||
| `disconnect` | Disconnect a player from the server. Arguments: the player (name or UUID), the reason (optional) |
|
||||
|
||||
## The `registry` table
|
||||
|
||||
The `registry` table is used to register commands. It is only available from the `registerCommands` event handler.
|
||||
|
||||
| Field | Description |
|
||||
|--------------|---------------------------------------------------|
|
||||
| `addCommand` | Add a command. Arguments: the name of the command |
|
||||
|
||||
## The `logger` table
|
||||
|
||||
The `logger` table is used to log information the the console. It has the following functions for different logging levels: `trace`, `debug`, `info`, `error`, `warn`. A logger should be initialized in the `init` event handler.
|
||||
|
||||
## Chat components
|
||||
|
||||
Wherever a chat component is expected (chat messages, disconnect reasons), the plugin can either provide a string or a chat component. Lua tables are a very good approximation for JSON, and as such translating between JSON chat components and tables is not very difficult. See [the wiki.vg documentation for chat components](https://wiki.vg/Chat) for more information.
|
|
@ -1,23 +0,0 @@
|
|||
local plugin = {
|
||||
id = "example_plugin",
|
||||
name = "Example Plugin",
|
||||
version = "0.1.0",
|
||||
}
|
||||
|
||||
function plugin.init()
|
||||
print("PLUGIN init")
|
||||
end
|
||||
|
||||
function plugin.playerJoin(name, uuid)
|
||||
print("PLUGIN player joined: " .. name .. " uuid " .. uuid)
|
||||
end
|
||||
|
||||
function plugin.playerLeave(name, uuid)
|
||||
print("PLUGIN player left: " .. name .. " uuid " .. uuid)
|
||||
end
|
||||
|
||||
function plugin.chatMessage(message, author, authorUuid)
|
||||
print("PLUGIN message from " .. author .. ": " .. message)
|
||||
end
|
||||
|
||||
return plugin
|
|
@ -0,0 +1,45 @@
|
|||
local plugin = {
|
||||
id = "mcchat",
|
||||
name = "MCChat",
|
||||
description = "Provides Minecraft-style chat. Messages sent by one client will be broadcasted to every client.",
|
||||
authors = { "trimill" },
|
||||
version = "0.1.0",
|
||||
}
|
||||
|
||||
local logger = nil
|
||||
|
||||
function plugin.init()
|
||||
logger = server.initLogger(plugin)
|
||||
logger.info("MCChat version " .. plugin.version)
|
||||
end
|
||||
|
||||
function plugin.playerJoin(name)
|
||||
logger.info(name .. " joined the game")
|
||||
server.broadcast({
|
||||
translate = "multiplayer.player.joined",
|
||||
with = { {text = name} },
|
||||
color = "yellow"
|
||||
})
|
||||
end
|
||||
|
||||
function plugin.playerLeave(name)
|
||||
logger.info(name .. " left the game")
|
||||
server.broadcast({
|
||||
translate = "multiplayer.player.left",
|
||||
with = { {text = name} },
|
||||
color = "yellow"
|
||||
})
|
||||
end
|
||||
|
||||
function plugin.chatMessage(message, author)
|
||||
logger.info("<" .. author .. "> " .. message)
|
||||
server.broadcast({
|
||||
translate = "chat.type.text",
|
||||
with = {
|
||||
{text = author},
|
||||
{text = message}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
return plugin
|
|
@ -0,0 +1,27 @@
|
|||
local plugin = {
|
||||
id = "testcmd",
|
||||
name = "TestCmd",
|
||||
description = "eufdahjklfhjakl",
|
||||
authors = { "trimill" },
|
||||
version = "0.1.0",
|
||||
}
|
||||
|
||||
local logger = nil
|
||||
|
||||
function plugin.init()
|
||||
logger = server.initLogger(plugin)
|
||||
end
|
||||
|
||||
function plugin.registerCommands(registry)
|
||||
registry.addCommand("test")
|
||||
end
|
||||
|
||||
function plugin.command(command, args, name, uuid)
|
||||
logger.info("player " .. name .. " ran /" .. command .. " " .. args)
|
||||
end
|
||||
|
||||
function plugin.playerJoin(name, uuid)
|
||||
logger.info("player joined: " .. name .. " with uuid " .. uuid)
|
||||
end
|
||||
|
||||
return plugin
|
|
@ -0,0 +1,27 @@
|
|||
use std::{net::IpAddr, fs::OpenOptions};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
pub enum LoginMode {
|
||||
Offline,
|
||||
Velocity,
|
||||
// TODO online, bungeecord
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub addr: IpAddr,
|
||||
pub port: u16,
|
||||
pub login: LoginMode,
|
||||
pub velocity_secret: Option<String>,
|
||||
}
|
||||
|
||||
pub fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
|
||||
let config: Config = serde_json::from_reader(OpenOptions::new().read(true).open("./config.json")?)?;
|
||||
if config.login == LoginMode::Velocity && config.velocity_secret.is_none() {
|
||||
Err("Velocity is enabled but no secret is configured")?
|
||||
}
|
||||
Ok(config)
|
||||
}
|
53
src/main.rs
53
src/main.rs
|
@ -1,30 +1,69 @@
|
|||
use std::borrow::Cow;
|
||||
use std::time::Duration;
|
||||
use std::io::Write;
|
||||
|
||||
use chrono::Utc;
|
||||
use env_logger::Env;
|
||||
use log::{info, warn};
|
||||
use mlua::Lua;
|
||||
use network::NetworkServer;
|
||||
use plugins::{Plugin, Plugins};
|
||||
use plugins::Plugins;
|
||||
|
||||
use crate::config::{load_config, LoginMode};
|
||||
|
||||
mod config;
|
||||
mod plugins;
|
||||
mod protocol;
|
||||
mod network;
|
||||
|
||||
pub const VERSION: &str = std::env!("CARGO_PKG_VERSION");
|
||||
|
||||
fn main() {
|
||||
let lua = Lua::new();
|
||||
let plugin = Plugin::load("plugins/example_plugin/main.lua".into(), &lua).unwrap();
|
||||
let mut plugins = Plugins::new(&lua).unwrap();
|
||||
plugins.add_plugin(plugin);
|
||||
env_logger::Builder::from_env(
|
||||
Env::default().default_filter_or("info")
|
||||
).format(|buf, record| {
|
||||
|
||||
let now = Utc::now().format("%Y-%m-%d %H:%M:%S");
|
||||
let mut target = Cow::Borrowed(record.target());
|
||||
if target.starts_with("quectocraft") {
|
||||
target = Cow::Owned(record.target().replacen("quectocraft", "qc", 1));
|
||||
}
|
||||
|
||||
let color = match record.level() {
|
||||
log::Level::Error => "\x1b[31m",
|
||||
log::Level::Warn => "\x1b[33m",
|
||||
log::Level::Info => "\x1b[32m",
|
||||
log::Level::Debug => "\x1b[37m",
|
||||
log::Level::Trace => "\x1b[37m",
|
||||
};
|
||||
|
||||
writeln!(buf, "\x1b[90m[\x1b[37m{} {color}{}\x1b[37m {}\x1b[90m]\x1b[0m {}", now, record.level(), target, record.args())
|
||||
}).init();
|
||||
|
||||
let mut server = NetworkServer::new("127.0.0.1:25565".to_owned(), plugins);
|
||||
info!("Starting Quectocraft version {}", VERSION);
|
||||
|
||||
let config = load_config().expect("Failed to load config");
|
||||
match config.login {
|
||||
LoginMode::Offline => warn!("Running in offline mode!"),
|
||||
LoginMode::Velocity => info!("Running in velocity mode"),
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
let mut plugins = Plugins::new(&lua).expect("Error initializing lua environment");
|
||||
std::fs::create_dir_all("plugins").expect("Couldn't create the plugins directory");
|
||||
plugins.load_plugins();
|
||||
|
||||
let mut server = NetworkServer::new(config, plugins);
|
||||
let sleep_dur = Duration::from_millis(5);
|
||||
let mut i = 0;
|
||||
loop {
|
||||
server.get_new_clients();
|
||||
server.handle_connections();
|
||||
std::thread::sleep(sleep_dur);
|
||||
if i % 1024 == 0 {
|
||||
server.send_keep_alive();
|
||||
i = 0;
|
||||
}
|
||||
i += 1;
|
||||
std::thread::sleep(sleep_dur);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
use std::{net::{TcpStream, Shutdown}, thread, io::Write, sync::mpsc::{Receiver, Sender, TryRecvError}, time::Duration};
|
||||
|
||||
use log::debug;
|
||||
|
||||
use crate::{protocol::{data::{PacketDecoder}, serverbound::*, clientbound::*, NetworkState}};
|
||||
|
||||
use super::Player;
|
||||
|
||||
pub struct NetworkClient {
|
||||
pub id: i32,
|
||||
pub verified: bool,
|
||||
pub closed: bool,
|
||||
pub stream: TcpStream,
|
||||
pub serverbound: Receiver<ServerBoundPacket>,
|
||||
pub player: Option<Player>,
|
||||
}
|
||||
|
||||
impl NetworkClient {
|
||||
pub fn listen(mut stream: TcpStream, send: Sender<ServerBoundPacket>) {
|
||||
let mut state = NetworkState::Handshake;
|
||||
let dur = Duration::from_millis(5);
|
||||
loop {
|
||||
match PacketDecoder::decode(&mut stream) {
|
||||
Ok(decoder) => {
|
||||
let packet = ServerBoundPacket::decode(&mut state, decoder);
|
||||
send.send(packet).unwrap();
|
||||
}
|
||||
Err(_) => break
|
||||
}
|
||||
thread::sleep(dur)
|
||||
}
|
||||
let _ = stream.shutdown(Shutdown::Both);
|
||||
}
|
||||
|
||||
pub fn recv_packet(&mut self, alive: &mut bool) -> Option<ServerBoundPacket> {
|
||||
match self.serverbound.try_recv() {
|
||||
Ok(packet) => Some(packet),
|
||||
Err(TryRecvError::Empty) => None,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
*alive = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_packet(&mut self, packet: impl ClientBoundPacket) -> std::io::Result<()> {
|
||||
self.stream.write_all(&encode_packet(packet))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
debug!("Closed connection id {}", self.id);
|
||||
let _ = self.stream.shutdown(Shutdown::Both);
|
||||
self.closed = true;
|
||||
}
|
||||
}
|
|
@ -1,243 +1,12 @@
|
|||
use std::{net::{TcpStream, TcpListener, Shutdown}, thread, io::Write, sync::mpsc::{Receiver, Sender, channel, TryRecvError}, time::Duration};
|
||||
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{protocol::{data::{PacketDecoder, PacketEncoder}, serverbound::*, clientbound::*, NetworkState}, plugins::Plugins};
|
||||
mod client;
|
||||
mod server;
|
||||
|
||||
pub struct NetworkServer<'lua> {
|
||||
plugins: Plugins<'lua>,
|
||||
new_clients: Receiver<NetworkClient>,
|
||||
clients: Vec<NetworkClient>,
|
||||
}
|
||||
|
||||
impl <'lua> NetworkServer<'lua> {
|
||||
pub fn new(addr: String, plugins: Plugins<'lua>) -> Self {
|
||||
let (send, recv) = channel();
|
||||
thread::spawn(move || Self::listen(&addr, send));
|
||||
plugins.init();
|
||||
Self {
|
||||
plugins,
|
||||
new_clients: recv,
|
||||
clients: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn listen(addr: &str, send_clients: Sender<NetworkClient>) {
|
||||
println!("listening for connections");
|
||||
let listener = TcpListener::bind(addr).unwrap();
|
||||
for (id, stream) in listener.incoming().enumerate() {
|
||||
let stream = stream.unwrap();
|
||||
println!("got connection from {} (id {})", stream.peer_addr().unwrap(), id);
|
||||
let stream_2 = stream.try_clone().unwrap();
|
||||
let (send, recv) = channel();
|
||||
thread::spawn(|| NetworkClient::listen(stream_2, send));
|
||||
let client = NetworkClient {
|
||||
id: id as i32,
|
||||
play: false,
|
||||
closed: false,
|
||||
stream,
|
||||
serverbound: recv,
|
||||
player: None,
|
||||
};
|
||||
send_clients.send(client).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_new_clients(&mut self) {
|
||||
while let Ok(client) = self.new_clients.try_recv() {
|
||||
self.clients.push(client);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_keep_alive(&mut self) {
|
||||
let mut closed = Vec::new();
|
||||
for client in self.clients.iter_mut() {
|
||||
if client.play {
|
||||
if let Err(_) = client.send_packet(ClientBoundPacket::KeepAlive(0)) {
|
||||
client.close();
|
||||
if let Some(pl) = &client.player {
|
||||
self.plugins.player_leave(pl);
|
||||
}
|
||||
closed.push(client.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.clients.retain(|x| !closed.contains(&x.id));
|
||||
}
|
||||
|
||||
pub fn handle_connections(&mut self) {
|
||||
let mut closed = Vec::new();
|
||||
for i in 0..self.clients.len() {
|
||||
let client: &mut NetworkClient = unsafe {
|
||||
&mut *(self.clients.get_unchecked_mut(i) as *mut _)
|
||||
};
|
||||
let mut alive = true;
|
||||
while let Some(packet) = client.recv_packet(&mut alive) {
|
||||
if let Err(_) = self.handle_packet(client, packet) {
|
||||
alive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !alive && !client.closed {
|
||||
closed.push(client.id);
|
||||
if let Some(pl) = &client.player {
|
||||
self.plugins.player_leave(pl);
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
self.clients.retain(|x| !closed.contains(&x.id));
|
||||
}
|
||||
|
||||
fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> std::io::Result<()> {
|
||||
match packet {
|
||||
ServerBoundPacket::Ignored(_) => (),
|
||||
ServerBoundPacket::Unknown(id) => println!("unknown: {}", id),
|
||||
ServerBoundPacket::Handshake(_) => (),
|
||||
ServerBoundPacket::StatusRequest()
|
||||
=> client.send_packet(ClientBoundPacket::StatusResponse(
|
||||
r#"{"version":{"name":"1.19.2","protocol":760}}"#.to_owned()
|
||||
))?,
|
||||
ServerBoundPacket::PingRequest(n) => {
|
||||
client.send_packet(ClientBoundPacket::PingResponse(n))?;
|
||||
client.close();
|
||||
}
|
||||
ServerBoundPacket::LoginStart(login_start) => {
|
||||
client.player = Some(Player {
|
||||
name: login_start.name.clone(),
|
||||
uuid: login_start.uuid.unwrap(),
|
||||
});
|
||||
client.play = true;
|
||||
client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess {
|
||||
name: login_start.name,
|
||||
uuid: login_start.uuid.unwrap(),
|
||||
}))?;
|
||||
self.plugins.player_join(client.player.as_ref().unwrap());
|
||||
self.post_login(client)?;
|
||||
}
|
||||
ServerBoundPacket::ChatMessage(msg) => {
|
||||
self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> {
|
||||
client.send_packet(ClientBoundPacket::LoginPlay(LoginPlay {
|
||||
eid: client.id,
|
||||
is_hardcore: false,
|
||||
gamemode: 1,
|
||||
prev_gamemode: 1,
|
||||
dimensions: vec![
|
||||
"minecraft:world".to_owned(),
|
||||
],
|
||||
registry_codec: include_bytes!("../resources/registry_codec.nbt").to_vec(),
|
||||
dimension_type: "minecraft:the_end".to_owned(),
|
||||
dimension_name: "minecraft:world".to_owned(),
|
||||
seed_hash: 0,
|
||||
max_players: 0,
|
||||
view_distance: 8,
|
||||
sim_distance: 8,
|
||||
reduced_debug_info: false,
|
||||
respawn_screen: false,
|
||||
is_debug: false,
|
||||
is_flat: false,
|
||||
death_location: None,
|
||||
}))?;
|
||||
client.send_packet(ClientBoundPacket::PluginMessage(PluginMessage {
|
||||
channel: "minecraft:brand".to_owned(),
|
||||
data: {
|
||||
let mut data = Vec::new();
|
||||
data.write_string(32767, "QuectoCraft");
|
||||
data
|
||||
}
|
||||
}))?;
|
||||
client.send_packet(ClientBoundPacket::PlayerAbilities(0x0d, 0.05, 0.1))?;
|
||||
let mut chunk_data: Vec<u8> = Vec::new();
|
||||
for _ in 0..(384 / 16) {
|
||||
// number of non-air blocks
|
||||
chunk_data.write_short(0);
|
||||
// block states
|
||||
chunk_data.write_ubyte(0);
|
||||
chunk_data.write_varint(0);
|
||||
chunk_data.write_varint(0);
|
||||
// biomes
|
||||
chunk_data.write_ubyte(0);
|
||||
chunk_data.write_varint(0);
|
||||
chunk_data.write_varint(0);
|
||||
}
|
||||
let hmdata = vec![0i64; 37];
|
||||
let mut heightmap = nbt::Blob::new();
|
||||
heightmap.insert("MOTION_BLOCKING", hmdata).unwrap();
|
||||
client.send_packet(ClientBoundPacket::ChunkData(ChunkData {
|
||||
x: 0,
|
||||
z: 0,
|
||||
heightmap,
|
||||
chunk_data,
|
||||
}))?;
|
||||
client.send_packet(ClientBoundPacket::SyncPlayerPosition(SyncPlayerPosition {
|
||||
x: 0.0,
|
||||
y: 64.0,
|
||||
z: 0.0,
|
||||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
flags: 0,
|
||||
teleport_id: 0,
|
||||
dismount: false
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
pub use server::NetworkServer;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Player {
|
||||
pub name: String,
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
struct NetworkClient {
|
||||
pub id: i32,
|
||||
pub play: bool,
|
||||
pub closed: bool,
|
||||
stream: TcpStream,
|
||||
serverbound: Receiver<ServerBoundPacket>,
|
||||
player: Option<Player>,
|
||||
}
|
||||
|
||||
impl NetworkClient {
|
||||
pub fn listen(mut stream: TcpStream, send: Sender<ServerBoundPacket>) {
|
||||
let mut state = NetworkState::Handshake;
|
||||
let dur = Duration::from_millis(5);
|
||||
loop {
|
||||
if let Some(decoder) = PacketDecoder::decode(&mut stream) {
|
||||
let packet = ServerBoundPacket::decode(&mut state, decoder);
|
||||
send.send(packet).unwrap();
|
||||
}
|
||||
thread::sleep(dur)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recv_packet(&mut self, alive: &mut bool) -> Option<ServerBoundPacket> {
|
||||
match self.serverbound.try_recv() {
|
||||
Ok(packet) => Some(packet),
|
||||
Err(TryRecvError::Empty) => None,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
*alive = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_packet(&mut self, packet: ClientBoundPacket) -> std::io::Result<()> {
|
||||
self.stream.write_all(&packet.encode())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
println!("closed connection with {}", self.id);
|
||||
let _ = self.stream.shutdown(Shutdown::Both);
|
||||
self.closed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,367 @@
|
|||
use std::{net::{TcpListener, SocketAddr}, thread, sync::mpsc::{Receiver, Sender, channel}, collections::HashSet};
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use log::{info, warn, debug, trace};
|
||||
use serde_json::json;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::{protocol::{data::PacketEncoder, serverbound::*, clientbound::*, command::Commands, Position}, config::{Config, LoginMode}};
|
||||
use crate::plugins::{Plugins, Response};
|
||||
use crate::VERSION;
|
||||
|
||||
use super::{client::NetworkClient, Player};
|
||||
|
||||
pub struct NetworkServer<'lua> {
|
||||
plugins: Plugins<'lua>,
|
||||
commands: Commands,
|
||||
new_clients: Receiver<NetworkClient>,
|
||||
clients: Vec<NetworkClient>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl <'lua> NetworkServer<'lua> {
|
||||
pub fn new(config: Config, mut plugins: Plugins<'lua>) -> Self {
|
||||
let (send, recv) = channel();
|
||||
info!("Initializing plugins");
|
||||
plugins.init();
|
||||
let mut commands = Commands::new();
|
||||
commands.create_simple_cmd("qc");
|
||||
let commands = plugins.register_commands(commands).unwrap();
|
||||
thread::spawn(move || Self::listen(&SocketAddr::new(config.addr, config.port), send));
|
||||
Self {
|
||||
config,
|
||||
plugins,
|
||||
commands,
|
||||
new_clients: recv,
|
||||
clients: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn listen(addr: &SocketAddr, send_clients: Sender<NetworkClient>) {
|
||||
info!("Listening on {}", addr);
|
||||
let listener = TcpListener::bind(addr).unwrap();
|
||||
for (id, stream) in listener.incoming().enumerate() {
|
||||
let stream = stream.unwrap();
|
||||
debug!("Connection from {} (id {})", stream.peer_addr().unwrap(), id);
|
||||
let stream_2 = stream.try_clone().unwrap();
|
||||
let (send, recv) = channel();
|
||||
thread::spawn(|| NetworkClient::listen(stream_2, send));
|
||||
let client = NetworkClient {
|
||||
id: id as i32,
|
||||
verified: false,
|
||||
closed: false,
|
||||
stream,
|
||||
serverbound: recv,
|
||||
player: None,
|
||||
};
|
||||
send_clients.send(client).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_new_clients(&mut self) {
|
||||
while let Ok(client) = self.new_clients.try_recv() {
|
||||
self.clients.push(client);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_keep_alive(&mut self) {
|
||||
let mut closed = Vec::new();
|
||||
for client in self.clients.iter_mut() {
|
||||
if client.player.is_some() {
|
||||
let result = client.send_packet(KeepAlive { data: 0 });
|
||||
if result.is_err() {
|
||||
client.close();
|
||||
self.plugins.player_leave(client.player.as_ref().unwrap());
|
||||
closed.push(client.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.clients.retain(|x| !closed.contains(&x.id));
|
||||
}
|
||||
|
||||
pub fn handle_connections(&mut self) {
|
||||
let mut closed = HashSet::new();
|
||||
for i in 0..self.clients.len() {
|
||||
let client: &mut NetworkClient = unsafe {
|
||||
&mut *(self.clients.get_unchecked_mut(i) as *mut _)
|
||||
};
|
||||
let mut alive = true;
|
||||
while let Some(packet) = client.recv_packet(&mut alive) {
|
||||
let result = self.handle_packet(client, packet);
|
||||
if result.is_err() {
|
||||
warn!("error: {}", result.unwrap_err());
|
||||
alive = false;
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alive && !client.closed {
|
||||
closed.insert(client.id);
|
||||
if let Some(pl) = &client.player {
|
||||
self.plugins.player_leave(pl);
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
for response in self.plugins.get_responses() {
|
||||
let _ = self.handle_plugin_response(response);
|
||||
}
|
||||
self.clients.retain(|x| !closed.contains(&x.id) && !x.closed);
|
||||
}
|
||||
|
||||
fn handle_plugin_response(&mut self, response: Response) -> std::io::Result<()> {
|
||||
match response {
|
||||
Response::Message { player, message } => {
|
||||
for client in self.clients.iter_mut() {
|
||||
if let Some(p) = &client.player {
|
||||
if p.name == player || p.uuid.to_string() == player {
|
||||
client.send_packet(SystemChatMessage { message, overlay: false })?;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Response::Broadcast { message } => {
|
||||
for client in self.clients.iter_mut() {
|
||||
if client.player.is_some() {
|
||||
client.send_packet(SystemChatMessage { message: message.clone(), overlay: false })?;
|
||||
}
|
||||
}
|
||||
},
|
||||
Response::Disconnect { player, reason } => {
|
||||
for client in self.clients.iter_mut() {
|
||||
if let Some(pl) = &client.player {
|
||||
if pl.name == player || pl.uuid.to_string() == player {
|
||||
client.send_packet(Disconnect { reason: reason.clone() })?;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Response::PluginMessage { player, channel, data } => {
|
||||
for client in self.clients.iter_mut() {
|
||||
if let Some(pl) = &client.player {
|
||||
if pl.name == player || pl.uuid.to_string() == player {
|
||||
client.send_packet(CPluginMessage { channel: channel.clone(), data: data.clone() })?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> Result<(), Box<dyn std::error::Error>> {
|
||||
trace!("Recieved packet from client {}:", client.id);
|
||||
match packet {
|
||||
ServerBoundPacket::Ignored(_) => (),
|
||||
ServerBoundPacket::Unknown(id) => warn!("Unknown packet: {}", id),
|
||||
ServerBoundPacket::Handshake(_) => (),
|
||||
ServerBoundPacket::StatusRequest()
|
||||
=> client.send_packet(StatusResponse {
|
||||
data: r#"{"version":{"name":"1.19.3","protocol":761}}"#.to_owned()
|
||||
})?,
|
||||
ServerBoundPacket::PingRequest(n) => {
|
||||
client.send_packet(PingResponse { data: n })?;
|
||||
client.close();
|
||||
}
|
||||
ServerBoundPacket::LoginStart(login_start)
|
||||
=> self.start_login(client, login_start)?,
|
||||
ServerBoundPacket::LoginPluginResponse(LoginPluginResponse { id: 10, data })
|
||||
=> self.velocity_login(client, data)?,
|
||||
ServerBoundPacket::LoginPluginResponse(LoginPluginResponse{ id: -1, .. })
|
||||
=> self.login(client)?,
|
||||
ServerBoundPacket::LoginPluginResponse { .. } => {
|
||||
client.send_packet(LoginDisconnect { reason: json!({"text": "bad plugin response"}) })?;
|
||||
client.close();
|
||||
}
|
||||
ServerBoundPacket::ChatMessage(msg) => {
|
||||
self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message);
|
||||
}
|
||||
ServerBoundPacket::ChatCommand(msg) => {
|
||||
let mut parts = msg.message.splitn(2, ' ');
|
||||
if let Some(cmd) = parts.next() {
|
||||
if cmd == "qc" {
|
||||
client.send_packet(SystemChatMessage { message: json!({
|
||||
"text": format!("QuectoCraft version {}", VERSION),
|
||||
"color": "green"
|
||||
}), overlay: false })?;
|
||||
} else {
|
||||
let args = parts.next().unwrap_or_default();
|
||||
self.plugins.command(client.player.as_ref().unwrap(), cmd, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerBoundPacket::PluginMessage(SPluginMessage { channel, data }) => {
|
||||
self.plugins.plugin_message(client.player.as_ref().unwrap(), &channel, &data);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_login(&mut self, client: &mut NetworkClient, login_start: LoginStart) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if self.clients.iter().filter_map(|x| x.player.as_ref()).any(|x| x.uuid == login_start.uuid) {
|
||||
client.send_packet(LoginDisconnect { reason: json!({
|
||||
"translate": "multiplayer.disconnect.duplicate_login"
|
||||
})})?;
|
||||
client.close();
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
client.player = Some(Player {
|
||||
name: login_start.name.clone(),
|
||||
uuid: login_start.uuid,
|
||||
});
|
||||
|
||||
match self.config.login {
|
||||
LoginMode::Offline => {
|
||||
client.verified = true;
|
||||
client.send_packet(LoginPluginRequest{
|
||||
id: -1,
|
||||
channel: "qc:init".to_owned(),
|
||||
data: Vec::new()
|
||||
})?;
|
||||
},
|
||||
LoginMode::Velocity => {
|
||||
client.send_packet(LoginPluginRequest{
|
||||
id: 10,
|
||||
channel: "velocity:player_info".to_owned(),
|
||||
data: vec![1],
|
||||
})?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn velocity_login(&mut self, client: &mut NetworkClient, data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let Some(data) = data else {
|
||||
client.send_packet(LoginDisconnect { reason: json!({
|
||||
"text": "This server can only be connected to via a Velocity proxy",
|
||||
"color": "red"
|
||||
})})?;
|
||||
client.close();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let (sig, data) = data.split_at(32);
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(self.config.velocity_secret.clone().unwrap().as_bytes())?;
|
||||
mac.update(data);
|
||||
|
||||
if mac.verify_slice(sig).is_err() {
|
||||
client.send_packet(Disconnect { reason: json!({
|
||||
"text": "Could not verify secret. Ensure that the secrets configured for Velocity and Quectocraft match."
|
||||
})})?;
|
||||
client.close();
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
client.verified = true;
|
||||
|
||||
client.send_packet(LoginPluginRequest{
|
||||
id: -1,
|
||||
channel: "qc:init".to_owned(),
|
||||
data: Vec::new()
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//
|
||||
// Handle the end of "login" and beginning of "play"
|
||||
//
|
||||
fn login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> {
|
||||
if !client.verified {
|
||||
client.send_packet(Disconnect { reason: json!({
|
||||
"text": "Failed to verify your connection",
|
||||
"color": "red",
|
||||
})})?;
|
||||
client.close();
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
client.send_packet(LoginSuccess {
|
||||
name: client.player.as_ref().unwrap().name.to_owned(),
|
||||
uuid: client.player.as_ref().unwrap().uuid,
|
||||
})?;
|
||||
|
||||
self.plugins.player_join(client.player.as_ref().unwrap());
|
||||
|
||||
client.send_packet(LoginPlay {
|
||||
eid: client.id,
|
||||
is_hardcore: false,
|
||||
gamemode: 3,
|
||||
prev_gamemode: 3,
|
||||
dimensions: vec![
|
||||
"qc:world".to_owned(),
|
||||
],
|
||||
registry_codec: include_bytes!("../resources/registry_codec.nbt").to_vec(),
|
||||
dimension_type: "minecraft:the_end".to_owned(),
|
||||
dimension_name: "qc:world".to_owned(),
|
||||
seed_hash: 0,
|
||||
max_players: 0,
|
||||
view_distance: 8,
|
||||
sim_distance: 8,
|
||||
reduced_debug_info: false,
|
||||
respawn_screen: false,
|
||||
is_debug: false,
|
||||
is_flat: false,
|
||||
death_location: None,
|
||||
})?;
|
||||
|
||||
client.send_packet(CPluginMessage {
|
||||
channel: "minecraft:brand".to_owned(),
|
||||
data: {
|
||||
let mut data = Vec::new();
|
||||
data.write_string(32767, &format!("Quectocraft {}", VERSION));
|
||||
data
|
||||
}
|
||||
})?;
|
||||
|
||||
client.send_packet(self.commands.clone())?;
|
||||
|
||||
// Send 3x3 square of empty chunks.
|
||||
let mut chunk_data: Vec<u8> = Vec::new();
|
||||
for _ in 0..(384 / 16) {
|
||||
// number of non-air blocks
|
||||
chunk_data.write_short(0);
|
||||
// block states
|
||||
chunk_data.write_ubyte(0);
|
||||
chunk_data.write_varint(0);
|
||||
chunk_data.write_varint(0);
|
||||
// biomes
|
||||
chunk_data.write_ubyte(0);
|
||||
chunk_data.write_varint(0);
|
||||
chunk_data.write_varint(0);
|
||||
}
|
||||
let hmdata = vec![0i64; 37];
|
||||
let mut heightmap = nbt::Blob::new();
|
||||
heightmap.insert("MOTION_BLOCKING", hmdata).unwrap();
|
||||
for x in -1..=1 {
|
||||
for z in -1..=1 {
|
||||
client.send_packet(ChunkData {
|
||||
x,
|
||||
z,
|
||||
heightmap: heightmap.clone(),
|
||||
chunk_data: chunk_data.clone(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
client.send_packet(SetDefaultSpawnPosition {
|
||||
pos: Position { x: 0, y: 0, z: 0 }, angle: 0.0
|
||||
})?;
|
||||
|
||||
client.send_packet(SyncPlayerPosition {
|
||||
x: 0.0,
|
||||
y: 64.0,
|
||||
z: 0.0,
|
||||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
flags: 0,
|
||||
teleport_id: 0,
|
||||
dismount: false
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
128
src/plugins.rs
128
src/plugins.rs
|
@ -1,128 +0,0 @@
|
|||
use std::{path::{Path, PathBuf}, str::FromStr};
|
||||
|
||||
use mlua::{Function, Lua, Error, Table};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::network::Player;
|
||||
|
||||
pub struct EventHandlers<'lua> {
|
||||
init: Option<Function<'lua>>,
|
||||
player_join: Option<Function<'lua>>,
|
||||
player_leave: Option<Function<'lua>>,
|
||||
chat_message: Option<Function<'lua>>,
|
||||
}
|
||||
|
||||
pub struct Plugin<'lua> {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub event_handlers: EventHandlers<'lua>,
|
||||
}
|
||||
|
||||
impl <'lua> Plugin<'lua> {
|
||||
pub fn load(path: &str, lua: &'lua Lua) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let path = PathBuf::from_str(path).unwrap();
|
||||
let chunk = lua.load(&path);
|
||||
let module: Table = chunk.eval()?;
|
||||
|
||||
let id: String = module.get("id")?;
|
||||
let name: String = module.get("name").unwrap_or_else(|_| id.clone());
|
||||
let version: String = module.get("version").unwrap_or_else(|_| "?".to_owned());
|
||||
|
||||
let init: Option<Function<'lua>> = module.get("init").ok();
|
||||
let player_join: Option<Function<'lua>> = module.get("playerJoin").ok();
|
||||
let player_leave: Option<Function<'lua>> = module.get("playerLeave").ok();
|
||||
let chat_message: Option<Function<'lua>> = module.get("chatMessage").ok();
|
||||
|
||||
let event_handlers = EventHandlers { init, player_join, player_leave, chat_message };
|
||||
Ok(Plugin { id, name, version, event_handlers })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Plugins<'lua> {
|
||||
lua: &'lua Lua,
|
||||
plugins: Vec<Plugin<'lua>>
|
||||
}
|
||||
|
||||
impl <'lua> Plugins<'lua> {
|
||||
pub fn new(lua: &'lua Lua) -> Result<Self, mlua::Error> {
|
||||
let server = lua.create_table()?;
|
||||
let players = lua.create_table()?;
|
||||
server.set("players", players)?;
|
||||
let fn_send = lua.create_function(|_, (uuid, message): (String, String)| {
|
||||
Ok(())
|
||||
})?;
|
||||
server.set("send", fn_send)?;
|
||||
lua.globals().set("server", server)?;
|
||||
Ok(Self {
|
||||
lua,
|
||||
plugins: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_plugin(&mut self, pl: Plugin<'lua>) {
|
||||
self.plugins.push(pl);
|
||||
}
|
||||
|
||||
pub fn init(&self) {
|
||||
for pl in &self.plugins {
|
||||
if let Some(init) = &pl.event_handlers.init {
|
||||
if let Err(e) = init.call::<_, ()>(()) {
|
||||
println!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn player_join(&self, player: &Player) {
|
||||
if let Err(e) = self.add_player(player) {
|
||||
println!("Error adding player: {}", e);
|
||||
return
|
||||
}
|
||||
for pl in &self.plugins {
|
||||
if let Some(init) = &pl.event_handlers.player_join {
|
||||
if let Err(e) = init.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
|
||||
println!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_player(&self, player: &Player) -> Result<(), mlua::Error> {
|
||||
let server: Table = self.lua.globals().get("server")?;
|
||||
let players: Table = server.get("players")?;
|
||||
players.set(player.uuid.to_string(), player.name.as_str())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn player_leave(&self, player: &Player) {
|
||||
if let Err(e) = self.remove_player(player.uuid) {
|
||||
println!("Error removing player: {}", e);
|
||||
return
|
||||
}
|
||||
for pl in &self.plugins {
|
||||
if let Some(func) = &pl.event_handlers.player_leave {
|
||||
if let Err(e) = func.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
|
||||
println!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_player(&self, uuid: Uuid) -> Result<(), mlua::Error> {
|
||||
let server: Table = self.lua.globals().get("server")?;
|
||||
let players: Table = server.get("players")?;
|
||||
players.set(uuid.to_string(), mlua::Nil)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn chat_message(&self, player: &Player, message: &str) {
|
||||
for pl in &self.plugins {
|
||||
if let Some(func) = &pl.event_handlers.chat_message {
|
||||
if let Err(e) = func.call::<_, ()>((message, player.name.as_str(), player.uuid.to_string())) {
|
||||
println!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
server = { players = {} }
|
||||
_qc = { responses = {} }
|
||||
|
||||
local function to_chat(message, default)
|
||||
if message == nil then
|
||||
if default ~= nil then
|
||||
return default
|
||||
else
|
||||
error("message must be a string or table")
|
||||
end
|
||||
elseif type(message) == "table" then
|
||||
return message
|
||||
elseif type(message) == "string" then
|
||||
return { text = message }
|
||||
elseif default == nil then
|
||||
error("message must be a string or table")
|
||||
else
|
||||
error("message must be a string, table, or nil for the default message")
|
||||
end
|
||||
end
|
||||
|
||||
function server.sendMessage(player, message)
|
||||
if type(player) ~= "string" then
|
||||
error("player must be a string")
|
||||
end
|
||||
local message = assert(to_chat(message))
|
||||
table.insert(_qc.responses, {type = "message", player = player, message = message})
|
||||
end
|
||||
|
||||
function server.broadcast(message)
|
||||
local message = assert(to_chat(message))
|
||||
table.insert(_qc.responses, { type = "broadcast", message = message })
|
||||
end
|
||||
|
||||
function server.sendPluginMessage(player, channel, data)
|
||||
if type(player) ~= "string" then
|
||||
error("player must be a string")
|
||||
end
|
||||
if type(channel) ~= "string" then
|
||||
error("channel must be a string")
|
||||
end
|
||||
table.insert(_qc.responses, {type = "plugin_message", player = player, channel = channel, data = data})
|
||||
end
|
||||
|
||||
function server.disconnect(player, reason)
|
||||
local reason = assert(to_chat(reason, { translate = "multiplayer.disconnect.generic" }))
|
||||
table.insert(_qc.responses, { type = "disconnect", player = player, reason = reason })
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
use log::{info, warn, trace, error, debug};
|
||||
use mlua::{Lua, chunk};
|
||||
use crate::VERSION;
|
||||
|
||||
|
||||
pub fn init(lua: &Lua) -> Result<(), mlua::Error> {
|
||||
macro_rules! log_any {
|
||||
($level:tt) => {
|
||||
lua.create_function(|_, args: (String, String)| {
|
||||
$level!(target: &args.0, "{}", args.1);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
let log_trace = log_any!(trace)?;
|
||||
let log_debug = log_any!(debug)?;
|
||||
let log_info = log_any!(info)?;
|
||||
let log_warn = log_any!(warn)?;
|
||||
let log_error = log_any!(error)?;
|
||||
lua.load(include_str!("init.lua")).exec()?;
|
||||
lua.load(chunk!{
|
||||
function server.initLogger(plugin)
|
||||
local id = "pl::" .. assert(plugin["id"])
|
||||
return {
|
||||
trace = function(msg) $log_trace(id, msg) end,
|
||||
debug = function(msg) $log_debug(id, msg) end,
|
||||
info = function(msg) $log_info(id, msg) end,
|
||||
warn = function(msg) $log_warn(id, msg) end,
|
||||
error = function(msg) $log_error(id, msg) end,
|
||||
}
|
||||
end
|
||||
|
||||
server.version = $VERSION
|
||||
}).exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
use std::{fs::read_dir, rc::Rc, cell::RefCell, collections::HashMap};
|
||||
|
||||
use log::{warn, info};
|
||||
use mlua::{Lua, Table, LuaSerdeExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{network::Player, protocol::command::Commands};
|
||||
|
||||
use self::plugin::Plugin;
|
||||
|
||||
mod init_lua;
|
||||
mod plugin;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag="type")]
|
||||
pub enum Response {
|
||||
#[serde(rename = "message")]
|
||||
Message { player: String, message: serde_json::Value },
|
||||
#[serde(rename = "plugin_message")]
|
||||
PluginMessage { player: String, channel: String, data: Vec<u8> },
|
||||
#[serde(rename = "broadcast")]
|
||||
Broadcast { message: serde_json::Value },
|
||||
#[serde(rename = "disconnect")]
|
||||
Disconnect { player: String, reason: serde_json::Value },
|
||||
}
|
||||
|
||||
|
||||
pub struct Plugins<'lua> {
|
||||
lua: &'lua Lua,
|
||||
plugins: Vec<Plugin<'lua>>,
|
||||
cmd_owners: HashMap<String, usize>,
|
||||
}
|
||||
|
||||
impl <'lua> Plugins<'lua> {
|
||||
pub fn new(lua: &'lua Lua) -> Result<Self, mlua::Error> {
|
||||
init_lua::init(lua)?;
|
||||
Ok(Self {
|
||||
lua,
|
||||
plugins: Vec::new(),
|
||||
cmd_owners: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_plugins(&mut self) {
|
||||
let files = read_dir("plugins").expect("couldn't read plugins directory");
|
||||
for file in files {
|
||||
let file = file.expect("couldn't read contents of plugins directory");
|
||||
let path = if file.file_type().expect("couldn't get type of plugin file").is_dir() {
|
||||
let mut main = file.path();
|
||||
main.push("main.lua");
|
||||
main
|
||||
} else {
|
||||
file.path()
|
||||
};
|
||||
let pl = Plugin::load(&path, self.lua).expect("error loading plugin");
|
||||
self.plugins.push(pl);
|
||||
info!("Loaded plugin '{}'", file.file_name().to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_responses(&self) -> Vec<Response> {
|
||||
match self.get_responses_inner() {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
warn!("Error getting responses: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_responses_inner(&self) -> Result<Vec<Response>, Box<dyn std::error::Error>> {
|
||||
let qc: Table = self.lua.globals().get("_qc")?;
|
||||
let responses: Vec<Response> = self.lua.from_value(qc.get("responses")?)?;
|
||||
qc.set("responses", self.lua.create_table()?)?;
|
||||
Ok(responses)
|
||||
}
|
||||
|
||||
pub fn init(&self) {
|
||||
for pl in &self.plugins {
|
||||
if let Some(init) = &pl.event_handlers.init {
|
||||
if let Err(e) = init.call::<_, ()>(()) {
|
||||
warn!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_commands(&mut self, commands: Commands) -> Result<Commands, mlua::Error> {
|
||||
let commands = Rc::new(RefCell::new(commands));
|
||||
let cmd_owners = Rc::new(RefCell::new(HashMap::new()));
|
||||
for (i, pl) in self.plugins.iter().enumerate() {
|
||||
let commands_2 = commands.clone();
|
||||
let cmd_owners_2 = cmd_owners.clone();
|
||||
let pl_id = pl.id.clone();
|
||||
let add_command = self.lua.create_function(move |_, name: String| {
|
||||
let scoped_name = format!("{}:{}", pl_id, name);
|
||||
let mut cmds = commands_2.borrow_mut();
|
||||
let id1 = cmds.create_simple_cmd(&name);
|
||||
let id2 = cmds.create_simple_cmd(&scoped_name);
|
||||
if id1.is_none() || id2.is_none() {
|
||||
return Ok(mlua::Nil)
|
||||
}
|
||||
cmd_owners_2.borrow_mut().insert(name, i);
|
||||
cmd_owners_2.borrow_mut().insert(scoped_name, i);
|
||||
Ok(mlua::Nil)
|
||||
})?;
|
||||
let registry = self.lua.create_table()?;
|
||||
registry.set("addCommand", add_command)?;
|
||||
if let Some(init) = &pl.event_handlers.register_commands {
|
||||
if let Err(e) = init.call::<_, ()>((registry.clone(),)) {
|
||||
warn!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
let cb = commands.borrow();
|
||||
self.cmd_owners = (*cmd_owners.borrow()).clone();
|
||||
Ok((*cb).clone())
|
||||
}
|
||||
|
||||
pub fn player_join(&self, player: &Player) {
|
||||
if let Err(e) = self.add_player(player) {
|
||||
warn!("Error adding player: {}", e);
|
||||
return
|
||||
}
|
||||
for pl in &self.plugins {
|
||||
if let Some(init) = &pl.event_handlers.player_join {
|
||||
if let Err(e) = init.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
|
||||
warn!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_player(&self, player: &Player) -> Result<(), mlua::Error> {
|
||||
let server: Table = self.lua.globals().get("server")?;
|
||||
let players: Table = server.get("players")?;
|
||||
players.set(player.uuid.to_string(), player.name.as_str())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn player_leave(&self, player: &Player) {
|
||||
if let Err(e) = self.remove_player(player.uuid) {
|
||||
warn!("Error removing player: {}", e);
|
||||
return
|
||||
}
|
||||
for pl in &self.plugins {
|
||||
if let Some(func) = &pl.event_handlers.player_leave {
|
||||
if let Err(e) = func.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
|
||||
warn!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_player(&self, uuid: Uuid) -> Result<(), mlua::Error> {
|
||||
let server: Table = self.lua.globals().get("server")?;
|
||||
let players: Table = server.get("players")?;
|
||||
players.set(uuid.to_string(), mlua::Nil)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn chat_message(&self, player: &Player, message: &str) {
|
||||
for pl in &self.plugins {
|
||||
if let Some(func) = &pl.event_handlers.chat_message {
|
||||
if let Err(e) = func.call::<_, ()>((message, player.name.as_str(), player.uuid.to_string())) {
|
||||
warn!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(&self, player: &Player, command: &str, args: &str) {
|
||||
if let Some(owner) = self.cmd_owners.get(command) {
|
||||
let pl = &self.plugins[*owner];
|
||||
if let Some(func) = &pl.event_handlers.command {
|
||||
if let Err(e) = func.call::<_, ()>((command, args, player.name.as_str(), player.uuid.to_string())) {
|
||||
warn!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
} else {
|
||||
warn!("Plugin {} registered a command but no command handler was found", pl.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_message(&self, player: &Player, channel: &str, data: &[u8]) {
|
||||
for pl in &self.plugins {
|
||||
if let Some(func) = &pl.event_handlers.plugin_message {
|
||||
if let Err(e) = func.call::<_, ()>((channel, data, player.name.as_str(), player.uuid.to_string())) {
|
||||
warn!("Error in plugin {}: {}", pl.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
use std::path::Path;
|
||||
|
||||
use mlua::{Function, Table, Lua};
|
||||
|
||||
pub struct EventHandlers<'lua> {
|
||||
pub init: Option<Function<'lua>>,
|
||||
pub register_commands: Option<Function<'lua>>,
|
||||
pub player_join: Option<Function<'lua>>,
|
||||
pub player_leave: Option<Function<'lua>>,
|
||||
pub chat_message: Option<Function<'lua>>,
|
||||
pub command: Option<Function<'lua>>,
|
||||
pub plugin_message: Option<Function<'lua>>,
|
||||
}
|
||||
|
||||
pub struct Plugin<'lua> {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub event_handlers: EventHandlers<'lua>,
|
||||
}
|
||||
|
||||
impl <'lua> Plugin<'lua> {
|
||||
pub fn load(path: &Path, lua: &'lua Lua) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let chunk = lua.load(path);
|
||||
let module: Table = chunk.eval()?;
|
||||
|
||||
let id: String = module.get("id")?;
|
||||
let name: String = module.get("name").unwrap_or_else(|_| id.clone());
|
||||
let version: String = module.get("version").unwrap_or_else(|_| "?".to_owned());
|
||||
|
||||
let init: Option<Function<'lua>> = module.get("init").ok();
|
||||
let register_commands: Option<Function<'lua>> = module.get("registerCommands").ok();
|
||||
let player_join: Option<Function<'lua>> = module.get("playerJoin").ok();
|
||||
let player_leave: Option<Function<'lua>> = module.get("playerLeave").ok();
|
||||
let chat_message: Option<Function<'lua>> = module.get("chatMessage").ok();
|
||||
let command: Option<Function<'lua>> = module.get("command").ok();
|
||||
let plugin_message: Option<Function<'lua>> = module.get("pluginMessage").ok();
|
||||
|
||||
let event_handlers = EventHandlers {
|
||||
init,
|
||||
register_commands,
|
||||
player_join,
|
||||
player_leave,
|
||||
chat_message,
|
||||
command,
|
||||
plugin_message,
|
||||
};
|
||||
Ok(Plugin { id, name, version, event_handlers })
|
||||
}
|
||||
}
|
|
@ -2,20 +2,102 @@ use uuid::Uuid;
|
|||
|
||||
use super::{data::{PacketEncoder, finalize_packet}, Position};
|
||||
|
||||
pub trait ClientBoundPacket: std::fmt::Debug {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder);
|
||||
fn packet_id(&self) -> i32;
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// //
|
||||
// Status //
|
||||
// //
|
||||
//////////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PingResponse {
|
||||
pub data: i64
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for PingResponse {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_long(self.data)
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x01 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StatusResponse {
|
||||
pub data: String
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for StatusResponse {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_string(32767, &self.data)
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x00 }
|
||||
}
|
||||
|
||||
|
||||
/////////////////
|
||||
// //
|
||||
// Login //
|
||||
// //
|
||||
/////////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoginSuccess {
|
||||
pub uuid: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl LoginSuccess {
|
||||
pub fn encode(self, encoder: &mut impl PacketEncoder) {
|
||||
impl ClientBoundPacket for LoginSuccess {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_uuid(self.uuid);
|
||||
encoder.write_string(16, &self.name);
|
||||
encoder.write_varint(0);
|
||||
}
|
||||
fn packet_id(&self) -> i32 { 0x02 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoginPluginRequest {
|
||||
pub id: i32,
|
||||
pub channel: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for LoginPluginRequest {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_varint(self.id);
|
||||
encoder.write_string(32767, &self.channel);
|
||||
encoder.write_bytes(&self.data);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x04 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoginDisconnect {
|
||||
pub reason: serde_json::Value
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for LoginDisconnect {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_string(262144, &self.reason.to_string())
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x00 }
|
||||
}
|
||||
|
||||
|
||||
////////////////
|
||||
// //
|
||||
// Play //
|
||||
// //
|
||||
////////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoginPlay {
|
||||
pub eid: i32,
|
||||
|
@ -37,15 +119,15 @@ pub struct LoginPlay {
|
|||
pub death_location: Option<(String, Position)>
|
||||
}
|
||||
|
||||
impl LoginPlay {
|
||||
pub fn encode(self, encoder: &mut impl PacketEncoder) {
|
||||
impl ClientBoundPacket for LoginPlay {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_int(self.eid);
|
||||
encoder.write_bool(self.is_hardcore);
|
||||
encoder.write_ubyte(self.gamemode);
|
||||
encoder.write_ubyte(self.prev_gamemode);
|
||||
encoder.write_varint(self.dimensions.len() as i32);
|
||||
for dim in self.dimensions {
|
||||
encoder.write_string(32767, &dim);
|
||||
for dim in &self.dimensions {
|
||||
encoder.write_string(32767, dim);
|
||||
}
|
||||
encoder.write_bytes(&self.registry_codec);
|
||||
encoder.write_string(32767, &self.dimension_type);
|
||||
|
@ -59,24 +141,28 @@ impl LoginPlay {
|
|||
encoder.write_bool(self.is_debug);
|
||||
encoder.write_bool(self.is_flat);
|
||||
encoder.write_bool(self.death_location.is_some());
|
||||
if let Some(dl) = self.death_location {
|
||||
if let Some(dl) = &self.death_location {
|
||||
encoder.write_string(32767, &dl.0);
|
||||
encoder.write_position(dl.1);
|
||||
}
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x24 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PluginMessage {
|
||||
pub struct CPluginMessage {
|
||||
pub channel: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PluginMessage {
|
||||
pub fn encode(self, encoder: &mut impl PacketEncoder) {
|
||||
impl ClientBoundPacket for CPluginMessage {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_string(32767, &self.channel);
|
||||
encoder.write_bytes(&self.data);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x15 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -91,8 +177,8 @@ pub struct SyncPlayerPosition {
|
|||
pub dismount: bool,
|
||||
}
|
||||
|
||||
impl SyncPlayerPosition {
|
||||
pub fn encode(self, encoder: &mut impl PacketEncoder) {
|
||||
impl ClientBoundPacket for SyncPlayerPosition {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_double(self.x);
|
||||
encoder.write_double(self.y);
|
||||
encoder.write_double(self.z);
|
||||
|
@ -102,6 +188,8 @@ impl SyncPlayerPosition {
|
|||
encoder.write_varint(self.teleport_id);
|
||||
encoder.write_bool(self.dismount);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x38 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -112,8 +200,8 @@ pub struct ChunkData {
|
|||
pub chunk_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ChunkData {
|
||||
pub fn encode(self, encoder: &mut impl PacketEncoder) {
|
||||
impl ClientBoundPacket for ChunkData {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_int(self.x);
|
||||
encoder.write_int(self.z);
|
||||
self.heightmap.to_writer(encoder).unwrap();
|
||||
|
@ -133,72 +221,86 @@ impl ChunkData {
|
|||
// block light array
|
||||
encoder.write_varint(0);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x20 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientBoundPacket {
|
||||
// status
|
||||
StatusResponse(String),
|
||||
PingResponse(i64),
|
||||
// login
|
||||
LoginSuccess(LoginSuccess),
|
||||
// play
|
||||
LoginPlay(LoginPlay),
|
||||
PluginMessage(PluginMessage),
|
||||
SyncPlayerPosition(SyncPlayerPosition),
|
||||
ChunkData(ChunkData),
|
||||
KeepAlive(i64),
|
||||
PlayerAbilities(i8, f32, f32),
|
||||
SystemChatMessage(serde_json::Value, bool),
|
||||
pub struct KeepAlive {
|
||||
pub data: i64
|
||||
}
|
||||
|
||||
impl ClientBoundPacket {
|
||||
pub fn encode(self) -> Vec<u8> {
|
||||
let mut packet = Vec::new();
|
||||
match self {
|
||||
Self::StatusResponse(status) => {
|
||||
packet.write_string(32767, &status);
|
||||
finalize_packet(packet, 0)
|
||||
},
|
||||
Self::PingResponse(n) => {
|
||||
packet.write_long(n);
|
||||
finalize_packet(packet, 1)
|
||||
},
|
||||
Self::LoginSuccess(login_success) => {
|
||||
login_success.encode(&mut packet);
|
||||
finalize_packet(packet, 2)
|
||||
}
|
||||
Self::PluginMessage(plugin_message) => {
|
||||
plugin_message.encode(&mut packet);
|
||||
finalize_packet(packet, 22)
|
||||
}
|
||||
Self::SyncPlayerPosition(sync_player_position) => {
|
||||
sync_player_position.encode(&mut packet);
|
||||
finalize_packet(packet, 57)
|
||||
}
|
||||
Self::LoginPlay(login_play) => {
|
||||
login_play.encode(&mut packet);
|
||||
finalize_packet(packet, 37)
|
||||
}
|
||||
Self::ChunkData(chunk_data) => {
|
||||
chunk_data.encode(&mut packet);
|
||||
finalize_packet(packet, 33)
|
||||
}
|
||||
Self::KeepAlive(n) => {
|
||||
packet.write_long(n);
|
||||
finalize_packet(packet, 32)
|
||||
}
|
||||
Self::PlayerAbilities(flags, speed, view) => {
|
||||
packet.write_byte(flags);
|
||||
packet.write_float(speed);
|
||||
packet.write_float(view);
|
||||
finalize_packet(packet, 49)
|
||||
}
|
||||
Self::SystemChatMessage(msg, overlay) => {
|
||||
packet.write_string(262144, &msg.to_string());
|
||||
packet.write_bool(overlay);
|
||||
finalize_packet(packet, 98)
|
||||
}
|
||||
}
|
||||
impl ClientBoundPacket for KeepAlive {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_long(self.data)
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x1f }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlayerAbilities {
|
||||
pub flags: i8,
|
||||
pub fly_speed: f32,
|
||||
pub fov_modifier: f32,
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for PlayerAbilities {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_byte(self.flags);
|
||||
encoder.write_float(self.fly_speed);
|
||||
encoder.write_float(self.fov_modifier);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x30 }
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Disconnect {
|
||||
pub reason: serde_json::Value
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for Disconnect {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_string(262144, &self.reason.to_string())
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x17 }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SetDefaultSpawnPosition {
|
||||
pub pos: Position,
|
||||
pub angle: f32
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for SetDefaultSpawnPosition {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_position(self.pos);
|
||||
encoder.write_float(self.angle);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x4c }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SystemChatMessage {
|
||||
pub message: serde_json::Value,
|
||||
pub overlay: bool
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for SystemChatMessage {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_string(262144, &self.message.to_string());
|
||||
encoder.write_bool(self.overlay);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x60 }
|
||||
}
|
||||
|
||||
pub fn encode_packet(packet: impl ClientBoundPacket) -> Vec<u8> {
|
||||
let mut buffer = Vec::new();
|
||||
packet.encode(&mut buffer);
|
||||
finalize_packet(buffer, packet.packet_id())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
use super::{data::PacketEncoder, clientbound::ClientBoundPacket};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Commands {
|
||||
nodes: Vec<CommandNode>,
|
||||
}
|
||||
|
||||
impl ClientBoundPacket for Commands {
|
||||
fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
encoder.write_varint(self.nodes.len() as i32);
|
||||
for node in &self.nodes {
|
||||
node.encode(encoder);
|
||||
}
|
||||
// root node
|
||||
encoder.write_varint(0);
|
||||
}
|
||||
|
||||
fn packet_id(&self) -> i32 { 0x0e }
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
pub fn new() -> Self {
|
||||
let root = CommandNode {
|
||||
executable: false,
|
||||
redirect: None,
|
||||
children: Vec::new(),
|
||||
suggestion: None,
|
||||
type_data: CommandNodeType::Root
|
||||
};
|
||||
let simple_cmd_arg = CommandNode {
|
||||
executable: true,
|
||||
redirect: None,
|
||||
children: Vec::new(),
|
||||
suggestion: None,
|
||||
type_data: CommandNodeType::Argument { name: "[args]".to_owned(), parser: Parser::String { kind: StringKind::Greedy } }
|
||||
};
|
||||
Self {
|
||||
nodes: vec![root, simple_cmd_arg],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_node(
|
||||
&mut self,
|
||||
parent: i32,
|
||||
type_data: CommandNodeType,
|
||||
executable: bool,
|
||||
redirect: Option<i32>,
|
||||
suggestion: Option<String>
|
||||
) -> Option<i32> {
|
||||
if parent < 0 || parent >= self.nodes.len() as i32 {
|
||||
return None
|
||||
}
|
||||
if let Some(redirect) = redirect {
|
||||
if redirect < 0 || redirect >= self.nodes.len() as i32 {
|
||||
return None
|
||||
}
|
||||
}
|
||||
if let CommandNodeType::Root = type_data {
|
||||
return None
|
||||
}
|
||||
let id = self.nodes.len() as i32;
|
||||
self.nodes.push(CommandNode {
|
||||
executable,
|
||||
redirect,
|
||||
children: Vec::new(),
|
||||
suggestion,
|
||||
type_data,
|
||||
});
|
||||
self.nodes[parent as usize].children.push(id);
|
||||
Some(id)
|
||||
}
|
||||
|
||||
pub fn create_simple_cmd(&mut self, name: &str) -> Option<i32> {
|
||||
let id = self.create_node(0, CommandNodeType::Literal { name: name.to_owned() }, true, None, None)?;
|
||||
self.add_child(id, 1);
|
||||
Some(id)
|
||||
}
|
||||
|
||||
pub fn add_child(&mut self, node: i32, child: i32) {
|
||||
self.nodes[node as usize].children.push(child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommandNode {
|
||||
executable: bool,
|
||||
redirect: Option<i32>,
|
||||
children: Vec<i32>,
|
||||
suggestion: Option<String>,
|
||||
type_data: CommandNodeType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CommandNodeType {
|
||||
Root,
|
||||
Literal { name: String },
|
||||
Argument { name: String, parser: Parser }
|
||||
}
|
||||
|
||||
impl CommandNode {
|
||||
pub fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
let mut flags = match self.type_data {
|
||||
CommandNodeType::Root => 0,
|
||||
CommandNodeType::Literal{..} => 1,
|
||||
CommandNodeType::Argument{..} => 2,
|
||||
};
|
||||
if self.executable { flags |= 4; }
|
||||
if self.redirect.is_some() { flags |= 8; }
|
||||
if self.suggestion.is_some() { flags |= 16; }
|
||||
encoder.write_byte(flags);
|
||||
encoder.write_varint(self.children.len() as i32);
|
||||
for child in &self.children {
|
||||
encoder.write_varint(*child);
|
||||
}
|
||||
if let Some(redirect) = &self.redirect {
|
||||
encoder.write_varint(*redirect);
|
||||
}
|
||||
match &self.type_data {
|
||||
CommandNodeType::Root => (),
|
||||
CommandNodeType::Literal { name } => {
|
||||
encoder.write_string(32767, name);
|
||||
}
|
||||
CommandNodeType::Argument { name, parser } => {
|
||||
encoder.write_string(32767, name);
|
||||
parser.encode(encoder);
|
||||
}
|
||||
}
|
||||
if let Some(suggestion) = &self.suggestion {
|
||||
encoder.write_string(32767, suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Parser {
|
||||
Bool,
|
||||
Float { min: Option<f32>, max: Option<f32>, },
|
||||
Double { min: Option<f64>, max: Option<f64>, },
|
||||
Int { min: Option<i32>, max: Option<i32>, },
|
||||
Long { min: Option<i64>, max: Option<i64>, },
|
||||
String { kind: StringKind },
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn encode(&self, encoder: &mut impl PacketEncoder) {
|
||||
match self {
|
||||
Self::Bool => encoder.write_varint(0),
|
||||
Self::Float{ min, max } => {
|
||||
encoder.write_varint(1);
|
||||
encoder.write_byte( i8::from(min.is_some()) + 2 * i8::from(max.is_some()) );
|
||||
if let Some(min) = min { encoder.write_float(*min) };
|
||||
if let Some(max) = max { encoder.write_float(*max) };
|
||||
},
|
||||
Self::Double{ min, max } => {
|
||||
encoder.write_varint(2);
|
||||
encoder.write_byte( i8::from(min.is_some()) + 2 * i8::from(max.is_some()) );
|
||||
if let Some(min) = min { encoder.write_double(*min) };
|
||||
if let Some(max) = max { encoder.write_double(*max) };
|
||||
},
|
||||
Self::Int{ min, max } => {
|
||||
encoder.write_varint(3);
|
||||
encoder.write_byte( i8::from(min.is_some()) + 2 * i8::from(max.is_some()) );
|
||||
if let Some(min) = min { encoder.write_int(*min) };
|
||||
if let Some(max) = max { encoder.write_int(*max) };
|
||||
},
|
||||
Self::Long{ min, max } => {
|
||||
encoder.write_varint(4);
|
||||
encoder.write_byte( i8::from(min.is_some()) + 2 * i8::from(max.is_some()) );
|
||||
if let Some(min) = min { encoder.write_long(*min) };
|
||||
if let Some(max) = max { encoder.write_long(*max) };
|
||||
},
|
||||
Self::String{ kind } => {
|
||||
encoder.write_varint(5);
|
||||
encoder.write_varint(match kind {
|
||||
StringKind::Single => 0,
|
||||
StringKind::Quoted => 1,
|
||||
StringKind::Greedy => 2,
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StringKind {
|
||||
Single,
|
||||
Quoted,
|
||||
Greedy,
|
||||
}
|
|
@ -50,10 +50,11 @@ pub trait PacketEncoder: Write {
|
|||
self.write_all(&data.to_be_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn write_varint(&mut self, mut data: i32) {
|
||||
fn write_varint(&mut self, data: i32) {
|
||||
let mut data = data as u32;
|
||||
loop {
|
||||
let mut byte = (data & 0b11111111) as u8;
|
||||
data = data >> 7;
|
||||
data >>= 7;
|
||||
if data != 0 {
|
||||
byte |= 0b10000000;
|
||||
}
|
||||
|
@ -64,10 +65,11 @@ pub trait PacketEncoder: Write {
|
|||
}
|
||||
}
|
||||
|
||||
fn write_varlong(&mut self, mut data: i64) {
|
||||
fn write_varlong(&mut self, data: i64) {
|
||||
let mut data = data as u64;
|
||||
loop {
|
||||
let mut byte = (data & 0b11111111) as u8;
|
||||
data = data >> 7;
|
||||
data >>= 7;
|
||||
if data != 0 {
|
||||
byte |= 0b10000000;
|
||||
}
|
||||
|
@ -109,7 +111,7 @@ fn encode_varint(mut data: i32) -> Vec<u8> {
|
|||
let mut res = Vec::new();
|
||||
loop {
|
||||
let mut byte = (data & 0b11111111) as u8;
|
||||
data = data >> 7;
|
||||
data >>= 7;
|
||||
if data != 0 {
|
||||
byte |= 0b10000000;
|
||||
}
|
||||
|
@ -136,8 +138,9 @@ pub struct PacketDecoder {
|
|||
packet_id: i32,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl PacketDecoder {
|
||||
pub fn decode(read: &mut impl Read) -> Option<PacketDecoder> {
|
||||
pub fn decode(read: &mut impl Read) -> Result<PacketDecoder, std::io::Error> {
|
||||
let size = read_varint(read)? as usize;
|
||||
let mut data = vec![0; size];
|
||||
read.read_exact(&mut data).unwrap();
|
||||
|
@ -147,11 +150,11 @@ impl PacketDecoder {
|
|||
packet_id: 0
|
||||
};
|
||||
decoder.packet_id = decoder.read_varint();
|
||||
return Some(decoder);
|
||||
Ok(decoder)
|
||||
}
|
||||
|
||||
pub fn packet_id(&self) -> i32 {
|
||||
return self.packet_id;
|
||||
self.packet_id
|
||||
}
|
||||
|
||||
pub fn read_bytes(&mut self, n: usize) -> &[u8] {
|
||||
|
@ -160,6 +163,12 @@ impl PacketDecoder {
|
|||
ret
|
||||
}
|
||||
|
||||
pub fn read_to_end(&mut self) -> &[u8] {
|
||||
let ret = &self.data[self.idx..];
|
||||
self.idx = self.data.len();
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn read_bool(&mut self) -> bool {
|
||||
self.idx += 1;
|
||||
self.data[self.idx-1] != 0
|
||||
|
@ -264,12 +273,12 @@ impl PacketDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_varint(read: &mut impl Read) -> Option<i32> {
|
||||
fn read_varint(read: &mut impl Read) -> Result<i32, std::io::Error> {
|
||||
let mut result = 0;
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let mut byte = [0];
|
||||
read.read_exact(&mut byte).ok()?;
|
||||
read.read_exact(&mut byte)?;
|
||||
let byte = byte[0];
|
||||
result |= ((byte & 0x7f) as i32) << (7 * count);
|
||||
count += 1;
|
||||
|
@ -280,5 +289,5 @@ fn read_varint(read: &mut impl Read) -> Option<i32> {
|
|||
break
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
pub mod command;
|
||||
pub mod data;
|
||||
pub mod serverbound;
|
||||
pub mod clientbound;
|
||||
|
@ -12,7 +14,7 @@ pub enum NetworkState {
|
|||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Position {
|
||||
x: i32,
|
||||
y: i16,
|
||||
z: i32
|
||||
pub x: i32,
|
||||
pub y: i16,
|
||||
pub z: i32
|
||||
}
|
||||
|
|
|
@ -30,31 +30,18 @@ pub struct SigData {
|
|||
#[derive(Debug)]
|
||||
pub struct LoginStart {
|
||||
pub name: String,
|
||||
pub sig_data: Option<SigData>,
|
||||
pub uuid: Option<Uuid>,
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
impl LoginStart {
|
||||
pub fn decode(mut decoder: PacketDecoder) -> Self {
|
||||
let name = decoder.read_string();
|
||||
let has_sig_data = decoder.read_bool();
|
||||
let sig_data = if has_sig_data {
|
||||
let timestamp = decoder.read_long();
|
||||
let pubkey_len = decoder.read_varint();
|
||||
let pubkey = decoder.read_bytes(pubkey_len as usize).to_vec();
|
||||
let sig_len = decoder.read_varint();
|
||||
let sig = decoder.read_bytes(sig_len as usize).to_vec();
|
||||
Some(SigData { timestamp, pubkey, sig })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let has_uuid = decoder.read_bool();
|
||||
let uuid = if has_uuid {
|
||||
Some(decoder.read_uuid())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self { name, sig_data, uuid }
|
||||
if !has_uuid {
|
||||
panic!("Client didn't supply UUID");
|
||||
}
|
||||
let uuid = decoder.read_uuid();
|
||||
Self { name, uuid }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,6 +60,39 @@ impl ChatMessage {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoginPluginResponse {
|
||||
pub id: i32,
|
||||
pub data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl LoginPluginResponse {
|
||||
pub fn decode(mut decoder: PacketDecoder) -> Self {
|
||||
let id = decoder.read_varint();
|
||||
let success = decoder.read_bool();
|
||||
let data = if success {
|
||||
Some(decoder.read_to_end().to_vec())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self { id, data }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SPluginMessage {
|
||||
pub channel: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SPluginMessage {
|
||||
pub fn decode(mut decoder: PacketDecoder) -> Self {
|
||||
let channel = decoder.read_string();
|
||||
let data = decoder.read_to_end().to_vec();
|
||||
Self { channel, data }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerBoundPacket {
|
||||
Unknown(i32),
|
||||
|
@ -84,15 +104,18 @@ pub enum ServerBoundPacket {
|
|||
PingRequest(i64),
|
||||
// login
|
||||
LoginStart(LoginStart),
|
||||
LoginPluginResponse(LoginPluginResponse),
|
||||
// play
|
||||
ChatMessage(ChatMessage),
|
||||
ChatCommand(ChatMessage),
|
||||
PluginMessage(SPluginMessage),
|
||||
}
|
||||
|
||||
impl ServerBoundPacket {
|
||||
pub fn decode(state: &mut NetworkState, mut decoder: PacketDecoder) -> ServerBoundPacket {
|
||||
use NetworkState as NS;
|
||||
match (*state, decoder.packet_id()) {
|
||||
(NS::Handshake, 0) => {
|
||||
(NS::Handshake, 0x00) => {
|
||||
let hs = Handshake::decode(decoder);
|
||||
match hs.next_state {
|
||||
1 => *state = NS::Status,
|
||||
|
@ -101,16 +124,24 @@ impl ServerBoundPacket {
|
|||
}
|
||||
ServerBoundPacket::Handshake(hs)
|
||||
},
|
||||
(NS::Status, 0)
|
||||
(NS::Status, 0x00)
|
||||
=> ServerBoundPacket::StatusRequest(),
|
||||
(NS::Status, 1)
|
||||
(NS::Status, 0x01)
|
||||
=> ServerBoundPacket::PingRequest(decoder.read_long()),
|
||||
(NS::Login, 0) => {
|
||||
*state = NS::Play;
|
||||
(NS::Login, 0x00) => {
|
||||
ServerBoundPacket::LoginStart(LoginStart::decode(decoder))
|
||||
},
|
||||
(NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),
|
||||
(NS::Play, id @ (18 | 20 | 21 | 22 | 30)) => ServerBoundPacket::Ignored(id),
|
||||
(NS::Login, 0x02) => {
|
||||
let lpr = LoginPluginResponse::decode(decoder);
|
||||
if lpr.id == -1 {
|
||||
*state = NetworkState::Play;
|
||||
}
|
||||
ServerBoundPacket::LoginPluginResponse(lpr)
|
||||
},
|
||||
(NS::Play, 0x04) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)),
|
||||
(NS::Play, 0x05) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),
|
||||
(NS::Play, 0x0C) => ServerBoundPacket::PluginMessage(SPluginMessage::decode(decoder)),
|
||||
(NS::Play, id @ (0x11 | 0x13 | 0x14 | 0x15 | 0x1d)) => ServerBoundPacket::Ignored(id),
|
||||
(_, id) => ServerBoundPacket::Unknown(id),
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue