aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/Cargo.lock1151
-rw-r--r--core/Cargo.toml20
-rw-r--r--core/examples/start_client_console.rs24
-rw-r--r--core/examples/start_server_console.rs30
-rw-r--r--core/examples/tetris_multicmd_game.rs3
-rw-r--r--core/src/lib.rs10
-rw-r--r--core/src/pad_data/game_layout.rs9
-rw-r--r--core/src/pad_data/game_profile.rs99
-rw-r--r--core/src/pad_data/mod.rs4
-rw-r--r--core/src/pad_data/pad_messages.rs176
-rw-r--r--core/src/pad_data/pad_player_info.rs126
-rw-r--r--core/src/pad_io/client.rs448
-rw-r--r--core/src/pad_io/client_debug_cli.rs91
-rw-r--r--core/src/pad_io/mod.rs5
-rw-r--r--core/src/pad_io/server.rs646
-rw-r--r--core/src/pad_io/server_debug_cli.rs204
16 files changed, 3046 insertions, 0 deletions
diff --git a/core/Cargo.lock b/core/Cargo.lock
new file mode 100644
index 0000000..21c26ea
--- /dev/null
+++ b/core/Cargo.lock
@@ -0,0 +1,1151 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+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 0.59.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "backtrace"
+version = "0.3.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets",
+]
+
+[[package]]
+name = "bincode"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
+dependencies = [
+ "bincode_derive",
+ "serde",
+ "unty",
+]
+
+[[package]]
+name = "bincode_derive"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
+dependencies = [
+ "virtue",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cc"
+version = "1.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[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",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "clearscreen"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4"
+dependencies = [
+ "nix",
+ "terminfo",
+ "thiserror",
+ "which",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "env_home"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
+
+[[package]]
+name = "env_logger"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "humantime"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
+
+[[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 = "is-terminal"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[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 = "libc"
+version = "0.2.172"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[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 = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "nogamepads-core"
+version = "0.1.0"
+dependencies = [
+ "bincode",
+ "chrono",
+ "clap",
+ "clearscreen",
+ "env_logger",
+ "hex",
+ "log",
+ "serde",
+ "sha1",
+ "shell-words",
+ "tokio",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2611b99ab098a31bdc8be48b4f1a285ca0ced28bd5b4f23e45efa8c63b09efa5"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[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 = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "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_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[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.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
+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 = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
+[[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_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
+dependencies = [
+ "libc",
+]
+
+[[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 = "socket2"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[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 = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "terminfo"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
+dependencies = [
+ "fnv",
+ "nom",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio"
+version = "1.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "unty"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "virtue"
+version = "0.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.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 = "which"
+version = "7.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
+dependencies = [
+ "either",
+ "env_home",
+ "rustix",
+ "winsafe",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[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 = "winsafe"
+version = "0.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
diff --git a/core/Cargo.toml b/core/Cargo.toml
new file mode 100644
index 0000000..fade27a
--- /dev/null
+++ b/core/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "nogamepads-core"
+
+homepage = "https://github.com/CatilGrass/NoGamepads"
+repository = "https://github.com/CatilGrass/NoGamepads"
+readme = "../README.md"
+
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+nogamepads = { path = "../../NoGamepads" }
+bincode = { version = "2.0.1", features = ["serde"]}
+clearscreen = "4.0.1"
+hex = "0.4.3"
+tokio = { version = "1.45.0", features = ["full"] }
+serde = { version = "1.0.219", features = ["derive"] }
+clap = { version = "4.5.38", features = ["derive"] }
+sha1 = "0.10.6"
+log = "0.4.27" \ No newline at end of file
diff --git a/core/examples/start_client_console.rs b/core/examples/start_client_console.rs
new file mode 100644
index 0000000..3d6653b
--- /dev/null
+++ b/core/examples/start_client_console.rs
@@ -0,0 +1,24 @@
+use std::net::{IpAddr, Ipv4Addr};
+use nogamepads_core::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo;
+use nogamepads_core::pad_io::client::nogamepads_client::PadClient;
+
+const PASSWORD : &str = "password";
+
+fn main() {
+
+ // 构建玩家信息
+ let mut player_info = PlayerInfo::new();
+ player_info.setup_account_info("juliet", PASSWORD);
+ player_info.set_nickname("Juliet_Smile");
+ player_info.set_customize_color_hsv(320, 0.5, 1.0); // PINK
+
+ // 构建客户端
+ let mut client = PadClient::bind_addr(
+ IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
+ );
+ client.bind_player(player_info); // 绑定玩家
+ client.enable_console(); // 启用控制台
+
+ // 连接至目标地址
+ client.connect();
+} \ No newline at end of file
diff --git a/core/examples/start_server_console.rs b/core/examples/start_server_console.rs
new file mode 100644
index 0000000..e643390
--- /dev/null
+++ b/core/examples/start_server_console.rs
@@ -0,0 +1,30 @@
+use std::net::{IpAddr, Ipv4Addr};
+use nogamepads_core::DEFAULT_PORT;
+use nogamepads_core::pad_data::game_profile::game_profile::GameProfile;
+use nogamepads_core::pad_io::server::nogamepads_server::PadServer;
+
+fn main() {
+
+ // 简易构建服务端
+ // let server = PadServer::build_simple();
+ // server.start_listening_debug()
+
+ // 构建服务端
+ let server = PadServer::default()
+ .addr(
+ IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
+ DEFAULT_PORT
+ )
+ .put_profile(
+ GameProfile::default()
+ .game_name("My Multiplayer Game")
+ .game_description("My Game Description")
+ .version("0.1 alpha")
+ .to_owned()
+ )
+ .enable_console()
+ .build();
+
+ // 运行服务端
+ server.start_listening();
+} \ No newline at end of file
diff --git a/core/examples/tetris_multicmd_game.rs b/core/examples/tetris_multicmd_game.rs
new file mode 100644
index 0000000..cf74f49
--- /dev/null
+++ b/core/examples/tetris_multicmd_game.rs
@@ -0,0 +1,3 @@
+fn main() {
+
+} \ No newline at end of file
diff --git a/core/src/lib.rs b/core/src/lib.rs
new file mode 100644
index 0000000..f70fc4e
--- /dev/null
+++ b/core/src/lib.rs
@@ -0,0 +1,10 @@
+use bincode::config;
+use bincode::config::Configuration;
+
+pub mod pad_io;
+pub mod pad_data;
+
+pub const DEFAULT_PORT : u16 = 5989;
+pub const BINCODE_CONVERT_FAILED : Vec<u8> = Vec::new();
+pub const BINCODE_CONFIG : Configuration = config::standard();
+
diff --git a/core/src/pad_data/game_layout.rs b/core/src/pad_data/game_layout.rs
new file mode 100644
index 0000000..62afbcc
--- /dev/null
+++ b/core/src/pad_data/game_layout.rs
@@ -0,0 +1,9 @@
+pub mod game_layout {
+ use bincode::{Decode, Encode};
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Encode, Decode, Serialize, Deserialize, PartialEq, Debug)]
+ pub struct GameLayout {
+
+ }
+} \ No newline at end of file
diff --git a/core/src/pad_data/game_profile.rs b/core/src/pad_data/game_profile.rs
new file mode 100644
index 0000000..8d6e279
--- /dev/null
+++ b/core/src/pad_data/game_profile.rs
@@ -0,0 +1,99 @@
+pub mod game_profile {
+ use std::fmt::Display;
+ use bincode::{Decode, Encode};
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Encode, Decode, Serialize, Deserialize, PartialEq, Debug)]
+ pub struct GameProfile {
+
+ // 游戏名称
+ pub game_name: String,
+
+ // 游戏描述
+ pub game_description: String,
+
+ // 游戏组织
+ pub organization: String,
+
+ // 游戏版本
+ pub version: String,
+
+ // 工作室 & 游戏 主页
+ pub website: String,
+
+ // 交流邮箱
+ pub email: String
+ }
+
+ impl Default for GameProfile {
+ fn default() -> Self {
+ GameProfile {
+ game_name: "Unnamed Game".to_string(),
+ game_description: "".to_string(),
+ organization: "".to_string(),
+ version: "0.1".to_string(),
+ website: "".to_string(),
+ email: "".to_string()
+ }
+ }
+ }
+
+ impl Clone for GameProfile {
+ fn clone(&self) -> Self {
+ GameProfile {
+ game_name: self.game_name.clone(),
+ game_description: self.game_description.clone(),
+ organization: self.organization.clone(),
+ version: self.version.clone(),
+ website: self.website.clone(),
+ email: self.email.clone()
+ }
+ }
+ }
+
+ impl Display for GameProfile {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let mut string = String::new();
+ string += format!("Game Name: {}\n", self.game_name).as_str();
+ if !self.game_description.eq("") { string += format!("Description: {}\n", self.game_description).as_str(); }
+ if !self.organization.eq("") { string += format!("Org: {}\n", self.organization).as_str(); }
+ if !self.website.eq("") { string += format!("- Web: {}\n", self.website).as_str(); }
+ if !self.version.eq("") { string += format!("Version: {}\n", self.version).as_str(); }
+ if !self.email.eq("") { string += format!("- E-mail: {}\n", self.email).as_str(); }
+
+ write!(f, "{}", string)
+ }
+ }
+
+ impl GameProfile {
+ pub fn game_name(&mut self, game_name: &str) -> &mut GameProfile {
+ self.game_name = game_name.to_string();
+ self
+ }
+
+ pub fn game_description(&mut self, game_description: &str) -> &mut GameProfile {
+ self.game_description = game_description.to_string();
+ self
+ }
+
+ pub fn organization(&mut self, organization: &str) -> &mut GameProfile {
+ self.organization = organization.to_string();
+ self
+ }
+
+ pub fn version(&mut self, version: &str) -> &mut GameProfile {
+ self.version = version.to_string();
+ self
+ }
+
+ pub fn website(&mut self, website: &str) -> &mut GameProfile {
+ self.website = website.to_string();
+ self
+ }
+
+ pub fn email(&mut self, email: &str) -> &mut GameProfile {
+ self.email = email.to_string();
+ self
+ }
+ }
+} \ No newline at end of file
diff --git a/core/src/pad_data/mod.rs b/core/src/pad_data/mod.rs
new file mode 100644
index 0000000..8ff024a
--- /dev/null
+++ b/core/src/pad_data/mod.rs
@@ -0,0 +1,4 @@
+pub mod pad_messages;
+pub mod pad_player_info;
+pub mod game_profile;
+mod game_layout; \ No newline at end of file
diff --git a/core/src/pad_data/pad_messages.rs b/core/src/pad_data/pad_messages.rs
new file mode 100644
index 0000000..f081e5d
--- /dev/null
+++ b/core/src/pad_data/pad_messages.rs
@@ -0,0 +1,176 @@
+pub mod nogamepads_messages {
+ use bincode::{Decode, Encode};
+ use crate::pad_data::game_profile::game_profile::GameProfile;
+ use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo;
+
+ #[derive(Encode, Decode, PartialEq, Debug, Clone)]
+ pub enum ControlMessage {
+
+ Msg(String),
+
+ Pressed(u8),
+
+ Released(u8),
+
+ Axis(u8, f64),
+
+ Dir(u8, (f64, f64)),
+
+ Exit,
+
+ Err
+ }
+
+ #[derive(Encode, Decode, PartialEq, Debug, Clone)]
+ pub enum GameMessage {
+
+ SkinEventTrigger(u8),
+
+ DisableKey(u8),
+
+ EnableKey(u8),
+
+ Leave(LeaveReason),
+
+ Err
+ }
+
+ #[derive(Encode, Decode, PartialEq, Debug, Clone)]
+ pub enum LeaveReason {
+
+ GameOver,
+
+ ServerClosed,
+
+ YouAreKicked,
+
+ YouAreBanned
+ }
+
+ #[derive(Encode, Decode, PartialEq, Debug, Clone)]
+ pub enum ConnectionMessage {
+
+ Connection(PlayerInfo),
+
+ RequestProfile,
+
+ RequestLayoutConfigure,
+
+ RequestSkinPackage,
+
+ Ready,
+
+ Err
+ }
+
+ #[derive(Encode, Decode, PartialEq, Debug, Clone)]
+ pub enum ConnectionCallbackMessage {
+
+ Profile(GameProfile),
+
+ Deny(ConnectionErrorType),
+
+ Fail(ConnectionErrorType),
+
+ Ok,
+
+ Welcome,
+
+ Err
+ }
+
+ #[derive(Encode, Decode, PartialEq, Debug, Clone)]
+ pub enum ConnectionErrorType {
+
+ ContainSamePlayer,
+
+ PlayerBanned,
+
+ Timeout,
+
+ GameLocked,
+
+ WhatTheHell
+ }
+}
+
+pub mod nogamepads_message_encoder {
+ use bincode::{Decode, Encode};
+ use crate::{BINCODE_CONFIG, BINCODE_CONVERT_FAILED};
+ use crate::pad_data::pad_messages::nogamepads_messages::{ConnectionCallbackMessage, ConnectionMessage, ControlMessage, GameMessage};
+
+ pub trait NgpdMessageEncoder<Message: Encode + Decode<()>> {
+ fn err_result_decode () -> Message;
+ fn err_result_encode () -> Vec<u8> {
+ BINCODE_CONVERT_FAILED
+ }
+
+ fn en(&self) -> Vec<u8> where Self : Encode {
+ bincode::encode_to_vec(self, BINCODE_CONFIG)
+ .unwrap_or_else(|_| Self::err_result_encode())
+ }
+
+ fn de(encoded : Vec<u8>) -> Message {
+ match bincode::decode_from_slice(&encoded[..], BINCODE_CONFIG) {
+ Ok((decoded, _)) => decoded,
+ Err(_) => Self::err_result_decode()
+ }
+ }
+ }
+
+ impl NgpdMessageEncoder<ControlMessage> for ControlMessage {
+ fn err_result_decode() -> ControlMessage {
+ ControlMessage::Err
+ }
+ }
+
+ impl NgpdMessageEncoder<GameMessage> for GameMessage {
+ fn err_result_decode() -> GameMessage {
+ GameMessage::Err
+ }
+ }
+
+ impl NgpdMessageEncoder<ConnectionMessage> for ConnectionMessage {
+ fn err_result_decode() -> ConnectionMessage {
+ ConnectionMessage::Err
+ }
+ }
+
+ impl NgpdMessageEncoder<ConnectionCallbackMessage> for ConnectionCallbackMessage {
+ fn err_result_decode() -> ConnectionCallbackMessage {
+ ConnectionCallbackMessage::Err
+ }
+ }
+}
+
+pub mod nogamepads_message_transfer {
+ use bincode::{Decode, Encode};
+ use log::error;
+ use tokio::io::{AsyncReadExt, AsyncWriteExt};
+ use tokio::net::TcpStream;
+ use crate::pad_data::pad_messages::nogamepads_message_encoder::NgpdMessageEncoder;
+
+ pub async fn send_msg <Message>(stream: &mut TcpStream, msg: impl NgpdMessageEncoder<Message> + Decode<()> + Encode)
+ where Message: NgpdMessageEncoder<Message> + Decode<()> + Encode {
+ match stream.write_all(NgpdMessageEncoder::en(&msg).as_slice()).await {
+ Ok(_) => {}
+ Err(_) => {
+ error!("Failed to send message.");
+ }
+ }
+ }
+
+ pub async fn read_msg<Message>(buffer: &mut [u8], stream: &mut TcpStream) -> Message
+ where Message: NgpdMessageEncoder<Message> + Decode<()> + Encode {
+ match stream.read(buffer).await {
+ Ok(read) => {
+ let received = &buffer[..read];
+ <Message as NgpdMessageEncoder<Message>>::de(Vec::from(received))
+ }
+ Err(err) => {
+ error!("Error reading from socket: {}", err);
+ <Message as NgpdMessageEncoder<Message>>::err_result_decode()
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/core/src/pad_data/pad_player_info.rs b/core/src/pad_data/pad_player_info.rs
new file mode 100644
index 0000000..ab0f6fd
--- /dev/null
+++ b/core/src/pad_data/pad_player_info.rs
@@ -0,0 +1,126 @@
+pub mod nogamepads_player_info {
+
+ use bincode::{Decode, Encode};
+ use hex::encode;
+ use sha1::{Digest, Sha1};
+ use serde::{Deserialize, Serialize};
+
+ pub const ACCOUNT_HASH_SALT : &str = "Mr.Weicao";
+
+ #[derive(Encode, Decode,
+ Serialize, Deserialize,
+ PartialEq, Debug)]
+ pub struct PlayerInfo {
+ pub account: PlayerAccountInfo,
+ pub customize: PlayerCustomizeInfo
+ }
+
+ #[derive(Encode, Decode,
+ Serialize, Deserialize,
+ PartialEq, Debug)]
+ pub struct PlayerAccountInfo {
+ pub id: String,
+ pub player_hash: String
+ }
+
+ #[derive(Encode, Decode,
+ Serialize, Deserialize,
+ PartialEq, Debug)]
+ pub struct PlayerCustomizeInfo {
+ pub nickname: String,
+
+ pub color_hue: i32, // 0 - 360
+ pub color_saturation: f64, // 0 - 1
+ pub color_value: f64 // 0 - 1
+ }
+
+ impl PlayerInfo {
+
+ pub fn new() -> PlayerInfo {
+ PlayerInfo {
+ customize: PlayerCustomizeInfo::default(),
+ account: PlayerAccountInfo::default()
+ }
+ }
+
+ pub fn set_nickname(&mut self, name: &str) -> &mut PlayerInfo {
+ self.customize.nickname = String::from(name);
+ self
+ }
+
+ pub fn set_customize_color_hue(&mut self, mut hue: i32) -> &mut PlayerInfo {
+ hue = hue.clamp(0, 360);
+ self.customize.color_hue = hue;
+ self
+ }
+
+ pub fn set_customize_color_hsv(&mut self, mut hue: i32, mut saturation: f64, mut value: f64) -> &mut PlayerInfo {
+ hue = hue.clamp(0, 360);
+ saturation = saturation.clamp(0.0, 1.0);
+ value = value.clamp(0.0, 1.0);
+
+ self.customize.color_hue = hue;
+ self.customize.color_saturation = saturation;
+ self.customize.color_value = value;
+ self
+ }
+
+ pub fn setup_account_info(&mut self, id: &str, password: &str) -> &mut PlayerInfo {
+
+ let combined = format!("{}{}{}", id, password, ACCOUNT_HASH_SALT);
+ let mut hasher = Sha1::new();
+ hasher.update(combined);
+ let result = hasher.finalize();
+
+ self.account.id = String::from(id);
+ self.account.player_hash = encode(&result[..]);
+ self
+ }
+ }
+
+ impl Clone for PlayerInfo {
+ fn clone(&self) -> PlayerInfo {
+ PlayerInfo {
+ account: PlayerAccountInfo {
+ id: String::from(self.account.id.clone()),
+ player_hash: String::from(self.account.player_hash.clone())
+ },
+ customize: PlayerCustomizeInfo {
+ nickname: self.customize.nickname.clone(),
+ color_hue: self.customize.color_hue.clone(),
+ color_saturation: self.customize.color_saturation.clone(),
+ color_value: self.customize.color_value.clone()
+ }
+ }
+ }
+ }
+
+ impl Default for PlayerCustomizeInfo {
+ fn default() -> Self {
+ PlayerCustomizeInfo {
+ nickname: String::from("unnamed"),
+
+ color_hue: 120,
+ color_saturation: 1.0,
+ color_value: 1.0
+ }
+ }
+ }
+
+ impl Default for PlayerAccountInfo {
+ fn default() -> Self {
+ PlayerAccountInfo {
+ id: String::from("empty"),
+ player_hash: String::from("")
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod player_info_test {
+ #[test]
+ fn test_player_info_setup() {
+
+ }
+} \ No newline at end of file
diff --git a/core/src/pad_io/client.rs b/core/src/pad_io/client.rs
new file mode 100644
index 0000000..2969742
--- /dev/null
+++ b/core/src/pad_io/client.rs
@@ -0,0 +1,448 @@
+pub mod nogamepads_client {
+ use crate::pad_data::pad_messages::nogamepads_message_transfer::{read_msg, send_msg};
+ use crate::pad_data::pad_messages::nogamepads_messages::{ConnectionCallbackMessage, ConnectionErrorType, ConnectionMessage, ControlMessage, GameMessage, LeaveReason};
+ use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo;
+ use log::{error, info};
+ use std::net::{IpAddr, Ipv4Addr};
+ use std::process::exit;
+ use std::sync::atomic::AtomicBool;
+ use std::sync::atomic::Ordering::SeqCst;
+ use std::sync::{Arc, Mutex};
+ use std::time::Duration;
+ use clap::CommandFactory;
+ use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
+ use tokio::net::TcpStream;
+ use tokio::{io, spawn};
+ use nogamepads::debug_console::debug_console::read_cli;
+ use nogamepads::logger::logger_build;
+ use crate::pad_io::client_debug_cli::{process_debug_cmd, Pcc};
+ use crate::DEFAULT_PORT;
+ use crate::pad_data::game_profile::game_profile::GameProfile;
+ use crate::pad_data::pad_messages::nogamepads_message_encoder::NgpdMessageEncoder;
+
+ type WriteList = Arc<Mutex<Vec<ControlMessage>>>;
+ type ReadList = Arc<Mutex<Vec<GameMessage>>>;
+
+ pub struct PadClient {
+
+ // --- 主要参数 ---
+
+ // 目标地址
+ target_address: IpAddr,
+
+ // 目标端口
+ #[allow(dead_code)]
+ target_port: u16,
+
+ // 绑定的玩家
+ bind_player: PlayerInfo,
+
+ // 调试模式
+ enable_console: bool,
+
+ // 保持安静,不初始化 env_logger
+ quiet: bool,
+
+ // --- 运行时参数 ---
+
+ // 发送信息列表
+ write_list: WriteList,
+
+ // 读取信息列表
+ read_list: ReadList,
+
+ // 是否退出
+ exit: AtomicBool,
+ }
+
+ impl Default for PadClient {
+ fn default() -> Self {
+ PadClient {
+ enable_console: false,
+ target_address: IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)),
+ target_port: DEFAULT_PORT,
+ bind_player: PlayerInfo::new(),
+ quiet: false,
+
+ write_list: WriteList::default(),
+ read_list: ReadList::default(),
+ exit: AtomicBool::new(false)
+ }
+ }
+ }
+
+ // 客户端构建部分
+ impl PadClient {
+
+ pub fn bind_addr(address: IpAddr) -> PadClient {
+ PadClient {
+ target_address: address,
+ ..PadClient::default()
+ }
+ }
+
+ pub fn bind_addr_with_port(address: IpAddr, port: u16) -> PadClient {
+ PadClient {
+ target_address: address,
+ target_port: port,
+ ..PadClient::default()
+ }
+ }
+
+ pub fn enable_console(&mut self) {
+ self.enable_console = true;
+ }
+
+ pub fn quiet(&mut self) -> &mut PadClient {
+ self.quiet = true;
+ self
+ }
+
+ pub fn bind_player(&mut self, player: PlayerInfo) {
+ self.bind_player = player;
+ }
+ }
+
+ // 客户端消息管理
+ impl PadClient {
+
+
+ pub fn put_msg(&self, msg: ControlMessage) {
+ {
+ let mut guard = self.write_list.lock().unwrap();
+ guard.push(msg);
+ }
+ }
+
+ pub fn pop_a_msg(&self) -> Option<GameMessage> {
+ {
+ let mut guard = self.read_list.lock().unwrap();
+ if ! guard.is_empty() {
+ Some(guard.remove(0))
+ } else {
+ None
+ }
+ }
+ }
+
+ pub fn pop_msg_or(&self, or: GameMessage) -> GameMessage {
+ self.pop_a_msg().unwrap_or(or)
+ }
+
+ pub fn list_received(&self) -> Vec<GameMessage> {
+ match self.read_list.lock() {
+ Ok(guard) => {
+ guard.to_vec()
+ }
+ Err(_) => { Vec::new() }
+ }
+ }
+ }
+
+ // 客户端状态控制
+ impl PadClient {
+
+ pub fn connect(self) {
+
+ self.exit.store(false, SeqCst);
+
+ // 构建 Logger
+ if !self.quiet {
+ logger_build();
+ }
+
+ // 调试模式
+ let debug = self.enable_console;
+
+ // 客户端对象的 Arc
+ let arc_client = Arc::new(self);
+
+ // 部署环境
+ let runtime = tokio::runtime::Builder::new_multi_thread()
+ .thread_name("nogpad-pad_io")
+ .thread_stack_size(32 * 1024 * 1024)
+ .enable_time()
+ .enable_io()
+ .build()
+ .unwrap();
+
+ info!("Starting \"NoGamepads Client\".");
+
+ // 入口
+ let entry = async move {
+ let main_thread = spawn({
+ let client = Arc::clone(&arc_client);
+ async move {
+ Self::main_client_thread(client).await
+ }
+ });
+
+ let background_thread = spawn({
+ let client = Arc::clone(&arc_client);
+ async move {
+ Self::background_thread(client).await
+ }
+ });
+
+ if debug {
+ let debug_cli = spawn({
+ let client = Arc::clone(&arc_client);
+ async move {
+ Self::process_debug_cli(client).await
+ }
+ });
+ let _ = tokio::join!(debug_cli, main_thread, background_thread);
+ } else {
+ let _ = tokio::join!(main_thread, background_thread);
+ }
+ };
+
+ // 阻塞运行
+ runtime.block_on(entry);
+ }
+
+ pub fn exit_server(&self) {
+ self.exit.store(true, SeqCst);
+ }
+
+ async fn main_client_thread(self: Arc<Self>) {
+ let mut buffer : [u8; 1024] = [0; 1024];
+ let addr_str = format!("{}:{}", self.target_address.to_string(), DEFAULT_PORT);
+
+ info!("Connected to {}", &addr_str);
+
+ // 下载服务端配置文件
+ {
+ info!("Check: Downloaded game profile.");
+ let profile = self.check_server_profile(&mut buffer, addr_str.clone()).await;
+ if profile.is_some() {
+ info!("Success: Downloaded.");
+ let profile = profile.unwrap_or(GameProfile::default());
+ for line in profile.to_string().split('\n') {
+ info!("{}", line);
+ }
+ }
+ else {
+ error!("Failed: Can't download profile!");
+ self.exit_server();
+ }
+ }
+
+ // 尝试加入服务端,并建立长连接
+ {
+ if !self.try_join_game(&mut buffer, addr_str.clone()).await {
+ error!("Failed: Can't join the game!");
+ self.exit_server();
+ return;
+ }
+ }
+ }
+
+ async fn check_server_profile (self: &Arc<Self>, buffer: &mut [u8], addr_str: String) -> Option<GameProfile> {
+ match TcpStream::connect(&addr_str).await {
+ Ok(mut stream) => {
+ send_msg(&mut stream, ConnectionMessage::RequestProfile).await;
+ let callback : ConnectionCallbackMessage = read_msg(buffer, &mut stream).await;
+ match callback {
+ ConnectionCallbackMessage::Profile(profile) => {
+ Some(profile)
+ }
+ ConnectionCallbackMessage::Deny(err_type) => {
+ error!("Request failed: Server denied your request! ({:?})", err_type);
+ None
+ }
+ ConnectionCallbackMessage::Err => {
+ error!("Connection failed: Can't connect to server!");
+ None
+ }
+ _ => { None }
+ }
+ }
+ Err(_err) => {
+ None
+ }
+ }
+ }
+
+ async fn try_join_game(self: &Arc<Self>, buffer: &mut [u8], addr_str: String) -> bool {
+
+ match TcpStream::connect(&addr_str).await {
+ Ok(mut stream) => {
+
+ // 发送连接请求
+ let info = self.bind_player.clone();
+ send_msg(&mut stream, ConnectionMessage::Connection(info)).await;
+
+ // 读取回调
+ let callback : ConnectionCallbackMessage = read_msg(buffer, &mut stream).await;
+ match callback {
+ ConnectionCallbackMessage::Deny(error) => {
+ match error {
+ ConnectionErrorType::ContainSamePlayer => {
+ error!("Connection failed: Contains same player!");
+ false
+ }
+ ConnectionErrorType::PlayerBanned => {
+ error!("Connection failed: You are banned!");
+ false
+ }
+ ConnectionErrorType::Timeout => {
+ error!("Connection failed: Timeout!");
+ false
+ }
+ ConnectionErrorType::GameLocked => {
+ error!("Connection failed: Game was locked!");
+ false
+ }
+ _ => { false }
+ }
+ }
+ ConnectionCallbackMessage::Ok => {
+
+ // 服务端检查完毕,发送 Ready 以示加入游戏
+ send_msg(&mut stream, ConnectionMessage::Ready).await;
+ let callback : ConnectionCallbackMessage = read_msg(buffer, &mut stream).await;
+ match callback {
+ ConnectionCallbackMessage::Welcome => {
+ info!("Welcome!");
+ Self::long_connection(Arc::clone(&self), stream).await;
+ }
+ ConnectionCallbackMessage::Deny(_error) => {
+ error!("Request failed: Server denied your request",);
+ }
+ _ => {}
+ }
+ true
+ }
+ _ => { false }
+ }
+ }
+ Err(err) => {
+ error!("Failed to connect to server: {}", err);
+ false
+ }
+ }
+ }
+
+ async fn long_connection(self: Arc<Self>, stream: TcpStream) {
+ let (reader, writer) = io::split(stream);
+ spawn(Self::read_task(Arc::clone(&self), reader));
+ spawn(Self::write_task(Arc::clone(&self), writer));
+ }
+
+ async fn read_task(self: Arc<Self>, mut reader: ReadHalf<TcpStream>) {
+ let mut buf = [0u8; 1024];
+ loop {
+ match reader.read(&mut buf).await {
+ Ok(0) => break,
+ Ok(n) => {
+ let msg = GameMessage::de(buf[0..n].to_vec());
+ {
+ match self.read_list.lock() {
+ Ok(mut guard) => {
+ match &msg {
+ GameMessage::Leave(reason) => {
+ match reason {
+ LeaveReason::GameOver => {
+ info!("Leave Game: Game Over!");
+ self.exit_server();
+ }
+ LeaveReason::ServerClosed => {
+ info!("Leave Game: Server closed!");
+ self.exit_server();
+ }
+ LeaveReason::YouAreKicked => {
+ error!("Kick Game: You are kicked!");
+ self.exit_server();
+ }
+ LeaveReason::YouAreBanned => {
+ error!("Kick Game: You are banned!");
+ self.exit_server();
+ }
+ }
+ }
+ _ => {
+ info!("{:?}", &msg);
+ guard.push(msg);
+ }
+ }
+ }
+ Err(_) => {}
+ }
+ }
+ }
+ Err(e) => {
+ error!("Error reading from stream: {}", e);
+ self.exit_server();
+ break;
+ }
+ }
+ }
+ }
+
+ async fn write_task(self: Arc<Self>, mut writer: WriteHalf<TcpStream>) {
+ loop {
+ let msg : Option<ControlMessage>;
+ {
+ let lock = self.write_list.lock();
+ match lock {
+ Ok(mut guard) => {
+ if ! guard.is_empty() {
+ msg = Some(guard.remove(0));
+ }
+ else { msg = None; }
+ }
+ Err(_) => {
+ msg = None;
+ }
+ }
+ }
+ if msg.is_some() {
+ let msg = msg.unwrap();
+ match &writer.write_all(NgpdMessageEncoder::en(&msg).as_slice()).await {
+ Ok(_) => {
+ info!("Sent {:?}", msg);
+ }
+ Err(_error) => {
+ error!("Sent {:?} failed!", msg);
+ }
+ }
+ }
+ }
+ }
+
+ async fn background_thread(self: Arc<Self>) {
+ loop {
+ // 退出程序的监听
+ if self.exit.load(SeqCst) {
+ tokio::time::sleep(Duration::from_secs(1)).await;
+ info!("Main thread exited.");
+ exit(0);
+ }
+ }
+ }
+
+ async fn process_debug_cli(self: Arc<Self>) {
+ loop {
+ if self.exit.load(SeqCst) {
+ info!("Debug console exited");
+ break
+ }
+ tokio::time::sleep(Duration::from_secs_f64(0.2)).await;
+ let option: Option<Pcc> = read_cli(
+ format!("CLIENT {}/{}> ",
+ self.target_address.to_string(),
+ self.bind_player.account.id).as_str(),
+ "pcc".to_string(),
+ Pcc::command()
+ ).await;
+ match option {
+ None => {}
+ Some(cmd) => {
+ process_debug_cmd(cmd, Arc::clone(&self));
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/core/src/pad_io/client_debug_cli.rs b/core/src/pad_io/client_debug_cli.rs
new file mode 100644
index 0000000..c95fb94
--- /dev/null
+++ b/core/src/pad_io/client_debug_cli.rs
@@ -0,0 +1,91 @@
+use crate::pad_io::client::nogamepads_client::PadClient;
+use crate::pad_data::pad_messages::nogamepads_messages::{ControlMessage, GameMessage};
+use clap::{Args, Parser, Subcommand};
+use std::sync::Arc;
+
+/// NoGamePads Client - Cli
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+pub struct Pcc {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+/// 主要命令
+#[derive(Subcommand, Debug)]
+enum Commands {
+
+ // 清屏
+ #[command(about = "Clean the screen")]
+ Clear,
+
+ // 断开当前连接
+ #[command(about = "Exit from server")]
+ Exit,
+
+ // 检查收到的消息
+ #[command(about = "Check received")]
+ Received(ReceivedArgs),
+
+ // 取出一条消息
+ #[command(about = "Pop a message")]
+ Pop(PopArgs),
+
+ // 发送消息
+ #[command(about = "Send Message")]
+ Msg(MsgArgs),
+}
+
+#[derive(Args, Debug)]
+struct ReceivedArgs {
+
+ #[arg(long)]
+ list: bool
+}
+
+/// 发送消息 参数
+#[derive(Args, Debug)]
+struct MsgArgs {
+
+ // 消息内容
+ #[arg(value_name = "CONTENT")]
+ message: String,
+}
+
+#[derive(Args, Debug)]
+struct PopArgs { }
+
+pub fn process_debug_cmd (cmd: Pcc, client: Arc<PadClient>) {
+ match cmd.command {
+ Commands::Clear => {
+ clearscreen::clear().expect("Failed to clear screen");
+ }
+
+ Commands::Exit => {
+ client.exit_server();
+ }
+
+ Commands::Received(args) => {
+ if args.list {
+ for msg in client.list_received() {
+ println!("{:?}", msg);
+ }
+ } else {
+ println!("Total {} messsage(s)!", client.list_received().iter().count());
+ }
+ }
+
+ Commands::Pop(_args) => {
+ println!("{:?}", client.pop_msg_or(GameMessage::Err));
+ }
+
+ Commands::Msg(args) => {
+ client.put_msg(ControlMessage::Msg(args.message));
+ }
+ }
+}
+
+#[allow(dead_code)]
+fn put_to_list(client: Arc<PadClient>, message: ControlMessage) {
+ client.put_msg(message);
+} \ No newline at end of file
diff --git a/core/src/pad_io/mod.rs b/core/src/pad_io/mod.rs
new file mode 100644
index 0000000..ae10f2d
--- /dev/null
+++ b/core/src/pad_io/mod.rs
@@ -0,0 +1,5 @@
+pub mod client;
+pub mod client_debug_cli;
+
+pub mod server;
+pub mod server_debug_cli; \ No newline at end of file
diff --git a/core/src/pad_io/server.rs b/core/src/pad_io/server.rs
new file mode 100644
index 0000000..c530753
--- /dev/null
+++ b/core/src/pad_io/server.rs
@@ -0,0 +1,646 @@
+pub mod nogamepads_server {
+ use std::collections::{HashMap, VecDeque};
+ use crate::pad_data::pad_messages::nogamepads_message_encoder::NgpdMessageEncoder;
+ use crate::pad_data::pad_messages::nogamepads_message_transfer::{read_msg, send_msg};
+ use crate::pad_data::pad_messages::nogamepads_messages::{ConnectionCallbackMessage, ConnectionMessage, ControlMessage, GameMessage, LeaveReason};
+ use log::{error, info, warn};
+ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ use std::process::exit;
+ use std::sync::atomic::AtomicBool;
+ use std::sync::atomic::Ordering::SeqCst;
+ use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
+ use std::time::Duration;
+ use clap::CommandFactory;
+ use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
+ use tokio::net::{TcpListener, TcpStream};
+ use tokio::{io, spawn};
+ use tokio::runtime::Runtime;
+ use nogamepads::debug_console::debug_console::read_cli;
+ use nogamepads::logger::logger_build;
+ use crate::DEFAULT_PORT;
+ use crate::pad_data::game_profile::game_profile::GameProfile;
+ use crate::pad_data::pad_messages::nogamepads_messages::ConnectionErrorType::{ContainSamePlayer, GameLocked, PlayerBanned, WhatTheHell};
+ use crate::pad_data::pad_messages::nogamepads_messages::GameMessage::Leave;
+ use crate::pad_data::pad_messages::nogamepads_messages::LeaveReason::ServerClosed;
+ use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo;
+ use crate::pad_io::server_debug_cli::{process_debug_cmd, Psc};
+
+ type PlayerMap = Arc<Mutex<HashMap<String, PlayerInfo>>>;
+ type WriteList = Arc<Mutex<HashMap<String, VecDeque<GameMessage>>>>;
+ type ReadList = Arc<Mutex<HashMap<String, VecDeque<ControlMessage>>>>;
+
+ pub struct PadServer {
+
+ // --- 主要参数 ---
+
+ // 本地监听地址
+ address: IpAddr,
+
+ // 游戏信息
+ game_profile: GameProfile,
+
+ // 绑定端口
+ port: u16,
+
+ // 调试模式
+ enable_console: bool,
+
+ // 保持安静,不初始化 env_logger
+ quiet: bool,
+
+ // --- 运行时参数 ---
+
+ // 发送信息列表
+ write_list: WriteList,
+
+ // 读取信息列表
+ read_list: ReadList,
+
+ // 在线玩家
+ online_players: PlayerMap,
+
+ // 被封禁的玩家
+ banned_players: PlayerMap,
+
+ // 是否锁定该游戏:禁止后续玩家加入
+ game_locked: AtomicBool,
+
+ // 是否停止服务器
+ stop: AtomicBool,
+ }
+
+ impl Clone for PadServer {
+ fn clone(&self) -> Self {
+ PadServer {
+ address: self.address.clone(),
+ game_profile: self.game_profile.clone(),
+ port: self.port.clone(),
+ enable_console: self.enable_console,
+ quiet: self.quiet,
+
+ write_list: self.write_list.clone(),
+ read_list: self.read_list.clone(),
+ online_players: self.online_players.clone(),
+ banned_players: self.banned_players.clone(),
+ game_locked: AtomicBool::new((&self.game_locked.load(SeqCst)).clone()),
+ stop: AtomicBool::new((&self.stop.load(SeqCst)).clone()),
+ }
+ }
+ }
+
+ impl Default for PadServer {
+ fn default() -> Self {
+ PadServer {
+ address: IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)),
+ game_profile: GameProfile::default(),
+ port: DEFAULT_PORT,
+ enable_console: false,
+ quiet: false,
+
+ write_list: WriteList::default(),
+ read_list: ReadList::default(),
+ online_players: PlayerMap::default(),
+ banned_players: PlayerMap::default(),
+ game_locked: AtomicBool::new(false),
+ stop: AtomicBool::new(false),
+ }
+ }
+ }
+
+ // 服务端构建部分
+ impl PadServer {
+
+ pub fn build_simple() -> Arc<PadServer> {
+ Arc::new(Self::default()
+ .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), DEFAULT_PORT)
+ .put_profile(GameProfile::default()).to_owned())
+ }
+
+ pub fn addr(&mut self, ip_addr: IpAddr, port: u16) -> &mut PadServer {
+ self.ip_addr(ip_addr).port(port)
+ }
+
+ pub fn socket_addr(&mut self, socket_addr: SocketAddr) -> &mut PadServer {
+ self.ip_addr(socket_addr.ip()).port(socket_addr.port())
+ }
+
+ pub fn port(&mut self, port: u16) -> &mut PadServer {
+ self.port = port;
+ self
+ }
+
+ pub fn ip_addr(&mut self, ip_addr: IpAddr) -> &mut PadServer {
+ self.address = ip_addr;
+ self
+ }
+
+ pub fn put_profile(&mut self, profile: GameProfile) -> &mut PadServer {
+ self.game_profile = profile;
+ self
+ }
+
+ pub fn enable_console(&mut self) -> &mut PadServer {
+ self.enable_console = true;
+ self
+ }
+
+ pub fn quiet(&mut self) -> &mut PadServer {
+ self.quiet = true;
+ self
+ }
+
+ pub fn build(&self) -> Arc<PadServer> {
+ Arc::new(self.clone())
+ }
+
+ }
+
+ // 服务端消息管理
+ impl PadServer {
+
+ pub fn put_msg_to(&self, msg: GameMessage, player: &PlayerInfo) {
+ match self.write_list.lock() {
+ Ok(mut guard) => {
+ let hash = &player.account.player_hash.clone();
+ if ! guard.contains_key(hash.as_str()) {
+ guard.entry(player.account.player_hash.clone())
+ .or_insert_with(VecDeque::new)
+ .push_back(msg);
+ }
+ }
+ Err(_) => {
+ error!("Cannot lock \"{:?}\" in write_list", player.account.player_hash);
+ }
+ }
+ }
+
+ pub fn put_msg_to_all(&self, msg: &GameMessage) {
+ match self.list_players() {
+ Ok(list) => {
+ for player in list {
+ self.put_msg_to(msg.clone(), &player);
+ }
+ }
+ Err(_) => {
+ error!("Cannot put GameMessage with no players.");
+ }
+ }
+ }
+
+ pub fn pop_a_msg(&self, player: &PlayerInfo) -> Option<ControlMessage> {
+ match self.read_list.lock() {
+ Ok(mut guard) => {
+ match guard.get_mut(&player.account.player_hash) {
+ None => { None }
+ Some(queue) => {
+ if ! queue.is_empty() {
+ queue.pop_front()
+ } else {
+ guard.remove(&player.account.player_hash);
+ None
+ }
+ }
+ }
+ }
+ Err(_) => {
+ error!("Cannot lock \"{:?}\" in read_list", player.account.player_hash);
+ None
+ }
+ }
+ }
+
+ pub fn pop_msg_or(&self, player: &PlayerInfo, or: ControlMessage) -> ControlMessage {
+ self.pop_a_msg(player).unwrap_or(or)
+ }
+
+ pub fn list_received(&self, player: &PlayerInfo) -> Vec<ControlMessage> {
+ match self.read_list.lock() {
+ Ok(guard) => {
+ match guard.get_key_value(player.account.player_hash.as_str()) {
+ None => { Vec::new() }
+ Some(result) => {
+ Self::convert_deque_to_vec(result.1)
+ }
+ }
+ }
+ Err(_) => { Vec::new() }
+ }
+ }
+ }
+
+ // 服务端玩家管理
+ impl PadServer {
+
+ pub fn is_player_online (&self, player: &PlayerInfo) -> bool {
+ let guard = self.online_players.lock().unwrap();
+ guard.contains_key(&player.account.player_hash)
+ }
+
+ pub fn set_player_online (&self, player: &PlayerInfo, online: bool) {
+ let online_current = self.is_player_online(player);
+ if online_current && !online {
+ let mut guard = self.online_players.lock().unwrap();
+ guard.remove(&player.account.player_hash);
+ info!("{} is OFFLINE!", player.account.id);
+ } else if !online_current && online {
+ let mut guard = self.online_players.lock().unwrap();
+ guard.insert(player.account.player_hash.clone(), player.clone());
+ info!("{} is ONLINE!", player.account.id);
+ }
+ }
+
+ pub fn is_player_banned (&self, player: &PlayerInfo) -> bool {
+ let guard = self.banned_players.lock().unwrap();
+ guard.contains_key(&player.account.player_hash)
+ }
+
+ pub fn kick_player(&self, player: &PlayerInfo) {
+ if self.is_player_online(player) {
+ self.put_msg_to(Leave(LeaveReason::YouAreKicked), player);
+ }
+ }
+
+ pub fn ban_player(&self, player: &PlayerInfo) {
+ self.set_player_banned(player, true);
+ if self.is_player_online(player) {
+ self.put_msg_to(Leave(LeaveReason::YouAreBanned), player);
+ }
+ }
+
+ pub fn pardon_player(&self, player: &PlayerInfo) {
+ self.set_player_banned(player, false);
+ }
+
+ fn set_player_banned (&self, player: &PlayerInfo, banned: bool) {
+ let banned_current = self.is_player_banned(player);
+ if banned_current && !banned {
+ let mut guard = self.banned_players.lock().unwrap();
+ guard.remove(&player.account.player_hash);
+ info!("Pardoned player {}", player.account.id);
+ } else if !banned_current && banned {
+ let mut guard = self.banned_players.lock().unwrap();
+ guard.insert(player.account.player_hash.clone(), player.clone());
+ info!("Banned player {}!", player.account.id);
+ }
+ }
+
+ pub fn list_players(&self) -> Result<Vec<PlayerInfo>, PoisonError<MutexGuard<HashMap<String, PlayerInfo>>>> {
+ match self.online_players.lock() {
+ Ok(guard) => {
+ Ok(guard.values().cloned().collect())
+ }
+ Err(err) => Err(err)
+ }
+ }
+
+ pub fn list_players_banned(&self) -> Result<Vec<PlayerInfo>, PoisonError<MutexGuard<HashMap<String, PlayerInfo>>>> {
+ match self.banned_players.lock() {
+ Ok(guard) => {
+ Ok(guard.values().cloned().collect())
+ }
+ Err(err) => Err(err)
+ }
+ }
+
+ fn convert_deque_to_vec (deque: &VecDeque<ControlMessage>) -> Vec<ControlMessage> {
+ let vec_deque_ref = deque;
+ let mut vec = Vec::new();
+ for item in vec_deque_ref {
+ vec.push(item.clone())
+ }
+ vec
+ }
+ }
+
+ // 服务端状态控制
+ #[allow(dead_code)]
+ impl PadServer {
+
+ pub fn stop_listening(&self) {
+ self.put_msg_to_all(&Leave(ServerClosed));
+ self.stop.store(true, SeqCst);
+ }
+
+ pub fn start_listening(self: Arc<Self>) {
+
+ // 构建 Logger
+ if ! self.quiet {
+ logger_build();
+ }
+
+ // 运行时
+ let runtime = Self::get_runtime();
+
+ info!("Starting \"NoGamepads Server\".");
+
+ // 入口
+ let console = self.enable_console;
+ let entry = self.get_entry(console);
+
+ // 阻塞运行
+ runtime.block_on(entry);
+ }
+
+ fn get_runtime() -> Runtime {
+ tokio::runtime::Builder::new_multi_thread()
+ .thread_name("nogpad-server")
+ .thread_stack_size(32 * 1024 * 1024)
+ .enable_time()
+ .enable_io()
+ .build()
+ .unwrap()
+ }
+
+ fn get_entry(self: Arc<Self>, debug: bool) -> impl Future<Output = ()> + Send + 'static {
+ async move {
+ let main_thread = spawn({
+ let client = Arc::clone(&self);
+ async move {
+ Self::main_request_thread(client).await
+ }
+ });
+
+ let background_thread = spawn({
+ let client = Arc::clone(&self);
+ async move {
+ Self::background_thread(client).await
+ }
+ });
+
+ if debug {
+ let debug_cli = spawn({
+ let client = Arc::clone(&self);
+ async move {
+ Self::process_debug_cli(client).await
+ }
+ });
+
+ let _ = tokio::join!(debug_cli, main_thread, background_thread);
+ } else {
+ let _ = tokio::join!(main_thread, background_thread);
+ }
+ }
+ }
+
+ fn lock_game(&self) {
+ self.game_locked.store(true, SeqCst);
+ }
+
+ fn unlock_game(&self) {
+ self.game_locked.store(false, SeqCst);
+ }
+
+ fn is_game_locked(&self) -> bool {
+ self.game_locked.load(SeqCst)
+ }
+
+ async fn main_request_thread(self: Arc<Self>) {
+
+ let addr_str = format!("{}:{}", self.address.to_string(), self.port);
+ info!("Server listening at {}", addr_str);
+
+ // Tcp 监听器
+ let listener : TcpListener;
+ match TcpListener::bind(&addr_str).await {
+ Ok(result) => {
+ info!("Listener created.");
+ listener = result;
+ }
+ Err(_) => {
+ error!("Server listening at {} failed!", addr_str);
+ exit(1);
+ }
+ }
+
+ // 请求信息循环
+ loop {
+ match listener.accept().await {
+ Ok((stream, _)) => {
+ spawn(Self::process_request(Arc::clone(&self), stream));
+ }
+ Err(error) => {
+ error!("Error: {}", error);
+ }
+ }
+ }
+ }
+
+ async fn process_request(self: Arc<Self>, mut stream: TcpStream) {
+ let mut buffer = [0; 1024];
+ let connection_msg : ConnectionMessage = read_msg(&mut buffer, &mut stream).await;
+ match connection_msg {
+
+ // 客户端请求加入游戏,并建立长连接
+ ConnectionMessage::Connection(info) => {
+
+ // 加入游戏资格检测
+ info!("Account {} trying to connect.", info.account.player_hash);
+
+ // 0. 当前游戏是否已经锁定?
+ if self.is_game_locked() {
+ // 当前游戏已经锁定,禁止加入玩家,发送失败信息,并断开连接
+ send_msg(&mut stream, ConnectionCallbackMessage::Deny(GameLocked)).await;
+ return;
+ }
+
+ // 1. 是否存在重复玩家?
+ let online = self.is_player_online(&info);
+ if online {
+ // 当前玩家已在线,发送失败信息,并断开连接
+ send_msg(&mut stream, ConnectionCallbackMessage::Deny(ContainSamePlayer)).await;
+ return;
+ }
+
+ // 2. 该玩家是否被封禁?
+ let banned = self.is_player_banned(&info);
+ if banned {
+ // 当前玩家已被封禁,发送失败信息,并断开连接
+ send_msg(&mut stream, ConnectionCallbackMessage::Deny(PlayerBanned)).await;
+ return;
+ }
+
+ // OK!若执行到此处,说明该玩家具有加入资格,Welcome!
+
+ send_msg(&mut stream, ConnectionCallbackMessage::Ok).await;
+ let callback : ConnectionMessage = read_msg(&mut buffer, &mut stream).await;
+
+ match callback {
+ // 玩家已就绪,发送 Welcome 信息以邀请该玩家加入游戏
+ ConnectionMessage::Ready => {
+ info!("Player \"{}\" is ready!", info.account.id);
+
+ // 发送 Welcome
+ send_msg(&mut stream, ConnectionCallbackMessage::Welcome).await;
+
+ // 注册该玩家到在线列表
+ self.set_player_online(&info, true);
+
+ // 启动控制循环
+ spawn(Self::long_connection(Arc::clone(&self), stream, info));
+ },
+ _ => {
+ send_msg(&mut stream, ConnectionCallbackMessage::Deny(WhatTheHell)).await; // WTH ?
+ }
+ }
+ }
+
+ // 客户端请求获得游戏信息
+ ConnectionMessage::RequestProfile => {
+ // 发送游戏信息到客户端
+ send_msg(&mut stream, ConnectionCallbackMessage::Profile(self.game_profile.clone())).await;
+ }
+
+ // 客户端发来了错误信息
+ ConnectionMessage::Err => {
+ match stream.peer_addr() {
+ Ok(addr) => {
+ warn!("Received an error message from {}.", addr.to_string());
+ }
+ Err(_) => {
+ warn!("Received an error message from unknown pad_io.");
+ }
+ }
+ }
+
+ // 客户端发来了不相干的信息
+ _ => {
+ match stream.peer_addr() {
+ Ok(addr) => {
+ warn!("Received unknown connection message from {}.", addr.to_string());
+ }
+ Err(_) => {
+ warn!("Received unknown connection message from unknown pad_io.");
+ }
+ }
+ }
+ }
+ }
+
+ async fn long_connection(self: Arc<Self>, stream: TcpStream, player_info: PlayerInfo) {
+ let player_info_arc = Arc::new(player_info);
+ let (reader, writer) = io::split(stream);
+ spawn(Self::read_task(Arc::clone(&self), reader, Arc::clone(&player_info_arc)));
+ spawn(Self::write_task(Arc::clone(&self), writer, Arc::clone(&player_info_arc)));
+ }
+
+ async fn read_task(self: Arc<Self>,
+ mut reader: ReadHalf<TcpStream>,
+ player_info: Arc<PlayerInfo>) {
+ let player_hash = player_info.account.player_hash.clone();
+ let mut buf = [0u8; 1024];
+ loop {
+ match reader.read(&mut buf).await {
+ Ok(0) => break,
+ Ok(n) => {
+ let msg = ControlMessage::de(buf[0..n].to_vec());
+ {
+ match self.read_list.lock() {
+ Ok(mut guard) => {
+ info!("{:?} from {}({})", &msg, player_info.customize.nickname, player_info.account.id);
+ guard
+ .entry(player_hash.clone())
+ .or_insert_with(VecDeque::new)
+ .push_back(msg);
+ }
+ Err(_) => {
+ }
+ }
+ }
+ }
+ Err(e) => {
+ warn!("Error reading from stream: {}", e);
+ self.set_player_online(&player_info, false);
+
+ // 放入一条错误信息到队列,使 write_task 及时发现该玩家离开
+ self.put_msg_to(GameMessage::Err, &player_info);
+
+ break;
+ }
+ }
+ }
+ }
+
+ async fn write_task(self: Arc<Self>,
+ mut writer: WriteHalf<TcpStream>,
+ player_info: Arc<PlayerInfo>) {
+ let player_hash = player_info.account.player_hash.clone();
+ let mut exit = false;
+ loop {
+ let msg : Option<GameMessage>;
+ match self.write_list.lock() {
+ Ok(mut hash_map) => {
+ if ! hash_map.is_empty() {
+ match hash_map.get_mut(&player_hash) {
+ None => {
+ msg = None;
+ }
+ Some(queue) => {
+ if ! queue.is_empty() {
+ msg = queue.pop_front();
+ } else {
+ msg = None;
+ hash_map.remove(&player_hash);
+ }
+ }
+ }
+ }
+ else { msg = None; }
+ }
+ Err(_) => {
+ msg = None;
+ }
+ }
+ if msg.is_some() {
+ let msg = msg.unwrap();
+ match &writer.write_all(NgpdMessageEncoder::en(&msg).as_slice()).await {
+ Ok(_) => {
+ info!("Sent {:?} to {}", msg, &player_info.account.id);
+ }
+ Err(error) => {
+ warn!("Sent {:?} to {} failed!", msg, &player_info.account.id);
+ warn!("{:?}", error);
+
+ exit = true;
+ }
+ }
+ }
+ if exit {
+ warn!("Long connection between \"{}\" closed.", &player_info.account.id);
+ break
+ }
+ }
+ }
+
+ async fn background_thread(self: Arc<Self>) {
+ loop {
+ // 退出程序的监听
+ if self.stop.load(SeqCst) {
+ tokio::time::sleep(Duration::from_secs(1)).await;
+ info!("Main thread exited.");
+ exit(0);
+ }
+ }
+ }
+
+ async fn process_debug_cli(self: Arc<Self>) {
+ loop {
+ if self.stop.load(SeqCst) {
+ info!("Debug console exited");
+ return;
+ }
+ tokio::time::sleep(Duration::from_secs_f64(0.2)).await;
+ let option: Option<Psc> = read_cli(
+ format!("SERVER {}> ", self.address.to_string()).as_str(),
+ "psc".to_string(),
+ Psc::command()
+ ).await;
+ match option {
+ None => {}
+ Some(cmd) => {
+ process_debug_cmd(cmd, Arc::clone(&self));
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/core/src/pad_io/server_debug_cli.rs b/core/src/pad_io/server_debug_cli.rs
new file mode 100644
index 0000000..76e331f
--- /dev/null
+++ b/core/src/pad_io/server_debug_cli.rs
@@ -0,0 +1,204 @@
+use std::collections::HashMap;
+use crate::pad_data::pad_messages::nogamepads_messages::{ControlMessage, GameMessage};
+use crate::pad_io::server::nogamepads_server::PadServer;
+use clap::{Args, Parser, Subcommand};
+use std::ops::{Index};
+use std::sync::{Arc, MutexGuard, PoisonError};
+use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo;
+
+/// NoGamePads Server - Cli
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+pub struct Psc {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+
+ // 清屏
+ #[command(about = "Clean the screen")]
+ Clear,
+
+ // 关闭服务器
+ #[command(about = "Close the server")]
+ Stop,
+
+ // 展示所有玩家
+ #[command(about = "List all online players")]
+ List,
+
+ // 展示所有封禁的玩家
+ #[command(about = "List all banned players")]
+ Banned,
+
+ // 检查收到的消息
+ #[command(about = "Check received")]
+ Received(ReceivedArgs),
+
+ // 取出一条消息
+ #[command(about = "Pop a message")]
+ Pop(PlayerArgs),
+
+ // 踢出玩家
+ #[command(about = "Kick a player")]
+ Kick(PlayerArgs),
+
+ // 封禁玩家
+ #[command(about = "Ban a player")]
+ Ban(PlayerArgs),
+
+ // 解封(赦免)玩家
+ #[command(about = "Pardon a player")]
+ Pardon(PlayerArgs),
+
+ // 激活事件触发器
+ #[command(about = "Send SkinEventTrigger")]
+ Event(EventArgs)
+}
+
+// 检查收到的消息
+#[derive(Args, Debug)]
+struct ReceivedArgs {
+
+ #[arg(default_value = "0")]
+ player: usize,
+
+ #[arg(long)]
+ list: bool,
+}
+
+/// 激活事件触发器 参数
+#[derive(Args, Debug)]
+struct EventArgs {
+
+ // 玩家序号
+ #[arg(value_name = "PLAYER_INDEX")]
+ index: usize,
+
+ // 事件编号
+ #[arg(value_name = "CONTENT")]
+ message: u8,
+}
+
+#[derive(Args, Debug)]
+struct PlayerArgs {
+
+ // 玩家序号
+ #[arg(value_name = "PLAYER_INDEX")]
+ index: usize
+}
+
+pub fn process_debug_cmd (cmd: Psc, server: Arc<PadServer>) {
+ match cmd.command {
+
+ Commands::Clear => {
+ clearscreen::clear().expect("Failed to clear screen");
+ }
+
+ Commands::Stop => {
+ server.stop_listening();
+ }
+
+ Commands::List => {
+ print_player_list(server.list_players());
+ }
+
+ Commands::Banned => {
+ print_player_list(server.list_players_banned());
+ }
+
+ Commands::Received(args) => {
+ let players = server.list_players().unwrap_or(Vec::new());
+ let player = players.index(args.player.clamp(0, players.iter().count() -1));
+ if args.list {
+ for msg in server.list_received(player) {
+ println!("{:?}", msg);
+ }
+ } else {
+ println!("Total {} messsage(s)!", server.list_received(player).iter().count());
+ }
+ }
+
+ Commands::Pop(args) => {
+ match get_player_by_index(&server, args.index) {
+ None => {
+ eprintln!("Pup message failed : Player index \"{}\" not found!", args.index);
+ }
+ Some(player) => {
+ let message = server.pop_msg_or(&player, ControlMessage::Err);
+ println!("{:?}", message);
+ }
+ }
+ }
+
+ Commands::Kick(args) => {
+ let player = get_player_by_index(&server, args.index);
+ if player.is_some() {
+ let player = player.unwrap();
+ server.kick_player(&player);
+ }
+ }
+
+ Commands::Ban(args) => {
+ let player = get_player_by_index(&server, args.index);
+ if player.is_some() {
+ let player = player.unwrap();
+ server.ban_player(&player);
+ }
+ }
+
+ Commands::Pardon(args) => {
+ let player = get_player_by_ban_index(&server, args.index);
+ if player.is_some() {
+ let player = player.unwrap();
+ server.pardon_player(&player);
+ }
+ }
+
+ Commands::Event(args) => {
+ put_to_list(server, args.index, GameMessage::SkinEventTrigger(args.message));
+ }
+ }
+}
+
+fn put_to_list(server: Arc<PadServer>, player_index: usize, message: GameMessage) {
+ match get_player_by_index(&server, player_index) {
+ None => {
+ eprintln!("Put message failed : Player index \"{}\" not found!", player_index);
+ }
+ Some(player) => {
+ server.put_msg_to(message, &player);
+ }
+ }
+}
+
+fn get_player_by_index(server: &Arc<PadServer>, index: usize) -> Option<PlayerInfo> {
+ let list = server.list_players().unwrap_or(Vec::new());
+ let max = list.iter().count();
+ let index = if max > 0 { index.clamp(0, max - 1) } else { 0 };
+
+ let result = list.get(index).cloned();
+ result
+}
+
+fn get_player_by_ban_index(server: &Arc<PadServer>, index: usize) -> Option<PlayerInfo> {
+ let list = server.list_players_banned().unwrap_or(Vec::new());
+ let max = list.iter().count();
+ let index = if max > 0 { index.clamp(0, max - 1) } else { 0 };
+
+ let result = list.get(index).cloned();
+ result
+}
+
+fn print_player_list(list: Result<Vec<PlayerInfo>, PoisonError<MutexGuard<HashMap<String, PlayerInfo>>>>) {
+ let list = list.unwrap_or(Vec::new());
+ let mut i = 0;
+ for player in list {
+ let n = player.customize.nickname;
+ print!("({}){} ", i, n);
+ i += 1;
+ println!();
+ }
+} \ No newline at end of file