diff --git a/.gitignore b/.gitignore index ea8c4bf..153bca6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -/target +target/ +server.toml +Packets.html \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a2a5321..7f05113 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,19 @@ { "editor.fontFamily": "Fira Code", - "editor.fontLigatures": true + "editor.fontLigatures": true, + + "editor.tabSize": 2, + "editor.insertSpaces": false, + "editor.detectIndentation": false, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true + }, + "rust-analyzer.rustfmt.extraArgs": [ + "--config", + "tab_spaces=2" + ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index fc8e05c..2e24daf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,5 +3,1183 @@ version = 4 [[package]] -name = "rust_minecraft_server" +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "cc" +version = "1.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "colog" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c426b7af8d5e0ad79de6713996632ce31f0d68ba84068fb0d654b396e519df0" +dependencies = [ + "colored", + "env_logger", + "log", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "craftflow-nbt" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2d5312462b00f8420ace884a696f243be136ada9f50bf5f3d9858ff0c8e8e" +dependencies = [ + "cesu8", + "serde", + "thiserror", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "ignore-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "665ff4dce8edd10d490641ccb78949832f1ddbff02c584fb1f85ab888fe0e50c" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-conv" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rust_mc_proto" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734168f5b9aef1991db4b11c0bcd71c0336b0a5ba98269f0df39b41b8463ac8c" +dependencies = [ + "flate2", + "uuid", +] + +[[package]] +name = "rust_mc_serv" +version = "0.1.0" +dependencies = [ + "colog", + "craftflow-nbt", + "dashmap", + "ignore-result", + "itertools", + "log", + "palette", + "paste", + "rust_mc_proto", + "serde", + "serde_default", + "serde_json", + "serde_with", + "toml", + "uuid", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_default" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486b028b311aaaea83e0ba65a3e6e3cbef381e74e9d0bd6263faefd1fb503c1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 5f154ec..b94c043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,21 @@ [package] -name = "rust_minecraft_server" +name = "rust_mc_serv" version = "0.1.0" edition = "2024" -[dependencies] \ No newline at end of file +[dependencies] +rust_mc_proto = "0.1.19" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +serde_with = { version = "3.12.0", features = ["macros"] } +serde_default = "0.2.0" +toml = "0.8.22" +itertools = "0.14.0" +palette = "0.7.6" +craftflow-nbt = "2.1.0" +colog = "1.3.0" +log = "0.4.27" +uuid = "1.16.0" +dashmap = "6.1.0" +paste = "1.0.15" +ignore-result = "0.2.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..07b7a81 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a8c75a --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# rust_mc_serv + +Простой майнкрафт сервер на расте. Поддерживаемая версия: 1.21.5 (PVN 770) + +## Как запустить + +Перед тем как запускать, вам нужно получить бинарник, это можно сделать следующими способами: + +### Скачать из релиза + +На данный момент проект находится в разработке, так что релизов нет + +Если хотите собрать последнюю версию сервера вручную, обратитесь к следующему способу. + +### Собрать самим + +Для того чтобы собрать проект самим, вам нужно: + +1. Скачать и установить [Rust](https://www.rust-lang.org/) +2. Скачать исходный код проекта (через zip или `git clone`) +3. Открыть терминал в папке проекта и выполнить следующие команды: + +Для запуска: +```bash +cargo run +``` + +Для сборки (готовый бинарник будет в `target/release`): +```bash +cargo build -r +``` + +### Использовать как библиотеку + +Вы можете использовать проект как библиотеку для своих серверов + +Пример добавления в `Cargo.toml`: + +```toml +rust_mc_serv = { git = "https://github.com/GIKExe/rust_minecraft_server.git" } +``` + +Пример запуска сервера: + +```rust +let config = Arc::new(Config::default()); +let mut server = ServerContext::new(config); + +server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера +server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + +start_server(Arc::new(server)); +``` + +## Конфигурация + +По умолчанию, конфиг будет создан в файле `config.toml` в рабочей директории. Чтобы изменить этот путь, укажите его в первом аргументе к серверу, пример: `./rust_mc_serv /path/to/config.toml` + +## Лицензия + +Этот проект полностью лицензирован под лицензией WTFPL. Он абсолютно бесплатен и не имеет ограничений в использовании. + +## Содействие + +Если вы хотите помочь проекту, не стесняйтесь отправлять пулл реквесты! diff --git a/parse_ids.py b/parse_ids.py new file mode 100644 index 0000000..b3770de --- /dev/null +++ b/parse_ids.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +# Использование: +# +# ./parse_ids.py < Packets.html > src/server/protocol/id.rs + +import sys +from bs4 import BeautifulSoup +import re + +BOUNDS = ["clientbound", "serverbound"] +MODES = { + "#Handshaking": "handshake", + "#Status": "status", + "#Login": "login", + "#Play": "play", + "#Configuration": "configuration" +} + +def sanitize_name(name, bound, mode): + name = (" " + name.lower() + " ").replace(" " + bound.lower() + " ", "").replace(" " + mode.lower() + " ", "") \ + if name.lower() != bound.lower() and name.lower() != mode.lower() else name + name = re.sub(r'\(.*?\)', '', name) + name = name.strip() + name = name.upper() + name = name.replace(' ', '_') + return name + +def parse_packet_id_table(span): + table = span.parent.find_next_sibling("table") + if not table: + return None + rows = table.find_all("tr") + if len(rows) < 2: + return None + code_tag = rows[1].find("td").find("code") + if not code_tag: + return None + return code_tag.text.strip() + +def main(): + soup = BeautifulSoup(sys.stdin.read(), "html.parser") + + print("/*\n") + print(" Generated with parse_ids.py \n") + print(" */\n") + + toc = soup.select_one("#toc") + + for bound_type in BOUNDS: + print(f"pub mod {bound_type} {{") + + for li in toc.find("ul").find_all("li", recursive=False): + a = li.find("a", href=True) + if not a or a["href"] not in MODES: + continue + + mode = MODES[a["href"]] + ul = li.find("ul", recursive=False) + if not ul: + continue + lis = ul.find_all("li", recursive=False) + + mode_size = 0 + + try: + bound_list = lis[BOUNDS.index(bound_type)].find_all("li") + except KeyError: + continue + + for item in bound_list: + packet_a = item.find("a", href=True) + if not packet_a or not packet_a["href"].startswith("#"): + continue + + href = packet_a["href"].lstrip("#") + span = soup.find("span", id=href) + if not span: + continue + + packet_id = parse_packet_id_table(span) + if not packet_id: + continue + + name = sanitize_name(" ".join(packet_a.text.split(" ")[1:]), bound_type, mode) + if len(name) > 0: + mode_size += 1 + + if mode_size == 1: + print(f" pub mod {mode} {{") + print(f" pub const {name}: u8 = {packet_id};") + + if mode_size > 0: + print(" }\n") + + print("}\n") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..292fe49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..8cef326 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +# Пример настроек +tab_spaces = 2 +hard_tabs = true \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..85640ef --- /dev/null +++ b/shell.nix @@ -0,0 +1,16 @@ + +with import { }; + +mkShell { + nativeBuildInputs = [ + direnv + rustc + cargo + rustfmt + python3 + python3Packages.beautifulsoup4 + python3Packages.requests + ]; + + NIX_ENFORCE_PURITY = true; +} \ No newline at end of file diff --git a/sniff_packets/.gitignore b/sniff_packets/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/sniff_packets/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/sniff_packets/Cargo.lock b/sniff_packets/Cargo.lock new file mode 100644 index 0000000..17f1a38 --- /dev/null +++ b/sniff_packets/Cargo.lock @@ -0,0 +1,192 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "craftflow-nbt" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2d5312462b00f8420ace884a696f243be136ada9f50bf5f3d9858ff0c8e8e" +dependencies = [ + "cesu8", + "serde", + "thiserror", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust_mc_proto" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734168f5b9aef1991db4b11c0bcd71c0336b0a5ba98269f0df39b41b8463ac8c" +dependencies = [ + "flate2", + "uuid", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sniff-packets" +version = "0.1.0" +dependencies = [ + "craftflow-nbt", + "rust_mc_proto", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" diff --git a/sniff_packets/Cargo.toml b/sniff_packets/Cargo.toml new file mode 100644 index 0000000..97470d4 --- /dev/null +++ b/sniff_packets/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sniff_packets" +version = "0.1.0" +edition = "2024" + +[dependencies] +rust_mc_proto = "0.1.19" +serde = "1.0.219" +serde_json = "1.0.140" +craftflow-nbt = "2.1.0" +uuid = "1.16.0" \ No newline at end of file diff --git a/sniff_packets/src/main.rs b/sniff_packets/src/main.rs new file mode 100644 index 0000000..0a84c2d --- /dev/null +++ b/sniff_packets/src/main.rs @@ -0,0 +1,198 @@ +use std::{fs, io::Read}; + +use craftflow_nbt::DynNBT; +use rust_mc_proto::{prelude::*, write_packet, MCConnTcp, Packet, ProtocolError}; +use uuid::Uuid; + + +pub trait ReadWriteNBT: DataReader + DataWriter { + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ProtocolError>; +} + +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + let mut data = Vec::new(); + let pos = self.get_ref().position(); + self.get_mut() + .read_to_end(&mut data) + .map_err(|_| ProtocolError::StringParseError)?; + let (remaining, value) = + craftflow_nbt::from_slice(&data).map_err(|_| ProtocolError::StringParseError)?; + self.get_mut() + .set_position(pos + (data.len() - remaining.len()) as u64); + Ok(value) + } + + fn write_nbt(&mut self, val: &DynNBT) -> Result<(), ProtocolError> { + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ProtocolError::StringParseError)?; + Ok(()) + } +} + + +fn main() -> Result<(), ProtocolError> { + let mut conn = MCConnTcp::connect("localhost:25565").unwrap(); + + conn.write_packet(&Packet::build(0x00, |packet| { + packet.write_varint(770)?; + packet.write_string("localhost")?; + packet.write_unsigned_short(25565)?; + packet.write_varint(2) + })?)?; + + conn.write_packet(&Packet::build(0x00, |packet| { + packet.write_string("TheMixRay")?; + packet.write_uuid(&Uuid::default()) + })?)?; + + loop { + let mut packet = conn.read_packet()?; + + if packet.id() == 0x03 { + let threshold = packet.read_varint()?; + + if threshold >= 0 { + conn.set_compression(Some(threshold as usize)); + } + } else if packet.id() == 0x02 { + break; + } + } + + conn.write_packet(&Packet::empty(0x03))?; + + conn.write_packet(&Packet::build(0x02, |packet| { + packet.write_string("minecraft:brand")?; + packet.write_string("vanilla") + })?)?; + + conn.write_packet(&Packet::build(0x00, |packet| { + packet.write_string("en_us")?; + packet.write_signed_byte(12)?; + packet.write_varint(0)?; + packet.write_boolean(true)?; + packet.write_byte(127)?; + packet.write_varint(1)?; + packet.write_boolean(true)?; + packet.write_boolean(true)?; + packet.write_varint(0) + })?)?; + + let mut packet = conn.read_packet()?; // server brand + + let id = packet.read_string()?; + println!("message id: {}", id); + println!("message data: {}", String::from_utf8_lossy(&packet.get_bytes()[id.len()+1..])); + + let mut packet = conn.read_packet()?; // feature flags + + let flags_len = packet.read_varint()?; + + println!("got {} feature flags:", flags_len); + + for _ in 0..flags_len { + let flag = packet.read_string()?; + + println!("flag: {}", flag); + } + + let mut packet = conn.read_packet()?; // wait for known packs packet + + if packet.id() != 0x0E { + println!("got unexpected packet while looking for 0x0E: 0x{:02X}", packet.id()); + return Ok(()); + } + + let packs_len = packet.read_varint()?; + + println!("got {} known packs:", packs_len); + + for _ in 0..packs_len { + println!("{}:{} v{}", packet.read_string()?, packet.read_string()?, packet.read_string()?); + } + + packet.set_id(0x07); // make it serverbound + + conn.write_packet(&packet)?; + + let mut data = Vec::new(); + + loop { + let mut packet = conn.read_packet()?; + + if packet.id() != 0x07 { // update tags + let registries_len = packet.read_varint()?; + + println!("got update tags: {}", registries_len); + + for _ in 0..registries_len { + let registry = packet.read_string()?; + + println!("registry: {}", registry); + + let tags_len = packet.read_varint()?; + + for _ in 0..tags_len { + let tag_name = packet.read_string()?; + + println!("tag: {}", tag_name); + + let entries_len = packet.read_varint()?; + + for _ in 0..entries_len { + let entry = packet.read_varint()?; + + println!("entry: {}", entry); + } + } + } + + fs::write("update-tags.bin", packet.get_bytes()).unwrap(); + + break; + } + + println!("got registry: {}", packet.read_string()?); + + let entries_len = packet.read_varint()?; + + for _ in 0..entries_len { + let entry_id = packet.read_string()?; + let has_data = packet.read_boolean()?; + + if has_data { + let entry_data = packet.read_nbt()?; + + println!("entry: {}, data: {:?}", entry_id, entry_data); + } else { + println!("entry: {}, no data", entry_id); + } + } + + write_packet(&mut data, None, 0, &packet)?; + } + + fs::write("registry-data.bin", &data).unwrap(); + + let packet = conn.read_packet()?; + conn.write_packet(&packet)?; // finish conf + + loop { + let mut packet = conn.read_packet()?; + + if packet.id() == 0x41 { + let id = packet.read_varint()?; + + conn.write_packet(&Packet::build(0x00, |packet| packet.write_varint(id))?)?; + } + + if packet.id() == 0x27 { + // here you can read "Chunk Data and Update Light" packet + + break; + } + } + + Ok(()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..8977e04 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,52 @@ +use std::{fs, path::PathBuf}; + +use serde::{Deserialize, Serialize}; +use serde_default::DefaultFromSerde; + +#[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] +pub struct BindConfig { + #[serde(default = "default_host")] + pub host: String, + #[serde(default = "default_timeout")] + pub timeout: u64, +} + +#[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] +pub struct ServerConfig { + #[serde(default)] + pub online_mode: bool, + #[serde(default = "default_compression")] + pub compression_threshold: Option, +} + +#[derive(Debug, DefaultFromSerde, Serialize, Deserialize, Clone)] +pub struct Config { + #[serde(default)] + pub bind: BindConfig, + #[serde(default)] + pub server: ServerConfig, +} + +fn default_host() -> String { + "127.0.0.1:25565".to_string() +} +fn default_timeout() -> u64 { + 5 +} +fn default_compression() -> Option { + Some(256) +} + +impl Config { + pub fn load_from_file(path: PathBuf) -> Option { + if !fs::exists(&path).unwrap_or_default() { + let table = Config::default(); + fs::create_dir_all(&path.parent()?).ok()?; + fs::write(&path, toml::to_string_pretty(&table).ok()?).ok()?; + return Some(table); + } + let content = fs::read_to_string(&path).ok()?; + let table = toml::from_str::(&content).ok()?; + Some(table) + } +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..5d0a706 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,94 @@ +use std::{net::SocketAddr, sync::Arc}; + +use dashmap::DashMap; +use itertools::Itertools; +use uuid::Uuid; + +use super::{ + config::Config, + event::{Listener, PacketHandler}, + player::context::ClientContext, +}; + +// Контекст сервера +// Должен быть обернут в Arc для передачи между потоками +pub struct ServerContext { + pub config: Arc, + pub clients: DashMap>, + listeners: Vec>, + handlers: Vec>, +} + +impl ServerContext { + pub fn new(config: Arc) -> ServerContext { + ServerContext { + config, + listeners: Vec::new(), + handlers: Vec::new(), + clients: DashMap::new(), + } + } + + pub fn get_player_by_uuid(self: &Arc, uuid: Uuid) -> Option> { + self + .clients + .iter() + .find(|o| { + let info = o.player_info(); + if let Some(info) = info { + info.uuid == uuid + } else { + false + } + }) + .map(|o| o.clone()) + } + + pub fn get_player_by_name(self: &Arc, name: &str) -> Option> { + self + .clients + .iter() + .find(|o| { + let info = o.player_info(); + if let Some(info) = info { + info.name == name + } else { + false + } + }) + .map(|o| o.clone()) + } + + pub fn players(self: &Arc) -> Vec> { + self + .clients + .iter() + .filter(|o| o.player_info().is_some()) + .map(|o| o.clone()) + .collect() + } + + pub fn add_packet_handler(&mut self, handler: Box) { + self.handlers.push(handler); + } + + pub fn add_listener(&mut self, listener: Box) { + self.listeners.push(listener); + } + + pub fn packet_handlers(self: &Arc, sort_by: F) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K, + { + self.handlers.iter().sorted_by_key(sort_by).collect_vec() + } + + pub fn listeners(self: &Arc, sort_by: F) -> Vec<&Box> + where + K: Ord, + F: FnMut(&&Box) -> K, + { + self.listeners.iter().sorted_by_key(sort_by).collect_vec() + } +} diff --git a/src/d.rs b/src/d.rs deleted file mode 100644 index e50c356..0000000 --- a/src/d.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::ops::Index; - - - - - - - - -pub enum BufferError { - EndOfBuffer -} - -pub struct Buffer { - bytes: Vec, - index: usize -} - -impl Buffer { - pub fn new(bytes: Vec, index: usize) -> Self { - Buffer { bytes, index } - } - - pub fn read(&self, size: usize) -> Result, BufferError> { - if self.index + size >= self.bytes.len() {return Err(BufferError::EndOfBuffer);} - // self.index += size; - Ok(self.bytes[self.index..self.index+size-1].to_vec()) - } - - pub fn read2(&mut self, size: usize) -> Result, BufferError> { - if self.index + size >= self.bytes.len() {return Err(BufferError::EndOfBuffer);} - self.index += size; - Ok(self.bytes[self.index..self.index+size-1].to_vec()) - } -} - -pub trait Sas { - fn ts(&mut self); -} - -impl Sas for Buffer { - fn ts(&mut self) { - self.index += 1; - } -} \ No newline at end of file diff --git a/src/data.rs b/src/data.rs deleted file mode 100644 index e3367aa..0000000 --- a/src/data.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::{io::Read, net::{SocketAddr, TcpListener, TcpStream}}; - - - -pub enum ServerError { - ReadPacketError, - ConnectionClosedError, - ReadError, - BindError, - VarIntIsTooBig, - PacketIsEnd -} - - - -pub struct Packet { - size: i32, - data: Vec -} - -impl Packet { - pub fn read_from(socket: &Socket) -> Result { - let (size, n) = socket.read_varint_size()?; - let data = socket.read((size - n as i32) as usize)?; - Ok(Packet { size, data }) - } -} - - - -pub struct Socket { - pub stream: TcpStream, - pub addr: SocketAddr -} - -impl Socket { - pub fn read(&self, size: usize) -> Result, ServerError>{ - let mut buf: Vec = vec![0; size]; - match (&self.stream).read(&mut buf) { - Ok(n) => if n == size { - Ok(buf) - } else if n == 0 { - Err(ServerError::ConnectionClosedError) - } else { - buf.truncate(n); - buf.append(&mut self.read(size-n)?); - Ok(buf) - }, - Err(_) => Err(ServerError::ReadError) - } - } - - pub fn read_varint_size(&self) -> Result<(i32, u8), ServerError>{ - let mut result = 0i32; - let mut offset = 0; - let mut byte: u8; - loop { - byte = self.read(1)?[0]; - result |= ((byte & 0x7F) << offset) as i32; - if (byte & 0x80) == 0 {break;}; - offset += 7; - if offset >= 32 {return Err(ServerError::VarIntIsTooBig)} - } - Ok((result, offset / 7)) - } - - pub fn read_varint(&self) -> Result{ - Ok(self.read_varint_size()?.0) - } -} - - - -pub struct Server { - listener: TcpListener -} - -impl Server { - pub fn new(addr: &str) -> Result { - match TcpListener::bind(addr) { - Ok(listener) => Ok(Server { listener }), - Err(_) => Err(ServerError::BindError) - } - } - - pub fn accept(&self) -> Socket { - loop { - match self.listener.accept() { - Ok((stream, addr)) => return Socket {stream, addr}, - Err(_) => continue - } - } - } -} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..47ea8f6 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,51 @@ +use std::io::Read; + +use craftflow_nbt::DynNBT; +use rust_mc_proto::{DataReader, DataWriter, Packet}; + +use super::ServerError; + +pub mod text_component; + +// Трейт для чтения NBT-совместимых приколов +pub trait ReadWriteNBT: DataReader + DataWriter { + fn read_nbt(&mut self) -> Result; + fn write_nbt(&mut self, val: &T) -> Result<(), ServerError>; +} + +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + let mut data = Vec::new(); + let pos = self.get_ref().position(); + self + .get_mut() + .read_to_end(&mut data) + .map_err(|_| ServerError::DeNbt)?; + let (remaining, value) = craftflow_nbt::from_slice(&data).map_err(|_| ServerError::DeNbt)?; + self + .get_mut() + .set_position(pos + (data.len() - remaining.len()) as u64); + Ok(value) + } + + fn write_nbt(&mut self, val: &DynNBT) -> Result<(), ServerError> { + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ServerError::SerNbt)?; + Ok(()) + } +} + +pub trait ReadWritePosition: DataReader + DataWriter { + fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError>; + fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError>; +} + +impl ReadWritePosition for Packet { + fn read_position(&mut self) -> Result<(i64, i64, i64), ServerError> { + let val = self.read_long()?; + Ok((val >> 38, val << 52 >> 52, val << 26 >> 38)) + } + + fn write_position(&mut self, x: i64, y: i64, z: i64) -> Result<(), ServerError> { + Ok(self.write_long(((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF))?) + } +} diff --git a/src/data/text_component.rs b/src/data/text_component.rs new file mode 100644 index 0000000..d08264b --- /dev/null +++ b/src/data/text_component.rs @@ -0,0 +1,184 @@ +use std::io::Read; + +use palette::{Hsl, IntoColor, Srgb}; +use rust_mc_proto::Packet; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +use crate::ServerError; + +use super::ReadWriteNBT; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[skip_serializing_none] +pub struct TextComponent { + pub text: String, + pub color: Option, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, + pub extra: Option>, + // TODO: добавить все остальные стандартные поля для текст-компонента типа клик ивентов и сделать отдельный структ для транслейт компонент +} + +impl TextComponent { + pub fn new(text: String) -> Self { + Self { + text, + color: None, + bold: None, + italic: None, + underlined: None, + strikethrough: None, + obfuscated: None, + extra: None, + } + } + + pub fn rainbow(text: String) -> TextComponent { + if text.is_empty() { + return TextComponent::new(text); + } + + let children = text + .char_indices() + .map(|(i, c)| { + let hue = (i as f32) / (text.chars().count() as f32) * 360.0; + let hsl = Hsl::new(hue, 1.0, 0.5); + let rgb: Srgb = hsl.into_color(); + let r = (rgb.red * 255.0).round() as u8; + let g = (rgb.green * 255.0).round() as u8; + let b = (rgb.blue * 255.0).round() as u8; + let mut component = TextComponent::new(c.to_string()); + component.color = Some(format!("#{:02X}{:02X}{:02X}", r, g, b)); + component + }) + .collect::>(); + + let mut parent = children[0].clone(); + parent.extra = Some(children[1..].to_vec()); + parent + } + + pub fn builder() -> TextComponentBuilder { + TextComponentBuilder::new() + } + + pub fn as_json(self) -> Result { + serde_json::to_string(&self).map_err(|_| ServerError::SerTextComponent) + } + + pub fn from_json(text: &str) -> Result { + serde_json::from_str(text).map_err(|_| ServerError::DeTextComponent) + } +} + +impl Default for TextComponent { + fn default() -> Self { + Self::new(String::new()) + } +} + +pub struct TextComponentBuilder { + text: String, + color: Option, + bold: Option, + italic: Option, + underlined: Option, + strikethrough: Option, + obfuscated: Option, + extra: Option>, +} + +impl TextComponentBuilder { + pub fn new() -> Self { + Self { + text: String::new(), + color: None, + bold: None, + italic: None, + underlined: None, + strikethrough: None, + obfuscated: None, + extra: None, + } + } + + pub fn text(mut self, text: &str) -> Self { + self.text = text.to_string(); + self + } + + pub fn color(mut self, color: &str) -> Self { + self.color = Some(color.to_string()); + self + } + + pub fn bold(mut self, bold: bool) -> Self { + self.bold = Some(bold); + self + } + + pub fn italic(mut self, italic: bool) -> Self { + self.italic = Some(italic); + self + } + + pub fn underlined(mut self, underlined: bool) -> Self { + self.underlined = Some(underlined); + self + } + + pub fn strikethrough(mut self, strikethrough: bool) -> Self { + self.strikethrough = Some(strikethrough); + self + } + + pub fn obfuscated(mut self, obfuscated: bool) -> Self { + self.obfuscated = Some(obfuscated); + self + } + + pub fn extra(mut self, extra: Vec) -> Self { + self.extra = Some(extra); + self + } + + pub fn build(self) -> TextComponent { + TextComponent { + text: self.text, + color: self.color, + bold: self.bold, + italic: self.italic, + underlined: self.underlined, + strikethrough: self.strikethrough, + obfuscated: self.obfuscated, + extra: self.extra, + } + } +} + +// Реализуем читалку-записывалку текст-компонентов для пакета +impl ReadWriteNBT for Packet { + fn read_nbt(&mut self) -> Result { + let mut data = Vec::new(); + let pos = self.get_ref().position(); + self + .get_mut() + .read_to_end(&mut data) + .map_err(|_| ServerError::DeTextComponent)?; + let (remaining, value) = + craftflow_nbt::from_slice(&data).map_err(|_| ServerError::DeTextComponent)?; + self + .get_mut() + .set_position(pos + (data.len() - remaining.len()) as u64); + Ok(value) + } + + fn write_nbt(&mut self, val: &TextComponent) -> Result<(), ServerError> { + craftflow_nbt::to_writer(self.get_mut(), val).map_err(|_| ServerError::SerTextComponent)?; + Ok(()) + } +} diff --git a/src/event/mod.rs b/src/event/mod.rs new file mode 100644 index 0000000..785aa70 --- /dev/null +++ b/src/event/mod.rs @@ -0,0 +1,49 @@ +use rust_mc_proto::Packet; + +use super::{ServerError, player::context::ClientContext, protocol::ConnectionState}; +use std::sync::Arc; + +#[macro_export] +macro_rules! generate_handlers { + ($name:ident $(, $arg_ty:ty)* $(,)?) => { + paste::paste! { + fn [](&self) -> i8 { + 0 + } + + fn [](&self, _: Arc $(, _: $arg_ty)*) -> Result<(), ServerError> { + Ok(()) + } + } + }; +} + +/// Пример использования: +/// +/// trigger_event!(client, status, &mut response, state); +#[macro_export] +macro_rules! trigger_event { + ($client:ident, $event:ident $(, $arg_ty:expr)* $(,)?) => {{ + paste::paste! { + for handler in $client.server.listeners( + |o| o.[]() + ).iter() { + handler.[]( + $client.clone() + $(, $arg_ty)* + )?; + } + } + }}; +} + +pub trait Listener: Sync + Send { + generate_handlers!(status, &mut String); + generate_handlers!(plugin_message, &str, &[u8]); +} + +pub trait PacketHandler: Sync + Send { + generate_handlers!(incoming_packet, &mut Packet, &mut bool, ConnectionState); + generate_handlers!(outcoming_packet, &mut Packet, &mut bool, ConnectionState); + generate_handlers!(state, ConnectionState); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ad4e1ab --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,136 @@ +use std::{error::Error, fmt::Display, net::TcpListener, sync::Arc, thread, time::Duration}; + +use config::Config; +use context::ServerContext; +use ignore_result::Ignore; +use log::{error, info}; +use player::context::ClientContext; +use protocol::handler::handle_connection; +use rust_mc_proto::{MinecraftConnection, ProtocolError}; + +pub mod config; +pub mod context; +pub mod data; +pub mod event; +pub mod player; +pub mod protocol; + +// Ошибки сервера +#[derive(Debug)] +pub enum ServerError { + UnexpectedPacket(u8), // Неожиданный пакет + WrongPacket, // Пакет поломан, неверные данные + Protocol(ProtocolError), // Ошибка в протоколе при работе с rust_mc_proto + ConnectionClosed, // Соединение закрыто, единственная ошибка которая не логируется у handle_connection + SerTextComponent, // Ошибка при сериализации текст-компонента + DeTextComponent, // Ошибка при десериализации текст-компонента + SerNbt, // Ошибка при сериализации nbt + DeNbt, // Ошибка при десериализации nbt + UnexpectedState, // Указывает на то что этот пакет не может быть отправлен в данном режиме (в основном через ProtocolHelper) + Other(String), // Другая ошибка, либо очень специфичная, либо хз, лучше не использовать и создавать новое поле ошибки +} + +impl Display for ServerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{:?}", self)) + } +} + +impl Error for ServerError {} + +// Делаем чтобы ProtocolError мог переделываться в наш ServerError +impl From for ServerError { + fn from(error: ProtocolError) -> ServerError { + match error { + // Если просто закрыто соединение, переделываем в нашу ошибку этого + ProtocolError::ConnectionClosedError => ServerError::ConnectionClosed, + // Все остальное просто засовываем в обертку + error => ServerError::Protocol(error), + } + } +} + +pub fn start_server(server: Arc) { + // Биндим сервер где надо + let Ok(listener) = TcpListener::bind(&server.config.bind.host) else { + error!( + "Не удалось забиндить сервер на {}", + &server.config.bind.host + ); + return; + }; + + info!("Сервер запущен на {}", &server.config.bind.host); + + while let Ok((stream, addr)) = listener.accept() { + let server = server.clone(); + + thread::spawn(move || { + info!("Подключение: {}", addr); + + // Установка таймаутов на чтение и запись + // По умолчанию пусть будет 5 секунд, надо будет сделать настройку через конфиг + stream + .set_read_timeout(Some(Duration::from_secs(server.config.bind.timeout))) + .ignore(); + stream + .set_write_timeout(Some(Duration::from_secs(server.config.bind.timeout))) + .ignore(); + + // Оборачиваем стрим в майнкрафт конекшн лично для нашего удовольствия + let conn = MinecraftConnection::new(stream); + + // Создаем контекст клиента + // Передавется во все листенеры и хандлеры чтобы определять именно этот клиент + let client = Arc::new(ClientContext::new(server.clone(), conn)); + + // Добавляем клиента в список клиентов сервера + // Используем адрес как ключ, врятли ipv4 будет нам врать + server.clients.insert(client.addr, client.clone()); + + // Обработка подключения + // Если ошибка -> выводим + match handle_connection(client.clone()) { + Ok(_) => {} + Err(ServerError::ConnectionClosed) => {} + Err(error) => { + error!("Ошибка подключения: {error:?}"); + } + }; + + // Удаляем клиента из списка клиентов + server.clients.remove(&client.addr); + + info!("Отключение: {}", addr); + }); + } +} + +// server start helper +// pub struct Server { +// context: Arc, +// } + +// impl Server { +// pub fn new(context: ServerContext) -> Self { +// Self { +// context: Arc::new(context), +// } +// } + +// pub fn context(&self) -> &ServerContext { +// &self.context +// } + +// pub fn start(&self) { +// start_server(self.context.clone()); +// } +// } + +// impl Default for Server { +// fn default() -> Self { +// Self { +// context: Arc::new(ServerContext::new(Arc::new(Config::default()))), +// } +// } +// } diff --git a/src/main.rs b/src/main.rs index 40c5fed..1701297 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,164 @@ -mod data; -use data::{Packet, Server, Socket}; +use std::{env::args, path::PathBuf, sync::Arc}; -mod d; -use d::*; +use log::{debug, error, info}; +use rust_mc_proto::Packet; +use rust_mc_serv::{ + ServerError, + config::Config, + context::ServerContext, + data::text_component::TextComponent, + event::{Listener, PacketHandler}, + player::context::ClientContext, + protocol::ConnectionState, + start_server, +}; -use std::thread; +struct ExampleListener; -fn get_byte_size(i: i32) -> u8 { - for j in 1..4 { - if (i & -1 << (j * 7)) == 0 { - return j; - } - }; return 5; +impl Listener for ExampleListener { + fn on_status( + &self, + client: Arc, + response: &mut String, + ) -> Result<(), ServerError> { + *response = format!( + "{{ + \"version\": {{ + \"name\": \"idk\", + \"protocol\": {} + }}, + \"players\": {{ + \"max\": 100, + \"online\": 42, + \"sample\": [ + {{ + \"name\": \"Жопа\", + \"id\": \"00000000-0000-0000-0000-000000000000\" + }} + ] + }}, + \"description\": {}, + \"favicon\": \"data:image/png;base64,\", + \"enforcesSecureChat\": false + }}", + client.handshake().unwrap().protocol_version, + TextComponent::builder() + .text("Hello World! ") + .extra(vec![ + TextComponent::builder() + .text("Protocol: ") + .color("gold") + .extra(vec![ + TextComponent::builder() + .text(&client.handshake().unwrap().protocol_version.to_string()) + .underlined(true) + .build() + ]) + .build(), + TextComponent::builder() + .text("\nServer Addr: ") + .color("green") + .extra(vec![ + TextComponent::builder() + .text(&format!( + "{}:{}", + client.handshake().unwrap().server_address, + client.handshake().unwrap().server_port + )) + .underlined(true) + .build() + ]) + .build() + ]) + .build() + .as_json()? + ); + + Ok(()) + } +} + +struct ExamplePacketHandler; + +impl PacketHandler for ExamplePacketHandler { + fn on_incoming_packet( + &self, + client: Arc, + packet: &mut Packet, + _: &mut bool, + state: ConnectionState, + ) -> Result<(), ServerError> { + debug!( + "{} -> S\t| 0x{:02x}\t| {:?}\t| {} bytes", + client.addr.clone(), + packet.id(), + state, + packet.len() + ); + + Ok(()) + } + + fn on_outcoming_packet( + &self, + client: Arc, + packet: &mut Packet, + _: &mut bool, + state: ConnectionState, + ) -> Result<(), ServerError> { + debug!( + "{} <- S\t| 0x{:02x}\t| {:?}\t| {} bytes", + client.addr.clone(), + packet.id(), + state, + packet.len() + ); + + Ok(()) + } } fn main() { - println!("{}", get_byte_size(-2147483648)); + // Инициализируем логи + // Чтобы читать debug-логи, юзаем `RUST_LOG=debug cargo run` + colog::init(); - // let Ok(server) = Server::new("127.0.0.1:25565") else { - // println!("Не удалось забиндить сервер"); return; - // }; + // Получение аргументов + let exec = args().next().expect("Неизвестная система"); + let args = args().skip(1).collect::>(); - // loop { - // let socket = server.accept(); - // thread::spawn(move || { handle_connection(socket); }); - // } -} - -fn handle_connection(socket: Socket) { - let Ok(packet) = Packet::read_from(&socket) else {return;}; - // пакет уже имеет свой размер (size) и данные (data) - // надо поместить пакет в очередь, обработать по шаблону и отдать обработчику - - // fn on_keep_alive(socket: Socket, time: u64) { - // if time != self.time { - // socket.close() - // } - // } + if args.len() > 1 { + info!("Использование: {exec} [путь до файла конфигурации]"); + return; + } + + // Берем путь из аргумента либо по дефолту берем "./server.toml" + let config_path = PathBuf::from(args.get(0).unwrap_or(&"server.toml".to_string())); + + // Чтение конфига, если ошибка - выводим + let config = match Config::load_from_file(config_path) { + Some(config) => config, + None => { + error!("Ошибка чтения конфигурации"); + return; + } + }; + + // Делаем немутабельную потокобезопасную ссылку на конфиг + // Впринципе можно и просто клонировать сам конфиг в каждый сука поток ебать того рот ебать блять + // но мы этого делать не будем чтобы не было мемори лик лишнего + let config = Arc::new(config); + + // Создаем контекст сервера + // Передается во все подключения + let mut server = ServerContext::new(config); + + server.add_listener(Box::new(ExampleListener)); // Добавляем пример листенера + server.add_packet_handler(Box::new(ExamplePacketHandler)); // Добавляем пример пакет хандлера + + // Бетонируем сервер контекст от изменений + let server = Arc::new(server); + + // Запускаем сервер из специально отведенной под это дело функцией + start_server(server); } diff --git a/src/player/context.rs b/src/player/context.rs new file mode 100644 index 0000000..74f6bdb --- /dev/null +++ b/src/player/context.rs @@ -0,0 +1,306 @@ +use std::{ + collections::VecDeque, + hash::Hash, + net::{SocketAddr, TcpStream}, + sync::{ + Arc, Mutex, RwLock, + atomic::{AtomicBool, Ordering}, + }, + thread, + time::Duration, +}; + +use rust_mc_proto::{MinecraftConnection, Packet}; +use uuid::Uuid; + +use super::helper::ProtocolHelper; +use crate::{ServerError, context::ServerContext, protocol::ConnectionState}; + +// Клиент контекст +// Должен быть обернут в Arc для передачи между потоками +pub struct ClientContext { + pub server: Arc, + pub addr: SocketAddr, + conn: RwLock>, + handshake: RwLock>, + client_info: RwLock>, + player_info: RwLock>, + state: RwLock, + packet_buffer: Mutex>, + read_loop: AtomicBool, + is_alive: AtomicBool, + position: RwLock<(f64, f64, f64)>, + velocity: RwLock<(f64, f64, f64)>, + rotation: RwLock<(f32, f32)>, +} + +// Реализуем сравнение через адрес +// IPv4 не должен обманывать, иначе у нас случится коллапс +impl PartialEq for ClientContext { + fn eq(&self, other: &Self) -> bool { + self.addr == other.addr + } +} + +impl Hash for ClientContext { + fn hash(&self, state: &mut H) { + self.addr.hash(state); + } +} + +impl Eq for ClientContext {} + +impl ClientContext { + pub fn new(server: Arc, conn: MinecraftConnection) -> ClientContext { + ClientContext { + server, + addr: conn.get_ref().peer_addr().unwrap(), + conn: RwLock::new(conn), + handshake: RwLock::new(None), + client_info: RwLock::new(None), + player_info: RwLock::new(None), + state: RwLock::new(ConnectionState::Handshake), + packet_buffer: Mutex::new(VecDeque::new()), + read_loop: AtomicBool::new(false), + is_alive: AtomicBool::new(true), + position: RwLock::new((0.0, 0.0, 0.0)), + velocity: RwLock::new((0.0, 0.0, 0.0)), + rotation: RwLock::new((0.0, 0.0)), + } + } + + pub fn set_handshake(self: &Arc, handshake: Handshake) { + *self.handshake.write().unwrap() = Some(handshake); + } + + pub fn set_client_info(self: &Arc, client_info: ClientInfo) { + *self.client_info.write().unwrap() = Some(client_info); + } + + pub fn set_player_info(self: &Arc, player_info: PlayerInfo) { + *self.player_info.write().unwrap() = Some(player_info); + } + + pub fn set_state(self: &Arc, state: ConnectionState) -> Result<(), ServerError> { + *self.state.write().unwrap() = state.clone(); + + for handler in self + .server + .packet_handlers(|o| o.on_state_priority()) + .iter() + { + handler.on_state(self.clone(), state.clone())?; + } + + Ok(()) + } + + pub fn handshake(self: &Arc) -> Option { + self.handshake.read().unwrap().clone() + } + + pub fn client_info(self: &Arc) -> Option { + self.client_info.read().unwrap().clone() + } + + pub fn player_info(self: &Arc) -> Option { + self.player_info.read().unwrap().clone() + } + + pub fn state(self: &Arc) -> ConnectionState { + self.state.read().unwrap().clone() + } + + pub fn set_position(self: &Arc, position: (f64, f64, f64)) { + *self.position.write().unwrap() = position; + } + + pub fn set_velocity(self: &Arc, velocity: (f64, f64, f64)) { + *self.velocity.write().unwrap() = velocity; + } + + pub fn set_rotation(self: &Arc, rotation: (f32, f32)) { + *self.rotation.write().unwrap() = rotation; + } + + pub fn position(self: &Arc) -> (f64, f64, f64) { + self.position.read().unwrap().clone() + } + + pub fn velocity(self: &Arc) -> (f64, f64, f64) { + self.velocity.read().unwrap().clone() + } + + pub fn rotation(self: &Arc) -> (f32, f32) { + self.rotation.read().unwrap().clone() + } + + pub fn write_packet(self: &Arc, packet: &Packet) -> Result<(), ServerError> { + let state = self.state(); + let mut packet = packet.clone(); + let mut cancelled = false; + for handler in self + .server + .packet_handlers(|o| o.on_outcoming_packet_priority()) + .iter() + { + handler.on_outcoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + match self.conn.write().unwrap().write_packet(&packet) { + Ok(_) => {} + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e.into()); + } + }; + } + Ok(()) + } + + pub fn run_read_loop(self: &Arc) -> Result<(), ServerError> { + self.read_loop.store(true, Ordering::SeqCst); + + let mut conn = self.conn.read().unwrap().try_clone()?; // так можно делать т.к сокет это просто поинтер + + while self.is_alive() { + let mut packet = match conn.read_packet() { + Ok(v) => v, + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e.into()); + } + }; + let mut cancelled = false; + let state = self.state(); + for handler in self + .server + .packet_handlers(|o| o.on_incoming_packet_priority()) + .iter() + { + handler.on_incoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + self.packet_buffer.lock().unwrap().push_back(packet); + } + } + + Ok(()) + } + + /// Please avoid using of this bullshit + pub fn read_any_packet(self: &Arc) -> Result { + if self.read_loop.load(Ordering::SeqCst) { + loop { + if let Some(packet) = self.packet_buffer.lock().unwrap().pop_front() { + return Ok(packet); + } + thread::sleep(Duration::from_millis(4)); + } + } else { + let state = self.state(); + + loop { + let mut packet = match self.conn.write().unwrap().read_packet() { + Ok(v) => v, + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e.into()); + } + }; + let mut cancelled = false; + for handler in self + .server + .packet_handlers(|o| o.on_incoming_packet_priority()) + .iter() + { + handler.on_incoming_packet(self.clone(), &mut packet, &mut cancelled, state.clone())?; + packet.get_mut().set_position(0); + } + if !cancelled { + break Ok(packet); + } + } + } + } + + pub fn read_packet(self: &Arc, ids: &[u8]) -> Result { + if self.read_loop.load(Ordering::SeqCst) { + loop { + { + let mut locked = self.packet_buffer.lock().unwrap(); + for (i, packet) in locked.clone().iter().enumerate() { + if ids.contains(&packet.id()) { + locked.remove(i); + return Ok(packet.clone()); + } + } + } + thread::sleep(Duration::from_millis(4)); + } + } else { + let packet = match self.read_any_packet() { + Ok(v) => v, + Err(e) => { + self.is_alive.store(false, Ordering::SeqCst); + return Err(e); + } + }; + + if ids.contains(&packet.id()) { + Ok(packet) + } else { + Err(ServerError::UnexpectedPacket(packet.id())) + } + } + } + + pub fn push_packet_back(self: &Arc, packet: Packet) { + self.packet_buffer.lock().unwrap().push_back(packet) + } + + pub fn close(self: &Arc) { + self.conn.write().unwrap().close(); + } + + pub fn set_compression(self: &Arc, threshold: Option) { + self.conn.write().unwrap().set_compression(threshold); + } + + pub fn is_alive(self: &Arc) -> bool { + self.is_alive.load(Ordering::SeqCst) + } + + pub fn protocol_helper(self: &Arc) -> ProtocolHelper { + ProtocolHelper::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct Handshake { + pub protocol_version: i32, + pub server_address: String, + pub server_port: u16, +} + +#[derive(Clone)] +pub struct ClientInfo { + pub brand: String, + pub locale: String, + pub view_distance: i8, + pub chat_mode: i32, + pub chat_colors: bool, + pub displayed_skin_parts: u8, + pub main_hand: i32, + pub enable_text_filtering: bool, + pub allow_server_listings: bool, + pub particle_status: i32, +} + +#[derive(Clone)] +pub struct PlayerInfo { + pub name: String, + pub uuid: Uuid, +} diff --git a/src/player/helper.rs b/src/player/helper.rs new file mode 100644 index 0000000..d33cc44 --- /dev/null +++ b/src/player/helper.rs @@ -0,0 +1,238 @@ +use std::{ + io::Read, + sync::Arc, + time::{Duration, SystemTime}, +}; + +use rust_mc_proto::{DataReader, DataWriter, Packet}; + +use crate::{ + ServerError, + data::{ReadWriteNBT, text_component::TextComponent}, + protocol::{ + id::{clientbound, serverbound}, + *, + }, +}; + +use super::context::ClientContext; + +// Помощник в работе с протоколом +// Может быть использован где угодно, но сделан именно для листенеров и пакет хандлеров +// Через него удобно делать всякую одинаковую херь +// Возможно надо было бы сделать прям обязательный какойто структ через который только можно было отправлять пакеты ... +// ... но мне лень +// Пусть юзают подключение и отправляют пакеты через него если хотят +// Почему бы и нет если да +pub struct ProtocolHelper { + client: Arc, + state: ConnectionState, +} + +impl ProtocolHelper { + pub fn new(client: Arc) -> Self { + Self { + state: client.state(), + client, + } + } + + pub fn reset_chat(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Configuration => { + self + .client + .write_packet(&Packet::empty(clientbound::configuration::RESET_CHAT))?; + Ok(()) + } + _ => Err(ServerError::UnexpectedState), + } + } + + pub fn store_cookie(&self, id: &str, data: &[u8]) -> Result<(), ServerError> { + self.client.write_packet(&Packet::build( + match self.state { + ConnectionState::Configuration => clientbound::configuration::STORE_COOKIE, + ConnectionState::Play => clientbound::play::STORE_COOKIE, + _ => return Err(ServerError::UnexpectedState), + }, + |p| { + p.write_string(id)?; + p.write_bytes(data) + }, + )?)?; + Ok(()) + } + + /// Leave from Configuration to Play state + pub fn leave_configuration(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Configuration => { + self + .client + .write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + self + .client + .read_packet(&[serverbound::configuration::ACKNOWLEDGE_FINISH])?; + self.client.set_state(ConnectionState::Play)?; + Ok(()) + } + _ => Err(ServerError::UnexpectedState), + } + } + + /// Enter to Configuration from Play state + pub fn enter_configuration(&self) -> Result<(), ServerError> { + match self.state { + ConnectionState::Play => { + self + .client + .write_packet(&Packet::empty(clientbound::play::START_CONFIGURATION))?; + self + .client + .read_packet(&[serverbound::play::ACKNOWLEDGE_CONFIGURATION])?; + self.client.set_state(ConnectionState::Configuration)?; + Ok(()) + } + _ => Err(ServerError::UnexpectedState), + } + } + + /// Enter to Configuration from Play state + pub fn ping(&self) -> Result { + match self.state { + ConnectionState::Play => { + let time = SystemTime::now(); + self + .client + .write_packet(&Packet::empty(clientbound::play::PING))?; + self.client.read_packet(&[serverbound::play::PONG])?; + Ok(SystemTime::now().duration_since(time).unwrap()) + } + ConnectionState::Configuration => { + let time = SystemTime::now(); + self + .client + .write_packet(&Packet::empty(clientbound::configuration::PING))?; + self + .client + .read_packet(&[serverbound::configuration::PONG])?; + Ok(SystemTime::now().duration_since(time).unwrap()) + } + _ => Err(ServerError::UnexpectedState), + } + } + + pub fn disconnect(&self, reason: TextComponent) -> Result<(), ServerError> { + let packet = match self.state { + ConnectionState::Login => { + let text = reason.as_json()?; + Packet::build(0x00, |p| p.write_string(&text))? + } + ConnectionState::Configuration => { + let mut packet = Packet::empty(0x02); + packet.write_nbt(&reason)?; + packet + } + ConnectionState::Play => { + let mut packet = Packet::empty(0x1C); + packet.write_nbt(&reason)?; + packet + } + _ => { + self.client.close(); + return Ok(()); + } + }; + self.client.write_packet(&packet)?; + Ok(()) + } + + /// Returns cookie content + pub fn request_cookie(&self, id: &str) -> Result>, ServerError> { + match self.state { + ConnectionState::Configuration => { + let mut packet = Packet::empty(clientbound::configuration::COOKIE_REQUEST); + packet.write_string(id)?; + self.client.write_packet(&packet)?; + + let mut packet = self + .client + .read_packet(&[serverbound::configuration::COOKIE_RESPONSE])?; + packet.read_string()?; + let data = if packet.read_boolean()? { + let n = packet.read_usize_varint()?; + Some(packet.read_bytes(n)?) + } else { + None + }; + + Ok(data) + } + ConnectionState::Play => { + let mut packet = Packet::empty(clientbound::play::COOKIE_REQUEST); + packet.write_string(id)?; + self.client.write_packet(&packet)?; + + let mut packet = self + .client + .read_packet(&[serverbound::play::COOKIE_RESPONSE])?; + packet.read_string()?; + let data = if packet.read_boolean()? { + let n = packet.read_usize_varint()?; + Some(packet.read_bytes(n)?) + } else { + None + }; + + Ok(data) + } + _ => Err(ServerError::UnexpectedState), + } + } + + /// Returns login plugin response - (message_id, payload) + pub fn send_login_plugin_request( + &self, + id: i32, + channel: &str, + data: &[u8], + ) -> Result<(i32, Option>), ServerError> { + match self.state { + ConnectionState::Login => { + let mut packet = Packet::empty(clientbound::login::PLUGIN_REQUEST); + packet.write_varint(id)?; + packet.write_string(channel)?; + packet.write_bytes(data)?; + self.client.write_packet(&packet)?; + + let mut packet = self + .client + .read_packet(&[serverbound::login::PLUGIN_RESPONSE])?; + let identifier = packet.read_varint()?; + let data = if packet.read_boolean()? { + let mut data = Vec::new(); + packet.get_mut().read_to_end(&mut data).unwrap(); + Some(data) + } else { + None + }; + + Ok((identifier, data)) + } + _ => Err(ServerError::UnexpectedState), + } + } + + pub fn send_plugin_message(&self, channel: &str, data: &[u8]) -> Result<(), ServerError> { + let mut packet = match self.state { + ConnectionState::Configuration => Packet::empty(clientbound::configuration::PLUGIN_MESSAGE), + ConnectionState::Play => Packet::empty(clientbound::play::PLUGIN_MESSAGE), + _ => return Err(ServerError::UnexpectedState), + }; + packet.write_string(channel)?; + packet.write_bytes(data)?; + self.client.write_packet(&packet)?; + Ok(()) + } +} diff --git a/src/player/mod.rs b/src/player/mod.rs new file mode 100644 index 0000000..ccbba0b --- /dev/null +++ b/src/player/mod.rs @@ -0,0 +1,2 @@ +pub mod context; +pub mod helper; diff --git a/src/protocol/handler.rs b/src/protocol/handler.rs new file mode 100644 index 0000000..3967afe --- /dev/null +++ b/src/protocol/handler.rs @@ -0,0 +1,191 @@ +use std::{io::Read, sync::Arc}; + +use crate::{ + ServerError, + player::context::{ClientContext, ClientInfo, Handshake, PlayerInfo}, +}; +use rust_mc_proto::{DataReader, DataWriter, Packet}; + +use crate::trigger_event; + +use super::{ + ConnectionState, + id::*, + play::{handle_configuration_state, handle_play_state}, +}; + +pub const BRAND: &str = "rust_mc_serv"; + +pub fn handle_connection( + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + // Чтение рукопожатия + // Получение пакетов производится через client.conn(), + // ВАЖНО: не помещать сам client.conn() в переменные, + // он должен сразу убиваться иначе соединение гдето задедлочится + let mut packet = client.read_packet(&[serverbound::handshake::HANDSHAKE])?; + + let protocol_version = packet.read_varint()?; // Получаем версия протокола, может быть отрицательным если наш клиент дэбил + let server_address = packet.read_string()?; // Получаем домен/адрес сервера к которому пытается подключиться клиент, например "play.example.com", а не айпи + let server_port = packet.read_unsigned_short()?; // Все тоже самое что и с адресом сервера и все потому же и за тем же + let next_state = packet.read_varint()?; // Тип подключения: 1 для получения статуса и пинга, 2 и 3 для обычного подключения + + client.set_handshake(Handshake { + protocol_version, + server_address, + server_port, + }); + + match next_state { + 1 => { + // Тип подключения - статус + client.set_state(ConnectionState::Status)?; // Мы находимся в режиме Status + + loop { + // Чтение запроса + let mut packet = client.read_any_packet()?; + + match packet.id() { + serverbound::status::REQUEST => { + // Запрос статуса + let mut packet = Packet::empty(clientbound::status::RESPONSE); + + // Дефолтный статус + let mut status = "{ + \"version\": { + \"name\": \"Error\", + \"protocol\": 0 + }, + \"description\": {\"text\": \"Internal server error\"} + }" + .to_string(); + + // Опрос всех листенеров + trigger_event!(client, status, &mut status); + + // Отправка статуса + packet.write_string(&status)?; + + client.write_packet(&packet)?; + } + serverbound::status::PING_REQUEST => { + // Пинг + // Раньше мы просто отправляли ему его-же пакет, но сейчас, + // С приходом к власти констант айди-пакетов, нам приходится делать такое непотребство + let timestamp = packet.read_long()?; + let mut packet = Packet::empty(clientbound::status::PONG_RESPONSE); + packet.write_long(timestamp)?; + client.write_packet(&packet)?; + } + id => { + return Err(ServerError::UnexpectedPacket(id)); + } + } + } + } + 2 => { + // Тип подключения - игра + client.set_state(ConnectionState::Login)?; // Мы находимся в режиме Login + + // Читаем пакет Login Start + let mut packet = client.read_packet(&[serverbound::login::START])?; + + let name = packet.read_string()?; + let uuid = packet.read_uuid()?; + + client.set_player_info(PlayerInfo { + name: name.clone(), + uuid: uuid.clone(), + }); + + if client.server.config.server.online_mode { + // TODO: encryption packets + } + + // Отправляем пакет Set Compression если сжатие указано + if let Some(threshold) = client.server.config.server.compression_threshold { + client.write_packet(&Packet::build(clientbound::login::SET_COMPRESSION, |p| { + p.write_usize_varint(threshold) + })?)?; + client.set_compression(Some(threshold)); // Устанавливаем сжатие на соединении + } + + // Отправка пакета Login Success + client.write_packet(&Packet::build(clientbound::login::SUCCESS, |p| { + p.write_uuid(&uuid)?; + p.write_string(&name)?; + p.write_varint(0) + })?)?; + + client.read_packet(&[serverbound::login::ACKNOWLEDGED])?; // Пакет Login Acknowledged + + client.set_state(ConnectionState::Configuration)?; // Мы перешли в режим Configuration + + // Получение бренда клиента из Serverbound Plugin Message + // Identifier канала откуда берется бренд: minecraft:brand + let brand = loop { + let mut packet = client.read_packet(&[serverbound::configuration::PLUGIN_MESSAGE])?; // Пакет Serverbound Plugin Message + + let identifier = packet.read_string()?; + + let mut data = Vec::new(); + packet.get_mut().read_to_end(&mut data).unwrap(); + + if identifier == "minecraft:brand" { + break String::from_utf8_lossy(&data).to_string(); + } else { + trigger_event!(client, plugin_message, &identifier, &data); + } + }; + + let mut packet = client.read_packet(&[serverbound::configuration::CLIENT_INFORMATION])?; // Пакет Client Information + + let locale = packet.read_string()?; // for example: en_us + let view_distance = packet.read_signed_byte()?; // client-side render distance in chunks + let chat_mode = packet.read_varint()?; // 0: enabled, 1: commands only, 2: hidden. See Chat#Client chat mode for more information. + let chat_colors = packet.read_boolean()?; // this settings does nothing on client but can be used on serverside + let displayed_skin_parts = packet.read_byte()?; // bit mask https://minecraft.wiki/w/Java_Edition_protocol#Client_Information_(configuration) + let main_hand = packet.read_varint()?; // 0 for left and 1 for right + let enable_text_filtering = packet.read_boolean()?; // filtering text for profanity, always false for offline mode + let allow_server_listings = packet.read_boolean()?; // allows showing player in server listings in status + let particle_status = packet.read_varint()?; // 0 for all, 1 for decreased, 2 for minimal + + client.set_client_info(ClientInfo { + brand, + locale, + view_distance, + chat_mode, + chat_colors, + displayed_skin_parts, + main_hand, + enable_text_filtering, + allow_server_listings, + particle_status, + }); + + client.write_packet(&Packet::build( + clientbound::configuration::PLUGIN_MESSAGE, + |p| { + p.write_string("minecraft:brand")?; + p.write_string(BRAND) + }, + )?)?; + + handle_configuration_state(client.clone())?; + + client.write_packet(&Packet::empty(clientbound::configuration::FINISH))?; + client.read_packet(&[serverbound::configuration::ACKNOWLEDGE_FINISH])?; + + client.set_state(ConnectionState::Play)?; // Мы перешли в режим Play + + // Дальше работаем с режимом игры + handle_play_state(client)?; + } + _ => { + // Тип подключения не рукопожатный + return Err(ServerError::UnexpectedState); + } + } + + Ok(()) +} diff --git a/src/protocol/id.rs b/src/protocol/id.rs new file mode 100644 index 0000000..58f14d1 --- /dev/null +++ b/src/protocol/id.rs @@ -0,0 +1,272 @@ +/* + +Generated with parse_ids.py + +*/ + +pub mod clientbound { + pub mod status { + pub const RESPONSE: u8 = 0x00; + pub const PONG_RESPONSE: u8 = 0x01; + } + + pub mod login { + pub const DISCONNECT: u8 = 0x00; + pub const ENCRYPTION_REQUEST: u8 = 0x01; + pub const SUCCESS: u8 = 0x02; + pub const SET_COMPRESSION: u8 = 0x03; + pub const PLUGIN_REQUEST: u8 = 0x04; + pub const COOKIE_REQUEST: u8 = 0x05; + } + + pub mod configuration { + pub const COOKIE_REQUEST: u8 = 0x00; + pub const PLUGIN_MESSAGE: u8 = 0x01; + pub const DISCONNECT: u8 = 0x02; + pub const FINISH: u8 = 0x03; + pub const KEEP_ALIVE: u8 = 0x04; + pub const PING: u8 = 0x05; + pub const RESET_CHAT: u8 = 0x06; + pub const REGISTRY_DATA: u8 = 0x07; + pub const REMOVE_RESOURCE_PACK: u8 = 0x08; + pub const ADD_RESOURCE_PACK: u8 = 0x09; + pub const STORE_COOKIE: u8 = 0x0A; + pub const TRANSFER: u8 = 0x0B; + pub const FEATURE_FLAGS: u8 = 0x0C; + pub const UPDATE_TAGS: u8 = 0x0D; + pub const KNOWN_PACKS: u8 = 0x0E; + pub const CUSTOM_REPORT_DETAILS: u8 = 0x0F; + pub const SERVER_LINKS: u8 = 0x10; + } + + pub mod play { + pub const BUNDLE_DELIMITER: u8 = 0x00; + pub const SPAWN_ENTITY: u8 = 0x01; + pub const ENTITY_ANIMATION: u8 = 0x02; + pub const AWARD_STATISTICS: u8 = 0x03; + pub const ACKNOWLEDGE_BLOCK_CHANGE: u8 = 0x04; + pub const SET_BLOCK_DESTROY_STAGE: u8 = 0x05; + pub const BLOCK_ENTITY_DATA: u8 = 0x06; + pub const BLOCK_ACTION: u8 = 0x07; + pub const BLOCK_UPDATE: u8 = 0x08; + pub const BOSS_BAR: u8 = 0x09; + pub const CHANGE_DIFFICULTY: u8 = 0x0A; + pub const CHUNK_BATCH_FINISHED: u8 = 0x0B; + pub const CHUNK_BATCH_START: u8 = 0x0C; + pub const CHUNK_BIOMES: u8 = 0x0D; + pub const CLEAR_TITLES: u8 = 0x0E; + pub const COMMAND_SUGGESTIONS_RESPONSE: u8 = 0x0F; + pub const COMMANDS: u8 = 0x10; + pub const CLOSE_CONTAINER: u8 = 0x11; + pub const SET_CONTAINER_CONTENT: u8 = 0x12; + pub const SET_CONTAINER_PROPERTY: u8 = 0x13; + pub const SET_CONTAINER_SLOT: u8 = 0x14; + pub const COOKIE_REQUEST: u8 = 0x15; + pub const SET_COOLDOWN: u8 = 0x16; + pub const CHAT_SUGGESTIONS: u8 = 0x17; + pub const PLUGIN_MESSAGE: u8 = 0x18; + pub const DAMAGE_EVENT: u8 = 0x19; + pub const DEBUG_SAMPLE: u8 = 0x1A; + pub const DELETE_MESSAGE: u8 = 0x1B; + pub const DISCONNECT: u8 = 0x1C; + pub const DISGUISED_CHAT_MESSAGE: u8 = 0x1D; + pub const ENTITY_EVENT: u8 = 0x1E; + pub const TELEPORT_ENTITY: u8 = 0x1F; + pub const EXPLOSION: u8 = 0x20; + pub const UNLOAD_CHUNK: u8 = 0x21; + pub const GAME_EVENT: u8 = 0x22; + pub const OPEN_HORSE_SCREEN: u8 = 0x23; + pub const HURT_ANIMATION: u8 = 0x24; + pub const INITIALIZE_WORLD_BORDER: u8 = 0x25; + pub const KEEP_ALIVE: u8 = 0x26; + pub const CHUNK_DATA_AND_UPDATE_LIGHT: u8 = 0x27; + pub const WORLD_EVENT: u8 = 0x28; + pub const PARTICLE: u8 = 0x29; + pub const UPDATE_LIGHT: u8 = 0x2A; + pub const LOGIN: u8 = 0x2B; + pub const MAP_DATA: u8 = 0x2C; + pub const MERCHANT_OFFERS: u8 = 0x2D; + pub const UPDATE_ENTITY_POSITION: u8 = 0x2E; + pub const UPDATE_ENTITY_POSITION_AND_ROTATION: u8 = 0x2F; + pub const MOVE_MINECART_ALONG_TRACK: u8 = 0x30; + pub const UPDATE_ENTITY_ROTATION: u8 = 0x31; + pub const MOVE_VEHICLE: u8 = 0x32; + pub const OPEN_BOOK: u8 = 0x33; + pub const OPEN_SCREEN: u8 = 0x34; + pub const OPEN_SIGN_EDITOR: u8 = 0x35; + pub const PING: u8 = 0x36; + pub const PING_RESPONSE: u8 = 0x37; + pub const PLACE_GHOST_RECIPE: u8 = 0x38; + pub const PLAYER_ABILITIES: u8 = 0x39; + pub const PLAYER_CHAT_MESSAGE: u8 = 0x3A; + pub const END_COMBAT: u8 = 0x3B; + pub const ENTER_COMBAT: u8 = 0x3C; + pub const COMBAT_DEATH: u8 = 0x3D; + pub const PLAYER_INFO_REMOVE: u8 = 0x3E; + pub const PLAYER_INFO_UPDATE: u8 = 0x3F; + pub const LOOK_AT: u8 = 0x40; + pub const SYNCHRONIZE_PLAYER_POSITION: u8 = 0x41; + pub const PLAYER_ROTATION: u8 = 0x42; + pub const RECIPE_BOOK_ADD: u8 = 0x43; + pub const RECIPE_BOOK_REMOVE: u8 = 0x44; + pub const RECIPE_BOOK_SETTINGS: u8 = 0x45; + pub const REMOVE_ENTITIES: u8 = 0x46; + pub const REMOVE_ENTITY_EFFECT: u8 = 0x47; + pub const RESET_SCORE: u8 = 0x48; + pub const REMOVE_RESOURCE_PACK: u8 = 0x49; + pub const ADD_RESOURCE_PACK: u8 = 0x4A; + pub const RESPAWN: u8 = 0x4B; + pub const SET_HEAD_ROTATION: u8 = 0x4C; + pub const UPDATE_SECTION_BLOCKS: u8 = 0x4D; + pub const SELECT_ADVANCEMENTS_TAB: u8 = 0x4E; + pub const SERVER_DATA: u8 = 0x4F; + pub const SET_ACTION_BAR_TEXT: u8 = 0x50; + pub const SET_BORDER_CENTER: u8 = 0x51; + pub const SET_BORDER_LERP_SIZE: u8 = 0x52; + pub const SET_BORDER_SIZE: u8 = 0x53; + pub const SET_BORDER_WARNING_DELAY: u8 = 0x54; + pub const SET_BORDER_WARNING_DISTANCE: u8 = 0x55; + pub const SET_CAMERA: u8 = 0x56; + pub const SET_CENTER_CHUNK: u8 = 0x57; + pub const SET_RENDER_DISTANCE: u8 = 0x58; + pub const SET_CURSOR_ITEM: u8 = 0x59; + pub const SET_DEFAULT_SPAWN_POSITION: u8 = 0x5A; + pub const DISPLAY_OBJECTIVE: u8 = 0x5B; + pub const SET_ENTITY_METADATA: u8 = 0x5C; + pub const LINK_ENTITIES: u8 = 0x5D; + pub const SET_ENTITY_VELOCITY: u8 = 0x5E; + pub const SET_EQUIPMENT: u8 = 0x5F; + pub const SET_EXPERIENCE: u8 = 0x60; + pub const SET_HEALTH: u8 = 0x61; + pub const SET_HELD_ITEM: u8 = 0x62; + pub const UPDATE_OBJECTIVES: u8 = 0x63; + pub const SET_PASSENGERS: u8 = 0x64; + pub const SET_PLAYER_INVENTORY_SLOT: u8 = 0x65; + pub const UPDATE_TEAMS: u8 = 0x66; + pub const UPDATE_SCORE: u8 = 0x67; + pub const SET_SIMULATION_DISTANCE: u8 = 0x68; + pub const SET_SUBTITLE_TEXT: u8 = 0x69; + pub const UPDATE_TIME: u8 = 0x6A; + pub const SET_TITLE_TEXT: u8 = 0x6B; + pub const SET_TITLE_ANIMATION_TIMES: u8 = 0x6C; + pub const ENTITY_SOUND_EFFECT: u8 = 0x6D; + pub const SOUND_EFFECT: u8 = 0x6E; + pub const START_CONFIGURATION: u8 = 0x6F; + pub const STOP_SOUND: u8 = 0x70; + pub const STORE_COOKIE: u8 = 0x71; + pub const SYSTEM_CHAT_MESSAGE: u8 = 0x72; + pub const SET_TAB_LIST_HEADER_AND_FOOTER: u8 = 0x73; + pub const TAG_QUERY_RESPONSE: u8 = 0x74; + pub const PICKUP_ITEM: u8 = 0x75; + pub const SYNCHRONIZE_VEHICLE_POSITION: u8 = 0x76; + pub const TEST_INSTANCE_BLOCK_STATUS: u8 = 0x77; + pub const SET_TICKING_STATE: u8 = 0x78; + pub const STEP_TICK: u8 = 0x79; + pub const TRANSFER: u8 = 0x7A; + pub const UPDATE_ADVANCEMENTS: u8 = 0x7B; + pub const UPDATE_ATTRIBUTES: u8 = 0x7C; + pub const ENTITY_EFFECT: u8 = 0x7D; + pub const UPDATE_RECIPES: u8 = 0x7E; + pub const UPDATE_TAGS: u8 = 0x7F; + pub const PROJECTILE_POWER: u8 = 0x80; + pub const CUSTOM_REPORT_DETAILS: u8 = 0x81; + pub const SERVER_LINKS: u8 = 0x82; + } +} + +pub mod serverbound { + pub mod handshake { + pub const HANDSHAKE: u8 = 0x00; + } + + pub mod status { + pub const REQUEST: u8 = 0x00; + pub const PING_REQUEST: u8 = 0x01; + } + + pub mod login { + pub const START: u8 = 0x00; + pub const ENCRYPTION_RESPONSE: u8 = 0x01; + pub const PLUGIN_RESPONSE: u8 = 0x02; + pub const ACKNOWLEDGED: u8 = 0x03; + pub const COOKIE_RESPONSE: u8 = 0x04; + } + + pub mod configuration { + pub const CLIENT_INFORMATION: u8 = 0x00; + pub const COOKIE_RESPONSE: u8 = 0x01; + pub const PLUGIN_MESSAGE: u8 = 0x02; + pub const ACKNOWLEDGE_FINISH: u8 = 0x03; + pub const KEEP_ALIVE: u8 = 0x04; + pub const PONG: u8 = 0x05; + pub const RESOURCE_PACK_RESPONSE: u8 = 0x06; + pub const KNOWN_PACKS: u8 = 0x07; + } + + pub mod play { + pub const CONFIRM_TELEPORTATION: u8 = 0x00; + pub const QUERY_BLOCK_ENTITY_TAG: u8 = 0x01; + pub const BUNDLE_ITEM_SELECTED: u8 = 0x02; + pub const CHANGE_DIFFICULTY: u8 = 0x03; + pub const ACKNOWLEDGE_MESSAGE: u8 = 0x04; + pub const CHAT_COMMAND: u8 = 0x05; + pub const SIGNED_CHAT_COMMAND: u8 = 0x06; + pub const CHAT_MESSAGE: u8 = 0x07; + pub const PLAYER_SESSION: u8 = 0x08; + pub const CHUNK_BATCH_RECEIVED: u8 = 0x09; + pub const CLIENT_STATUS: u8 = 0x0A; + pub const CLIENT_TICK_END: u8 = 0x0B; + pub const CLIENT_INFORMATION: u8 = 0x0C; + pub const COMMAND_SUGGESTIONS_REQUEST: u8 = 0x0D; + pub const ACKNOWLEDGE_CONFIGURATION: u8 = 0x0E; + pub const CLICK_CONTAINER_BUTTON: u8 = 0x0F; + pub const CLICK_CONTAINER: u8 = 0x10; + pub const CLOSE_CONTAINER: u8 = 0x11; + pub const CHANGE_CONTAINER_SLOT_STATE: u8 = 0x12; + pub const COOKIE_RESPONSE: u8 = 0x13; + pub const PLUGIN_MESSAGE: u8 = 0x14; + pub const DEBUG_SAMPLE_SUBSCRIPTION: u8 = 0x15; + pub const EDIT_BOOK: u8 = 0x16; + pub const QUERY_ENTITY_TAG: u8 = 0x17; + pub const INTERACT: u8 = 0x18; + pub const JIGSAW_GENERATE: u8 = 0x19; + pub const KEEP_ALIVE: u8 = 0x1A; + pub const LOCK_DIFFICULTY: u8 = 0x1B; + pub const SET_PLAYER_POSITION: u8 = 0x1C; + pub const SET_PLAYER_POSITION_AND_ROTATION: u8 = 0x1D; + pub const SET_PLAYER_ROTATION: u8 = 0x1E; + pub const SET_PLAYER_MOVEMENT_FLAGS: u8 = 0x1F; + pub const MOVE_VEHICLE: u8 = 0x20; + pub const PADDLE_BOAT: u8 = 0x21; + pub const PICK_ITEM_FROM_BLOCK: u8 = 0x22; + pub const PICK_ITEM_FROM_ENTITY: u8 = 0x23; + pub const PING_REQUEST: u8 = 0x24; + pub const PLACE_RECIPE: u8 = 0x25; + pub const PLAYER_ABILITIES: u8 = 0x26; + pub const PLAYER_ACTION: u8 = 0x27; + pub const PLAYER_COMMAND: u8 = 0x28; + pub const PLAYER_INPUT: u8 = 0x29; + pub const PLAYER_LOADED: u8 = 0x2A; + pub const PONG: u8 = 0x2B; + pub const CHANGE_RECIPE_BOOK_SETTINGS: u8 = 0x2C; + pub const SET_SEEN_RECIPE: u8 = 0x2D; + pub const RENAME_ITEM: u8 = 0x2E; + pub const RESOURCE_PACK_RESPONSE: u8 = 0x2F; + pub const SEEN_ADVANCEMENTS: u8 = 0x30; + pub const SELECT_TRADE: u8 = 0x31; + pub const SET_BEACON_EFFECT: u8 = 0x32; + pub const SET_HELD_ITEM: u8 = 0x33; + pub const PROGRAM_COMMAND_BLOCK: u8 = 0x34; + pub const PROGRAM_COMMAND_BLOCK_MINECART: u8 = 0x35; + pub const SET_CREATIVE_MODE_SLOT: u8 = 0x36; + pub const PROGRAM_JIGSAW_BLOCK: u8 = 0x37; + pub const PROGRAM_STRUCTURE_BLOCK: u8 = 0x38; + pub const SET_TEST_BLOCK: u8 = 0x39; + pub const UPDATE_SIGN: u8 = 0x3A; + pub const SWING_ARM: u8 = 0x3B; + pub const TELEPORT_TO_ENTITY: u8 = 0x3C; + pub const TEST_INSTANCE_BLOCK_ACTION: u8 = 0x3D; + pub const USE_ITEM_ON: u8 = 0x3E; + pub const USE_ITEM: u8 = 0x3F; + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..a1de46a --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,12 @@ +pub mod handler; +pub mod id; +pub mod play; + +#[derive(Debug, Clone)] +pub enum ConnectionState { + Handshake, + Status, + Login, + Configuration, + Play, +} diff --git a/src/protocol/play.rs b/src/protocol/play.rs new file mode 100644 index 0000000..ac02334 --- /dev/null +++ b/src/protocol/play.rs @@ -0,0 +1,378 @@ +use std::{ + io::Cursor, + sync::Arc, + thread, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use rust_mc_proto::{DataReader, DataWriter, Packet, read_packet}; + +use crate::{ + ServerError, + data::{ReadWriteNBT, text_component::TextComponent}, + player::context::ClientContext, +}; + +use super::id::*; + +pub fn send_update_tags(client: Arc) -> Result<(), ServerError> { + // TODO: rewrite this hardcode bullshit + + client.write_packet(&Packet::from_bytes( + clientbound::configuration::UPDATE_TAGS, + include_bytes!("update-tags.bin"), + )) +} + +pub fn send_registry_data(client: Arc) -> Result<(), ServerError> { + // TODO: rewrite this hardcode bullshit + + let mut registry_data = Cursor::new(include_bytes!("registry-data.bin")); + + while let Ok(mut packet) = read_packet(&mut registry_data, None) { + packet.set_id(clientbound::configuration::REGISTRY_DATA); + client.write_packet(&packet)?; + } + + Ok(()) +} + +// Добавки в Configuration стейт чтобы все работало +pub fn handle_configuration_state( + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::configuration::FEATURE_FLAGS); + packet.write_varint(1)?; + packet.write_string("minecraft:vanilla")?; + client.write_packet(&packet)?; + + let mut packet = Packet::empty(clientbound::configuration::KNOWN_PACKS); + packet.write_varint(1)?; + packet.write_string("minecraft")?; + packet.write_string("core")?; + packet.write_string("1.21.5")?; + client.write_packet(&packet)?; + + client.read_packet(&[serverbound::configuration::KNOWN_PACKS])?; + + send_registry_data(client.clone())?; + send_update_tags(client.clone()) +} + +pub fn send_login(client: Arc) -> Result<(), ServerError> { + // Отправка пакета Login + let mut packet = Packet::empty(clientbound::play::LOGIN); + + packet.write_int(0)?; // Entity ID + packet.write_boolean(false)?; // Is hardcore + packet.write_varint(4)?; // Dimension Names + packet.write_string("minecraft:overworld")?; + packet.write_string("minecraft:nether")?; + packet.write_string("minecraft:the_end")?; + packet.write_string("minecraft:overworld_caves")?; + packet.write_varint(0)?; // Max Players + packet.write_varint(8)?; // View Distance + packet.write_varint(5)?; // Simulation Distance + packet.write_boolean(false)?; // Reduced Debug Info + packet.write_boolean(true)?; // Enable respawn screen + packet.write_boolean(false)?; // Do limited crafting + + packet.write_varint(0)?; // Dimension Type + packet.write_string("minecraft:overworld")?; // Dimension Name + packet.write_long(0x0f38f26ad09c3e20)?; // Hashed seed + packet.write_byte(0)?; // Game mode + packet.write_signed_byte(-1)?; // Previous Game mode + packet.write_boolean(false)?; // Is Debug + packet.write_boolean(true)?; // Is Flat + packet.write_boolean(false)?; // Has death location + packet.write_varint(20)?; // Portal cooldown + packet.write_varint(60)?; // Sea level + + packet.write_boolean(false)?; // Enforces Secure Chat + + client.write_packet(&packet) +} + +pub fn send_game_event( + client: Arc, + event: u8, + value: f32, +) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::GAME_EVENT); + + packet.write_byte(event)?; + packet.write_float(value)?; + + client.write_packet(&packet) +} + +pub fn sync_player_pos( + client: Arc, + x: f64, + y: f64, + z: f64, + vel_x: f64, + vel_y: f64, + vel_z: f64, + yaw: f32, + pitch: f32, + flags: i32, +) -> Result<(), ServerError> { + let timestamp = (SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + & 0xFFFFFFFF) as i32; + + let mut packet = Packet::empty(clientbound::play::SYNCHRONIZE_PLAYER_POSITION); + + packet.write_varint(timestamp)?; + packet.write_double(x)?; + packet.write_double(y)?; + packet.write_double(z)?; + packet.write_double(vel_x)?; + packet.write_double(vel_y)?; + packet.write_double(vel_z)?; + packet.write_float(yaw)?; + packet.write_float(pitch)?; + packet.write_int(flags)?; + + client.write_packet(&packet)?; + + Ok(()) +} + +pub fn set_center_chunk(client: Arc, x: i32, z: i32) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::SET_CENTER_CHUNK); + + packet.write_varint(x)?; + packet.write_varint(z)?; + + client.write_packet(&packet) +} + +pub fn send_example_chunk(client: Arc, x: i32, z: i32) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::CHUNK_DATA_AND_UPDATE_LIGHT); + + packet.write_int(x)?; + packet.write_int(z)?; + + // heightmap + + packet.write_varint(1)?; // heightmaps count + packet.write_varint(0)?; // MOTION_BLOCKING + packet.write_varint(256)?; // Length of the following long array (16 * 16 = 256) + for _ in 0..256 { + packet.write_long(0)?; // height - 0 + } + + // sending chunk data + + let mut chunk_data = Vec::new(); + + // we want to fill the area from -64 to 0, so it will be 4 chunk sections + + for _ in 0..4 { + chunk_data.write_short(4096)?; // non-air blocks count, 16 * 16 * 16 = 4096 stone blocks + + // blocks paletted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(10)?; // block state id in the registry (1 for stone) + + // biomes palleted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(27)?; // biome id in the registry + } + + // air chunk sections + + for _ in 0..20 { + chunk_data.write_short(0)?; // non-air blocks count, 0 + + // blocks paletted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(0)?; // block state id in the registry (0 for air) + + // biomes palleted container + chunk_data.write_byte(0)?; // Bits Per Entry, use Single valued palette format + chunk_data.write_varint(27)?; // biome id in the registry + } + + packet.write_usize_varint(chunk_data.len())?; + packet.write_bytes(&chunk_data)?; + + packet.write_byte(0)?; + + // light data + + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + packet.write_byte(0)?; + + client.write_packet(&packet)?; + + Ok(()) +} + +pub fn send_keep_alive(client: Arc) -> Result<(), ServerError> { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64; + + let mut packet = Packet::empty(clientbound::play::KEEP_ALIVE); + packet.write_long(timestamp)?; + client.write_packet(&packet)?; + + let mut packet = client.read_packet(&[serverbound::play::KEEP_ALIVE])?; + let timestamp2 = packet.read_long()?; + if timestamp2 != timestamp { + // Послать клиента нахуй + Err(ServerError::WrongPacket) + } else { + Ok(()) + } +} + +pub fn send_system_message( + client: Arc, + message: TextComponent, + is_action_bar: bool, +) -> Result<(), ServerError> { + let mut packet = Packet::empty(clientbound::play::SYSTEM_CHAT_MESSAGE); + packet.write_nbt(&message)?; + packet.write_boolean(is_action_bar)?; + client.write_packet(&packet) +} + +pub fn send_example_chunks_in_distance( + client: Arc, + chunks: &mut Vec<(i32, i32)>, + distance: i32, + center: (i32, i32), +) -> Result<(), ServerError> { + for x in -distance + center.0..=distance + center.0 { + for z in -distance + center.1..=distance + center.1 { + if !chunks.contains(&(x, z)) { + send_example_chunk(client.clone(), x as i32, z as i32)?; + chunks.push((x, z)); + } + } + } + + Ok(()) +} + +// Отдельная функция для работы с самой игрой +pub fn handle_play_state( + client: Arc, // Контекст клиента +) -> Result<(), ServerError> { + thread::spawn({ + let client = client.clone(); + + move || { + let _ = client.run_read_loop(); + client.close(); + } + }); + + send_login(client.clone())?; + sync_player_pos(client.clone(), 8.0, 0.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0)?; + send_game_event(client.clone(), 13, 0.0)?; // 13 - Start waiting for level chunks + set_center_chunk(client.clone(), 0, 0)?; + + let mut chunks = Vec::new(); + + let view_distance = client.client_info().unwrap().view_distance as i32; + + send_example_chunks_in_distance(client.clone(), &mut chunks, view_distance, (0, 0))?; + + thread::spawn({ + let client = client.clone(); + + move || -> Result<(), ServerError> { + while client.is_alive() { + let mut packet = client.read_any_packet()?; + + match packet.id() { + serverbound::play::SET_PLAYER_POSITION => { + let x = packet.read_double()?; + let y = packet.read_double()?; + let z = packet.read_double()?; + let _ = packet.read_byte()?; // flags + + client.set_position((x, y, z)); + } + serverbound::play::SET_PLAYER_POSITION_AND_ROTATION => { + let x = packet.read_double()?; + let y = packet.read_double()?; + let z = packet.read_double()?; + let yaw = packet.read_float()?; + let pitch = packet.read_float()?; + let _ = packet.read_byte()?; // flags + + client.set_position((x, y, z)); + client.set_rotation((yaw, pitch)); + } + serverbound::play::SET_PLAYER_ROTATION => { + let yaw = packet.read_float()?; + let pitch = packet.read_float()?; + let _ = packet.read_byte()?; // flags + + client.set_rotation((yaw, pitch)); + } + _ => { + client.push_packet_back(packet); + } + } + } + + Ok(()) + } + }); + + let mut ticks_alive = 0u64; + + while client.is_alive() { + if ticks_alive % 200 == 0 { + // 10 secs timer + send_keep_alive(client.clone())?; + } + + if ticks_alive % 20 == 0 { + // 1 sec timer + let (x, y, z) = client.position(); + + let (chunk_x, chunk_z) = ((x / 16.0) as i64, (z / 16.0) as i64); + let (chunk_x, chunk_z) = (chunk_x as i32, chunk_z as i32); + + set_center_chunk(client.clone(), chunk_x, chunk_z)?; + send_example_chunks_in_distance( + client.clone(), + &mut chunks, + view_distance, + (chunk_x, chunk_z), + )?; + + send_system_message( + client.clone(), + TextComponent::rainbow(format!("Pos: {} {} {}", x as i64, y as i64, z as i64)), + false, + )?; + } + + send_system_message( + client.clone(), + TextComponent::rainbow(format!("Ticks alive: {}", ticks_alive)), + true, + )?; + + thread::sleep(Duration::from_millis(50)); // 1 tick + ticks_alive += 1; + } + + Ok(()) +} diff --git a/src/protocol/registry-data.bin b/src/protocol/registry-data.bin new file mode 100644 index 0000000..e466400 Binary files /dev/null and b/src/protocol/registry-data.bin differ diff --git a/src/protocol/update-tags.bin b/src/protocol/update-tags.bin new file mode 100644 index 0000000..1674b67 Binary files /dev/null and b/src/protocol/update-tags.bin differ