mirror of
https://github.com/zyllian/classics.git
synced 2025-01-18 03:32:41 -08:00
initial commit
This commit is contained in:
commit
ca94ec10f2
12 changed files with 1624 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
|||
hard_tabs = true
|
514
Cargo.lock
generated
Normal file
514
Cargo.lock
generated
Normal file
|
@ -0,0 +1,514 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "classics"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"half",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.5",
|
||||
"windows_aarch64_msvc 0.52.5",
|
||||
"windows_i686_gnu 0.52.5",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.5",
|
||||
"windows_x86_64_gnu 0.52.5",
|
||||
"windows_x86_64_gnullvm 0.52.5",
|
||||
"windows_x86_64_msvc 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "classics"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
flate2 = "1"
|
||||
half = "2"
|
||||
parking_lot = "0.12.1"
|
||||
rand = "0.8"
|
||||
tokio = {version = "1", features = ["full"]}
|
41
src/level.rs
Normal file
41
src/level.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
/// a classic level
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Level {
|
||||
/// the size of the level in the X direction
|
||||
pub x_size: usize,
|
||||
/// the size of the level in the Y direction
|
||||
pub y_size: usize,
|
||||
/// the size of the level in the Z direction
|
||||
pub z_size: usize,
|
||||
|
||||
/// the blocks which make up the level
|
||||
pub blocks: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Level {
|
||||
/// creates a new level with the given dimensions
|
||||
pub fn new(x_size: usize, y_size: usize, z_size: usize) -> Self {
|
||||
Self {
|
||||
x_size,
|
||||
y_size,
|
||||
z_size,
|
||||
blocks: vec![0; x_size * y_size * z_size],
|
||||
}
|
||||
}
|
||||
|
||||
/// gets the index for a given block position
|
||||
pub fn index(&self, x: usize, y: usize, z: usize) -> usize {
|
||||
x + z * self.x_size + y * self.x_size * self.z_size
|
||||
}
|
||||
|
||||
/// gets the block at the given position
|
||||
pub fn get_block(&self, x: usize, y: usize, z: usize) -> u8 {
|
||||
self.blocks[self.index(x, y, z)]
|
||||
}
|
||||
|
||||
/// sets the block at the given position
|
||||
pub fn set_block(&mut self, x: usize, y: usize, z: usize, block: u8) {
|
||||
let index = self.index(x, y, z);
|
||||
self.blocks[index] = block;
|
||||
}
|
||||
}
|
15
src/main.rs
Normal file
15
src/main.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use server::Server;
|
||||
|
||||
mod level;
|
||||
mod packet;
|
||||
mod player;
|
||||
mod server;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let mut server = Server::new().await?;
|
||||
|
||||
server.run().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
159
src/packet.rs
Normal file
159
src/packet.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use half::f16;
|
||||
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
|
||||
/// length of classic strings
|
||||
pub const STRING_LENGTH: usize = 64;
|
||||
/// length of classic level chunk arrays
|
||||
pub const ARRAY_LENGTH: usize = 1024;
|
||||
/// units in an f16 unit
|
||||
pub const F16_UNITS: f32 = 32.0;
|
||||
|
||||
/// helper for reading packets
|
||||
#[derive(Debug)]
|
||||
pub struct PacketReader<'p> {
|
||||
raw_packet: &'p [u8],
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
impl<'p> PacketReader<'p> {
|
||||
/// creates a new packet reader from the given packet data
|
||||
pub fn new(raw_packet: &'p [u8]) -> Self {
|
||||
Self {
|
||||
raw_packet,
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// gets the next u8 in the packet, if any
|
||||
fn next_u8(&mut self) -> Option<u8> {
|
||||
let r = self.raw_packet.get(self.cursor).copied();
|
||||
self.cursor = self.cursor.checked_add(1).unwrap_or(self.cursor);
|
||||
r
|
||||
}
|
||||
|
||||
/// gets the next i8 in the packet, if any
|
||||
fn next_i8(&mut self) -> Option<i8> {
|
||||
self.next_u8().map(|b| b as i8)
|
||||
}
|
||||
|
||||
/// gets the next u16 in the packet, if any
|
||||
fn next_u16(&mut self) -> Option<u16> {
|
||||
Some(u16::from_be_bytes([self.next_u8()?, self.next_u8()?]))
|
||||
}
|
||||
|
||||
/// gets the next i16 in the packet, if any
|
||||
fn next_i16(&mut self) -> Option<i16> {
|
||||
self.next_u16().map(|s| s as i16)
|
||||
}
|
||||
|
||||
/// gets the next f16 in the packet, if any
|
||||
fn next_f16(&mut self) -> Option<f16> {
|
||||
self.next_i16().map(|v| f16::from_f32(v as f32 / F16_UNITS))
|
||||
}
|
||||
|
||||
/// gets the next string in the packet, if any
|
||||
fn next_string(&mut self) -> Option<String> {
|
||||
let mut chars: Vec<char> = Vec::new();
|
||||
for _ in 0..STRING_LENGTH {
|
||||
chars.push(self.next_u8()? as char);
|
||||
}
|
||||
Some(String::from_iter(chars).trim().to_string())
|
||||
}
|
||||
|
||||
/// gets the next array of the given length in the packet, if any
|
||||
fn next_array_of_length(&mut self, len: usize) -> Option<Vec<u8>> {
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
let mut append = true;
|
||||
for _ in 0..len {
|
||||
let b = self.next_u8()?;
|
||||
if append {
|
||||
if b == 0 {
|
||||
append = false;
|
||||
} else {
|
||||
bytes.push(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(bytes)
|
||||
}
|
||||
|
||||
/// gets the next array of default size in the packet, if any
|
||||
fn next_array(&mut self) -> Option<Vec<u8>> {
|
||||
self.next_array_of_length(ARRAY_LENGTH)
|
||||
}
|
||||
}
|
||||
|
||||
/// helper for writing a packet
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PacketWriter {
|
||||
raw_packet: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PacketWriter {
|
||||
/// gets the actual raw packet data from the writer
|
||||
pub fn into_raw_packet(self) -> Vec<u8> {
|
||||
self.raw_packet
|
||||
}
|
||||
|
||||
/// writes a u8 to the packet
|
||||
pub fn write_u8(mut self, b: u8) -> Self {
|
||||
self.raw_packet.push(b);
|
||||
self
|
||||
}
|
||||
|
||||
/// writes an i8 to the packet
|
||||
fn write_i8(self, b: i8) -> Self {
|
||||
self.write_u8(b as u8)
|
||||
}
|
||||
|
||||
/// writes a u16 to the packet
|
||||
fn write_u16(self, sh: u16) -> Self {
|
||||
let mut s = self;
|
||||
for b in sh.to_be_bytes() {
|
||||
s = s.write_u8(b);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// writes an i16 to the packet
|
||||
fn write_i16(self, sh: i16) -> Self {
|
||||
self.write_u16(sh as u16)
|
||||
}
|
||||
|
||||
/// writes an f16 to the packet
|
||||
fn write_f16(self, f: f16) -> Self {
|
||||
let r = (f.to_f32() * F16_UNITS) as i16;
|
||||
self.write_i16(r)
|
||||
}
|
||||
|
||||
/// writes a string to the packet
|
||||
fn write_string(self, str: &str) -> Self {
|
||||
let mut s = self;
|
||||
for b in str
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(Some(0x20).into_iter().cycle())
|
||||
.take(STRING_LENGTH)
|
||||
{
|
||||
s = s.write_u8(b);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// writes an array of the given length to the packet
|
||||
fn write_array_of_length(self, bytes: &[u8], len: usize) -> Self {
|
||||
let mut s = self;
|
||||
for i in 0..len {
|
||||
s = s.write_u8(bytes.get(i).copied().unwrap_or_default());
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// writes an array of default length to the packet
|
||||
fn write_array(self, bytes: &[u8]) -> Self {
|
||||
self.write_array_of_length(bytes, ARRAY_LENGTH)
|
||||
}
|
||||
}
|
133
src/packet/client.rs
Normal file
133
src/packet/client.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use half::f16;
|
||||
|
||||
/// enum for a packet which can be received by the client
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ClientPacket {
|
||||
/// packet sent by a client to identify itself to the server
|
||||
PlayerIdentification {
|
||||
/// should always be 0x07 for classic clients >= 0.28
|
||||
protocol_version: u8,
|
||||
username: String,
|
||||
/// currently unverified, original minecraft auth for classic is gone anyway
|
||||
/// TODO: use verification key field as password protection? investigate
|
||||
verification_key: String,
|
||||
_unused: u8,
|
||||
},
|
||||
/// packet sent when a client changes a block
|
||||
/// because changes are reflected immediately, to restrict changes, server must send back its own SetBlock packet with the original block
|
||||
SetBlock {
|
||||
x: i16,
|
||||
y: i16,
|
||||
z: i16,
|
||||
/// 0x00 for destroy, 0x01 for create
|
||||
mode: u8,
|
||||
block_type: u8,
|
||||
},
|
||||
/// sent to update the player's current position and orientation with the server
|
||||
PositionOrientation {
|
||||
/// should always be 0xff (-1), referring to the player who sent it
|
||||
_player_id: i8,
|
||||
x: f16,
|
||||
y: f16,
|
||||
z: f16,
|
||||
yaw: u8,
|
||||
pitch: u8,
|
||||
},
|
||||
/// packet for the client to send chat messages
|
||||
Message {
|
||||
/// should always be 0xff (-1), referring to the player who sent it
|
||||
player_id: i8,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ClientPacket {
|
||||
// unused currently, so disabled
|
||||
// /// gets the packet's id
|
||||
// pub fn get_id(&self) -> u8 {
|
||||
// match self {
|
||||
// Self::PlayerIdentification { .. } => 0x00,
|
||||
// Self::SetBlock { .. } => 0x05,
|
||||
// Self::PositionOrientation { .. } => 0x08,
|
||||
// Self::Message { .. } => 0x0d,
|
||||
// }
|
||||
// }
|
||||
|
||||
/// reads the packet
|
||||
pub fn read(id: u8, packet: &mut super::PacketReader) -> Option<Self> {
|
||||
Some(match id {
|
||||
0x00 => Self::PlayerIdentification {
|
||||
protocol_version: packet.next_u8()?,
|
||||
username: packet.next_string()?,
|
||||
verification_key: packet.next_string()?,
|
||||
_unused: packet.next_u8()?,
|
||||
},
|
||||
0x05 => Self::SetBlock {
|
||||
x: packet.next_i16()?,
|
||||
y: packet.next_i16()?,
|
||||
z: packet.next_i16()?,
|
||||
mode: packet.next_u8()?,
|
||||
block_type: packet.next_u8()?,
|
||||
},
|
||||
0x08 => Self::PositionOrientation {
|
||||
_player_id: packet.next_i8()?,
|
||||
x: packet.next_f16()?,
|
||||
y: packet.next_f16()?,
|
||||
z: packet.next_f16()?,
|
||||
yaw: packet.next_u8()?,
|
||||
pitch: packet.next_u8()?,
|
||||
},
|
||||
0x0d => Self::Message {
|
||||
player_id: packet.next_i8()?,
|
||||
message: packet.next_string()?,
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
// only needed on the client, so disabled for now
|
||||
// /// writes the packet
|
||||
// pub fn write(&self, writer: super::PacketWriter) -> super::PacketWriter {
|
||||
// match self {
|
||||
// Self::PlayerIdentification {
|
||||
// protocol_version,
|
||||
// username,
|
||||
// verification_key,
|
||||
// _unused,
|
||||
// } => writer
|
||||
// .write_u8(*protocol_version)
|
||||
// .write_string(username)
|
||||
// .write_string(verification_key)
|
||||
// .write_u8(*_unused),
|
||||
// Self::SetBlock {
|
||||
// x,
|
||||
// y,
|
||||
// z,
|
||||
// mode,
|
||||
// block_type,
|
||||
// } => writer
|
||||
// .write_i16(*x)
|
||||
// .write_i16(*y)
|
||||
// .write_i16(*z)
|
||||
// .write_u8(*mode)
|
||||
// .write_u8(*block_type),
|
||||
// Self::PositionOrientation {
|
||||
// player_id,
|
||||
// x,
|
||||
// y,
|
||||
// z,
|
||||
// yaw,
|
||||
// pitch,
|
||||
// } => writer
|
||||
// .write_i8(*player_id)
|
||||
// .write_f16(*x)
|
||||
// .write_f16(*y)
|
||||
// .write_f16(*z)
|
||||
// .write_u8(*yaw)
|
||||
// .write_u8(*pitch),
|
||||
// Self::Message { player_id, message } => {
|
||||
// writer.write_i8(*player_id).write_string(message)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
262
src/packet/server.rs
Normal file
262
src/packet/server.rs
Normal file
|
@ -0,0 +1,262 @@
|
|||
use half::f16;
|
||||
|
||||
use crate::player::PlayerType;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(unused)]
|
||||
pub enum ServerPacket {
|
||||
/// packet sent as a response to joining clients
|
||||
ServerIdentification {
|
||||
/// should be 0x07
|
||||
protocol_version: u8,
|
||||
server_name: String,
|
||||
server_motd: String,
|
||||
user_type: PlayerType,
|
||||
},
|
||||
/// since clients do not notify the server when leaving, the ping packet is used to check if the client is still connected
|
||||
/// TODO: implement pinging? classicube works fine without it
|
||||
Ping {},
|
||||
/// informs clients that there is incoming level data
|
||||
LevelInitialize {},
|
||||
/// packet to send a chunk (not minecraft chunk) of gzipped level data
|
||||
LevelDataChunk {
|
||||
chunk_length: i16,
|
||||
chunk_data: Vec<u8>,
|
||||
percent_complete: u8,
|
||||
},
|
||||
/// packet sent after chunk data is finished sending containing the level dimensions
|
||||
LevelFinalize {
|
||||
x_size: i16,
|
||||
y_size: i16,
|
||||
z_size: i16,
|
||||
},
|
||||
|
||||
/// indicates a block change
|
||||
/// when a player changes a block, their own change is echoed back to them
|
||||
SetBlock {
|
||||
x: i16,
|
||||
y: i16,
|
||||
z: i16,
|
||||
block_type: u8,
|
||||
},
|
||||
/// packet sent when a new player spawns
|
||||
/// also contains their spawn point
|
||||
SpawnPlayer {
|
||||
player_id: i8,
|
||||
player_name: String,
|
||||
x: f16,
|
||||
y: f16,
|
||||
z: f16,
|
||||
yaw: u8,
|
||||
pitch: u8,
|
||||
},
|
||||
/// packet to set a player's position and orientation
|
||||
SetPositionOrientation {
|
||||
player_id: i8,
|
||||
x: f16,
|
||||
y: f16,
|
||||
z: f16,
|
||||
yaw: u8,
|
||||
pitch: u8,
|
||||
},
|
||||
/// packet to update a player's position and orientation
|
||||
/// TODO: implement?
|
||||
UpdatePositionOrientation {
|
||||
player_id: i8,
|
||||
x_change: f16,
|
||||
y_change: f16,
|
||||
z_change: f16,
|
||||
yaw: u8,
|
||||
pitch: u8,
|
||||
},
|
||||
/// packet to update a player's position
|
||||
/// TODO: implement?
|
||||
UpdatePosition {
|
||||
player_id: i8,
|
||||
x_change: f16,
|
||||
y_change: f16,
|
||||
z_change: f16,
|
||||
},
|
||||
/// packet to update a player's orientation
|
||||
/// TODO: implement?
|
||||
UpdateOrientation { player_id: i8, yaw: u8, pitch: u8 },
|
||||
/// packet sent when a player is despawned from the world (i.e. when leaving)
|
||||
DespawnPlayer { player_id: i8 },
|
||||
/// packet sent when there's a chat message to go out
|
||||
Message { player_id: i8, message: String },
|
||||
/// informs a client that they're being disconnected from the server and why
|
||||
DisconnectPlayer { disconnect_reason: String },
|
||||
/// packet sent to a user to inform them that their user type has changed
|
||||
UpdateUserType {
|
||||
/// 0x00 for normal, 0x64 for op
|
||||
user_type: PlayerType,
|
||||
},
|
||||
}
|
||||
|
||||
impl ServerPacket {
|
||||
/// gets the packet's id
|
||||
pub fn get_id(&self) -> u8 {
|
||||
match self {
|
||||
Self::ServerIdentification { .. } => 0x00,
|
||||
Self::Ping {} => 0x01,
|
||||
Self::LevelInitialize {} => 0x02,
|
||||
Self::LevelDataChunk { .. } => 0x03,
|
||||
Self::LevelFinalize { .. } => 0x04,
|
||||
Self::SetBlock { .. } => 0x06,
|
||||
Self::SpawnPlayer { .. } => 0x07,
|
||||
Self::SetPositionOrientation { .. } => 0x08,
|
||||
Self::UpdatePositionOrientation { .. } => 0x09,
|
||||
Self::UpdatePosition { .. } => 0x0a,
|
||||
Self::UpdateOrientation { .. } => 0x0b,
|
||||
Self::DespawnPlayer { .. } => 0x0c,
|
||||
Self::Message { .. } => 0x0d,
|
||||
Self::DisconnectPlayer { .. } => 0x0e,
|
||||
Self::UpdateUserType { .. } => 0x0f,
|
||||
}
|
||||
}
|
||||
|
||||
/// writes the packet
|
||||
pub fn write(&self, writer: super::PacketWriter) -> super::PacketWriter {
|
||||
match self {
|
||||
Self::ServerIdentification {
|
||||
protocol_version,
|
||||
server_name,
|
||||
server_motd,
|
||||
user_type,
|
||||
} => writer
|
||||
.write_u8(*protocol_version)
|
||||
.write_string(server_name)
|
||||
.write_string(server_motd)
|
||||
.write_u8(*user_type as u8),
|
||||
Self::Ping {} => writer,
|
||||
Self::LevelInitialize {} => writer,
|
||||
Self::LevelDataChunk {
|
||||
chunk_length,
|
||||
chunk_data,
|
||||
percent_complete,
|
||||
} => writer
|
||||
.write_i16(*chunk_length)
|
||||
.write_array(chunk_data)
|
||||
.write_u8(*percent_complete),
|
||||
Self::LevelFinalize {
|
||||
x_size,
|
||||
y_size,
|
||||
z_size,
|
||||
} => writer
|
||||
.write_i16(*x_size)
|
||||
.write_i16(*y_size)
|
||||
.write_i16(*z_size),
|
||||
Self::SetBlock {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
block_type,
|
||||
} => writer
|
||||
.write_i16(*x)
|
||||
.write_i16(*y)
|
||||
.write_i16(*z)
|
||||
.write_u8(*block_type),
|
||||
Self::SpawnPlayer {
|
||||
player_id,
|
||||
player_name,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
yaw,
|
||||
pitch,
|
||||
} => writer
|
||||
.write_i8(*player_id)
|
||||
.write_string(player_name)
|
||||
.write_f16(*x)
|
||||
.write_f16(*y)
|
||||
.write_f16(*z)
|
||||
.write_u8(*yaw)
|
||||
.write_u8(*pitch),
|
||||
Self::SetPositionOrientation {
|
||||
player_id,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
yaw,
|
||||
pitch,
|
||||
} => writer
|
||||
.write_i8(*player_id)
|
||||
.write_f16(*x)
|
||||
.write_f16(*y)
|
||||
.write_f16(*z)
|
||||
.write_u8(*yaw)
|
||||
.write_u8(*pitch),
|
||||
Self::UpdatePositionOrientation {
|
||||
player_id,
|
||||
x_change,
|
||||
y_change,
|
||||
z_change,
|
||||
yaw,
|
||||
pitch,
|
||||
} => writer
|
||||
.write_i8(*player_id)
|
||||
.write_f16(*x_change)
|
||||
.write_f16(*y_change)
|
||||
.write_f16(*z_change)
|
||||
.write_u8(*yaw)
|
||||
.write_u8(*pitch),
|
||||
Self::UpdatePosition {
|
||||
player_id,
|
||||
x_change,
|
||||
y_change,
|
||||
z_change,
|
||||
} => writer
|
||||
.write_i8(*player_id)
|
||||
.write_f16(*x_change)
|
||||
.write_f16(*y_change)
|
||||
.write_f16(*z_change),
|
||||
Self::UpdateOrientation {
|
||||
player_id,
|
||||
yaw,
|
||||
pitch,
|
||||
} => writer.write_i8(*player_id).write_u8(*yaw).write_u8(*pitch),
|
||||
Self::DespawnPlayer { player_id } => writer.write_i8(*player_id),
|
||||
Self::Message { player_id, message } => {
|
||||
writer.write_i8(*player_id).write_string(message)
|
||||
}
|
||||
Self::DisconnectPlayer { disconnect_reason } => writer.write_string(disconnect_reason),
|
||||
Self::UpdateUserType { user_type } => writer.write_u8(*user_type as u8),
|
||||
}
|
||||
}
|
||||
|
||||
/// gets the player id contained in the packet, if any
|
||||
pub fn get_player_id(&self) -> Option<i8> {
|
||||
Some(match self {
|
||||
Self::SpawnPlayer { player_id, .. } => *player_id,
|
||||
Self::SetPositionOrientation { player_id, .. } => *player_id,
|
||||
Self::UpdatePositionOrientation { player_id, .. } => *player_id,
|
||||
Self::UpdatePosition { player_id, .. } => *player_id,
|
||||
Self::UpdateOrientation { player_id, .. } => *player_id,
|
||||
Self::DespawnPlayer { player_id, .. } => *player_id,
|
||||
Self::Message { player_id, .. } => *player_id,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// sets the player id in the packet if possible
|
||||
pub fn set_player_id(&mut self, new_player_id: i8) {
|
||||
match self {
|
||||
Self::SpawnPlayer { player_id, .. } => *player_id = new_player_id,
|
||||
Self::SetPositionOrientation { player_id, .. } => *player_id = new_player_id,
|
||||
Self::UpdatePositionOrientation { player_id, .. } => *player_id = new_player_id,
|
||||
Self::UpdatePosition { player_id, .. } => *player_id = new_player_id,
|
||||
Self::UpdateOrientation { player_id, .. } => *player_id = new_player_id,
|
||||
Self::DespawnPlayer { player_id, .. } => *player_id = new_player_id,
|
||||
Self::Message { player_id, .. } => *player_id = new_player_id,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// gets whether this packet should echo back to the current player
|
||||
pub fn should_echo(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::SetBlock { .. } | Self::SpawnPlayer { .. } | Self::Message { .. }
|
||||
)
|
||||
}
|
||||
}
|
61
src/player.rs
Normal file
61
src/player.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use half::f16;
|
||||
|
||||
use crate::packet::server::ServerPacket;
|
||||
|
||||
/// struct for players
|
||||
#[derive(Debug)]
|
||||
pub struct Player {
|
||||
/// the player's id
|
||||
pub id: i8,
|
||||
/// the player's username
|
||||
pub username: String,
|
||||
/// the player's X coordinate
|
||||
pub x: f16,
|
||||
/// the player's Y coordinate
|
||||
pub y: f16,
|
||||
/// the player's Z coordinate
|
||||
pub z: f16,
|
||||
/// the player's yaw
|
||||
pub yaw: u8,
|
||||
/// the player's pitch
|
||||
pub pitch: u8,
|
||||
/// the player's permission state
|
||||
pub player_type: PlayerType,
|
||||
|
||||
/// the player's IP address
|
||||
pub _addr: SocketAddr,
|
||||
/// queue of packets to be sent to this player
|
||||
pub packets_to_send: Vec<ServerPacket>,
|
||||
}
|
||||
|
||||
/// enum describing types of players
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum PlayerType {
|
||||
/// a normal player
|
||||
Normal = 0x00,
|
||||
/// a player who's an operator
|
||||
Operator = 0x64,
|
||||
}
|
||||
|
||||
impl Default for PlayerType {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PlayerType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
if value == Self::Normal as u8 {
|
||||
Ok(Self::Normal)
|
||||
} else if value == Self::Operator as u8 {
|
||||
Ok(Self::Operator)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
82
src/server.rs
Normal file
82
src/server.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
mod network;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
// use parking_lot::RwLock;
|
||||
use rand::Rng;
|
||||
use tokio::{net::TcpListener, sync::RwLock};
|
||||
|
||||
use crate::{level::Level, player::Player};
|
||||
|
||||
const DEFAULT_SERVER_SIZE: usize = 128;
|
||||
|
||||
/// the server
|
||||
#[derive(Debug)]
|
||||
pub struct Server {
|
||||
/// shared server data
|
||||
pub data: Arc<RwLock<ServerData>>,
|
||||
/// the server's listener
|
||||
pub listener: TcpListener,
|
||||
}
|
||||
|
||||
/// shared server data
|
||||
#[derive(Debug)]
|
||||
pub struct ServerData {
|
||||
/// the level
|
||||
pub level: Level,
|
||||
/// list of players connected to the server
|
||||
pub players: Vec<Player>,
|
||||
/// list of player ids which have been freed up
|
||||
pub free_player_ids: Vec<i8>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// creates a new server with a generated level
|
||||
pub async fn new() -> std::io::Result<Self> {
|
||||
println!("generating level");
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut level = Level::new(
|
||||
DEFAULT_SERVER_SIZE,
|
||||
DEFAULT_SERVER_SIZE,
|
||||
DEFAULT_SERVER_SIZE,
|
||||
);
|
||||
for x in 0..level.x_size {
|
||||
for y in 0..(level.y_size / 2) {
|
||||
for z in 0..level.z_size {
|
||||
level.set_block(x, y, z, rng.gen_range(0..50));
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("done!");
|
||||
|
||||
Self::new_with_level(level).await
|
||||
}
|
||||
|
||||
/// creates a new server with the given level
|
||||
pub async fn new_with_level(level: Level) -> std::io::Result<Self> {
|
||||
let listener = TcpListener::bind("127.0.0.1:25565").await?;
|
||||
|
||||
Ok(Self {
|
||||
data: Arc::new(RwLock::new(ServerData {
|
||||
level,
|
||||
players: Default::default(),
|
||||
free_player_ids: Vec::new(),
|
||||
})),
|
||||
listener,
|
||||
})
|
||||
}
|
||||
|
||||
/// starts the server
|
||||
pub async fn run(&mut self) -> std::io::Result<()> {
|
||||
loop {
|
||||
let (stream, addr) = self.listener.accept().await?;
|
||||
println!("connection from {addr}");
|
||||
let data = self.data.clone();
|
||||
tokio::spawn(async move {
|
||||
network::handle_stream(stream, addr, data)
|
||||
.await
|
||||
.expect("failed to handle client stream");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
344
src/server/network.rs
Normal file
344
src/server/network.rs
Normal file
|
@ -0,0 +1,344 @@
|
|||
use std::{collections::VecDeque, io::Write, net::SocketAddr, sync::Arc};
|
||||
|
||||
use flate2::{write::GzEncoder, Compression};
|
||||
use half::f16;
|
||||
use tokio::{
|
||||
io::{AsyncWriteExt, Interest},
|
||||
net::TcpStream,
|
||||
sync::RwLock,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
level::Level,
|
||||
packet::{
|
||||
client::ClientPacket, server::ServerPacket, PacketReader, PacketWriter, ARRAY_LENGTH,
|
||||
},
|
||||
player::{Player, PlayerType},
|
||||
};
|
||||
|
||||
use super::ServerData;
|
||||
|
||||
pub(super) async fn handle_stream(
|
||||
mut stream: TcpStream,
|
||||
addr: SocketAddr,
|
||||
data: Arc<RwLock<ServerData>>,
|
||||
) -> std::io::Result<()> {
|
||||
let mut own_id: i8 = -1;
|
||||
let r = handle_stream_inner(&mut stream, addr, data.clone(), &mut own_id).await;
|
||||
|
||||
match r {
|
||||
Ok(disconnect_reason) => {
|
||||
if let Some(disconnect_reason) = disconnect_reason {
|
||||
let packet = ServerPacket::DisconnectPlayer { disconnect_reason };
|
||||
let writer = PacketWriter::default().write_u8(packet.get_id());
|
||||
let msg = packet.write(writer).into_raw_packet();
|
||||
if let Err(e) = stream.write_all(&msg).await {
|
||||
eprintln!("Failed to write disconnect packet for <{addr}>: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Error in stream handler for <{addr}>: {e}"),
|
||||
}
|
||||
|
||||
if let Err(e) = stream.shutdown().await {
|
||||
eprintln!("Failed to properly shut down stream for <{addr}>: {e}");
|
||||
}
|
||||
|
||||
let mut data = data.write().await;
|
||||
if let Some(index) = data.players.iter().position(|p| p.id == own_id) {
|
||||
let player = data.players.remove(index);
|
||||
|
||||
let despawn_packet = ServerPacket::DespawnPlayer { player_id: own_id };
|
||||
let message_packet = ServerPacket::Message {
|
||||
player_id: own_id,
|
||||
message: format!("&e{} has left the server.", player.username),
|
||||
};
|
||||
for player in &mut data.players {
|
||||
player.packets_to_send.push(despawn_packet.clone());
|
||||
player.packets_to_send.push(message_packet.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_stream_inner(
|
||||
stream: &mut TcpStream,
|
||||
addr: SocketAddr,
|
||||
data: Arc<RwLock<ServerData>>,
|
||||
own_id: &mut i8,
|
||||
) -> std::io::Result<Option<String>> {
|
||||
const BUF_SIZE: usize = 130;
|
||||
|
||||
let mut reply_queue: VecDeque<ServerPacket> = VecDeque::new();
|
||||
let mut packet_buf = [0u8];
|
||||
let mut read_buf;
|
||||
|
||||
loop {
|
||||
let ready = stream
|
||||
.ready(Interest::READABLE | Interest::WRITABLE)
|
||||
.await?;
|
||||
|
||||
if ready.is_read_closed() {
|
||||
println!("disconnecting {addr}");
|
||||
break;
|
||||
}
|
||||
|
||||
if ready.is_readable() {
|
||||
match stream.try_read(&mut packet_buf) {
|
||||
Ok(n) => {
|
||||
if n == 1 {
|
||||
read_buf = [0; BUF_SIZE];
|
||||
match stream.try_read(&mut read_buf) {
|
||||
Ok(_n) => {}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
let mut reader = PacketReader::new(&read_buf);
|
||||
|
||||
if let Some(packet) = ClientPacket::read(packet_buf[0], &mut reader) {
|
||||
// println!("{packet:#?}");
|
||||
match packet {
|
||||
ClientPacket::PlayerIdentification {
|
||||
protocol_version,
|
||||
username,
|
||||
verification_key: _,
|
||||
_unused,
|
||||
} => {
|
||||
if protocol_version != 0x07 {
|
||||
return Ok(Some("Unknown protocol version! Please connect with a classic 0.30-compatible client.".to_string()));
|
||||
}
|
||||
|
||||
let zero = f16::from_f32(0.0);
|
||||
|
||||
let mut data = data.write().await;
|
||||
|
||||
for player in &data.players {
|
||||
if player.username == username {
|
||||
return Ok(Some(
|
||||
"Player with username already connected!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
*own_id = data
|
||||
.free_player_ids
|
||||
.pop()
|
||||
.unwrap_or_else(|| data.players.len() as i8);
|
||||
|
||||
let player = Player {
|
||||
_addr: addr,
|
||||
id: *own_id, // TODO: actually assign user ids
|
||||
username,
|
||||
// TODO: properly assign spawn stuff
|
||||
x: zero,
|
||||
y: zero,
|
||||
z: zero,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
player_type: PlayerType::Normal,
|
||||
packets_to_send: Vec::new(),
|
||||
};
|
||||
|
||||
reply_queue.push_back(ServerPacket::ServerIdentification {
|
||||
protocol_version: 0x07,
|
||||
server_name: "test server".to_string(),
|
||||
server_motd: "whoaaaaaa".to_string(),
|
||||
user_type: PlayerType::Normal,
|
||||
});
|
||||
|
||||
println!("generating level packets");
|
||||
reply_queue
|
||||
.extend(build_level_packets(&data.level).into_iter());
|
||||
|
||||
let username = player.username.clone();
|
||||
data.players.push(player);
|
||||
|
||||
let spawn_packet = ServerPacket::SpawnPlayer {
|
||||
player_id: *own_id,
|
||||
player_name: username.clone(),
|
||||
x: f16::from_f32(16.5),
|
||||
y: f16::from_f32((data.level.y_size / 2 + 2) as f32),
|
||||
z: f16::from_f32(16.5),
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
};
|
||||
let message_packet = ServerPacket::Message {
|
||||
player_id: *own_id,
|
||||
message: format!("&e{} has joined the server.", username),
|
||||
};
|
||||
for player in &mut data.players {
|
||||
player.packets_to_send.push(spawn_packet.clone());
|
||||
if player.id != *own_id {
|
||||
reply_queue.push_back(ServerPacket::SpawnPlayer {
|
||||
player_id: player.id,
|
||||
player_name: player.username.clone(),
|
||||
x: player.x,
|
||||
y: player.y,
|
||||
z: player.z,
|
||||
yaw: player.yaw,
|
||||
pitch: player.pitch,
|
||||
});
|
||||
player.packets_to_send.push(message_packet.clone());
|
||||
}
|
||||
}
|
||||
reply_queue.push_back(ServerPacket::Message {
|
||||
player_id: *own_id,
|
||||
message: "Welcome to the server! Enjoyyyyyy".to_string(),
|
||||
});
|
||||
reply_queue.push_back(ServerPacket::UpdateUserType {
|
||||
user_type: PlayerType::Operator,
|
||||
});
|
||||
}
|
||||
ClientPacket::SetBlock {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
mode,
|
||||
block_type,
|
||||
} => {
|
||||
let block_type = if mode == 0x00 { 0 } else { block_type };
|
||||
let mut data = data.write().await;
|
||||
let block =
|
||||
data.level.get_block(x as usize, y as usize, z as usize);
|
||||
// check if bedrock
|
||||
if block == 0x07
|
||||
&& data
|
||||
.players
|
||||
.iter()
|
||||
.find_map(|p| {
|
||||
(p.id == *own_id).then_some(p.player_type)
|
||||
})
|
||||
.unwrap_or_default() != PlayerType::Operator
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let packet = ServerPacket::SetBlock {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
block_type,
|
||||
};
|
||||
data.level
|
||||
.set_block(x as usize, y as usize, z as usize, block_type);
|
||||
for player in &mut data.players {
|
||||
player.packets_to_send.push(packet.clone());
|
||||
}
|
||||
}
|
||||
ClientPacket::PositionOrientation {
|
||||
_player_id: _,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
yaw,
|
||||
pitch,
|
||||
} => {
|
||||
let packet = ServerPacket::SetPositionOrientation {
|
||||
player_id: *own_id,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
yaw,
|
||||
pitch,
|
||||
};
|
||||
let mut data = data.write().await;
|
||||
for player in &mut data.players {
|
||||
player.packets_to_send.push(packet.clone());
|
||||
}
|
||||
}
|
||||
ClientPacket::Message { player_id, message } => {
|
||||
let mut data = data.write().await;
|
||||
println!("{message}");
|
||||
let message = format!(
|
||||
"&f<{}> {message}",
|
||||
data.players
|
||||
.iter()
|
||||
.find(|p| p.id == *own_id)
|
||||
.expect("should never fail")
|
||||
.username
|
||||
);
|
||||
let packet = ServerPacket::Message { player_id, message };
|
||||
for player in &mut data.players {
|
||||
player.packets_to_send.push(packet.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("unknown packet id: {:0x}", packet_buf[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
if ready.is_writable() {
|
||||
{
|
||||
let mut data = data.write().await;
|
||||
if let Some(player) = data.players.iter_mut().find(|p| p.id == *own_id) {
|
||||
for mut packet in player.packets_to_send.drain(..) {
|
||||
if let Some(id) = packet.get_player_id() {
|
||||
if id == *own_id {
|
||||
if !packet.should_echo() {
|
||||
continue;
|
||||
}
|
||||
packet.set_player_id(-1);
|
||||
}
|
||||
}
|
||||
reply_queue.push_back(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(packet) = reply_queue.pop_front() {
|
||||
let writer = PacketWriter::default().write_u8(packet.get_id());
|
||||
let msg = packet.write(writer).into_raw_packet();
|
||||
stream.write_all(&msg).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("remaining packets: {}", reply_queue.len());
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// helper to put together packets that need to be sent to send full level data for the given level
|
||||
fn build_level_packets(level: &Level) -> Vec<ServerPacket> {
|
||||
let mut packets: Vec<ServerPacket> = vec![ServerPacket::LevelInitialize {}];
|
||||
|
||||
// TODO: the type conversions in here may be weird idk
|
||||
let volume = level.x_size * level.y_size * level.z_size;
|
||||
let mut data = Vec::with_capacity(volume + 4);
|
||||
data.extend_from_slice(&(volume as i32).to_be_bytes());
|
||||
data.extend_from_slice(&level.blocks);
|
||||
|
||||
let mut e = GzEncoder::new(Vec::new(), Compression::best());
|
||||
e.write_all(&data).expect("failed to gzip level data");
|
||||
let data = e.finish().expect("failed to gzip level data");
|
||||
let data_len = data.len();
|
||||
let mut total_bytes = 0;
|
||||
|
||||
for chunk in data.chunks(ARRAY_LENGTH) {
|
||||
let chunk_len = chunk.len();
|
||||
let percent_complete = (total_bytes * 100 / data_len) as u8;
|
||||
packets.push(ServerPacket::LevelDataChunk {
|
||||
chunk_length: chunk_len as i16,
|
||||
chunk_data: chunk.to_vec(),
|
||||
percent_complete,
|
||||
});
|
||||
|
||||
total_bytes += chunk_len;
|
||||
}
|
||||
|
||||
packets.push(ServerPacket::LevelFinalize {
|
||||
x_size: level.x_size as i16,
|
||||
y_size: level.y_size as i16,
|
||||
z_size: level.z_size as i16,
|
||||
});
|
||||
|
||||
packets
|
||||
}
|
Loading…
Add table
Reference in a new issue