From 866f39a3563157fa400c62c70611b2dc5b92e1bb Mon Sep 17 00:00:00 2001 From: Garen Tyler Date: Tue, 2 May 2023 13:49:07 -0600 Subject: [PATCH] Restructure project --- .gitignore | 3 +- Cargo.lock | 819 +++++++++++------- Cargo.toml | 36 +- README.md | 1 + build.rs | 24 + crates/composition-core/Cargo.toml | 31 - crates/composition-core/build.rs | 9 - crates/composition-core/src/config.rs | 246 ------ crates/composition-core/src/lib.rs | 69 -- .../composition-core/src/net/mctypes/mod.rs | 5 - .../src/net/mctypes/numbers.rs | 211 ----- .../composition-core/src/net/mctypes/other.rs | 92 -- crates/composition-core/src/net/mod.rs | 97 --- crates/composition-core/src/net/packets.rs | 301 ------- crates/composition-core/src/server/mod.rs | 196 ----- crates/composition-protocol/Cargo.toml | 12 +- .../examples/packet_logger/Cargo.toml | 9 - .../examples/packet_logger/src/main.rs | 117 --- crates/composition-protocol/src/lib.rs | 6 +- .../src/packet/clientbound/login.rs | 270 ++---- .../src/packet/clientbound/play.rs | 562 +++++------- .../src/packet/clientbound/status.rs | 98 +-- crates/composition-protocol/src/packet/mod.rs | 418 ++++----- .../src/packet/serverbound/handshake.rs | 59 +- .../src/packet/serverbound/login.rs | 165 +--- .../src/packet/serverbound/play.rs | 326 +++---- .../src/packet/serverbound/status.rs | 92 +- crates/composition-protocol/src/util.rs | 83 +- src/config.rs | 263 ++++++ src/lib.rs | 39 + src/main.rs | 98 ++- src/net.rs | 212 +++++ .../src => src}/server-icon.png | Bin .../src => src}/server/messages.rs | 0 src/server/mod.rs | 270 ++++++ 35 files changed, 2171 insertions(+), 3068 deletions(-) create mode 100644 build.rs delete mode 100644 crates/composition-core/Cargo.toml delete mode 100644 crates/composition-core/build.rs delete mode 100644 crates/composition-core/src/config.rs delete mode 100644 crates/composition-core/src/lib.rs delete mode 100644 crates/composition-core/src/net/mctypes/mod.rs delete mode 100644 crates/composition-core/src/net/mctypes/numbers.rs delete mode 100644 crates/composition-core/src/net/mctypes/other.rs delete mode 100644 crates/composition-core/src/net/mod.rs delete mode 100644 crates/composition-core/src/net/packets.rs delete mode 100644 crates/composition-core/src/server/mod.rs delete mode 100644 crates/composition-protocol/examples/packet_logger/Cargo.toml delete mode 100644 crates/composition-protocol/examples/packet_logger/src/main.rs create mode 100644 src/config.rs create mode 100644 src/lib.rs create mode 100644 src/net.rs rename {crates/composition-core/src => src}/server-icon.png (100%) rename {crates/composition-core/src => src}/server/messages.rs (100%) create mode 100644 src/server/mod.rs diff --git a/.gitignore b/.gitignore index fd3b96a..2590eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store target -/output.log +/*.log +/logs /world /server-icon.png /composition.toml diff --git a/Cargo.lock b/Cargo.lock index 478fb66..f0f3464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,12 +9,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "anstream" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" dependencies = [ - "libc", + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", ] [[package]] @@ -23,23 +54,6 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -52,12 +66,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - [[package]] name = "byteorder" version = "1.4.3" @@ -82,12 +90,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -95,66 +97,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.24" +name = "clap" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time", - "wasm-bindgen", - "winapi", + "clap_builder", + "clap_derive", + "once_cell", ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "clap_builder" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" dependencies = [ - "termcolor", - "unicode-width", + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", ] [[package]] -name = "colored" -version = "1.9.3" +name = "clap_derive" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ - "atty", - "lazy_static", - "winapi", + "heck", + "proc-macro2", + "quote", + "syn 2.0.13", ] +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + [[package]] name = "composition" version = "0.1.0" dependencies = [ - "composition-core", - "log", - "tokio", -] - -[[package]] -name = "composition-core" -version = "0.1.0" -dependencies = [ - "chrono", + "clap", + "composition-protocol", "ctrlc", - "fern", - "lazy_static", - "log", + "futures", + "once_cell", "quartz_nbt", - "radix64", "serde", "serde_json", - "substring", "tokio", + "tokio-util", "toml", + "tracing", + "tracing-appender", + "tracing-subscriber", "uuid", ] @@ -163,16 +164,26 @@ name = "composition-protocol" version = "0.1.0" dependencies = [ "anyhow", - "nom", + "byteorder", "serde_json", "thiserror", + "tracing", ] [[package]] -name = "core-foundation-sys" -version = "0.8.3" +name = "concolor-override" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] [[package]] name = "crc32fast" @@ -180,7 +191,26 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", ] [[package]] @@ -190,61 +220,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" dependencies = [ "nix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] -name = "cxx" -version = "1.0.92" +name = "errno" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "fern" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a" -dependencies = [ - "colored", - "log", + "libc", ] [[package]] @@ -258,14 +255,100 @@ dependencies = [ ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "futures" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ - "libc", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -276,27 +359,32 @@ dependencies = [ ] [[package]] -name = "iana-time-zone" -version = "0.1.53" +name = "hermit-abi" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" +name = "is-terminal" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "cxx", - "cxx-build", + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -305,15 +393,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" -[[package]] -name = "js-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -327,13 +406,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] -name = "link-cplusplus" -version = "1.0.8" +name = "linux-raw-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" [[package]] name = "lock_api" @@ -351,7 +427,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -360,12 +436,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.6.2" @@ -383,8 +453,8 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "wasi", + "windows-sys 0.45.0", ] [[package]] @@ -394,38 +464,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "libc", "static_assertions", ] [[package]] -name = "nom" -version = "7.1.3" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "memchr", - "minimal-lexical", -] - -[[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]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", + "overload", + "winapi", ] [[package]] @@ -444,6 +495,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "packet_logger" version = "0.1.0" @@ -469,11 +526,11 @@ version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -483,10 +540,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] -name = "proc-macro2" -version = "1.0.51" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -513,28 +576,18 @@ checksum = "289baa0c8a4d1f840d2de528a7f8c29e0e9af48b3018172b3edad4f716e8daed" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] -[[package]] -name = "radix64" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999718fa65c3be3a74f3f6dae5a98526ff436ea58a82a574f0de89eecd342bee" -dependencies = [ - "arrayref", - "cfg-if 0.1.10", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -544,6 +597,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rustix" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "ryu" version = "1.0.13" @@ -556,12 +623,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - [[package]] name = "serde" version = "1.0.152" @@ -579,7 +640,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -593,6 +654,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -602,6 +672,15 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -625,13 +704,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "substring" -version = "1.4.5" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" -dependencies = [ - "autocfg", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -645,12 +721,14 @@ dependencies = [ ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "syn" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -670,30 +748,55 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", ] [[package]] name = "time" -version = "0.1.45" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", ] [[package]] name = "tokio" -version = "1.26.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot", @@ -701,18 +804,31 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] @@ -724,6 +840,76 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + [[package]] name = "unicode-ident" version = "1.0.7" @@ -731,10 +917,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7" [[package]] -name = "unicode-width" -version = "0.1.10" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" @@ -743,10 +929,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "wasi" @@ -754,60 +940,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" - [[package]] name = "winapi" version = "0.3.9" @@ -824,15 +956,6 @@ 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" @@ -845,7 +968,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -854,13 +986,28 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 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", + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -869,38 +1016,80 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 28a8163..84eaa45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,9 @@ [workspace] -members = [ "crates/*", "crates/*/examples/*" ] +members = [ "crates/*" ] + +[workspace.dependencies] +tracing = { version = "0.1", features = ["log"] } +composition-protocol = { path = "./crates/composition-protocol" } [package] name = "composition" @@ -8,8 +12,32 @@ edition = "2021" authors = ["Garen Tyler "] description = "An extremely fast Minecraft server" license = "MIT" +build = "build.rs" [dependencies] -composition-core = { path = "./crates/composition-core" } -log = "0.4" -tokio = "1" +ctrlc = "3.1.8" +clap = { version = "4.2.1", features = ["derive"] } +composition-protocol = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { version = "*", features = ["tracing-log"] } +tracing-appender = "0.2" +toml = "0.5" +once_cell = "1.17" +serde = { version = "1.0.114", features = ["serde_derive"] } +serde_json = "1.0.59" +quartz_nbt = { version = "0.2.6", features = ["serde"] } +tokio = { version = "1", features = ["full"] } +uuid = "0.8.2" +tokio-util = "0.7.7" +futures = "0.3.28" + +# Unused but possibly useful dependencies: +# async-trait = "0.1.48" +# backtrace = "0.3.50" +# base64 = "0.12.3" +# colorful = "0.2.1" +# fastnbt = "*" +# mojang-api = "0.6.1" +# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2) +# radix64 = "0.6.2" +# toml = "0.5.6" diff --git a/README.md b/README.md index 9c19d9c..2f19222 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Composition is broken up into multiple crates to speed up build times and improv - `composition-core` implements the main server logic, such as handling clients and loading world chunks. - `composition-protocol` handles the types and packets needed for network communication. The library was designed to be able to used by anyone looking to implement a Minecraft server. +- `composition-config` handles the server configuration files and command line argument parsing. ## Useful Resources - [Protocol Specification](https://wiki.vg/Protocol) diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ac4068b --- /dev/null +++ b/build.rs @@ -0,0 +1,24 @@ +use std::process::Command; +fn main() { + if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() { + let git_hash = String::from_utf8_lossy(&output.stdout).to_string(); + println!("cargo:rustc-env=GIT_HASH={}", git_hash); + } else { + println!("cargo:rustc-env=GIT_HASH=000000000"); + } + + if let Ok(output) = Command::new("git") + .args(["log", "-1", "--format=%cI"]) + .output() + { + let iso_date = String::from_utf8_lossy(&output.stdout).to_string(); + println!("cargo:rustc-env=GIT_DATE={}", iso_date); + } else { + println!("cargo:rustc-env=GIT_DATE=1970-01-01"); + } + + println!( + "cargo:rustc-env=BUILD_TARGET={}", + std::env::var("TARGET").unwrap() + ); +} diff --git a/crates/composition-core/Cargo.toml b/crates/composition-core/Cargo.toml deleted file mode 100644 index e1ccadc..0000000 --- a/crates/composition-core/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "composition-core" -version = "0.1.0" -edition = "2021" -build = "build.rs" - -[dependencies] -chrono = "0.4.13" -ctrlc = "3.1.8" -fern = { version = "0.6", features = ["colored"] } -lazy_static = "1.4.0" -log = "0.4" -quartz_nbt = { version = "0.2.6", features = ["serde"] } -radix64 = "0.6.2" -serde = { version = "1.0.114", features = ["serde_derive"] } -serde_json = "1.0.59" -substring = "1.4.5" -tokio = { version = "1", features = ["full"] } -toml = "0.5" -uuid = "0.8.2" - -# async-trait = "0.1.48" -# backtrace = "0.3.50" -# base64 = "0.12.3" -# colorful = "0.2.1" -# composition-protocol = { path = "../composition-protocol" } -# fastnbt = "*" -# futures = "0.3.13" -# mojang-api = "0.6.1" -# ozelot = "0.9.0" # Ozelot 0.9.0 supports protocol version 578 (1.15.2) -# toml = "0.5.6" diff --git a/crates/composition-core/build.rs b/crates/composition-core/build.rs deleted file mode 100644 index e0af124..0000000 --- a/crates/composition-core/build.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::process::Command; -fn main() { - if let Ok(output) = Command::new("git").args(&["rev-parse", "HEAD"]).output() { - let git_hash = String::from_utf8_lossy(&output.stdout).to_string(); - println!("cargo:rustc-env=GIT_HASH={}", git_hash); - } else { - println!("cargo:rustc-env=GIT_HASH=00000000"); - } -} diff --git a/crates/composition-core/src/config.rs b/crates/composition-core/src/config.rs deleted file mode 100644 index e82421b..0000000 --- a/crates/composition-core/src/config.rs +++ /dev/null @@ -1,246 +0,0 @@ -use crate::prelude::*; -use std::{fs::File, path::Path}; - -fn read_file(path: &Path) -> std::io::Result> { - let mut data = vec![]; - let mut file = File::open(path)?; - file.read_to_end(&mut data)?; - Ok(data) -} - -pub struct Config { - pub port: u16, - pub max_players: usize, - pub motd: String, - pub server_icon: String, - pub server_icon_bytes: Vec, - pub server_string: String, - pub log_level: log::LevelFilter, - pub protocol_version: i32, - pub game_version: String, - pub server_version: String, -} -impl Default for Config { - fn default() -> Self { - let server_version = format!( - "composition/{} ({})", - env!("CARGO_PKG_VERSION"), - env!("GIT_HASH").substring(0, 8) - ); - Config { - port: 25565, - max_players: 20, - motd: "Hello world!".to_owned(), - server_icon: "server-icon.png".to_owned(), - server_icon_bytes: include_bytes!("./server-icon.png").to_vec(), - server_string: server_version.clone(), - log_level: if cfg!(debug_assertions) { - log::LevelFilter::Debug - } else { - log::LevelFilter::Info - }, - protocol_version: 761, - game_version: "1.19.3".to_owned(), - server_version, - } - } -} -impl Config { - pub fn from_toml(cfg: toml::Value) -> Config { - let mut config = Config::default(); - - let get_string = |cfg: &toml::Value, field: &str, default: &str, error: &str| -> String { - if let Some(s) = cfg.get(field) { - if let Some(s) = s.as_str() { - return s.to_owned(); - } else { - warn!("{}", error); - } - } else { - warn!("{}", error); - } - default.to_owned() - }; - - if let Some(&toml::Value::Integer(port)) = cfg.get("port") { - if port < u16::MIN as i64 || port > u16::MAX as i64 { - warn!( - "Config port must be an integer in the range of {}-{}, using default port: {}", - u16::MIN, - u16::MAX, - config.port - ); - } else { - config.port = port as u16; - } - } else { - warn!( - "Config port must be an integer in the range of {}-{}, using default port: {}", - u16::MIN, - u16::MAX, - config.port - ); - } - - if let Some(&toml::Value::Integer(max_players)) = cfg.get("max_players") { - if max_players < 0 { - warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players); - } else { - config.max_players = max_players as usize; - } - } else { - warn!("Config max_players must be an integer in the range of {}-{}, using default max_players: {}", usize::MIN, usize::MAX, config.max_players); - } - - config.motd = get_string( - &cfg, - "motd", - &config.motd, - &format!( - "Config motd must be a string, using default motd: \"{}\"", - config.motd - ), - ); - config.game_version = get_string( - &cfg, - "ping_game_version", - &config.game_version, - &format!( - "Config ping_game_version must be a string, using default ping_game_version: \"{}\"", - config.game_version - ), - ); - config.server_icon = get_string( - &cfg, - "server_icon", - &config.server_icon, - &format!( - "Config server_icon must be a string, using default server_icon: \"{}\"", - config.server_icon - ), - ); - let default_log_level = format!("{}", config.log_level).to_ascii_lowercase(); - config.log_level = match &get_string( - &cfg, - "log_level", - &default_log_level, - &format!( - "Config log_level must be a string, using default log_level: {}", - default_log_level - ), - )[..] - { - "off" => log::LevelFilter::Off, - "error" => log::LevelFilter::Error, - "warn" => log::LevelFilter::Warn, - "info" => log::LevelFilter::Info, - "debug" => log::LevelFilter::Debug, - "trace" => log::LevelFilter::Trace, - _ => { - warn!("Config log_level must be one of the predefined levels: off, error, warn, info, debug, trace"); - config.log_level - } - }; - config - } - pub fn load() -> Config { - let mut config = Config::default(); - - // Load the config - let config_path = Path::new("composition.toml"); - if config_path.exists() { - if let Ok(cfg) = read_file(config_path) { - let cfg = String::from_utf8_lossy(&cfg); - if let Ok(cfg) = cfg.parse::() { - config = Config::from_toml(cfg); - } else { - error!("Could not parse configuration file"); - std::process::exit(1); - } - } else { - warn!( - "Could not read configuration file, creating {}", - config_path.to_str().unwrap_or("") - ); - if config.write(config_path).is_err() { - error!("Could not write configuration file"); - std::process::exit(1); - } - } - } else { - warn!( - "Configuration file does not exist, creating {}", - config_path.to_str().unwrap_or("") - ); - if config.write(config_path).is_err() { - error!("Could not write configuration file"); - std::process::exit(1); - } - } - - // Load the server icon - let server_icon_path = Path::new(&config.server_icon); - if server_icon_path.exists() { - if let Ok(server_icon_bytes) = read_file(server_icon_path) { - config.server_icon_bytes = server_icon_bytes; - } else { - warn!( - "Could not read server icon file, creating {}", - server_icon_path.to_str().unwrap_or("") - ); - if config.write_server_icon(server_icon_path).is_err() { - error!("Could not write server icon file"); - std::process::exit(1); - } - } - } else { - warn!( - "Server icon file does not exist, creating {}", - server_icon_path.to_str().unwrap_or("") - ); - if config.write_server_icon(server_icon_path).is_err() { - error!("Could not write server icon file"); - std::process::exit(1); - } - } - - config - } - pub fn write(&self, path: &Path) -> std::io::Result<()> { - use toml::{map::Map, Value}; - - let config = Value::Table({ - let mut m = Map::new(); - m.insert( - "server_icon".to_owned(), - Value::String(self.server_icon.clone()), - ); - m.insert( - "log_level".to_owned(), - Value::String(format!("{}", self.log_level).to_ascii_lowercase()), - ); - m.insert("max_players".to_owned(), Value::Integer(20)); - m.insert("motd".to_owned(), Value::String(self.motd.clone())); - m.insert( - "ping_game_version".to_owned(), - Value::String(self.game_version.clone()), - ); - m.insert("port".to_owned(), Value::Integer(25565)); - m - }); - if path.exists() { - std::fs::remove_file(path)?; - } - let mut file = File::create(path)?; - file.write_all(&toml::ser::to_vec(&config).unwrap())?; - Ok(()) - } - pub fn write_server_icon(&self, path: &Path) -> std::io::Result<()> { - if path.exists() { - std::fs::remove_file(path)?; - } - let mut file = File::create(path)?; - file.write_all(&self.server_icon_bytes)?; - Ok(()) - } -} diff --git a/crates/composition-core/src/lib.rs b/crates/composition-core/src/lib.rs deleted file mode 100644 index bcd2d25..0000000 --- a/crates/composition-core/src/lib.rs +++ /dev/null @@ -1,69 +0,0 @@ -#[macro_use] -extern crate lazy_static; - -pub mod config; -pub mod net; -pub mod server; - -use crate::prelude::*; -use std::sync::mpsc::{self, Receiver}; - -lazy_static! { - pub static ref CONFIG: Config = Config::load(); - pub static ref START_TIME: std::time::Instant = std::time::Instant::now(); -} - -/// Set up logging, read the config file, etc. -pub fn init() -> Receiver<()> { - let _ = START_TIME.elapsed(); - // Set up fern logging. - fern::Dispatch::new() - .format(move |out, message, record| { - out.finish(format_args!( - "[{date}][{target}][{level}] {message}", - date = chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), - target = record.target(), - level = record.level(), - message = message, - )) - }) - .level(log::LevelFilter::Trace) - .chain(std::io::stdout()) - .chain(fern::log_file("output.log").unwrap()) - .apply() - .unwrap(); - log::set_max_level(CONFIG.log_level); - // Set up the ctrl-c handler. - let (ctrlc_tx, ctrlc_rx) = mpsc::channel(); - ctrlc::set_handler(move || { - ctrlc_tx.send(()).expect("Ctrl-C receiver disconnected"); - }) - .expect("Error setting Ctrl-C handler"); - ctrlc_rx -} - -/// Start the server. -pub async fn start_server() -> server::Server { - server::Server::new(format!("0.0.0.0:{}", CONFIG.port)).await -} - -pub mod prelude { - pub use crate::{config::Config, CONFIG, START_TIME}; - pub use log::*; - pub use serde::{Deserialize, Serialize}; - pub use serde_json::json; - pub use uuid::Uuid; - pub type JSON = serde_json::Value; - pub type NBT = quartz_nbt::NbtCompound; - pub use std::collections::VecDeque; - pub use std::io::{Read, Write}; - pub use substring::Substring; - pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; - #[derive(Clone, Debug, PartialEq)] - pub enum ParseError { - NotEnoughData, - InvalidData, - VarIntTooBig, - } - pub type ParseResult = Result<(T, usize), ParseError>; -} diff --git a/crates/composition-core/src/net/mctypes/mod.rs b/crates/composition-core/src/net/mctypes/mod.rs deleted file mode 100644 index 7abb13b..0000000 --- a/crates/composition-core/src/net/mctypes/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod numbers; -pub mod other; - -pub use numbers::*; -pub use other::*; diff --git a/crates/composition-core/src/net/mctypes/numbers.rs b/crates/composition-core/src/net/mctypes/numbers.rs deleted file mode 100644 index c5dada9..0000000 --- a/crates/composition-core/src/net/mctypes/numbers.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::prelude::*; - -pub fn parse_byte(data: &[u8]) -> ParseResult { - if data.is_empty() { - Err(ParseError::NotEnoughData) - } else { - let value = i8::from_be_bytes([data[0]]); - Ok((value, 1)) - } -} -pub fn serialize_byte(value: i8) -> [u8; 1] { - value.to_be_bytes() -} - -pub fn parse_short(data: &[u8]) -> ParseResult { - if data.len() < 2 { - Err(ParseError::NotEnoughData) - } else { - let value = i16::from_be_bytes([data[0], data[1]]); - Ok((value, 2)) - } -} -pub fn serialize_short(value: i16) -> [u8; 2] { - value.to_be_bytes() -} - -pub fn parse_int(data: &[u8]) -> ParseResult { - if data.len() < 4 { - Err(ParseError::NotEnoughData) - } else { - let value = i32::from_be_bytes([data[0], data[1], data[2], data[3]]); - Ok((value, 4)) - } -} -pub fn serialize_int(value: i32) -> [u8; 4] { - value.to_be_bytes() -} - -pub fn parse_long(data: &[u8]) -> ParseResult { - if data.len() < 8 { - Err(ParseError::NotEnoughData) - } else { - let value = i64::from_be_bytes([ - data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], - ]); - Ok((value, 8)) - } -} -pub fn serialize_long(value: i64) -> [u8; 8] { - value.to_be_bytes() -} - -pub fn parse_varint(data: &[u8]) -> ParseResult { - let mut offset = 0; - let mut output = 0i32; - let mut bytes_read = 0i32; - - loop { - if data.len() <= offset { - return Err(ParseError::NotEnoughData); - } - - output |= (((data[offset] & 0x7f) as i32) << (bytes_read * 7)) as i32; - bytes_read += 1; - if data[offset] & 0x80 != 0x80 { - break; - } - offset += 1; - if bytes_read >= 5 { - return Err(ParseError::VarIntTooBig); - } - } - - Ok((output, bytes_read as usize)) -} -pub fn serialize_varint(value: i32) -> Vec { - let mut value = value as u32; - let mut output = vec![]; - loop { - let data = (value & 0x7f) as u8; - value >>= 7; - - if value == 0 { - output.push(data); - break; - } else { - output.push(data | 0x80); - } - } - output -} - -pub fn parse_float(data: &[u8]) -> ParseResult { - if data.len() < 4 { - Err(ParseError::NotEnoughData) - } else { - let value = f32::from_be_bytes([data[0], data[1], data[2], data[3]]); - Ok((value, 4)) - } -} -pub fn serialize_float(value: f32) -> [u8; 4] { - value.to_be_bytes() -} - -pub fn parse_double(data: &[u8]) -> ParseResult { - if data.len() < 8 { - Err(ParseError::NotEnoughData) - } else { - let value = f64::from_be_bytes([ - data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], - ]); - Ok((value, 4)) - } -} -pub fn serialize_double(value: f64) -> [u8; 8] { - value.to_be_bytes() -} - -pub fn parse_unsigned_byte(data: &[u8]) -> ParseResult { - if data.is_empty() { - Err(ParseError::NotEnoughData) - } else { - let value = u8::from_be_bytes([data[0]]); - Ok((value, 1)) - } -} -pub fn serialize_unsigned_byte(value: u8) -> [u8; 1] { - value.to_be_bytes() -} - -pub fn parse_unsigned_short(data: &[u8]) -> ParseResult { - if data.len() < 2 { - Err(ParseError::NotEnoughData) - } else { - let value = u16::from_be_bytes([data[0], data[1]]); - Ok((value, 2)) - } -} -pub fn serialize_unsigned_short(value: u16) -> [u8; 2] { - value.to_be_bytes() -} - -pub fn parse_unsigned_int(data: &[u8]) -> ParseResult { - if data.len() < 4 { - Err(ParseError::NotEnoughData) - } else { - let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); - Ok((value, 4)) - } -} -pub fn serialize_unsigned_int(value: u32) -> [u8; 4] { - value.to_be_bytes() -} - -pub fn parse_unsigned_long(data: &[u8]) -> ParseResult { - if data.len() < 8 { - Err(ParseError::NotEnoughData) - } else { - let value = u64::from_be_bytes([ - data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], - ]); - Ok((value, 8)) - } -} -pub fn serialize_unsigned_long(value: u64) -> [u8; 8] { - value.to_be_bytes() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_varint_works() { - let tests = vec![ - (Ok((0, 1)), vec![0x00]), - (Ok((1, 1)), vec![0x01]), - (Ok((2, 1)), vec![0x02]), - (Ok((127, 1)), vec![0x7f]), - (Ok((128, 2)), vec![0x80, 0x01]), - (Ok((255, 2)), vec![0xff, 0x01]), - (Ok((25565, 3)), vec![0xdd, 0xc7, 0x01]), - (Ok((2097151, 3)), vec![0xff, 0xff, 0x7f]), - (Ok((2147483647, 5)), vec![0xff, 0xff, 0xff, 0xff, 0x07]), - (Ok((-1, 5)), vec![0xff, 0xff, 0xff, 0xff, 0x0f]), - (Ok((-2147483648, 5)), vec![0x80, 0x80, 0x80, 0x80, 0x08]), - ]; - for test in &tests { - assert_eq!(test.0, parse_varint(&test.1)); - } - } - #[test] - fn serialize_varint_works() { - let tests = vec![ - (0, vec![0x00]), - (1, vec![0x01]), - (2, vec![0x02]), - (127, vec![0x7f]), - (128, vec![0x80, 0x01]), - (255, vec![0xff, 0x01]), - (25565, vec![0xdd, 0xc7, 0x01]), - (2097151, vec![0xff, 0xff, 0x7f]), - (2147483647, vec![0xff, 0xff, 0xff, 0xff, 0x07]), - (-1, vec![0xff, 0xff, 0xff, 0xff, 0x0f]), - (-2147483648, vec![0x80, 0x80, 0x80, 0x80, 0x08]), - ]; - for test in &tests { - assert_eq!(serialize_varint(test.0), test.1); - } - } -} diff --git a/crates/composition-core/src/net/mctypes/other.rs b/crates/composition-core/src/net/mctypes/other.rs deleted file mode 100644 index fedae02..0000000 --- a/crates/composition-core/src/net/mctypes/other.rs +++ /dev/null @@ -1,92 +0,0 @@ -use super::*; -use crate::prelude::*; - -pub fn parse_bool(data: &[u8]) -> ParseResult { - if data.is_empty() { - Err(ParseError::NotEnoughData) - } else { - Ok((data[0] == 1, 1)) - } -} -pub fn serialize_bool(value: bool) -> [u8; 1] { - if value { - [0x01] - } else { - [0x00] - } -} - -pub fn parse_string(data: &[u8]) -> ParseResult { - let mut offset = 0; - let (length, offset_delta) = parse_varint(&data[offset..])?; - offset += offset_delta; - let length = length as usize; - if data.len() < offset + length { - return Err(ParseError::NotEnoughData); - } - let output = String::from_utf8_lossy(&data[offset..offset + length]).to_string(); - offset += length; - Ok((output, offset)) -} -pub fn serialize_string(value: &str) -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(value.len() as i32)); - output.extend_from_slice(value.as_bytes()); - output -} - -pub fn parse_json(data: &[u8]) -> ParseResult { - let (value_string, offset) = parse_string(data)?; - if let Ok(value) = serde_json::from_str(&value_string) { - Ok((value, offset)) - } else { - Err(ParseError::InvalidData) - } -} -pub fn serialize_json(value: JSON) -> Vec { - serialize_string(&serde_json::to_string(&value).expect("Could not serialize JSON")) -} - -pub fn parse_nbt(data: &[u8]) -> ParseResult { - use quartz_nbt::io::{read_nbt, Flavor}; - use std::io::Cursor; - let mut data = Cursor::new(data); - // let (value_string, offset) = parse_string(data)?; - if let Ok(value) = read_nbt(&mut data, Flavor::Uncompressed) { - Ok((value.0, data.position() as usize)) - } else { - Err(ParseError::InvalidData) - } -} -pub fn serialize_nbt(value: NBT) -> Vec { - use quartz_nbt::io::{write_nbt, Flavor}; - // serialize_string(&fastnbt::to_string(&value).expect("Could not serialize JSON")) - let mut out = vec![]; - write_nbt(&mut out, None, &value, Flavor::Uncompressed).expect("Could not serialize NBT"); - out -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Position { - pub x: i32, - pub y: i16, - pub z: i32, -} -impl Position { - pub fn new(x: i32, y: i16, z: i32) -> Position { - Position { x, y, z } - } - pub fn parse(data: &[u8]) -> ParseResult { - let (value, offset) = parse_unsigned_long(data)?; - let x = (value >> 38) as i32; - let y = (value & 0xFFF) as i16; - let z = ((value >> 12) & 0x3FFFFFF) as i32; - Ok((Position::new(x, y, z), offset)) - } - pub fn serialize(&self) -> [u8; 8] { - (((self.x as u64 & 0x3FFFFFF) << 38) - | ((self.z as u64 & 0x3FFFFFF) << 12) - | (self.y as u64 & 0xFFF)) - .to_be_bytes() - } -} diff --git a/crates/composition-core/src/net/mod.rs b/crates/composition-core/src/net/mod.rs deleted file mode 100644 index 6148c75..0000000 --- a/crates/composition-core/src/net/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub mod mctypes; -pub mod packets; - -use crate::prelude::*; -use mctypes::*; -pub use packets::Packet; -use std::time::Instant; -use tokio::net::TcpStream; - -#[derive(PartialEq, Copy, Clone, Debug)] -pub enum NetworkClientState { - Disconnected, - Handshake, - Status, - Login, - Play, -} - -pub struct NetworkClient { - pub id: u128, - pub state: NetworkClientState, - pub stream: TcpStream, - pub buffer: VecDeque, - pub packet_queue: VecDeque, - pub last_data_time: Instant, -} -impl NetworkClient { - pub fn new(id: u128, stream: TcpStream) -> NetworkClient { - NetworkClient { - id, - state: NetworkClientState::Handshake, - stream, - buffer: VecDeque::new(), - packet_queue: VecDeque::new(), - last_data_time: Instant::now(), - } - } - pub async fn read_data(&mut self) -> Result<(), tokio::io::Error> { - trace!("NetworkClient.read_data() id {}", self.id); - // Try to read 4kb at a time until there is no more data. - loop { - let mut buf = [0; 4096]; - match self.stream.try_read(&mut buf) { - // There is no data available. - Ok(0) => break, - // Data was read. - Ok(n) => { - trace!("Setting last_data_time for client {}", self.id); - self.last_data_time = Instant::now(); - self.buffer.extend(&buf[0..n]); - debug!("Read {} bytes from client {}", n, self.id); - } - Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => break, - Err(e) => { - return Err(e); - } - } - } - Ok(()) - } - pub fn read_packet(&mut self) -> Result<(), ParseError> { - trace!("NetworkClient.read_packet() id {}", self.id); - self.buffer.make_contiguous(); - if let (data, &[]) = self.buffer.as_slices() { - let mut offset = 0; - let (packet_length, offset_delta) = parse_varint(&data[offset..])?; - offset += offset_delta; - let packet_length = packet_length as usize; - let (packet_id, offset_delta) = parse_varint(&data[offset..])?; - offset += offset_delta; - let packet_id = packet_id as usize; - let (packet, offset_delta) = - Packet::parse_body(&data[offset..], packet_length, packet_id, self.state, true)?; - debug!("Got packet {:?} from client {}", packet, self.id); - offset += offset_delta; - self.packet_queue.push_back(packet); - let remaining_data = self.buffer.split_off(offset); - self.buffer = remaining_data; - } - Ok(()) - } - pub async fn send_packet(&mut self, packet: Packet) -> Result<(), tokio::io::Error> { - trace!("NetworkClient.send_packet()"); - debug!("Sending packet {:?} to client {}", packet, self.id); - let bytes = packet.serialize(); - self.stream.write(&bytes).await?; - Ok(()) - } - pub async fn disconnect(&mut self, reason: Option) { - if let Some(reason) = reason { - if self.state == NetworkClientState::Login { - let _ = self.send_packet(Packet::CL00Disconnect { reason }).await; - } - } - self.state = NetworkClientState::Disconnected; - } -} diff --git a/crates/composition-core/src/net/packets.rs b/crates/composition-core/src/net/packets.rs deleted file mode 100644 index 887d681..0000000 --- a/crates/composition-core/src/net/packets.rs +++ /dev/null @@ -1,301 +0,0 @@ -use super::{mctypes::*, NetworkClientState}; -use crate::prelude::*; - -#[derive(Clone, PartialEq, Debug)] -pub enum Packet { - // Handshake - SH00Handshake { - protocol_version: i32, - server_address: String, - server_port: u16, - next_state: NetworkClientState, - }, - - // Status - CS00Response { - version_name: String, - protocol_version: i32, - max_players: usize, - current_players: usize, - description: JSON, - }, - CS01Pong { - payload: i64, - }, - SS00Request, - SS01Ping { - payload: i64, - }, - - // Login - CL00Disconnect { - reason: JSON, - }, - CL01EncryptionRequest { - server_id: String, - public_key: Vec, - verify_token: Vec, - }, - CL02LoginSuccess { - uuid: u128, - username: String, - }, - CL03SetCompression { - threshold: usize, - }, - CL04LoginPluginRequest { - message_id: i32, - channel: String, - data: Vec, - }, - SL00LoginStart { - username: String, - }, - SL01EncryptionResponse { - shared_secret: Vec, - verify_token: Vec, - }, - SL02LoginPluginResponse { - message_id: i32, - successful: bool, - data: Option>, - }, - - // Play - CP14WindowItems { - window_id: u8, - state_id: i32, - slots: Vec, - carried_item: NBT, - }, - CP26JoinGame, - CP48HeldItemChange, - CP66DeclareRecipes, - CP67Tags, - CP1BEntityStatus, - CP12DeclareCommands, - CP39UnlockRecipes, - CP22ChunkDataAndUpdateLight, - CP38PlayerPositionAndLook { - x: (f64, bool), - y: (f64, bool), - z: (f64, bool), - yaw: (f32, bool), - pitch: (f32, bool), - teleport_id: i32, - dismount_vehicle: bool, - }, - CP36PlayerInfo, - CP49UpdateViewPosition, - CP25UpdateLight, - CP4BSpawnPosition { - location: Position, - angle: f32, - }, - CP00TeleportConfirm, - - SP05ClientSettings, - SP04ClientStatus, -} -impl Packet { - pub fn parse_body( - data: &[u8], - _length: usize, - id: usize, - state: NetworkClientState, - serverbound: bool, - ) -> ParseResult { - use NetworkClientState::*; - use Packet::*; - - let mut offset = 0; - match state { - Disconnected => Err(ParseError::InvalidData), - Handshake => { - if id == 0x00 && serverbound { - let (protocol_version, offset_delta) = parse_varint(&data[offset..])?; - offset += offset_delta; - let (server_address, offset_delta) = parse_string(&data[offset..])?; - offset += offset_delta; - let (server_port, offset_delta) = parse_unsigned_short(&data[offset..])?; - offset += offset_delta; - let (next_state, offset_delta) = parse_varint(&data[offset..])?; - offset += offset_delta; - let next_state = match next_state { - 1 => NetworkClientState::Status, - 2 => NetworkClientState::Login, - _ => return Err(ParseError::InvalidData), - }; - Ok(( - SH00Handshake { - protocol_version, - server_address, - server_port, - next_state, - }, - offset, - )) - } else { - Err(ParseError::InvalidData) - } - } - Status => match id { - 0x00 => { - if serverbound { - Ok((SS00Request, offset)) - } else { - unimplemented!("Parse CS00Response") - } - } - 0x01 => { - if serverbound { - let (payload, offset_delta) = parse_long(&data[offset..])?; - offset += offset_delta; - Ok((SS01Ping { payload }, offset)) - } else { - unimplemented!("Parse CS01Pong") - } - } - _ => Err(ParseError::InvalidData), - }, - Login => match id { - 0x00 => { - if serverbound { - let (username, offset_delta) = parse_string(&data[offset..])?; - offset += offset_delta; - Ok((SL00LoginStart { username }, offset)) - } else { - unimplemented!("Parse CL00Disconnect") - } - } - 0x01 => { - if serverbound { - unimplemented!("Parse SL01EncryptionResponse") - } else { - unimplemented!("Parse CL01EncryptionRequest") - } - } - 0x02 => { - if serverbound { - unimplemented!("Parse SL02LoginPluginResponse") - } else { - unimplemented!("Parse CL02LoginSuccess") - } - } - 0x03 => { - if serverbound { - Err(ParseError::InvalidData) - } else { - unimplemented!("Parse CL03SetCompression") - } - } - 0x04 => { - if serverbound { - Err(ParseError::InvalidData) - } else { - unimplemented!("Parse CL04LoginPluginRequest") - } - } - _ => Err(ParseError::InvalidData), - }, - Play => unimplemented!("Parse Play packet"), - } - } - pub fn serialize(&self) -> Vec { - use Packet::*; - let (id, mut body): (usize, Vec) = match self { - CS00Response { - version_name, - protocol_version, - max_players, - current_players, - description, - } => ( - 0x00, - serialize_json(json!({ - "version": { - "name": version_name, - "protocol": protocol_version, - }, - "players": { - "max": max_players, - "online": current_players, - }, - "description": description, - "favicon": format!("data:image/png;base64,{}", radix64::STD_NO_PAD.encode(&CONFIG.server_icon_bytes)), - })), - ), - CS01Pong { payload } => (0x01, serialize_long(*payload).to_vec()), - CL00Disconnect { reason } => (0x00, serialize_json(reason.clone())), - CL02LoginSuccess { uuid, username } => (0x02, { - let mut out = vec![]; - out.extend(uuid.to_be_bytes()); - out.extend(serialize_string(username)); - out - }), - CP14WindowItems { - window_id, - state_id, - slots, - carried_item, - } => (0x14, { - let mut out = vec![*window_id]; - out.extend(serialize_varint(*state_id)); - out.extend(serialize_varint(slots.len() as i32)); - for slot in slots { - out.extend(serialize_nbt(slot.clone())); - } - out.extend(serialize_nbt(carried_item.clone())); - out - }), - CP38PlayerPositionAndLook { - x, - y, - z, - yaw, - pitch, - teleport_id, - dismount_vehicle, - } => (0x38, { - let mut out = vec![]; - out.extend(serialize_double(x.0)); - out.extend(serialize_double(y.0)); - out.extend(serialize_double(z.0)); - out.extend(serialize_float(yaw.0)); - out.extend(serialize_float(pitch.0)); - let mut flags = 0x00; - if x.1 { - flags |= 0x01; - } - if y.1 { - flags |= 0x02; - } - if z.1 { - flags |= 0x04; - } - if yaw.1 { - flags |= 0x10; - } - if pitch.1 { - flags |= 0x08; - } - out.push(flags); - out.extend(serialize_varint(*teleport_id)); - out.extend(serialize_bool(*dismount_vehicle)); - out - }), - CP4BSpawnPosition { location, angle } => (0x4b, { - let mut out = vec![]; - out.extend(location.serialize()); - out.extend(serialize_float(*angle)); - out - }), - _ => unimplemented!("Serializing unknown packet"), - }; - let mut id_and_body = serialize_varint(id as i32); - id_and_body.append(&mut body); - let mut output = serialize_varint(id_and_body.len() as i32); - output.append(&mut id_and_body); - output - } -} diff --git a/crates/composition-core/src/server/mod.rs b/crates/composition-core/src/server/mod.rs deleted file mode 100644 index cd4ed8f..0000000 --- a/crates/composition-core/src/server/mod.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::{ - net::{mctypes::Position, *}, - prelude::*, -}; -use std::time::Duration; -use tokio::{ - net::{TcpListener, ToSocketAddrs}, - sync::mpsc::{self, error::TryRecvError, UnboundedReceiver}, -}; - -#[derive(Clone, Debug, PartialEq)] -pub enum ServerError {} - -pub struct Server { - network_client_receiver: UnboundedReceiver, - clients: Vec, -} -impl Server { - pub async fn new(bind_address: A) -> Server { - let (client_tx, client_rx) = mpsc::unbounded_channel(); - tokio::task::spawn(async move { - let listener = TcpListener::bind(bind_address) - .await - .expect("Could not bind to given address"); - let mut id = 0u128; - loop { - trace!("Server accepting new client"); - match listener.accept().await { - Ok((socket, addr)) => { - let _ = client_tx.send(NetworkClient::new(id, socket)); - debug!("Connected client {} at {:?}", id, addr); - id += 1; - } - Err(_) => break, - } - } - }); - Server { - network_client_receiver: client_rx, - clients: vec![], - } - } - pub async fn update(&mut self) -> Result<(), ServerError> { - trace!("Server.update()"); - // Read new clients from the receiver - loop { - match self.network_client_receiver.try_recv() { - Ok(client) => self.clients.push(client), - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => panic!("Client sender disconnected"), - } - } - // Remove disconnected clients. - let mut i = 0; - while i < self.clients.len() { - if self.clients[i].state == NetworkClientState::Disconnected { - debug!("Removed client {}", self.clients[i].id); - self.clients.remove(i); - } else if self.clients[i].last_data_time.elapsed() > Duration::from_secs(10) { - debug!("Client {} timed out", self.clients[i].id); - self.clients[i].disconnect(None).await; - } else { - i += 1; - } - } - // Read data and try to parse packets from each client. - for client in self.clients.iter_mut() { - if client.state == NetworkClientState::Disconnected { - continue; - } - let _ = client.read_data().await; - 'packet: loop { - match client.read_packet() { - Ok(_) => {} - Err(ParseError::NotEnoughData) => break 'packet, - Err(_) => {} - } - } - } - // Handle each packet for each player. - 'client: for i in 0..self.clients.len() { - while let Some(packet) = self.clients[i].packet_queue.pop_front() { - if self.handle_packet(i, packet).await.is_err() { - continue 'client; - } - } - } - // Handle general world updates. - // Send out packets to each client. - - Ok(()) - } - pub async fn handle_packet(&mut self, client_index: usize, packet: Packet) -> Result<(), ()> { - use Packet::*; - trace!("Server.handle_packet()"); - debug!("Handling packet {:?}", packet); - let mut current_players = 0; - for client in &self.clients { - if client.state == NetworkClientState::Play { - current_players += 1; - } - } - let client = &mut self.clients[client_index]; - match packet { - SH00Handshake { - protocol_version, - server_address: _, - server_port: _, - next_state, - } => { - if protocol_version != CONFIG.protocol_version - && next_state == NetworkClientState::Login - { - debug!( - "Disconnecting client {} for mismatched protocols: {} (expected {})", - client.id, protocol_version, CONFIG.protocol_version - ); - client.disconnect(None).await; - return Err(()); - } - client.state = next_state; - } - SS00Request => { - let _ = client - .send_packet(CS00Response { - version_name: CONFIG.game_version.clone(), - protocol_version: CONFIG.protocol_version, - max_players: CONFIG.max_players, - current_players, - description: json!({ - "text": CONFIG.motd - }), - }) - .await; - } - SS01Ping { payload } => { - let _ = client.send_packet(CS01Pong { payload }).await; - debug!("Disconnecting client {}, SLP completed", client.id); - client.disconnect(None).await; - } - SL00LoginStart { username } => { - debug!( - "Client {} is connecting with username {}", - client.id, username - ); - if current_players >= CONFIG.max_players { - client - .disconnect(Some(json!({ "text": "Server full!" }))) - .await; - } - // TODO: Authentication - // TODO: Encryption - // TODO: Compression - let _ = client - .send_packet(CL02LoginSuccess { - uuid: client.id, - username, - }) - .await; - client.state = NetworkClientState::Play; - // Log them in. - let _ = client - .send_packet(CP4BSpawnPosition { - location: Position::new(0, 0, 0), - angle: 0.0, - }) - .await; - let _ = client - .send_packet(CP14WindowItems { - window_id: 0, - state_id: 0, - slots: vec![quartz_nbt::compound! {}; 44], - carried_item: quartz_nbt::compound! {}, - }) - .await; - let _ = client - .send_packet(CP38PlayerPositionAndLook { - x: (0.0, false), - y: (0.0, false), - z: (0.0, false), - yaw: (0.0, false), - pitch: (0.0, false), - teleport_id: 0, - dismount_vehicle: false, - }) - .await; - } - _ => unimplemented!("Handling unknown packet"), - } - Ok(()) - } - pub async fn shutdown(self) -> Result<(), ServerError> { - trace!("Server.shutdown()"); - Ok(()) - } -} diff --git a/crates/composition-protocol/Cargo.toml b/crates/composition-protocol/Cargo.toml index c0a435c..8cc17f1 100644 --- a/crates/composition-protocol/Cargo.toml +++ b/crates/composition-protocol/Cargo.toml @@ -2,13 +2,13 @@ name = "composition-protocol" version = "0.1.0" edition = "2021" +authors = ["Garen Tyler "] +description = "The Minecraft protocol implemented in a network-agnostic way" +license = "MIT" [dependencies] -nom = "7" -serde_json = "1.0.94" anyhow = "1.0.69" +tracing = { workspace = true } +byteorder = "1" +serde_json = "1.0.94" thiserror = "1.0.39" - -[[example]] -name = "packet_logger" -path = "examples/packet_logger/src/main.rs" diff --git a/crates/composition-protocol/examples/packet_logger/Cargo.toml b/crates/composition-protocol/examples/packet_logger/Cargo.toml deleted file mode 100644 index 7e54a97..0000000 --- a/crates/composition-protocol/examples/packet_logger/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "packet_logger" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1.0.69" -composition-protocol = { path = "../../" } -tokio = { version = "1.26.0", features = [ "full" ] } diff --git a/crates/composition-protocol/examples/packet_logger/src/main.rs b/crates/composition-protocol/examples/packet_logger/src/main.rs deleted file mode 100644 index 78d0adb..0000000 --- a/crates/composition-protocol/examples/packet_logger/src/main.rs +++ /dev/null @@ -1,117 +0,0 @@ -use tokio::net::{TcpListener, TcpStream}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let listener = TcpListener::bind("0.0.0.0:25566").await?; - - println!("Proxy listening on port 25566, expecting server at 25565"); - - loop { - let (stream, _) = listener.accept().await?; - process_client(stream).await?; - } -} - -async fn process_client(client: TcpStream) -> anyhow::Result<()> { - use composition_protocol::packet::GenericPacket; - client.readable().await?; - println!("Client connected"); - let server = TcpStream::connect("localhost:25565").await?; - server.writable().await?; - println!("Server connected"); - - let mut client_state = composition_protocol::ClientState::Handshake; - let mut last_client_data_time = std::time::Instant::now(); - let mut last_client_data = vec![]; - let mut last_server_data_time = std::time::Instant::now(); - let mut last_server_data = vec![]; - - loop { - let bytes = copy_bytes(&client, &server).await?; - if !bytes.is_empty() { - last_client_data_time = std::time::Instant::now(); - last_client_data.extend_from_slice(&bytes); - - if let Ok((d, packet)) = - GenericPacket::parse_uncompressed(&client_state, true, &last_client_data) - { - last_client_data = d.to_vec(); - println!("C -> S: {:?}", packet); - match packet { - GenericPacket::SH00Handshake(handshake) => { - client_state = handshake.next_state; - } - GenericPacket::CP17Disconnect(_) => { - break; - } - _ => {} - } - } - } - - let bytes = copy_bytes(&server, &client).await?; - if !bytes.is_empty() { - last_server_data_time = std::time::Instant::now(); - last_server_data.extend_from_slice(&bytes); - - if let Ok((d, packet)) = - GenericPacket::parse_uncompressed(&client_state, false, &last_server_data) - { - last_server_data = d.to_vec(); - println!("S -> C: {:?}", packet); - match packet { - GenericPacket::CS01PingResponse(_) | GenericPacket::CL00Disconnect(_) => { - break; - } - GenericPacket::CL02LoginSuccess(_) => { - client_state = composition_protocol::ClientState::Play; - } - _ => {} - } - } - } - - if last_client_data_time.elapsed() > std::time::Duration::from_secs(10) - || last_server_data_time.elapsed() > std::time::Duration::from_secs(10) - { - println!("timed out"); - break; - } - } - - Ok(()) -} - -async fn copy_bytes(from: &TcpStream, to: &TcpStream) -> anyhow::Result> { - let mut bytes = vec![]; - - loop { - // Read 8kb at a time - let mut buf = vec![0u8; 8192]; - - let num_bytes = match from.try_read(&mut buf) { - Ok(0) => break, - Ok(n) => n, - Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => { - break; - } - Err(e) => { - return Err(e.into()); - } - }; - - bytes.extend_from_slice(&buf[0..num_bytes]); - - match to.try_write(&buf[0..num_bytes]) { - Ok(_n) => {} - Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => { - break; - } - Err(e) => { - return Err(e.into()); - } - } - } - - Ok(bytes) -} diff --git a/crates/composition-protocol/src/lib.rs b/crates/composition-protocol/src/lib.rs index ed3dab4..8e24fea 100644 --- a/crates/composition-protocol/src/lib.rs +++ b/crates/composition-protocol/src/lib.rs @@ -38,12 +38,10 @@ impl TryFrom for Difficulty { #[derive(Error, Debug)] pub enum ProtocolError { - #[error("invalid packet data")] - InvalidPacket, + #[error("invalid data")] + InvalidData, #[error("not enough data")] NotEnoughData, - #[error("unexpected end of file")] - UnexpectedEOF, #[error("stream timed out")] Timeout, #[error("communicating to disconnected client")] diff --git a/crates/composition-protocol/src/packet/clientbound/login.rs b/crates/composition-protocol/src/packet/clientbound/login.rs index 2a0a533..a386581 100644 --- a/crates/composition-protocol/src/packet/clientbound/login.rs +++ b/crates/composition-protocol/src/packet/clientbound/login.rs @@ -1,74 +1,38 @@ -use crate::{ - packet::{GenericPacket, Packet, PacketId}, - util::{ - parse_json, parse_string, parse_uuid, parse_varint, serialize_json, serialize_string, - serialize_uuid, serialize_varint, - }, - Chat, Uuid, -}; +use crate::{util::*, Chat, Uuid}; #[derive(Clone, Debug, PartialEq)] pub struct CL00Disconnect { - reason: Chat, + pub reason: Chat, } -impl Packet for CL00Disconnect { - fn id() -> PacketId { - 0x00 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CL00Disconnect, + 0x00, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> ParseResult<'data, CL00Disconnect> { let (data, reason) = parse_json(data)?; Ok((data, CL00Disconnect { reason })) - } - fn serialize_body(&self) -> Vec { - serialize_json(&self.reason) - } -} -impl From for GenericPacket { - fn from(value: CL00Disconnect) -> Self { - GenericPacket::CL00Disconnect(value) - } -} -impl TryFrom for CL00Disconnect { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CL00Disconnect(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &CL00Disconnect| -> Vec { serialize_json(&packet.reason) } +); #[derive(Clone, Debug, PartialEq)] pub struct CL01EncryptionRequest { - server_id: String, - public_key: Vec, - verify_token: Vec, + pub server_id: String, + pub public_key: Vec, + pub verify_token: Vec, } -impl Packet for CL01EncryptionRequest { - fn id() -> PacketId { - 0x01 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CL01EncryptionRequest, + 0x01, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> ParseResult<'data, CL01EncryptionRequest> { let (data, server_id) = parse_string(data)?; let (data, public_key_len) = parse_varint(data)?; - let (data, public_key) = nom::bytes::streaming::take(public_key_len as usize)(data)?; + let (data, public_key) = take_bytes(public_key_len as usize)(data)?; let (data, verify_token_len) = parse_varint(data)?; - let (data, verify_token) = nom::bytes::streaming::take(verify_token_len as usize)(data)?; + let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?; Ok(( data, @@ -78,50 +42,35 @@ impl Packet for CL01EncryptionRequest { verify_token: verify_token.to_vec(), }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CL01EncryptionRequest| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_string(&self.server_id)); - output.extend_from_slice(&serialize_varint(self.public_key.len() as i32)); - output.extend_from_slice(&self.public_key); - output.extend_from_slice(&serialize_varint(self.verify_token.len() as i32)); - output.extend_from_slice(&self.verify_token); + output.extend_from_slice(&serialize_string(&packet.server_id)); + output.extend_from_slice(&serialize_varint(packet.public_key.len() as i32)); + output.extend_from_slice(&packet.public_key); + output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32)); + output.extend_from_slice(&packet.verify_token); output } -} -impl From for GenericPacket { - fn from(value: CL01EncryptionRequest) -> Self { - GenericPacket::CL01EncryptionRequest(value) - } -} -impl TryFrom for CL01EncryptionRequest { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CL01EncryptionRequest(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Clone, Debug, PartialEq)] pub struct CL02LoginSuccess { - uuid: Uuid, - username: String, - properties: Vec, + pub uuid: Uuid, + pub username: String, + pub properties: Vec, } #[derive(Clone, Debug, PartialEq)] pub struct CL02LoginSuccessProperty { - name: String, - value: String, - signature: Option, + pub name: String, + pub value: String, + pub signature: Option, } impl CL02LoginSuccessProperty { - pub fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> { + pub fn parse(data: &[u8]) -> ParseResult<'_, Self> { let (data, name) = parse_string(data)?; let (data, value) = parse_string(data)?; - let (data, is_signed) = nom::bytes::streaming::take(1usize)(data)?; + let (data, is_signed) = take_bytes(1usize)(data)?; if is_signed == [0x01] { let (data, signature) = parse_string(data)?; Ok(( @@ -157,18 +106,12 @@ impl CL02LoginSuccessProperty { output } } -impl Packet for CL02LoginSuccess { - fn id() -> PacketId { - 0x02 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CL02LoginSuccess, + 0x02, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> ParseResult<'data, CL02LoginSuccess> { let (data, uuid) = parse_uuid(data)?; let (data, username) = parse_string(data)?; let (mut data, properties_len) = parse_varint(data)?; @@ -187,91 +130,47 @@ impl Packet for CL02LoginSuccess { properties, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CL02LoginSuccess| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_uuid(&self.uuid)); - output.extend_from_slice(&serialize_string(&self.username)); - output.extend_from_slice(&serialize_varint(self.properties.len() as i32)); - for property in &self.properties { + output.extend_from_slice(&serialize_uuid(&packet.uuid)); + output.extend_from_slice(&serialize_string(&packet.username)); + output.extend_from_slice(&serialize_varint(packet.properties.len() as i32)); + for property in &packet.properties { output.extend_from_slice(&property.serialize()); } output } -} -impl From for GenericPacket { - fn from(value: CL02LoginSuccess) -> Self { - GenericPacket::CL02LoginSuccess(value) - } -} -impl TryFrom for CL02LoginSuccess { - type Error = (); +); - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CL02LoginSuccess(packet) => Ok(packet), - _ => Err(()), - } - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct CL03SetCompression { - threshold: i32, + pub threshold: i32, } -impl Packet for CL03SetCompression { - fn id() -> PacketId { - 0x03 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CL03SetCompression, + 0x03, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> ParseResult<'data, CL03SetCompression> { let (data, threshold) = parse_varint(data)?; Ok((data, CL03SetCompression { threshold })) - } - fn serialize_body(&self) -> Vec { - serialize_varint(self.threshold) - } -} -impl From for GenericPacket { - fn from(value: CL03SetCompression) -> Self { - GenericPacket::CL03SetCompression(value) - } -} -impl TryFrom for CL03SetCompression { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CL03SetCompression(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &CL03SetCompression| -> Vec { serialize_varint(packet.threshold) } +); #[derive(Clone, Debug, PartialEq)] pub struct CL04LoginPluginRequest { - message_id: i32, - channel: String, - data: Vec, + pub message_id: i32, + pub channel: String, + pub data: Vec, } -impl Packet for CL04LoginPluginRequest { - fn id() -> PacketId { - 0x04 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CL04LoginPluginRequest, + 0x04, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> ParseResult<'data, CL04LoginPluginRequest> { let (data, message_id) = parse_varint(data)?; let (data, channel) = parse_string(data)?; Ok(( @@ -282,27 +181,12 @@ impl Packet for CL04LoginPluginRequest { data: data.to_vec(), }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CL04LoginPluginRequest| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_varint(self.message_id)); - output.extend_from_slice(&serialize_string(&self.channel)); - output.extend_from_slice(&self.data); + output.extend_from_slice(&serialize_varint(packet.message_id)); + output.extend_from_slice(&serialize_string(&packet.channel)); + output.extend_from_slice(&packet.data); output } -} -impl From for GenericPacket { - fn from(value: CL04LoginPluginRequest) -> Self { - GenericPacket::CL04LoginPluginRequest(value) - } -} -impl TryFrom for CL04LoginPluginRequest { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CL04LoginPluginRequest(packet) => Ok(packet), - _ => Err(()), - } - } -} +); diff --git a/crates/composition-protocol/src/packet/clientbound/play.rs b/crates/composition-protocol/src/packet/clientbound/play.rs index fbd23c5..0421521 100644 --- a/crates/composition-protocol/src/packet/clientbound/play.rs +++ b/crates/composition-protocol/src/packet/clientbound/play.rs @@ -1,51 +1,58 @@ -use crate::{ - packet::{GenericPacket, Packet, PacketId}, - util::{ - parse_json, parse_uuid, parse_varint, serialize_json, serialize_uuid, serialize_varint, - Position, - }, - Chat, Difficulty, Uuid, -}; +use crate::{util::*, Chat, Difficulty, ProtocolError, Uuid}; +use byteorder::{BigEndian, ReadBytesExt}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct CP00SpawnEntity { - entity_id: i32, - entity_uuid: Uuid, - kind: i32, - x: f64, - y: f64, - z: f64, - pitch: u8, - yaw: u8, - head_yaw: u8, - data: i32, - velocity_x: i16, - velocity_y: i16, - velocity_z: i16, + pub entity_id: i32, + pub entity_uuid: Uuid, + pub kind: i32, + pub x: f64, + pub y: f64, + pub z: f64, + pub pitch: u8, + pub yaw: u8, + pub head_yaw: u8, + pub data: i32, + pub velocity_x: i16, + pub velocity_y: i16, + pub velocity_z: i16, } -impl Packet for CP00SpawnEntity { - fn id() -> PacketId { - 0x00 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CP00SpawnEntity, + 0x00, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP00SpawnEntity> { let (data, entity_id) = parse_varint(data)?; let (data, entity_uuid) = parse_uuid(data)?; let (data, kind) = parse_varint(data)?; - let (data, x) = nom::number::streaming::be_f64(data)?; - let (data, y) = nom::number::streaming::be_f64(data)?; - let (data, z) = nom::number::streaming::be_f64(data)?; - let (data, t) = nom::bytes::streaming::take(3usize)(data)?; + let (data, mut bytes) = take_bytes(8)(data)?; + let x = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(8)(data)?; + let y = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(8)(data)?; + let z = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, t) = take_bytes(3usize)(data)?; let (data, d) = parse_varint(data)?; - let (data, velocity_x) = nom::number::streaming::be_i16(data)?; - let (data, velocity_y) = nom::number::streaming::be_i16(data)?; - let (data, velocity_z) = nom::number::streaming::be_i16(data)?; + let (data, mut bytes) = take_bytes(2)(data)?; + let velocity_x = bytes + .read_i16::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(2)(data)?; + let velocity_y = bytes + .read_i16::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(2)(data)?; + let velocity_z = bytes + .read_i16::() + .map_err(|_| ProtocolError::NotEnoughData)?; + Ok(( data, CP00SpawnEntity { @@ -64,64 +71,43 @@ impl Packet for CP00SpawnEntity { velocity_z, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CP00SpawnEntity| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_varint(self.entity_id)); - output.extend_from_slice(&serialize_uuid(&self.entity_uuid)); - output.extend_from_slice(&serialize_varint(self.kind)); - output.extend_from_slice(&self.x.to_be_bytes()); - output.extend_from_slice(&self.y.to_be_bytes()); - output.extend_from_slice(&self.z.to_be_bytes()); - output.push(self.pitch); - output.push(self.yaw); - output.push(self.head_yaw); - output.extend_from_slice(&serialize_varint(self.data)); - output.extend_from_slice(&self.velocity_x.to_be_bytes()); - output.extend_from_slice(&self.velocity_y.to_be_bytes()); - output.extend_from_slice(&self.velocity_z.to_be_bytes()); + output.extend_from_slice(&serialize_varint(packet.entity_id)); + output.extend_from_slice(&serialize_uuid(&packet.entity_uuid)); + output.extend_from_slice(&serialize_varint(packet.kind)); + output.extend_from_slice(&packet.x.to_be_bytes()); + output.extend_from_slice(&packet.y.to_be_bytes()); + output.extend_from_slice(&packet.z.to_be_bytes()); + output.push(packet.pitch); + output.push(packet.yaw); + output.push(packet.head_yaw); + output.extend_from_slice(&serialize_varint(packet.data)); + output.extend_from_slice(&packet.velocity_x.to_be_bytes()); + output.extend_from_slice(&packet.velocity_y.to_be_bytes()); + output.extend_from_slice(&packet.velocity_z.to_be_bytes()); output } -} -impl From for GenericPacket { - fn from(value: CP00SpawnEntity) -> Self { - GenericPacket::CP00SpawnEntity(value) - } -} -impl TryFrom for CP00SpawnEntity { - type Error = (); +); - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP00SpawnEntity(packet) => Ok(packet), - _ => Err(()), - } - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct CP0BChangeDifficulty { - difficulty: Difficulty, - is_locked: bool, + pub difficulty: Difficulty, + pub is_locked: bool, } -impl Packet for CP0BChangeDifficulty { - fn id() -> PacketId { - 0x0b - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, difficulty) = nom::number::streaming::be_u8(data)?; - let difficulty: Difficulty = difficulty +crate::packet::packet!( + CP0BChangeDifficulty, + 0x0b, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP0BChangeDifficulty> { + let (data, difficulty) = take_bytes(1)(data)?; + let difficulty: Difficulty = difficulty[0] .try_into() .expect("TODO: handle incorrect difficulty"); - let (data, is_locked) = nom::number::streaming::be_u8(data)?; - let is_locked = is_locked > 0; + let (data, is_locked) = take_bytes(1)(data)?; + let is_locked = is_locked[0] > 0; Ok(( data, CP0BChangeDifficulty { @@ -129,131 +115,73 @@ impl Packet for CP0BChangeDifficulty { is_locked, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CP0BChangeDifficulty| -> Vec { let mut output = vec![]; - output.push(self.difficulty as u8); - output.push(if self.is_locked { 0x01 } else { 0x00 }); + output.push(packet.difficulty as u8); + output.push(if packet.is_locked { 0x01 } else { 0x00 }); output } -} -impl From for GenericPacket { - fn from(value: CP0BChangeDifficulty) -> Self { - GenericPacket::CP0BChangeDifficulty(value) - } -} -impl TryFrom for CP0BChangeDifficulty { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP0BChangeDifficulty(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Clone, Debug, PartialEq)] pub struct CP17Disconnect { - reason: Chat, + pub reason: Chat, } -impl Packet for CP17Disconnect { - fn id() -> PacketId { - 0x17 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CP17Disconnect, + 0x17, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP17Disconnect> { let (data, reason) = parse_json(data)?; Ok((data, CP17Disconnect { reason })) - } - fn serialize_body(&self) -> Vec { - serialize_json(&self.reason) - } -} -impl From for GenericPacket { - fn from(value: CP17Disconnect) -> Self { - GenericPacket::CP17Disconnect(value) - } -} -impl TryFrom for CP17Disconnect { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP17Disconnect(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &CP17Disconnect| -> Vec { serialize_json(&packet.reason) } +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct CP1FKeepAlive { - payload: i64, + pub payload: i64, } -impl Packet for CP1FKeepAlive { - fn id() -> PacketId { - 0x1f - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, payload) = nom::number::streaming::be_i64(data)?; +crate::packet::packet!( + CP1FKeepAlive, + 0x1f, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP1FKeepAlive> { + let (data, mut bytes) = take_bytes(8)(data)?; + let payload = bytes + .read_i64::() + .map_err(|_| ProtocolError::NotEnoughData)?; Ok((data, CP1FKeepAlive { payload })) - } - fn serialize_body(&self) -> Vec { - self.payload.to_be_bytes().to_vec() - } -} -impl From for GenericPacket { - fn from(value: CP1FKeepAlive) -> Self { - GenericPacket::CP1FKeepAlive(value) - } -} -impl TryFrom for CP1FKeepAlive { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP1FKeepAlive(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &CP1FKeepAlive| -> Vec { packet.payload.to_be_bytes().to_vec() } +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct CP21WorldEvent { - event: i32, - location: Position, - data: i32, - disable_relative_volume: bool, + pub event: i32, + pub location: Position, + pub data: i32, + pub disable_relative_volume: bool, } -impl Packet for CP21WorldEvent { - fn id() -> PacketId { - 0x21 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, event) = nom::number::streaming::be_i32(data)?; +crate::packet::packet!( + CP21WorldEvent, + 0x21, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP21WorldEvent> { + let (data, mut bytes) = take_bytes(4)(data)?; + let event = bytes + .read_i32::() + .map_err(|_| ProtocolError::NotEnoughData)?; let (data, location) = Position::parse(data)?; - let (data, d) = nom::number::streaming::be_i32(data)?; - let (data, disable_relative_volume) = nom::bytes::streaming::take(1usize)(data)?; + let (data, mut bytes) = take_bytes(4)(data)?; + let d = bytes + .read_i32::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, disable_relative_volume) = take_bytes(1usize)(data)?; let disable_relative_volume = disable_relative_volume == [0x01]; Ok(( data, @@ -264,59 +192,47 @@ impl Packet for CP21WorldEvent { disable_relative_volume, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CP21WorldEvent| -> Vec { let mut output = vec![]; - output.extend_from_slice(&self.event.to_be_bytes()); - output.extend_from_slice(&self.location.serialize()); - output.extend_from_slice(&self.data.to_be_bytes()); - output.push(if self.disable_relative_volume { + output.extend_from_slice(&packet.event.to_be_bytes()); + output.extend_from_slice(&packet.location.serialize()); + output.extend_from_slice(&packet.data.to_be_bytes()); + output.push(if packet.disable_relative_volume { 0x01 } else { 0x00 }); output } -} -impl From for GenericPacket { - fn from(value: CP21WorldEvent) -> Self { - GenericPacket::CP21WorldEvent(value) - } -} -impl TryFrom for CP21WorldEvent { - type Error = (); +); - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP21WorldEvent(packet) => Ok(packet), - _ => Err(()), - } - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct CP50SetEntityVelocity { - entity_id: i32, - velocity_x: i16, - velocity_y: i16, - velocity_z: i16, + pub entity_id: i32, + pub velocity_x: i16, + pub velocity_y: i16, + pub velocity_z: i16, } -impl Packet for CP50SetEntityVelocity { - fn id() -> PacketId { - 0x50 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CP50SetEntityVelocity, + 0x50, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP50SetEntityVelocity> { let (data, entity_id) = parse_varint(data)?; - let (data, velocity_x) = nom::number::streaming::be_i16(data)?; - let (data, velocity_y) = nom::number::streaming::be_i16(data)?; - let (data, velocity_z) = nom::number::streaming::be_i16(data)?; + let (data, mut bytes) = take_bytes(2)(data)?; + let velocity_x = bytes + .read_i16::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(2)(data)?; + let velocity_y = bytes + .read_i16::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(2)(data)?; + let velocity_z = bytes + .read_i16::() + .map_err(|_| ProtocolError::NotEnoughData)?; Ok(( data, CP50SetEntityVelocity { @@ -326,51 +242,33 @@ impl Packet for CP50SetEntityVelocity { velocity_z, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CP50SetEntityVelocity| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_varint(self.entity_id)); - output.extend_from_slice(&self.velocity_x.to_be_bytes()); - output.extend_from_slice(&self.velocity_y.to_be_bytes()); - output.extend_from_slice(&self.velocity_z.to_be_bytes()); + output.extend_from_slice(&serialize_varint(packet.entity_id)); + output.extend_from_slice(&packet.velocity_x.to_be_bytes()); + output.extend_from_slice(&packet.velocity_y.to_be_bytes()); + output.extend_from_slice(&packet.velocity_z.to_be_bytes()); output } -} -impl From for GenericPacket { - fn from(value: CP50SetEntityVelocity) -> Self { - GenericPacket::CP50SetEntityVelocity(value) - } -} -impl TryFrom for CP50SetEntityVelocity { - type Error = (); +); - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP50SetEntityVelocity(packet) => Ok(packet), - _ => Err(()), - } - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct CP52SetExperience { - experience_bar: f32, - total_experience: i32, - level: i32, + pub experience_bar: f32, + pub total_experience: i32, + pub level: i32, } -impl Packet for CP52SetExperience { - fn id() -> PacketId { - 0x52 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, experience_bar) = nom::number::streaming::be_f32(data)?; +crate::packet::packet!( + CP52SetExperience, + 0x52, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP52SetExperience> { + let (data, mut bytes) = take_bytes(4)(data)?; + let experience_bar = bytes + .read_f32::() + .map_err(|_| ProtocolError::NotEnoughData)?; let (data, total_experience) = parse_varint(data)?; let (data, level) = parse_varint(data)?; Ok(( @@ -381,65 +279,46 @@ impl Packet for CP52SetExperience { level, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CP52SetExperience| -> Vec { let mut output = vec![]; - output.extend_from_slice(&self.experience_bar.to_be_bytes()); - output.extend_from_slice(&serialize_varint(self.total_experience)); - output.extend_from_slice(&serialize_varint(self.level)); + output.extend_from_slice(&packet.experience_bar.to_be_bytes()); + output.extend_from_slice(&serialize_varint(packet.total_experience)); + output.extend_from_slice(&serialize_varint(packet.level)); output } -} -impl From for GenericPacket { - fn from(value: CP52SetExperience) -> Self { - GenericPacket::CP52SetExperience(value) - } -} -impl TryFrom for CP52SetExperience { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP52SetExperience(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Clone, Debug, PartialEq)] pub struct CP68EntityEffect { - entity_id: i32, - effect_id: i32, - amplifier: i8, - duration: i32, - is_ambient: bool, - show_particles: bool, - show_icon: bool, - has_factor_data: bool, - // TODO: factor_codec: NBT + pub entity_id: i32, + pub effect_id: i32, + pub amplifier: i8, + pub duration: i32, + pub is_ambient: bool, + pub show_particles: bool, + pub show_icon: bool, + pub has_factor_data: bool, + // TODO: pub factor_codec: NBT } -impl Packet for CP68EntityEffect { - fn id() -> PacketId { - 0x68 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CP68EntityEffect, + 0x68, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> ParseResult<'data, CP68EntityEffect> { let (data, entity_id) = parse_varint(data)?; let (data, effect_id) = parse_varint(data)?; - let (data, amplifier) = nom::number::streaming::be_i8(data)?; + let (data, amplifier) = take_bytes(1)(data)?; + let amplifier = amplifier[0] as i8; let (data, duration) = parse_varint(data)?; - let (data, flags) = nom::number::streaming::be_i8(data)?; + let (data, flags) = take_bytes(1)(data)?; + let flags = flags[0] as i8; let is_ambient = flags & 0x01 > 0; let show_particles = flags & 0x02 > 0; let show_icon = flags & 0x04 > 0; - let (data, has_factor_data) = nom::number::streaming::be_u8(data)?; - let has_factor_data = has_factor_data > 0; + let (data, has_factor_data) = take_bytes(1)(data)?; + let has_factor_data = has_factor_data[0] > 0; // TODO: factor_codec Ok(( @@ -455,40 +334,25 @@ impl Packet for CP68EntityEffect { has_factor_data, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &CP68EntityEffect| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_varint(self.entity_id)); - output.extend_from_slice(&serialize_varint(self.effect_id)); - output.push(self.amplifier as u8); - output.extend_from_slice(&serialize_varint(self.duration)); + output.extend_from_slice(&serialize_varint(packet.entity_id)); + output.extend_from_slice(&serialize_varint(packet.effect_id)); + output.push(packet.amplifier as u8); + output.extend_from_slice(&serialize_varint(packet.duration)); let mut flags = 0x00i8; - if self.is_ambient { + if packet.is_ambient { flags |= 0x01; } - if self.show_particles { + if packet.show_particles { flags |= 0x02; } - if self.show_icon { + if packet.show_icon { flags |= 0x04; } output.push(flags as u8); // TODO: factor_codec output } -} -impl From for GenericPacket { - fn from(value: CP68EntityEffect) -> Self { - GenericPacket::CP68EntityEffect(value) - } -} -impl TryFrom for CP68EntityEffect { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CP68EntityEffect(packet) => Ok(packet), - _ => Err(()), - } - } -} +); diff --git a/crates/composition-protocol/src/packet/clientbound/status.rs b/crates/composition-protocol/src/packet/clientbound/status.rs index 257ef7b..64da8ea 100644 --- a/crates/composition-protocol/src/packet/clientbound/status.rs +++ b/crates/composition-protocol/src/packet/clientbound/status.rs @@ -1,83 +1,37 @@ -use crate::{ - packet::{GenericPacket, Packet, PacketId}, - util::{parse_json, serialize_json}, - Json, -}; +use crate::{util::*, Json, ProtocolError}; +use byteorder::{BigEndian, ReadBytesExt}; #[derive(Clone, Debug, PartialEq)] pub struct CS00StatusResponse { - response: Json, + pub response: Json, } -impl Packet for CS00StatusResponse { - fn id() -> PacketId { - 0x00 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Status - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + CS00StatusResponse, + 0x00, + crate::ClientState::Status, + false, + |data: &'data [u8]| -> ParseResult<'data, CS00StatusResponse> { let (data, response) = parse_json(data)?; Ok((data, CS00StatusResponse { response })) - } - fn serialize_body(&self) -> Vec { - serialize_json(&self.response) - } -} -impl From for GenericPacket { - fn from(value: CS00StatusResponse) -> Self { - GenericPacket::CS00StatusResponse(value) - } -} -impl TryFrom for CS00StatusResponse { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CS00StatusResponse(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &CS00StatusResponse| -> Vec { serialize_json(&packet.response) } +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct CS01PingResponse { - payload: i64, + pub payload: i64, } -impl Packet for CS01PingResponse { - fn id() -> PacketId { - 0x01 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Status - } - fn serverbound() -> bool { - false - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, payload) = nom::number::streaming::be_i64(data)?; +crate::packet::packet!( + CS01PingResponse, + 0x01, + crate::ClientState::Status, + false, + |data: &'data [u8]| -> ParseResult<'data, CS01PingResponse> { + let (data, mut bytes) = take_bytes(8)(data)?; + let payload = bytes + .read_i64::() + .map_err(|_| ProtocolError::NotEnoughData)?; Ok((data, CS01PingResponse { payload })) - } - fn serialize_body(&self) -> Vec { - self.payload.to_be_bytes().to_vec() - } -} -impl From for GenericPacket { - fn from(value: CS01PingResponse) -> Self { - GenericPacket::CS01PingResponse(value) - } -} -impl TryFrom for CS01PingResponse { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::CS01PingResponse(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &CS01PingResponse| -> Vec { packet.payload.to_be_bytes().to_vec() } +); diff --git a/crates/composition-protocol/src/packet/mod.rs b/crates/composition-protocol/src/packet/mod.rs index 897ec1d..1a9337c 100644 --- a/crates/composition-protocol/src/packet/mod.rs +++ b/crates/composition-protocol/src/packet/mod.rs @@ -1,288 +1,168 @@ pub mod clientbound; pub mod serverbound; -use crate::ClientState; - pub type PacketId = i32; -pub trait Packet: TryFrom + Into + std::fmt::Debug { - fn id() -> PacketId; - fn client_state() -> crate::ClientState; - fn serverbound() -> bool; +pub trait Packet: std::fmt::Debug + Clone + TryFrom + Into { + const ID: PacketId; + const CLIENT_STATE: crate::ClientState; + const IS_SERVERBOUND: bool; // The slice given should only be the exact amount of data in the body. - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> + fn parse_body(data: &[u8]) -> crate::util::ParseResult<'_, Self> where Self: Sized; fn serialize_body(&self) -> Vec; } -#[derive(Clone, Debug, PartialEq)] -pub enum GenericPacket { +macro_rules! generic_packet { + ($($packet_type: ident),*) => { + #[derive(Clone, Debug, PartialEq)] + pub enum GenericPacket { + $( + $packet_type($packet_type), + )* + } + impl GenericPacket { + #[tracing::instrument] + pub fn parse_uncompressed<'data>( + client_state: crate::ClientState, + is_serverbound: bool, + data: &'data [u8] + ) -> crate::util::ParseResult<'data, Self> { + tracing::trace!( + "GenericPacket::parse_uncompressed: {:?} {} {:?}", + client_state, + is_serverbound, + data + ); + + let (data, packet_length) = crate::util::parse_varint(data)?; + let (data, packet_data) = crate::util::take_bytes(packet_length as usize)(data)?; + + let (packet_data, packet_id) = crate::util::parse_varint(packet_data)?; + let (_packet_data, packet_body) = + Self::parse_body(client_state, packet_id, is_serverbound, packet_data)?; + + // if !packet_data.is_empty() { + // println!("Packet data not empty after parsing!"); + // } + + Ok((data, packet_body)) + } + + #[tracing::instrument] + pub fn parse_body<'data>( + client_state: crate::ClientState, + packet_id: crate::packet::PacketId, + is_serverbound: bool, + data: &'data [u8], + ) -> crate::util::ParseResult<'data, Self> { + tracing::trace!( + "GenericPacket::parse_body: {:?} {} {}", + client_state, + packet_id, + is_serverbound + ); + match (client_state, packet_id, is_serverbound) { + $( + ($packet_type::CLIENT_STATE, $packet_type::ID, $packet_type::IS_SERVERBOUND) => $packet_type::parse_body(data).map(|(data, packet)| (data, Into::::into(packet))), + )* + _ => Ok((data, Self::UnimplementedPacket(UnimplementedPacket(packet_id)))), + } + } + + #[tracing::instrument] + pub fn serialize(&self) -> (crate::packet::PacketId, Vec) { + tracing::trace!("GenericPacket::serialize: {:?}", self); + match self { + $( + Self::$packet_type(packet) => ($packet_type::ID, packet.serialize_body()), + )* + } + } + } + }; +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct UnimplementedPacket(i32); +packet!( + UnimplementedPacket, + 0x00, + crate::ClientState::Disconnected, + false, + |data: &'data [u8]| -> crate::util::ParseResult<'data, UnimplementedPacket> { + Ok((data, UnimplementedPacket(0i32))) + }, + |_packet: &UnimplementedPacket| -> Vec { vec![] } +); + +use clientbound::*; +use serverbound::*; +generic_packet!( + UnimplementedPacket, // Handshake - SH00Handshake(serverbound::SH00Handshake), - + SH00Handshake, // Status - SS00StatusRequest(serverbound::SS00StatusRequest), - SS01PingRequest(serverbound::SS01PingRequest), - - CS00StatusResponse(clientbound::CS00StatusResponse), - CS01PingResponse(clientbound::CS01PingResponse), - + SS00StatusRequest, + SS01PingRequest, + CS00StatusResponse, + CS01PingResponse, // Login - SL00LoginStart(serverbound::SL00LoginStart), - SL01EncryptionResponse(serverbound::SL01EncryptionResponse), - SL02LoginPluginResponse(serverbound::SL02LoginPluginResponse), - - CL00Disconnect(clientbound::CL00Disconnect), - CL01EncryptionRequest(clientbound::CL01EncryptionRequest), - CL02LoginSuccess(clientbound::CL02LoginSuccess), - CL03SetCompression(clientbound::CL03SetCompression), - CL04LoginPluginRequest(clientbound::CL04LoginPluginRequest), - + SL00LoginStart, + SL01EncryptionResponse, + SL02LoginPluginResponse, + CL00Disconnect, + CL01EncryptionRequest, + CL02LoginSuccess, + CL03SetCompression, + CL04LoginPluginRequest, // Play - SP08CommandSuggestionsRequest(serverbound::SP08CommandSuggestionsRequest), - SP11KeepAlive(serverbound::SP11KeepAlive), - SP13SetPlayerPosition(serverbound::SP13SetPlayerPosition), - SP14SetPlayerPositionAndRotation(serverbound::SP14SetPlayerPositionAndRotation), - SP15SetPlayerRotation(serverbound::SP15SetPlayerRotation), + SP08CommandSuggestionsRequest, + SP11KeepAlive, + SP13SetPlayerPosition, + SP14SetPlayerPositionAndRotation, + SP15SetPlayerRotation, + CP00SpawnEntity, + CP0BChangeDifficulty, + CP17Disconnect, + CP1FKeepAlive, + CP21WorldEvent, + CP50SetEntityVelocity, + CP52SetExperience, + CP68EntityEffect +); - CP00SpawnEntity(clientbound::CP00SpawnEntity), - CP0BChangeDifficulty(clientbound::CP0BChangeDifficulty), - CP17Disconnect(clientbound::CP17Disconnect), - CP1FKeepAlive(clientbound::CP1FKeepAlive), - CP21WorldEvent(clientbound::CP21WorldEvent), - CP50SetEntityVelocity(clientbound::CP50SetEntityVelocity), - CP52SetExperience(clientbound::CP52SetExperience), - CP68EntityEffect(clientbound::CP68EntityEffect), +macro_rules! packet { + ($packet_type: ident, $id: literal, $client_state: expr, $serverbound: literal, $parse_body: expr, $serialize_body: expr) => { + impl crate::packet::Packet for $packet_type { + const ID: crate::packet::PacketId = $id; + const CLIENT_STATE: crate::ClientState = $client_state; + const IS_SERVERBOUND: bool = $serverbound; - // Until we implement all the packets this will stay. - UnimplementedPacket(PacketId), -} -impl GenericPacket { - pub fn parse_uncompressed<'data>( - client_state: &ClientState, - serverbound: bool, - data: &'data [u8], - ) -> nom::IResult<&'data [u8], Self> { - let (data, packet_length) = crate::util::parse_varint(data)?; - let (data, packet_data) = nom::bytes::streaming::take(packet_length as usize)(data)?; - - let (packet_data, packet_id) = crate::util::parse_varint(packet_data)?; - let (_packet_data, packet_body) = - Self::parse_body(client_state, packet_id, serverbound, packet_data)?; - - // if !packet_data.is_empty() { - // println!("Packet data not empty after parsing!"); - // } - - Ok((data, packet_body)) - } - pub fn parse_body<'data>( - client_state: &ClientState, - packet_id: PacketId, - serverbound: bool, - data: &'data [u8], - ) -> nom::IResult<&'data [u8], Self> { - fn mapper + Sized>( - (data, packet): (&[u8], P), - ) -> (&[u8], GenericPacket) { - (data, Into::::into(packet)) - } - - match (client_state, packet_id, serverbound) { - // Handshake - (ClientState::Handshake, 0x00, true) => { - serverbound::SH00Handshake::parse_body(data).map(mapper) - } - - // Status - (ClientState::Status, 0x00, true) => { - serverbound::SS00StatusRequest::parse_body(data).map(mapper) - } - (ClientState::Status, 0x01, true) => { - serverbound::SS00StatusRequest::parse_body(data).map(mapper) - } - - (ClientState::Status, 0x00, false) => { - clientbound::CS00StatusResponse::parse_body(data).map(mapper) - } - (ClientState::Status, 0x01, false) => { - clientbound::CS01PingResponse::parse_body(data).map(mapper) - } - - // Login - (ClientState::Login, 0x00, true) => { - serverbound::SL00LoginStart::parse_body(data).map(mapper) - } - (ClientState::Login, 0x01, true) => { - serverbound::SL01EncryptionResponse::parse_body(data).map(mapper) - } - (ClientState::Login, 0x02, true) => { - serverbound::SL02LoginPluginResponse::parse_body(data).map(mapper) - } - - (ClientState::Login, 0x00, false) => { - clientbound::CL00Disconnect::parse_body(data).map(mapper) - } - (ClientState::Login, 0x01, false) => { - clientbound::CL01EncryptionRequest::parse_body(data).map(mapper) - } - (ClientState::Login, 0x02, false) => { - clientbound::CL02LoginSuccess::parse_body(data).map(mapper) - } - (ClientState::Login, 0x03, false) => { - clientbound::CL03SetCompression::parse_body(data).map(mapper) - } - (ClientState::Login, 0x04, false) => { - clientbound::CL04LoginPluginRequest::parse_body(data).map(mapper) - } - - // Play - (ClientState::Play, 0x08, true) => { - serverbound::SP08CommandSuggestionsRequest::parse_body(data).map(mapper) - } - (ClientState::Play, 0x11, true) => { - serverbound::SP11KeepAlive::parse_body(data).map(mapper) - } - (ClientState::Play, 0x13, true) => { - serverbound::SP13SetPlayerPosition::parse_body(data).map(mapper) - } - (ClientState::Play, 0x14, true) => { - serverbound::SP14SetPlayerPositionAndRotation::parse_body(data).map(mapper) - } - (ClientState::Play, 0x15, true) => { - serverbound::SP15SetPlayerRotation::parse_body(data).map(mapper) - } - - (ClientState::Play, 0x00, false) => { - clientbound::CP00SpawnEntity::parse_body(data).map(mapper) - } - (ClientState::Play, 0x0b, false) => { - clientbound::CP0BChangeDifficulty::parse_body(data).map(mapper) - } - (ClientState::Play, 0x17, false) => { - clientbound::CP17Disconnect::parse_body(data).map(mapper) - } - (ClientState::Play, 0x1f, false) => { - clientbound::CP1FKeepAlive::parse_body(data).map(mapper) - } - (ClientState::Play, 0x21, false) => { - clientbound::CP21WorldEvent::parse_body(data).map(mapper) - } - (ClientState::Play, 0x50, false) => { - clientbound::CP50SetEntityVelocity::parse_body(data).map(mapper) - } - (ClientState::Play, 0x52, false) => { - clientbound::CP52SetExperience::parse_body(data).map(mapper) - } - (ClientState::Play, 0x68, false) => { - clientbound::CP68EntityEffect::parse_body(data).map(mapper) - } - - _ => Ok((&data[0..0], GenericPacket::UnimplementedPacket(packet_id))), - // Invalid packet - // _ => Err(nom::Err::Failure(nom::error::Error::new( - // data, - // nom::error::ErrorKind::Verify, - // ))), - } - } - pub fn serialize(&self) -> (PacketId, Vec) { - use GenericPacket::*; - - match self { - // Handshake - SH00Handshake(packet) => (serverbound::SH00Handshake::id(), packet.serialize_body()), - - // Status - SS00StatusRequest(packet) => ( - serverbound::SS00StatusRequest::id(), - packet.serialize_body(), - ), - SS01PingRequest(packet) => { - (serverbound::SS01PingRequest::id(), packet.serialize_body()) - } - - CS00StatusResponse(packet) => ( - clientbound::CS00StatusResponse::id(), - packet.serialize_body(), - ), - CS01PingResponse(packet) => { - (clientbound::CS01PingResponse::id(), packet.serialize_body()) - } - - // Login - SL00LoginStart(packet) => (serverbound::SL00LoginStart::id(), packet.serialize_body()), - SL01EncryptionResponse(packet) => ( - serverbound::SL01EncryptionResponse::id(), - packet.serialize_body(), - ), - SL02LoginPluginResponse(packet) => ( - serverbound::SL02LoginPluginResponse::id(), - packet.serialize_body(), - ), - - CL00Disconnect(packet) => (clientbound::CL00Disconnect::id(), packet.serialize_body()), - CL01EncryptionRequest(packet) => ( - clientbound::CL01EncryptionRequest::id(), - packet.serialize_body(), - ), - CL02LoginSuccess(packet) => { - (clientbound::CL02LoginSuccess::id(), packet.serialize_body()) - } - CL03SetCompression(packet) => ( - clientbound::CL03SetCompression::id(), - packet.serialize_body(), - ), - CL04LoginPluginRequest(packet) => ( - clientbound::CL04LoginPluginRequest::id(), - packet.serialize_body(), - ), - - // Play - SP08CommandSuggestionsRequest(packet) => ( - serverbound::SP08CommandSuggestionsRequest::id(), - packet.serialize_body(), - ), - SP11KeepAlive(packet) => (serverbound::SP11KeepAlive::id(), packet.serialize_body()), - SP13SetPlayerPosition(packet) => ( - serverbound::SP13SetPlayerPosition::id(), - packet.serialize_body(), - ), - SP14SetPlayerPositionAndRotation(packet) => ( - serverbound::SP14SetPlayerPositionAndRotation::id(), - packet.serialize_body(), - ), - SP15SetPlayerRotation(packet) => ( - serverbound::SP15SetPlayerRotation::id(), - packet.serialize_body(), - ), - - CP00SpawnEntity(packet) => { - (clientbound::CP00SpawnEntity::id(), packet.serialize_body()) - } - CP0BChangeDifficulty(packet) => ( - clientbound::CP0BChangeDifficulty::id(), - packet.serialize_body(), - ), - CP17Disconnect(packet) => (clientbound::CP17Disconnect::id(), packet.serialize_body()), - CP1FKeepAlive(packet) => (clientbound::CP1FKeepAlive::id(), packet.serialize_body()), - CP21WorldEvent(packet) => (clientbound::CP21WorldEvent::id(), packet.serialize_body()), - CP50SetEntityVelocity(packet) => ( - clientbound::CP50SetEntityVelocity::id(), - packet.serialize_body(), - ), - CP52SetExperience(packet) => ( - clientbound::CP52SetExperience::id(), - packet.serialize_body(), - ), - CP68EntityEffect(packet) => { - (clientbound::CP68EntityEffect::id(), packet.serialize_body()) - } - - // Unimplemented packets get no body. - UnimplementedPacket(packet_id) => (*packet_id, vec![]), - } - } + fn parse_body<'data>(data: &'data [u8]) -> crate::util::ParseResult<'_, $packet_type> { + $parse_body(data) + } + fn serialize_body(&self) -> Vec { + $serialize_body(self) + } + } + impl From<$packet_type> for crate::packet::GenericPacket { + fn from(value: $packet_type) -> Self { + crate::packet::GenericPacket::$packet_type(value) + } + } + impl TryFrom for $packet_type { + type Error = (); + + fn try_from(value: crate::packet::GenericPacket) -> Result { + match value { + crate::packet::GenericPacket::$packet_type(packet) => Ok(packet), + _ => Err(()), + } + } + } + }; } +pub(crate) use packet; diff --git a/crates/composition-protocol/src/packet/serverbound/handshake.rs b/crates/composition-protocol/src/packet/serverbound/handshake.rs index f3ccbb8..43ce7ca 100644 --- a/crates/composition-protocol/src/packet/serverbound/handshake.rs +++ b/crates/composition-protocol/src/packet/serverbound/handshake.rs @@ -1,8 +1,5 @@ -use crate::{ - packet::{GenericPacket, Packet, PacketId}, - util::{parse_string, parse_varint, serialize_string, serialize_varint}, - ClientState, -}; +use crate::{util::*, ClientState, ProtocolError}; +use byteorder::{BigEndian, ReadBytesExt}; #[derive(Clone, Debug, PartialEq)] pub struct SH00Handshake { @@ -11,21 +8,18 @@ pub struct SH00Handshake { pub server_port: u16, pub next_state: ClientState, } -impl Packet for SH00Handshake { - fn id() -> PacketId { - 0x00 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Handshake - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + SH00Handshake, + 0x00, + ClientState::Handshake, + true, + |data: &'data [u8]| -> ParseResult<'data, SH00Handshake> { let (data, protocol_version) = parse_varint(data)?; let (data, server_address) = parse_string(data)?; - let (data, server_port) = nom::number::streaming::be_u16(data)?; + let (data, mut bytes) = take_bytes(2)(data)?; + let server_port = bytes + .read_u16::() + .map_err(|_| ProtocolError::NotEnoughData)?; let (data, next_state) = parse_varint(data)?; Ok(( @@ -41,32 +35,17 @@ impl Packet for SH00Handshake { }, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SH00Handshake| -> Vec { let mut output = vec![]; - output.extend_from_slice(&self.protocol_version.to_be_bytes()); - output.extend_from_slice(&serialize_string(&self.server_address)); - output.extend_from_slice(&self.server_port.to_be_bytes()); - output.extend_from_slice(&serialize_varint(match self.next_state { + output.extend_from_slice(&packet.protocol_version.to_be_bytes()); + output.extend_from_slice(&serialize_string(&packet.server_address)); + output.extend_from_slice(&packet.server_port.to_be_bytes()); + output.extend_from_slice(&serialize_varint(match packet.next_state { ClientState::Status => 0x01, ClientState::Login => 0x02, _ => panic!("invalid SH00Handshake next_state"), })); output } -} -impl From for GenericPacket { - fn from(value: SH00Handshake) -> Self { - GenericPacket::SH00Handshake(value) - } -} -impl TryFrom for SH00Handshake { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SH00Handshake(packet) => Ok(packet), - _ => Err(()), - } - } -} +); diff --git a/crates/composition-protocol/src/packet/serverbound/login.rs b/crates/composition-protocol/src/packet/serverbound/login.rs index 85c786d..30d45b6 100644 --- a/crates/composition-protocol/src/packet/serverbound/login.rs +++ b/crates/composition-protocol/src/packet/serverbound/login.rs @@ -1,30 +1,18 @@ -use crate::{ - packet::{GenericPacket, Packet, PacketId}, - util::{ - parse_string, parse_uuid, parse_varint, serialize_string, serialize_uuid, serialize_varint, - }, - Uuid, -}; +use crate::{util::*, Uuid}; #[derive(Clone, Debug, PartialEq)] pub struct SL00LoginStart { - name: String, - uuid: Option, + pub name: String, + pub uuid: Option, } -impl Packet for SL00LoginStart { - fn id() -> PacketId { - 0x00 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + SL00LoginStart, + 0x00, + crate::ClientState::Login, + true, + |data: &'data [u8]| -> ParseResult<'data, SL00LoginStart> { let (data, name) = parse_string(data)?; - let (data, has_uuid) = nom::bytes::streaming::take(1usize)(data)?; + let (data, has_uuid) = take_bytes(1usize)(data)?; if has_uuid == [0x01] { let (data, uuid) = parse_uuid(data)?; Ok(( @@ -37,11 +25,11 @@ impl Packet for SL00LoginStart { } else { Ok((data, SL00LoginStart { name, uuid: None })) } - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SL00LoginStart| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_string(&self.name)); - match self.uuid { + output.extend_from_slice(&serialize_string(&packet.name)); + match packet.uuid { Some(uuid) => { output.push(0x01); output.extend_from_slice(&serialize_uuid(&uuid)); @@ -50,44 +38,23 @@ impl Packet for SL00LoginStart { } output } -} -impl From for GenericPacket { - fn from(value: SL00LoginStart) -> Self { - GenericPacket::SL00LoginStart(value) - } -} -impl TryFrom for SL00LoginStart { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SL00LoginStart(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Clone, Debug, PartialEq)] pub struct SL01EncryptionResponse { - shared_secret: Vec, - verify_token: Vec, + pub shared_secret: Vec, + pub verify_token: Vec, } -impl Packet for SL01EncryptionResponse { - fn id() -> PacketId { - 0x01 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + SL01EncryptionResponse, + 0x01, + crate::ClientState::Login, + true, + |data: &'data [u8]| -> ParseResult<'data, SL01EncryptionResponse> { let (data, shared_secret_len) = parse_varint(data)?; - let (data, shared_secret) = nom::bytes::streaming::take(shared_secret_len as usize)(data)?; + let (data, shared_secret) = take_bytes(shared_secret_len as usize)(data)?; let (data, verify_token_len) = parse_varint(data)?; - let (data, verify_token) = nom::bytes::streaming::take(verify_token_len as usize)(data)?; + let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?; Ok(( data, @@ -96,52 +63,31 @@ impl Packet for SL01EncryptionResponse { verify_token: verify_token.to_vec(), }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SL01EncryptionResponse| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_varint(self.shared_secret.len() as i32)); - output.extend_from_slice(&self.shared_secret); - output.extend_from_slice(&serialize_varint(self.verify_token.len() as i32)); - output.extend_from_slice(&self.verify_token); + output.extend_from_slice(&serialize_varint(packet.shared_secret.len() as i32)); + output.extend_from_slice(&packet.shared_secret); + output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32)); + output.extend_from_slice(&packet.verify_token); output } -} -impl From for GenericPacket { - fn from(value: SL01EncryptionResponse) -> Self { - GenericPacket::SL01EncryptionResponse(value) - } -} -impl TryFrom for SL01EncryptionResponse { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SL01EncryptionResponse(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Clone, Debug, PartialEq)] pub struct SL02LoginPluginResponse { - message_id: i32, - successful: bool, - data: Vec, + pub message_id: i32, + pub successful: bool, + pub data: Vec, } -impl Packet for SL02LoginPluginResponse { - fn id() -> PacketId { - 0x02 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Login - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + SL02LoginPluginResponse, + 0x02, + crate::ClientState::Login, + true, + |data: &'data [u8]| -> ParseResult<'data, SL02LoginPluginResponse> { let (data, message_id) = parse_varint(data)?; - let (data, successful) = nom::bytes::streaming::take(1usize)(data)?; + let (data, successful) = take_bytes(1usize)(data)?; let successful = successful == [0x01]; Ok(( data, @@ -154,31 +100,16 @@ impl Packet for SL02LoginPluginResponse { }, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SL02LoginPluginResponse| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_varint(self.message_id)); - if self.successful { + output.extend_from_slice(&serialize_varint(packet.message_id)); + if packet.successful { output.push(0x01); - output.extend_from_slice(&self.data); + output.extend_from_slice(&packet.data); } else { output.push(0x00); } output } -} -impl From for GenericPacket { - fn from(value: SL02LoginPluginResponse) -> Self { - GenericPacket::SL02LoginPluginResponse(value) - } -} -impl TryFrom for SL02LoginPluginResponse { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SL02LoginPluginResponse(packet) => Ok(packet), - _ => Err(()), - } - } -} +); diff --git a/crates/composition-protocol/src/packet/serverbound/play.rs b/crates/composition-protocol/src/packet/serverbound/play.rs index defc588..8720ffd 100644 --- a/crates/composition-protocol/src/packet/serverbound/play.rs +++ b/crates/composition-protocol/src/packet/serverbound/play.rs @@ -1,25 +1,17 @@ -use crate::{ - packet::{GenericPacket, Packet, PacketId}, - util::{parse_string, parse_varint, serialize_string, serialize_varint}, -}; +use crate::{util::*, ProtocolError}; +use byteorder::{BigEndian, ReadBytesExt}; #[derive(Clone, Debug, PartialEq)] pub struct SP08CommandSuggestionsRequest { - transaction_id: i32, - text: String, + pub transaction_id: i32, + pub text: String, } -impl Packet for SP08CommandSuggestionsRequest { - fn id() -> PacketId { - 0x08 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { +crate::packet::packet!( + SP08CommandSuggestionsRequest, + 0x08, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> ParseResult<'data, SP08CommandSuggestionsRequest> { let (data, transaction_id) = parse_varint(data)?; let (data, text) = parse_string(data)?; Ok(( @@ -29,147 +21,109 @@ impl Packet for SP08CommandSuggestionsRequest { text, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SP08CommandSuggestionsRequest| -> Vec { let mut output = vec![]; - output.extend_from_slice(&serialize_varint(self.transaction_id)); - output.extend_from_slice(&serialize_string(&self.text)); + output.extend_from_slice(&serialize_varint(packet.transaction_id)); + output.extend_from_slice(&serialize_string(&packet.text)); output } -} -impl From for GenericPacket { - fn from(value: SP08CommandSuggestionsRequest) -> Self { - GenericPacket::SP08CommandSuggestionsRequest(value) - } -} -impl TryFrom for SP08CommandSuggestionsRequest { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SP08CommandSuggestionsRequest(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct SP11KeepAlive { - payload: i64, + pub payload: i64, } -impl Packet for SP11KeepAlive { - fn id() -> PacketId { - 0x11 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, payload) = nom::number::streaming::be_i64(data)?; +crate::packet::packet!( + SP11KeepAlive, + 0x11, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> ParseResult<'data, SP11KeepAlive> { + let (data, mut bytes) = take_bytes(8)(data)?; + let payload = bytes + .read_i64::() + .map_err(|_| ProtocolError::NotEnoughData)?; Ok((data, SP11KeepAlive { payload })) - } - fn serialize_body(&self) -> Vec { - self.payload.to_be_bytes().to_vec() - } -} -impl From for GenericPacket { - fn from(value: SP11KeepAlive) -> Self { - GenericPacket::SP11KeepAlive(value) - } -} -impl TryFrom for SP11KeepAlive { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SP11KeepAlive(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &SP11KeepAlive| -> Vec { packet.payload.to_be_bytes().to_vec() } +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct SP13SetPlayerPosition { - x: f64, - y: f64, - z: f64, - on_ground: bool, + pub x: f64, + pub y: f64, + pub z: f64, + pub on_ground: bool, } -impl Packet for SP13SetPlayerPosition { - fn id() -> PacketId { - 0x13 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, x) = nom::number::streaming::be_f64(data)?; - let (data, y) = nom::number::streaming::be_f64(data)?; - let (data, z) = nom::number::streaming::be_f64(data)?; - let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?; +crate::packet::packet!( + SP13SetPlayerPosition, + 0x13, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> ParseResult<'data, SP13SetPlayerPosition> { + let (data, mut bytes) = take_bytes(8)(data)?; + let x = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(8)(data)?; + let y = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(8)(data)?; + let z = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, on_ground) = take_bytes(1usize)(data)?; let on_ground = on_ground == [0x01]; Ok((data, SP13SetPlayerPosition { x, y, z, on_ground })) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SP13SetPlayerPosition| -> Vec { let mut output = vec![]; - output.extend_from_slice(&self.x.to_be_bytes()); - output.extend_from_slice(&self.y.to_be_bytes()); - output.extend_from_slice(&self.z.to_be_bytes()); - output.push(if self.on_ground { 0x01 } else { 0x00 }); + output.extend_from_slice(&packet.x.to_be_bytes()); + output.extend_from_slice(&packet.y.to_be_bytes()); + output.extend_from_slice(&packet.z.to_be_bytes()); + output.push(if packet.on_ground { 0x01 } else { 0x00 }); output } -} -impl From for GenericPacket { - fn from(value: SP13SetPlayerPosition) -> Self { - GenericPacket::SP13SetPlayerPosition(value) - } -} -impl TryFrom for SP13SetPlayerPosition { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SP13SetPlayerPosition(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct SP14SetPlayerPositionAndRotation { - x: f64, - y: f64, - z: f64, - yaw: f32, - pitch: f32, - on_ground: bool, + pub x: f64, + pub y: f64, + pub z: f64, + pub yaw: f32, + pub pitch: f32, + pub on_ground: bool, } -impl Packet for SP14SetPlayerPositionAndRotation { - fn id() -> PacketId { - 0x14 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, x) = nom::number::streaming::be_f64(data)?; - let (data, y) = nom::number::streaming::be_f64(data)?; - let (data, z) = nom::number::streaming::be_f64(data)?; - let (data, yaw) = nom::number::streaming::be_f32(data)?; - let (data, pitch) = nom::number::streaming::be_f32(data)?; - let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?; +crate::packet::packet!( + SP14SetPlayerPositionAndRotation, + 0x14, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> ParseResult<'data, SP14SetPlayerPositionAndRotation> { + let (data, mut bytes) = take_bytes(8)(data)?; + let x = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(8)(data)?; + let y = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(8)(data)?; + let z = bytes + .read_f64::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(4)(data)?; + let yaw = bytes + .read_f32::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(4)(data)?; + let pitch = bytes + .read_f32::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, on_ground) = take_bytes(1usize)(data)?; let on_ground = on_ground == [0x01]; Ok(( data, @@ -182,55 +136,40 @@ impl Packet for SP14SetPlayerPositionAndRotation { on_ground, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SP14SetPlayerPositionAndRotation| -> Vec { let mut output = vec![]; - output.extend_from_slice(&self.x.to_be_bytes()); - output.extend_from_slice(&self.y.to_be_bytes()); - output.extend_from_slice(&self.z.to_be_bytes()); - output.extend_from_slice(&self.yaw.to_be_bytes()); - output.extend_from_slice(&self.pitch.to_be_bytes()); - output.push(if self.on_ground { 0x01 } else { 0x00 }); + output.extend_from_slice(&packet.x.to_be_bytes()); + output.extend_from_slice(&packet.y.to_be_bytes()); + output.extend_from_slice(&packet.z.to_be_bytes()); + output.extend_from_slice(&packet.yaw.to_be_bytes()); + output.extend_from_slice(&packet.pitch.to_be_bytes()); + output.push(if packet.on_ground { 0x01 } else { 0x00 }); output } -} -impl From for GenericPacket { - fn from(value: SP14SetPlayerPositionAndRotation) -> Self { - GenericPacket::SP14SetPlayerPositionAndRotation(value) - } -} -impl TryFrom for SP14SetPlayerPositionAndRotation { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SP14SetPlayerPositionAndRotation(packet) => Ok(packet), - _ => Err(()), - } - } -} +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct SP15SetPlayerRotation { - yaw: f32, - pitch: f32, - on_ground: bool, + pub yaw: f32, + pub pitch: f32, + pub on_ground: bool, } -impl Packet for SP15SetPlayerRotation { - fn id() -> PacketId { - 0x15 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Play - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, yaw) = nom::number::streaming::be_f32(data)?; - let (data, pitch) = nom::number::streaming::be_f32(data)?; - let (data, on_ground) = nom::bytes::streaming::take(1usize)(data)?; +crate::packet::packet!( + SP15SetPlayerRotation, + 0x15, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> ParseResult<'data, SP15SetPlayerRotation> { + let (data, mut bytes) = take_bytes(4)(data)?; + let yaw = bytes + .read_f32::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, mut bytes) = take_bytes(4)(data)?; + let pitch = bytes + .read_f32::() + .map_err(|_| ProtocolError::NotEnoughData)?; + let (data, on_ground) = take_bytes(1usize)(data)?; let on_ground = on_ground == [0x01]; Ok(( data, @@ -240,27 +179,12 @@ impl Packet for SP15SetPlayerRotation { on_ground, }, )) - } - fn serialize_body(&self) -> Vec { + }, + |packet: &SP15SetPlayerRotation| -> Vec { let mut output = vec![]; - output.extend_from_slice(&self.yaw.to_be_bytes()); - output.extend_from_slice(&self.pitch.to_be_bytes()); - output.push(if self.on_ground { 0x01 } else { 0x00 }); + output.extend_from_slice(&packet.yaw.to_be_bytes()); + output.extend_from_slice(&packet.pitch.to_be_bytes()); + output.push(if packet.on_ground { 0x01 } else { 0x00 }); output } -} -impl From for GenericPacket { - fn from(value: SP15SetPlayerRotation) -> Self { - GenericPacket::SP15SetPlayerRotation(value) - } -} -impl TryFrom for SP15SetPlayerRotation { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SP15SetPlayerRotation(packet) => Ok(packet), - _ => Err(()), - } - } -} +); diff --git a/crates/composition-protocol/src/packet/serverbound/status.rs b/crates/composition-protocol/src/packet/serverbound/status.rs index 6973f5e..952c2fe 100644 --- a/crates/composition-protocol/src/packet/serverbound/status.rs +++ b/crates/composition-protocol/src/packet/serverbound/status.rs @@ -1,76 +1,32 @@ -use crate::packet::{GenericPacket, Packet, PacketId}; +use crate::{util::*, ProtocolError}; +use byteorder::{BigEndian, ReadBytesExt}; #[derive(Copy, Clone, Debug, PartialEq)] pub struct SS00StatusRequest; -impl Packet for SS00StatusRequest { - fn id() -> PacketId { - 0x00 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Status - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - Ok((data, SS00StatusRequest)) - } - fn serialize_body(&self) -> Vec { - vec![] - } -} -impl From for GenericPacket { - fn from(value: SS00StatusRequest) -> Self { - GenericPacket::SS00StatusRequest(value) - } -} -impl TryFrom for SS00StatusRequest { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SS00StatusRequest(packet) => Ok(packet), - _ => Err(()), - } - } -} +crate::packet::packet!( + SS00StatusRequest, + 0x00, + crate::ClientState::Status, + true, + |data: &'data [u8]| -> ParseResult<'data, SS00StatusRequest> { Ok((data, SS00StatusRequest)) }, + |_packet: &SS00StatusRequest| -> Vec { vec![] } +); #[derive(Copy, Clone, Debug, PartialEq)] pub struct SS01PingRequest { - payload: i64, + pub payload: i64, } -impl Packet for SS01PingRequest { - fn id() -> PacketId { - 0x01 - } - fn client_state() -> crate::ClientState { - crate::ClientState::Status - } - fn serverbound() -> bool { - true - } - - fn parse_body(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, payload) = nom::number::streaming::be_i64(data)?; +crate::packet::packet!( + SS01PingRequest, + 0x01, + crate::ClientState::Status, + true, + |data: &'data [u8]| -> ParseResult<'data, SS01PingRequest> { + let (data, mut bytes) = take_bytes(8)(data)?; + let payload = bytes + .read_i64::() + .map_err(|_| ProtocolError::NotEnoughData)?; Ok((data, SS01PingRequest { payload })) - } - fn serialize_body(&self) -> Vec { - self.payload.to_be_bytes().to_vec() - } -} -impl From for GenericPacket { - fn from(value: SS01PingRequest) -> Self { - GenericPacket::SS01PingRequest(value) - } -} -impl TryFrom for SS01PingRequest { - type Error = (); - - fn try_from(value: GenericPacket) -> Result { - match value { - GenericPacket::SS01PingRequest(packet) => Ok(packet), - _ => Err(()), - } - } -} + }, + |packet: &SS01PingRequest| -> Vec { packet.payload.to_be_bytes().to_vec() } +); diff --git a/crates/composition-protocol/src/util.rs b/crates/composition-protocol/src/util.rs index a632472..2ed2deb 100644 --- a/crates/composition-protocol/src/util.rs +++ b/crates/composition-protocol/src/util.rs @@ -1,13 +1,33 @@ -use nom::error::FromExternalError; +use crate::ProtocolError; +use byteorder::{BigEndian, ReadBytesExt}; +use tracing::trace; -pub fn parse_varint(mut data: &[u8]) -> nom::IResult<&[u8], i32> { +pub type ParseResult<'data, T> = crate::Result<(&'data [u8], T)>; + +pub fn take_bytes(num: usize) -> impl Fn(&'_ [u8]) -> ParseResult<'_, &'_ [u8]> { + move |data| { + if data.len() < num { + Err(ProtocolError::NotEnoughData) + } else { + Ok(data.split_at(num)) + } + } +} + +#[tracing::instrument] +pub fn parse_varint(mut data: &[u8]) -> ParseResult<'_, i32> { + trace!("{:?}", data); let mut output = 0i32; let mut bytes_read = 0i32; loop { - let (d, next_byte) = nom::bytes::streaming::take(1usize)(data)?; + let (d, next_byte) = take_bytes(1usize)(data)?; data = d; + if next_byte.is_empty() { + return Err(ProtocolError::NotEnoughData); + } + output |= ((next_byte[0] & 0x7f) as i32) << (bytes_read * 7); bytes_read += 1; if next_byte[0] & 0x80 != 0x80 { @@ -17,8 +37,9 @@ pub fn parse_varint(mut data: &[u8]) -> nom::IResult<&[u8], i32> { break; } } - nom::IResult::Ok((data, output)) + Ok((data, output)) } +#[tracing::instrument] pub fn serialize_varint(value: i32) -> Vec { let mut value = value as u32; let mut output = vec![]; @@ -36,12 +57,14 @@ pub fn serialize_varint(value: i32) -> Vec { output } -pub fn parse_string(data: &[u8]) -> nom::IResult<&[u8], String> { +#[tracing::instrument] +pub fn parse_string(data: &[u8]) -> ParseResult<'_, String> { let (data, len) = parse_varint(data)?; - let (data, str_bytes) = nom::bytes::streaming::take(len as usize)(data)?; + let (data, str_bytes) = take_bytes(len as usize)(data)?; let s = String::from_utf8_lossy(str_bytes).to_string(); - nom::IResult::Ok((data, s)) + Ok((data, s)) } +#[tracing::instrument] pub fn serialize_string(value: &str) -> Vec { let mut output = vec![]; output.extend_from_slice(&serialize_varint(value.len() as i32)); @@ -49,43 +72,63 @@ pub fn serialize_string(value: &str) -> Vec { output } -pub fn parse_json(data: &[u8]) -> nom::IResult<&[u8], crate::Json> { - use nom::error::{Error, ErrorKind}; +#[tracing::instrument] +pub fn parse_json(data: &[u8]) -> ParseResult<'_, crate::Json> { + trace!("parse_json: {:?}", data); let (data, json) = parse_string(data)?; - let json = serde_json::from_str(&json) - .map_err(|e| nom::Err::Error(Error::from_external_error(data, ErrorKind::Verify, e)))?; + let json = serde_json::from_str(&json)?; Ok((data, json)) } +#[tracing::instrument] pub fn serialize_json(value: &crate::Json) -> Vec { + trace!("serialize_json: {:?}", value); serialize_string(&serde_json::to_string(value).expect("valid json")) } -pub fn parse_chat(data: &[u8]) -> nom::IResult<&[u8], crate::Chat> { +#[tracing::instrument] +pub fn parse_chat(data: &[u8]) -> ParseResult<'_, crate::Chat> { + trace!("parse_chat: {:?}", data); parse_json(data) } +#[tracing::instrument] pub fn serialize_chat(value: &crate::Chat) -> Vec { + trace!("serialize_chat: {:?}", value); serialize_json(value) } -pub fn parse_uuid(data: &[u8]) -> nom::IResult<&[u8], crate::Uuid> { - nom::number::streaming::be_u128(data) +#[tracing::instrument] +pub fn parse_uuid(data: &[u8]) -> ParseResult<'_, crate::Uuid> { + trace!("parse_uuid: {:?}", data); + let (data, mut bytes) = take_bytes(16)(data)?; + let uuid = bytes + .read_u128::() + .map_err(|_| ProtocolError::NotEnoughData)?; + Ok((data, uuid)) } +#[tracing::instrument] pub fn serialize_uuid(value: &crate::Uuid) -> Vec { + trace!("serialize_uuid: {:?}", value); value.to_be_bytes().to_vec() } #[derive(Debug, Copy, Clone, PartialEq)] pub struct Position { - x: i32, - y: i32, - z: i32, + pub x: i32, + pub y: i32, + pub z: i32, } impl Position { + #[tracing::instrument] pub fn new(x: i32, y: i32, z: i32) -> Self { Position { x, y, z } } - pub fn parse(data: &[u8]) -> nom::IResult<&[u8], Self> { - let (data, i) = nom::number::streaming::be_i64(data)?; + #[tracing::instrument] + pub fn parse(data: &[u8]) -> ParseResult<'_, Self> { + trace!("Position::parse: {:?}", data); + let (data, mut bytes) = take_bytes(8)(data)?; + let i = bytes + .read_i64::() + .map_err(|_| ProtocolError::NotEnoughData)?; // x: i26, z: i26, y: i12 let x = i >> 38; @@ -97,7 +140,9 @@ impl Position { Ok((data, Position::new(x as i32, y as i32, z as i32))) } + #[tracing::instrument] pub fn serialize(&self) -> Vec { + trace!("Position::serialize: {:?}", self); let i: i64 = ((self.x as i64 & 0x3FF_FFFF) << 38) | ((self.z as i64 & 0x3FF_FFFF) << 12) | (self.y as i64 & 0xFFF); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..d25955a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,263 @@ +use clap::Arg; +use once_cell::sync::{Lazy, OnceCell}; +use serde::{Deserialize, Serialize}; +use std::io::{Read, Write}; +use std::{fs::File, path::Path, path::PathBuf}; +use tracing::{error, trace, warn}; + +/// The globally-accessible static instance of Config. +/// On program startup, Config::load() should be called to initialize it. +pub static CONFIG: OnceCell = OnceCell::new(); + +/// The globablly-accessible static instance of Args. +/// On program startup, Args::load() should be called to initialize it. +pub static ARGS: OnceCell = OnceCell::new(); +static DEFAULT_ARGS: Lazy = Lazy::new(Args::default); + +#[tracing::instrument] +fn read_file(path: &Path) -> std::io::Result> { + trace!("{:?}", path); + let mut data = vec![]; + let mut file = File::open(path)?; + file.read_to_end(&mut data)?; + Ok(data) +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct Config { + pub port: u16, + pub max_players: usize, + pub motd: String, + pub server_icon: PathBuf, + #[serde(skip)] + pub server_icon_bytes: Vec, + pub protocol_version: i32, + pub game_version: String, + #[serde(skip)] + pub server_version: String, + pub server_threads: Option, +} +impl Default for Config { + fn default() -> Self { + let server_version = format!( + "composition {} ({} {})", + env!("CARGO_PKG_VERSION"), + &env!("GIT_HASH")[0..9], + &env!("GIT_DATE")[0..10] + ); + Config { + port: 25565, + max_players: 20, + motd: "Hello world!".to_owned(), + server_icon: PathBuf::from("server-icon.png"), + server_icon_bytes: include_bytes!("./server-icon.png").to_vec(), + protocol_version: 761, + game_version: "1.19.3".to_owned(), + server_version, + server_threads: None, + } + } +} +impl Config { + pub fn instance() -> &'static Self { + match CONFIG.get() { + Some(a) => a, + None => Self::load(), + } + } + #[tracing::instrument] + pub fn load() -> &'static Self { + trace!("Config::load()"); + let args = Args::instance(); + let mut config = Config::default(); + let config_path = Path::new(&args.config_file); + + if !config_path.exists() { + warn!( + "Configuration file does not exist, creating {}", + config_path.to_str().unwrap_or("") + ); + config.write(config_path); + } + + if let Ok(cfg) = read_file(config_path) { + let cfg: Result = toml::from_slice(&cfg); + if let Ok(cfg) = cfg { + config = cfg; + } else { + error!("Could not parse configuration file, using default"); + } + } else { + error!("Could not read configuration file, using default"); + } + + // Load the server icon + config.server_icon = args.server_icon.clone(); + let server_icon_path = Path::new(&config.server_icon); + + if server_icon_path.exists() { + if let Ok(server_icon_bytes) = read_file(server_icon_path) { + config.server_icon_bytes = server_icon_bytes; + } else { + warn!("Could not read server icon file, using default"); + } + } else { + warn!( + "Server icon file does not exist, creating {}", + server_icon_path.to_str().unwrap_or("") + ); + config.write_server_icon(server_icon_path); + } + + CONFIG.set(config).expect("could not set CONFIG"); + Self::instance() + } + #[tracing::instrument] + fn write(&self, path: &Path) { + trace!("Config.write()"); + if let Ok(mut file) = File::options().write(true).create(true).open(path) { + if file + .write_all(toml::to_string(&self).unwrap().as_bytes()) + .is_ok() + { + return; + } + } + error!("Could not write configuration file"); + std::process::exit(1); + } + #[tracing::instrument] + fn write_server_icon(&self, path: &Path) { + trace!("Config.write_server_icon()"); + if let Ok(mut file) = File::options().write(true).create(true).open(path) { + if file.write_all(&self.server_icon_bytes).is_ok() { + return; + } + } + error!("Could not write server icon file"); + std::process::exit(1); + } +} + +#[derive(Debug)] +pub struct Args { + pub config_file: PathBuf, + pub server_icon: PathBuf, + pub log_level: Option, + pub log_dir: PathBuf, +} +impl Default for Args { + fn default() -> Self { + let config = Config::default(); + Args { + config_file: PathBuf::from("composition.toml"), + server_icon: config.server_icon, + log_level: None, + log_dir: PathBuf::from("logs"), + } + } +} +impl Args { + pub fn instance() -> &'static Self { + match ARGS.get() { + Some(a) => a, + None => Self::load(), + } + } + pub fn load() -> &'static Self { + ARGS.set(Self::parse()).expect("could not set ARGS"); + Self::instance() + } + fn parse() -> Self { + use std::ffi::OsStr; + + let m = clap::Command::new("composition") + .about(env!("CARGO_PKG_DESCRIPTION")) + .disable_version_flag(true) + .arg( + Arg::new("version") + .short('V') + .long("version") + .help("Print version") + .global(true) + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("verbose") + .short('v') + .long("verbose") + .help("Set log level to debug") + .global(true) + .action(clap::ArgAction::SetTrue), + ) + .arg( + Arg::new("config-file") + .short('c') + .long("config-file") + .help("Configuration file path") + .value_hint(clap::ValueHint::FilePath) + .default_value(OsStr::new(&DEFAULT_ARGS.config_file)), + ) + .arg( + Arg::new("server-icon") + .long("server-icon") + .help("Server icon file path") + .value_hint(clap::ValueHint::FilePath) + .default_value(OsStr::new(&DEFAULT_ARGS.server_icon)), + ) + .arg( + Arg::new("log-level") + .short('l') + .long("log-level") + .help("Set the log level") + .conflicts_with("verbose") + .value_name("level") + .value_parser(["trace", "debug", "info", "warn", "error"]), + ) + .arg( + Arg::new("log-dir") + .long("log-dir") + .help("Set the log output directory") + .value_name("dir") + .value_hint(clap::ValueHint::DirPath) + .default_value(OsStr::new(&DEFAULT_ARGS.log_dir)), + ) + .get_matches(); + + let mut args = Self::default(); + args.config_file = m + .get_one::("config-file") + .map_or(args.config_file, PathBuf::from); + args.server_icon = m + .get_one::("server-icon") + .map_or(args.server_icon, PathBuf::from); + args.log_dir = m + .get_one::("log-dir") + .map_or(args.log_dir, PathBuf::from); + + if m.get_flag("verbose") { + args.log_level = Some(tracing::Level::DEBUG); + } else { + args.log_level = m.get_one("log-level").map_or(args.log_level, |s: &String| { + Some(s.parse::().unwrap()) + }); + } + + if m.get_flag("version") { + println!("{}", Config::default().server_version); + if m.get_flag("verbose") { + println!("release: {}", env!("CARGO_PKG_VERSION")); + println!("commit-hash: {}", env!("GIT_HASH")); + println!("commit-date: {}", &env!("GIT_DATE")[0..10]); + println!("license: {}", env!("CARGO_PKG_LICENSE")); + println!("authors: {}", env!("CARGO_PKG_AUTHORS")); + println!("build-target: {}", env!("BUILD_TARGET")); + } + std::process::exit(0); + } + + args + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..903d82d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,39 @@ +pub mod config; +pub mod net; +pub mod server; + +use crate::config::Config; +use once_cell::sync::OnceCell; +use std::time::Instant; + +pub static START_TIME: OnceCell = OnceCell::new(); + +/// Start the server. +#[tracing::instrument] +pub async fn start_server( + start_time: Instant, +) -> (server::Server, tokio_util::sync::CancellationToken) { + START_TIME + .set(start_time) + .expect("could not set START_TIME"); + server::Server::new(format!("0.0.0.0:{}", Config::instance().port)).await +} + +pub mod prelude { + pub use crate::config::Config; + pub use crate::START_TIME; + pub use composition_protocol::{Chat, Json, Uuid}; + pub use serde::{Deserialize, Serialize}; + pub use serde_json::json; + pub use std::collections::VecDeque; + pub use std::io::{Read, Write}; + pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + pub use tracing::{debug, error, info, trace, warn}; + #[derive(Clone, Debug, PartialEq)] + pub enum ParseError { + NotEnoughData, + InvalidData, + VarIntTooBig, + } + pub type ParseResult = Result<(T, usize), ParseError>; +} diff --git a/src/main.rs b/src/main.rs index 3d3e619..217f9cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,76 @@ -use log::info; -use std::sync::mpsc::TryRecvError; -use std::time::Duration; +use std::time::Instant; +use tracing::{info, instrument, warn}; +use tracing_subscriber::prelude::*; -#[tokio::main] -pub async fn main() { - let ctrlc_rx = composition_core::init(); - info!( - "Starting {} on port {}", - composition_core::CONFIG.server_version, - composition_core::CONFIG.port - ); - let mut server = composition_core::start_server().await; - info!( - "Done! Start took {:?}", - composition_core::START_TIME.elapsed() - ); +#[instrument] +pub fn main() { + let start_time = Instant::now(); - // The main server loop. - loop { - match ctrlc_rx.try_recv() { - Ok(_) => { - let _ = server.shutdown().await; - break; // Exit the loop. - } - Err(TryRecvError::Empty) => {} // Doesn't matter if there's nothing for us. - Err(TryRecvError::Disconnected) => panic!("Ctrl-C sender disconnected"), + // Set up logging. + let file_writer = + tracing_appender::rolling::daily(&composition::config::Args::instance().log_dir, "log"); + let (file_writer, _guard) = tracing_appender::non_blocking(file_writer); + + tracing_subscriber::registry() + .with(tracing_subscriber::filter::LevelFilter::from_level( + composition::config::Args::instance() + .log_level + .unwrap_or(if cfg!(debug_assertions) { + tracing::Level::DEBUG + } else { + tracing::Level::INFO + }), + )) + .with( + tracing_subscriber::fmt::layer() + .compact() + .with_ansi(false) + .with_writer(file_writer), + ) + .with( + tracing_subscriber::fmt::layer() + .compact() + .with_writer(std::io::stdout), + ) + .init(); + + // Load the config. + let config = composition::config::Config::load(); + + match config.server_threads { + Some(1) => { + warn!("Running on only one thread"); + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() } - server.update().await.unwrap(); - std::thread::sleep(Duration::from_millis(2)); + Some(n) => { + info!("Running on {} threads", n); + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(n) + .build() + } + None => tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build(), } + .unwrap() + .block_on(async move { + info!("Starting {} on port {}", config.server_version, config.port); + let (mut server, running) = composition::start_server(start_time).await; + info!("Done! Start took {:?}", start_time.elapsed()); + + // The main server loop. + loop { + tokio::select! { + _ = running.cancelled() => { + break; + } + _ = server.update() => {} + } + } + + let _ = tokio::time::timeout(std::time::Duration::from_secs(10), server.shutdown()).await; + }); } diff --git a/src/net.rs b/src/net.rs new file mode 100644 index 0000000..2dca72f --- /dev/null +++ b/src/net.rs @@ -0,0 +1,212 @@ +use crate::prelude::*; +use composition_protocol::{packet::GenericPacket, ClientState, ProtocolError}; +use std::sync::Arc; +use std::time::Instant; +use tokio::net::TcpStream; +use tokio::sync::RwLock; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum NetworkClientState { + Handshake, + Status { + received_request: bool, + received_ping: bool, + }, + Login, + Play, + Disconnected, +} +impl From for ClientState { + fn from(value: NetworkClientState) -> Self { + match value { + NetworkClientState::Handshake => ClientState::Handshake, + NetworkClientState::Status { + received_request: _, + received_ping: _, + } => ClientState::Status, + NetworkClientState::Login => ClientState::Login, + NetworkClientState::Play => ClientState::Play, + NetworkClientState::Disconnected => ClientState::Disconnected, + } + } +} +impl AsRef for NetworkClientState { + fn as_ref(&self) -> &ClientState { + match self { + NetworkClientState::Handshake => &ClientState::Handshake, + NetworkClientState::Status { + received_request: _, + received_ping: _, + } => &ClientState::Status, + NetworkClientState::Login => &ClientState::Login, + NetworkClientState::Play => &ClientState::Play, + NetworkClientState::Disconnected => &ClientState::Disconnected, + } + } +} + +#[derive(Debug, Clone)] +pub struct NetworkClient { + pub id: u128, + pub state: NetworkClientState, + stream: Arc>, + incoming_data: VecDeque, + pub incoming_packet_queue: VecDeque, + pub last_received_data_time: Instant, + pub outgoing_packet_queue: VecDeque, +} +impl NetworkClient { + #[tracing::instrument] + pub fn new(id: u128, stream: TcpStream) -> NetworkClient { + NetworkClient { + id, + state: NetworkClientState::Handshake, + stream: Arc::new(RwLock::new(stream)), + incoming_data: VecDeque::new(), + incoming_packet_queue: VecDeque::new(), + last_received_data_time: Instant::now(), + outgoing_packet_queue: VecDeque::new(), + } + } + #[tracing::instrument] + async fn read_data(&mut self) -> tokio::io::Result<()> { + trace!("NetworkClient.read_data() id {}", self.id); + let stream = self.stream.read().await; + + // Try to read 4kb at a time until there is no more data. + loop { + let mut buf = [0; 4096]; + + let num_bytes = match stream.try_read(&mut buf) { + Ok(0) => break, + Ok(n) => n, + Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => { + break; + } + Err(e) => return Err(e), + }; + + debug!("Read {} bytes from client {}", num_bytes, self.id); + + self.last_received_data_time = Instant::now(); + self.incoming_data.extend(&buf[..num_bytes]); + } + + trace!("NetworkClient.read_data() end id {}", self.id); + Ok(()) + } + // TODO: Stream compression/encryption. + #[tracing::instrument] + pub async fn read_packets(&mut self) -> composition_protocol::Result<()> { + trace!("NetworkClient.read_packet() id {}", self.id); + + if self.read_data().await.is_err() { + self.disconnect(None).await; + return Err(ProtocolError::Disconnected); + } + + self.incoming_data.make_contiguous(); + let (mut data, &[..]) = self.incoming_data.as_slices(); + + let mut bytes_consumed = 0; + while !data.is_empty() { + match GenericPacket::parse_uncompressed(self.state.into(), true, data) { + Ok((d, packet)) => { + debug!("Got packet {:?} from client {}", packet, self.id); + bytes_consumed += data.len() - d.len(); + data = d; + self.incoming_packet_queue.push_back(packet); + } + Err(ProtocolError::NotEnoughData) => break, + Err(e) => { + // Remove the valid bytes before this packet. + self.incoming_data = self.incoming_data.split_off(bytes_consumed); + return Err(e); + } + } + } + + // Remove the bytes we just read. + self.incoming_data = self.incoming_data.split_off(bytes_consumed); + + Ok(()) + } + // None: There was no packet to read. + // Some(Err(())): The packet was the wrong type. + // Some(Ok(_)): The packet was successfully read. + #[tracing::instrument] + pub fn read_packet>( + &mut self, + ) -> Option> { + if let Some(generic_packet) = self.incoming_packet_queue.pop_back() { + if let Ok(packet) = TryInto::

::try_into(generic_packet.clone()) { + Some(Ok(packet)) + } else { + self.incoming_packet_queue.push_back(generic_packet.clone()); + Some(Err(generic_packet)) + } + } else { + None + } + } + #[tracing::instrument] + pub fn queue_packet>(&mut self, packet: P) { + self.outgoing_packet_queue.push_back(packet.into()); + } + #[tracing::instrument] + pub async fn send_queued_packets(&mut self) -> composition_protocol::Result<()> { + let packets: Vec<_> = self.outgoing_packet_queue.drain(..).collect(); + for packet in packets { + self.send_packet(packet) + .await + .map_err(|_| ProtocolError::Disconnected)?; + } + Ok(()) + } + #[tracing::instrument] + pub async fn send_packet>( + &self, + packet: P, + ) -> tokio::io::Result<()> { + use composition_protocol::util::serialize_varint; + let packet: GenericPacket = packet.into(); + + debug!("Sending packet {:?} to client {}", packet, self.id); + let (packet_id, mut packet_body) = packet.serialize(); + let mut packet_id = serialize_varint(packet_id); + + // TODO: Stream compression/encryption. + + let mut b = vec![]; + b.append(&mut packet_id); + b.append(&mut packet_body); + + // bytes: packet length as varint, packet id as varint, packet body + let mut bytes = serialize_varint(b.len() as i32); + bytes.append(&mut b); + + self.stream.write().await.write_all(&bytes).await?; + Ok(()) + } + #[tracing::instrument] + pub async fn disconnect(&mut self, reason: Option) { + use composition_protocol::packet::clientbound::{CL00Disconnect, CP17Disconnect}; + let reason = reason.unwrap_or(json!({ + "text": "You have been disconnected!" + })); + + match self.state.as_ref() { + ClientState::Disconnected | ClientState::Handshake | ClientState::Status => { + // Impossible to send a disconnect in these states. + } + ClientState::Login => { + let _ = self.send_packet(CL00Disconnect { reason }).await; + } + ClientState::Play => { + let _ = self.send_packet(CP17Disconnect { reason }).await; + } + } + + self.state = NetworkClientState::Disconnected; + } +} diff --git a/crates/composition-core/src/server-icon.png b/src/server-icon.png similarity index 100% rename from crates/composition-core/src/server-icon.png rename to src/server-icon.png diff --git a/crates/composition-core/src/server/messages.rs b/src/server/messages.rs similarity index 100% rename from crates/composition-core/src/server/messages.rs rename to src/server/messages.rs diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..2af1db1 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,270 @@ +use crate::net::{NetworkClient, NetworkClientState}; +use crate::prelude::*; +use composition_protocol::ClientState; +use std::sync::Arc; +use tokio::net::{TcpListener, ToSocketAddrs}; +use tokio::sync::RwLock; +use tokio::task::JoinHandle; +use tokio_util::sync::CancellationToken; + +#[derive(Clone, Debug, PartialEq)] +pub enum ServerError { + NotRunning, +} + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub struct Server { + clients: Arc>>, + net_tasks_handle: JoinHandle<()>, +} + +impl Server { + #[tracing::instrument] + pub async fn new( + bind_address: A, + ) -> (Server, CancellationToken) { + trace!("Server::new()"); + + let running = CancellationToken::new(); + let clients = Arc::new(RwLock::new(vec![])); + let net_tasks_handle = tokio::spawn(Self::create_network_tasks( + bind_address, + clients.clone(), + running.clone(), + )); + + let server = Server { + clients, + net_tasks_handle, + }; + + // let (shutdown_tx, shutdown_rx) = oneshot::channel(); + let r = running.clone(); + tokio::spawn(async move { + tokio::signal::ctrl_c().await.unwrap(); + info!("Ctrl-C received, shutting down"); + r.cancel(); + // shutdown_tx.send(()).unwrap(); + }); + + (server, running) + } + #[tracing::instrument] + async fn create_network_tasks( + bind_address: A, + network_clients: Arc>>, + running: CancellationToken, + ) { + // Start a task to receive new clients. + trace!("Creating listener task"); + let nc = network_clients.clone(); + let r = running.clone(); + let listener_task = tokio::spawn(async move { + trace!("Listener task created"); + let Ok(listener) = TcpListener::bind(bind_address).await else { + error!("Could not bind to given address, shutting down."); + std::process::exit(1); + }; + + let mut client_id = 0u128; + loop { + tokio::select! { + _ = r.cancelled() => { + trace!("Listener task received shutdown"); + break; + } + result = listener.accept() => { + if let Ok((stream, _)) = result { + trace!("Listener task got client (id {})", client_id); + nc.write().await.push(NetworkClient::new(client_id, stream)); + client_id += 1; + } else { + trace!("Listener task failed to accept client"); + } + } + } + } + }); + + // Start a task to update existing clients' packet queues. + trace!("Creating network task"); + let nc = network_clients.clone(); + let r = running.clone(); + let packet_task = tokio::spawn(async move { + trace!("Network task created"); + loop { + // Start tasks to read/write to clients concurrently. + tokio::select! { + _ = r.cancelled() => { + trace!("Network task received shutdown"); + break; + } + mut nc = nc.write() => { + trace!("Network task updating clients"); + let tasks: Vec> = nc + .drain(..) + .map(|mut client: NetworkClient| { + tokio::spawn(async move { + let _ = client.read_packets().await; + if client.send_queued_packets().await.is_err() { + client + .disconnect(Some(json!({ "text": "Error writing packets." }))) + .await; + } + client + }) + }) + .collect(); + *nc = Vec::with_capacity(tasks.len()); + for task in tasks { + nc.push(task.await.unwrap()); + } + trace!("Network task updated clients"); + } + } + } + }); + + // Start a task to remove disconnected clients. + trace!("Creating disconnection task"); + let nc = network_clients.clone(); + let r = running.clone(); + let disconnection_task = tokio::spawn(async move { + trace!("Disconnection task created"); + loop { + tokio::select! { + _ = r.cancelled() => { + trace!("Disconnection task received shutdown"); + break; + } + mut nc = nc.write() => { + let before = nc.len(); + nc.retain(|client| client.state != NetworkClientState::Disconnected); + let after = nc.len(); + trace!("Disconnection task removed {} clients", before - after); + } + } + } + }); + + // Join the tasks on shutdown. + listener_task.await.expect("Listener task crashed"); + packet_task.await.expect("Packet task crashed"); + disconnection_task + .await + .expect("Disconnection task crashed"); + } + #[tracing::instrument] + pub async fn update(&mut self) -> Result<()> { + trace!("Server.update()"); + + let mut clients = self.clients.write().await; + + // Handle packets from the clients. + let online_players = clients + .iter() + .filter(|client| matches!(client.state, NetworkClientState::Play)) + .count(); + 'clients: for client in clients.iter_mut() { + use composition_protocol::packet::{clientbound::*, serverbound::*}; + 'packets: while !client.incoming_packet_queue.is_empty() { + // client.read_packet() + // None: The client doesn't have any more packets. + // Some(Err(_)): The client read an unexpected packet. TODO: Handle this error. + // Some(Ok(_)): The client read the expected packet. + match client.state { + NetworkClientState::Handshake => { + let handshake = match client.read_packet::() { + None => continue 'packets, + Some(Err(_)) => continue 'clients, + Some(Ok(handshake)) => handshake, + }; + + if handshake.next_state == ClientState::Status { + client.state = NetworkClientState::Status { + received_request: false, + received_ping: false, + }; + } else if handshake.next_state == ClientState::Login { + client.state = NetworkClientState::Login; + } else { + client + .disconnect(Some( + json!({ "text": "Received invalid SH00Handshake packet" }), + )) + .await; + } + } + // Status !received_request: Read SS00StatusRequest and respond with CS00StatusResponse + NetworkClientState::Status { + received_request, + received_ping, + } if !received_request => { + let _status_request = match client.read_packet::() { + None => continue 'packets, + Some(Err(_)) => continue 'clients, + Some(Ok(p)) => p, + }; + client.state = NetworkClientState::Status { + received_request: true, + received_ping, + }; + let config = Config::instance(); + client.queue_packet(CS00StatusResponse { + response: json!({ + "version": { + "name": config.game_version, + "protocol": config.protocol_version + }, + "players": { + "max": config.max_players, + "online": online_players, + "sample": [] + }, + "description": { + "text": config.motd + } + }), + }); + } + // Status !received_ping: Read SS00StatusRequest and respond with CS00StatusResponse + NetworkClientState::Status { received_ping, .. } if !received_ping => { + let ping = match client.read_packet::() { + None => continue 'packets, + Some(Err(_)) => continue 'clients, + Some(Ok(p)) => p, + }; + client.queue_packet(CS01PingResponse { + payload: ping.payload, + }); + client.state = NetworkClientState::Disconnected; + } + NetworkClientState::Status { .. } => unreachable!(), + NetworkClientState::Login => unimplemented!(), + NetworkClientState::Play => unimplemented!(), + NetworkClientState::Disconnected => unimplemented!(), + } + // If continue was not + break 'packets; + } + } + + Ok(()) + } + #[tracing::instrument] + pub async fn shutdown(self) { + trace!("Server.shutdown()"); + + // Close the concurrent tasks. + let _ = self.net_tasks_handle.await; + + // Send disconnect messages to the clients. + for client in self.clients.write().await.iter_mut() { + client + .disconnect(Some(json!({ "text": "The server is shutting down." }))) + .await; + } + } +}