From f8336ed832fa8e1ddd2f1783bdfc23d3279d5e6a Mon Sep 17 00:00:00 2001
From: "1992414357@qq.com" <1992414357@qq.com>
Date: Tue, 27 May 2025 07:21:37 +0800
Subject: First commit
---
.cargo/config.toml | 2 +
.cargo/scripts/generate_documents.bat | 59 +
.../generate_console_release_file.bat | 63 +
.cargo/scripts/release_files_win | 6 +
.cargo/scripts/release_project.bat | 37 +
.gitignore | 3 +
.idea/.gitignore | 8 +
.idea/NoGamepads.iml | 20 +
.idea/modules.xml | 8 +
.idea/vcs.xml | 6 +
Cargo.lock | 1505 ++++++++++++++++++++
Cargo.toml | 35 +
LICENSE-LGPL-2 | 464 ++++++
LICENSE-THIRD-PARTY | 273 ++++
README.md | 0
console/Cargo.lock | 1482 +++++++++++++++++++
console/Cargo.toml | 18 +
console/src/bin/nogpadc.rs | 436 ++++++
console/src/bin/nogpads.rs | 257 ++++
core/Cargo.lock | 1151 +++++++++++++++
core/Cargo.toml | 20 +
core/examples/start_client_console.rs | 24 +
core/examples/start_server_console.rs | 30 +
core/examples/tetris_multicmd_game.rs | 3 +
core/src/lib.rs | 10 +
core/src/pad_data/game_layout.rs | 9 +
core/src/pad_data/game_profile.rs | 99 ++
core/src/pad_data/mod.rs | 4 +
core/src/pad_data/pad_messages.rs | 176 +++
core/src/pad_data/pad_player_info.rs | 126 ++
core/src/pad_io/client.rs | 448 ++++++
core/src/pad_io/client_debug_cli.rs | 91 ++
core/src/pad_io/mod.rs | 5 +
core/src/pad_io/server.rs | 646 +++++++++
core/src/pad_io/server_debug_cli.rs | 204 +++
core_c/Cargo.toml | 10 +
core_c/src/lib.rs | 16 +
core_c/src/test.rs | 6 +
...6 Rust \345\267\245\345\205\267\351\223\276.md" | 17 +
release/latest/LICENSE | 504 +++++++
release/latest/LICENSE-THIRD-PARTY | 29 +
release/latest/nogpadc.exe | Bin 0 -> 2226688 bytes
release/latest/nogpads.exe | Bin 0 -> 2176512 bytes
src/debug_console.rs | 64 +
src/lib.rs | 2 +
src/logger.rs | 22 +
46 files changed, 8398 insertions(+)
create mode 100644 .cargo/config.toml
create mode 100644 .cargo/scripts/generate_documents.bat
create mode 100644 .cargo/scripts/release_console/generate_console_release_file.bat
create mode 100644 .cargo/scripts/release_files_win
create mode 100644 .cargo/scripts/release_project.bat
create mode 100644 .gitignore
create mode 100644 .idea/.gitignore
create mode 100644 .idea/NoGamepads.iml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/vcs.xml
create mode 100644 Cargo.lock
create mode 100644 Cargo.toml
create mode 100644 LICENSE-LGPL-2
create mode 100644 LICENSE-THIRD-PARTY
create mode 100644 README.md
create mode 100644 console/Cargo.lock
create mode 100644 console/Cargo.toml
create mode 100644 console/src/bin/nogpadc.rs
create mode 100644 console/src/bin/nogpads.rs
create mode 100644 core/Cargo.lock
create mode 100644 core/Cargo.toml
create mode 100644 core/examples/start_client_console.rs
create mode 100644 core/examples/start_server_console.rs
create mode 100644 core/examples/tetris_multicmd_game.rs
create mode 100644 core/src/lib.rs
create mode 100644 core/src/pad_data/game_layout.rs
create mode 100644 core/src/pad_data/game_profile.rs
create mode 100644 core/src/pad_data/mod.rs
create mode 100644 core/src/pad_data/pad_messages.rs
create mode 100644 core/src/pad_data/pad_player_info.rs
create mode 100644 core/src/pad_io/client.rs
create mode 100644 core/src/pad_io/client_debug_cli.rs
create mode 100644 core/src/pad_io/mod.rs
create mode 100644 core/src/pad_io/server.rs
create mode 100644 core/src/pad_io/server_debug_cli.rs
create mode 100644 core_c/Cargo.toml
create mode 100644 core_c/src/lib.rs
create mode 100644 core_c/src/test.rs
create mode 100644 "documents/About Toolchain - \345\205\263\344\272\216 Rust \345\267\245\345\205\267\351\223\276.md"
create mode 100644 release/latest/LICENSE
create mode 100644 release/latest/LICENSE-THIRD-PARTY
create mode 100644 release/latest/nogpadc.exe
create mode 100644 release/latest/nogpads.exe
create mode 100644 src/debug_console.rs
create mode 100644 src/lib.rs
create mode 100644 src/logger.rs
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..fde969b
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target-dir = "./.cargo/shared/target"
diff --git a/.cargo/scripts/generate_documents.bat b/.cargo/scripts/generate_documents.bat
new file mode 100644
index 0000000..edde8ea
--- /dev/null
+++ b/.cargo/scripts/generate_documents.bat
@@ -0,0 +1,59 @@
+@echo off
+setlocal enabledelayedexpansion
+
+:: ================= 路径 =================
+set "CARGO_TARGET_DIR=.\.cargo\shared\target"
+set "DOC_SOURCE_DIR=%CARGO_TARGET_DIR%\doc"
+set "DOC_DEST_DIR=.\documents\cargo_doc"
+
+:: ============== Crate 列表 ==============
+set "CRATE_PATHS=core console"
+
+:: =============== 文档路径 ===============
+set "DOC_LINK_PATHS=nogamepads_core nogpadc nogpads"
+
+:: =============== 生成文档 ===============
+if not exist "%DOC_DEST_DIR%" mkdir "%DOC_DEST_DIR%"
+:: cargo clean --target-dir "%CARGO_TARGET_DIR%"
+for %%c in (%CRATE_PATHS%) do (
+ echo [INFO] Generating documentation for crate: %%c
+ cargo doc --no-deps --manifest-path "./%%c/Cargo.toml"
+ if !errorlevel! neq 0 (
+ echo [ERROR] Failed to generate docs for crate: %%c
+ exit /b !errorlevel!
+ )
+)
+
+:: ============== 清理旧文件 ==============
+if exist "%DOC_DEST_DIR%" (
+ echo [INFO] Removing old documents...
+ rmdir /s /q "%DOC_DEST_DIR%"
+)
+
+:: ============== 复制新文件 ==============
+echo [INFO] Copying new documents to %DOC_DEST_DIR%...
+xcopy /E /I /Q /Y "%DOC_SOURCE_DIR%" "%DOC_DEST_DIR%"
+if !errorlevel! neq 0 (
+ echo [ERROR] Failed to copy documents
+ exit /b !errorlevel!
+)
+
+:: ================= 清理 =================
+:: Uncomment to clean generated docs from target directory
+:: rmdir /s /q "%DOC_SOURCE_DIR%"
+
+:: =============== 生成链接 ===============
+echo.
+echo [DOCUMENTATION LINKS]
+
+:: 获取当前目录绝对路径(转换为URL格式)
+set "CURRENT_DIR=%CD:\=/%"
+
+:: 遍历自定义路径列表
+for %%p in (%DOC_LINK_PATHS%) do (
+ set "HTML_PATH=file:///%CURRENT_DIR%/documents/cargo_doc/%%p/index.html"
+ echo !HTML_PATH!
+)
+
+echo [SUCCESS] Documentation generated at: %DOC_DEST_DIR%
+endlocal
\ No newline at end of file
diff --git a/.cargo/scripts/release_console/generate_console_release_file.bat b/.cargo/scripts/release_console/generate_console_release_file.bat
new file mode 100644
index 0000000..601391b
--- /dev/null
+++ b/.cargo/scripts/release_console/generate_console_release_file.bat
@@ -0,0 +1,63 @@
+@echo off
+setlocal enabledelayedexpansion
+
+:: ================= 路径 =================
+:: 源文件目录
+set "CONSOLE_DIR=.cargo\shared\target\release\deps"
+:: 待复制文件列表
+set "FILE_LIST=.cargo\scripts\release_files_win"
+:: 控制台文件目标目录(基础路径)
+set "BASE_DEST_DIR=release\dev\"
+:: 证书文件
+set "LICENSE_FILE=LICENSE-LGPL-2"
+set "LICENSE_TP_FILE=LICENSE-THIRD-PARTY"
+
+:: ============= 创建基础目录 =============
+if not exist "%BASE_DEST_DIR%" mkdir "%BASE_DEST_DIR%"
+
+:: =========== 清空目标目录内容 ===========
+if exist "%BASE_DEST_DIR%" (
+ echo [INFO] Cleaning target directory "%BASE_DEST_DIR%"
+ :: 删除所有文件(包括隐藏/只读文件)
+ del /f /s /q "%BASE_DEST_DIR%\*.*" >nul 2>&1
+ :: 递归删除所有子目录
+ for /d %%D in ("%BASE_DEST_DIR%\*") do rd /s /q "%%D" >nul 2>&1
+)
+
+:: ======== 读取文件列表并复制文件 ========
+for /f "delims=" %%F in ('findstr /v /r "^// ^$" "%FILE_LIST%"') do (
+ set "line=%%F"
+ :: 分割行内容为 filename 和相对路径
+ for /f "tokens=1,* delims=:" %%A in ("!line!") do (
+ set "filename=%%A"
+ set "rel_path=%%B"
+ )
+ :: 去除两端的空格
+ for /f "tokens=* delims= " %%L in ("!filename!") do set "filename=%%L"
+ for /f "tokens=* delims= " %%P in ("!rel_path!") do set "rel_path=%%P"
+ :: 构造完整目标路径
+ set "dest_path=%BASE_DEST_DIR%!rel_path:.\=!"
+ :: 提取目标目录并创建
+ for %%I in ("!dest_path!") do set "dest_dir=%%~dpI"
+ if not exist "!dest_dir!" mkdir "!dest_dir!"
+ :: 复制文件
+ set "source_file=%CONSOLE_DIR%\!filename!"
+ if exist "!source_file!" (
+ echo [INFO] Copying "!filename!" to "!dest_path!"
+ copy /Y "!source_file!" "!dest_path!" >nul
+ if errorlevel 1 (
+ echo [ERROR] Failed to copy "!filename!"
+ ) else (
+ echo [SUCCESS] Copied "!filename!"
+ )
+ ) else (
+ echo [ERROR] Source file "!source_file!" not found
+ )
+)
+
+:: ============= 复制证书文件 =============
+copy /Y "%LICENSE_FILE%" "%BASE_DEST_DIR%\" >nul
+copy /Y "%LICENSE_TP_FILE%" "%BASE_DEST_DIR%\" >nul
+
+endlocal
+exit /b 0
\ No newline at end of file
diff --git a/.cargo/scripts/release_files_win b/.cargo/scripts/release_files_win
new file mode 100644
index 0000000..bfc7841
--- /dev/null
+++ b/.cargo/scripts/release_files_win
@@ -0,0 +1,6 @@
+// 控制台部分
+nogpadc.exe : .\bin\nogpadc.exe
+nogpads.exe : .\bin\nogpads.exe
+
+// Dll部分
+nogamepads_c.dll : .\bridge\libs\nogamepads_c.dll
\ No newline at end of file
diff --git a/.cargo/scripts/release_project.bat b/.cargo/scripts/release_project.bat
new file mode 100644
index 0000000..3979e9d
--- /dev/null
+++ b/.cargo/scripts/release_project.bat
@@ -0,0 +1,37 @@
+@echo off
+setlocal enabledelayedexpansion
+
+:: ================= 路径 =================
+:: 控制台程序的 CRATE
+set "CONSOLE_CRATE_DIR=console"
+
+:: C动态链接库的 CRATE
+set "DLL_CRATE_DIR=core_c"
+
+:: 打包地址
+set "RELEASE_DIR=release\dev"
+
+:: ================= 构建 =================
+:: 控制台程序
+echo [INFO] Building console ...
+pushd ".\%CONSOLE_CRATE_DIR%\"
+cargo build --release
+popd
+echo
+:: C动态链接库
+echo [INFO] Building dll ...
+pushd ".\%DLL_CRATE_DIR%\"
+cargo build --release
+popd
+
+echo Done.
+echo
+
+:: =============== 发布文件 ===============
+echo
+echo [INFO] Generating release files
+call .\.cargo\scripts\release_console\generate_console_release_file.bat
+
+explorer.exe ".\%RELEASE_DIR%\"
+
+echo Done.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f8dd64d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/.cargo/shared/
+/release/dev/
+/documents/cargo_doc/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/NoGamepads.iml b/.idea/NoGamepads.iml
new file mode 100644
index 0000000..188f62f
--- /dev/null
+++ b/.idea/NoGamepads.iml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..87df6da
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..faaeb0f
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1505 @@
+# 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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+
+[[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.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7"
+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 2.0.12",
+ "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 = "csv"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
+dependencies = [
+ "memchr",
+]
+
+[[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 = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[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 = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[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 = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
+
+[[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 = "indexmap"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[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 = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.172"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
+
+[[package]]
+name = "libredox"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[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 0.11.0+wasi-snapshot-preview1",
+ "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"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "clap",
+ "env_logger",
+ "log",
+ "shell-words",
+ "tokio",
+]
+
+[[package]]
+name = "nogamepads-console"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "nogamepads-core",
+ "prettytable-rs",
+ "rand 0.9.1",
+ "rpassword",
+ "serde",
+ "serde_yaml",
+]
+
+[[package]]
+name = "nogamepads-core"
+version = "0.1.0"
+dependencies = [
+ "bincode",
+ "clap",
+ "clearscreen",
+ "hex",
+ "log",
+ "nogamepads",
+ "serde",
+ "sha1",
+ "tokio",
+]
+
+[[package]]
+name = "nogamepads_c"
+version = "0.1.0"
+dependencies = [
+ "nogamepads-core",
+]
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[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 0.8.5",
+]
+
+[[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 = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prettytable-rs"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
+dependencies = [
+ "csv",
+ "encode_unicode",
+ "is-terminal",
+ "lazy_static",
+ "term",
+ "unicode-width",
+]
+
+[[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 = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
+dependencies = [
+ "rand_chacha",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
+dependencies = [
+ "getrandom 0.2.16",
+ "libredox",
+ "thiserror 1.0.69",
+]
+
+[[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 = "rpassword"
+version = "7.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39"
+dependencies = [
+ "libc",
+ "rtoolbox",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rtoolbox"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[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.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[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 = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
+[[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 = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "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 = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
+[[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 = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[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"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-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"
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..5507615
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "nogamepads"
+authors = [ "CatilGrass" ]
+description = "Connect everyone's phones to the game! A mobile device control solution for local multiplayer gaming, featuring an in-game interface, controller client, and support for universal devices."
+license = "LGPL-2"
+
+homepage = "https://github.com/CatilGrass/NoGamepads"
+repository = "https://github.com/CatilGrass/NoGamepads"
+readme = "README.md"
+
+version = "0.1.0"
+edition = "2024"
+
+[workspace]
+members = [
+ "core",
+ "console",
+ "core_c"
+]
+
+[dependencies]
+env_logger = "0.10.2"
+shell-words = "1.1.0"
+clap = { version = "4.5.38", features = ["derive"] }
+tokio = { version = "1.45.0", features = ["full"] }
+chrono = "0.4"
+log = "0.4.27"
+
+[profile.release]
+strip = true
+debug = false
+
+[profile.dev]
+strip = false
+debug = true
diff --git a/LICENSE-LGPL-2 b/LICENSE-LGPL-2
new file mode 100644
index 0000000..a00e7d8
--- /dev/null
+++ b/LICENSE-LGPL-2
@@ -0,0 +1,464 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ NoGamePads - Connect everyone's phones to the game!
+ Copyright (C) 2025-2026 Zewen Pan
+
+ Contact information :
+ - Email: catil_grass@qq.com
\ No newline at end of file
diff --git a/LICENSE-THIRD-PARTY b/LICENSE-THIRD-PARTY
new file mode 100644
index 0000000..12bfdd9
--- /dev/null
+++ b/LICENSE-THIRD-PARTY
@@ -0,0 +1,273 @@
+THIRD-PARTY LICENSES
+=====================
+
+This project uses third-party components with the following licenses:
+
+[MIT License Dependencies]
+-----------------------------------------------------------------------
+* bincode
+* tokio
+* chrono (original license: Apache-2.0 OR MIT)
+* clap (original license: Apache-2.0 OR MIT)
+* clearscreen (original license: Apache-2.0 OR MIT)
+* env_logger (original license: Apache-2.0 OR MIT)
+* hex (original license: Apache-2.0 OR MIT)
+* log (original license: Apache-2.0 OR MIT)
+* rand (original license: Apache-2.0 OR MIT)
+* serde (original license: Apache-2.0 OR MIT)
+* serde_yaml (original license: Apache-2.0 OR MIT)
+* sha1 (original license: Apache-2.0 OR MIT)
+* shell-words (original license: Apache-2.0 OR MIT)
+
+MIT License
+
+Copyright (c) 2009-2014 Mozilla Foundation
+Copyright (c) 2013-2014 The Rust Project Developers.
+
+* tokio - Copyright (c) Tokio Contributors.
+* chrono - Copyright (c) 2014, Kang Seonghoon.
+* clap - Copyright (c) The clap Project Developer(s).
+* clearscreen - Copyright (c) The clearscreen Project Developer(s).
+* env_logger - Copyright (c) The env_logger Project Developer(s).
+* hex - Copyright (c) 2015-2020 The rust-hex Developers.
+* log - Copyright (c) The log Project Developer(s).
+* rand - Copyright 2018 Developers of the Rand project.
+* serde - Copyright (c) The serde Project Developer(s).
+* serde_yaml - Copyright (c) The serde_yaml Project Developer(s).
+
+* sha1 -
+ Copyright (c) 2006-2009 Graydon Hoare
+ Copyright (c) 2016 Artyom Pavlov
+
+* shell-words - Copyright (c) 2016 Tomasz Miąsko
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the “Software”),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+[Apache-2.0 License Dependencies]
+-----------------------------------------------------------------------
+* rpassword
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+
+[BSD-3-Clause License Dependencies]
+-----------------------------------------------------------------------
+* prettytable-rs
+
+Copyright (c) 2022, Pierre-Henri Symoneaux
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of prettytable-rs nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/console/Cargo.lock b/console/Cargo.lock
new file mode 100644
index 0000000..17c623e
--- /dev/null
+++ b/console/Cargo.lock
@@ -0,0 +1,1482 @@
+# 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.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+dependencies = [
+ "anstyle",
+ "once_cell",
+ "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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+
+[[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 2.0.12",
+ "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 = "csv"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
+dependencies = [
+ "memchr",
+]
+
+[[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 = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[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 = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[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 = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
+
+[[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 = "indexmap"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[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 = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.172"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
+
+[[package]]
+name = "libredox"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[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 0.11.0+wasi-snapshot-preview1",
+ "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-console"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "nogamepads-core",
+ "prettytable-rs",
+ "rand 0.9.1",
+ "rpassword",
+ "serde",
+ "serde_yaml",
+]
+
+[[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 = "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 0.8.5",
+]
+
+[[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 = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prettytable-rs"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
+dependencies = [
+ "csv",
+ "encode_unicode",
+ "is-terminal",
+ "lazy_static",
+ "term",
+ "unicode-width",
+]
+
+[[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 = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
+dependencies = [
+ "rand_chacha",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
+dependencies = [
+ "getrandom 0.2.16",
+ "libredox",
+ "thiserror 1.0.69",
+]
+
+[[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 = "rpassword"
+version = "7.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39"
+dependencies = [
+ "libc",
+ "rtoolbox",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rtoolbox"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[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 = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[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 = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
+[[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 = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "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 = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
+[[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 = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[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"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-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"
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/console/Cargo.toml b/console/Cargo.toml
new file mode 100644
index 0000000..03ec382
--- /dev/null
+++ b/console/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "nogamepads-console"
+description = "This crate provides a set of tools for simulating and deploying the client and server of the NoGamepads library."
+
+homepage = "https://github.com/CatilGrass/NoGamepads"
+repository = "https://github.com/CatilGrass/NoGamepads"
+
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+nogamepads-core = { path = "../core" }
+clap = { version = "4.5.38", features = ["derive"] }
+rand = { version = "0.9.1", features = [] }
+rpassword = "7.4.0"
+serde = { version = "1.0.219", features = ["derive"] }
+serde_yaml = "0.9.34"
+prettytable-rs = "0.10.0"
\ No newline at end of file
diff --git a/console/src/bin/nogpadc.rs b/console/src/bin/nogpadc.rs
new file mode 100644
index 0000000..a4b6245
--- /dev/null
+++ b/console/src/bin/nogpadc.rs
@@ -0,0 +1,436 @@
+use crate::AccountCommands::{Add, Customize, List, Remove};
+use crate::Commands::{Account, Connect};
+use clap::{Args, Parser, Subcommand};
+use nogamepads_core::pad_io::client::nogamepads_client::PadClient;
+use nogamepads_core::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo;
+use nogamepads_core::DEFAULT_PORT;
+use prettytable::{row, Table};
+use rand::Rng;
+use std::env::current_dir;
+use std::fs::{create_dir_all, remove_file, File};
+use std::io::{BufReader, Write};
+use std::net::{IpAddr, Ipv4Addr};
+use std::path::PathBuf;
+use std::process::exit;
+use std::str::FromStr;
+
+/// NoGamePads Console - Client Cli
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+struct NoGamepadClientCli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+/// 主要命令
+#[derive(Subcommand, Debug)]
+enum Commands {
+
+ // 账户设置
+ #[command(subcommand, about = "Operation of player account")]
+ Account(AccountCommands),
+
+ // 连接到服务器
+ #[command(about = "Connect a player to server")]
+ Connect(ConnectArgs)
+}
+
+/// 账户设置 命令
+#[derive(Subcommand, Debug)]
+enum AccountCommands {
+
+ // 列出所有账号
+ #[command(about = "List all local players")]
+ List(ListAccountArgs),
+
+ // 添加账号
+ #[command(about = "Add a local player")]
+ Add(AddAccountArgs),
+
+ // 移除账号
+ #[command(about = "Remove a local player")]
+ Remove(RemoveAccountArgs),
+
+ // 自定义账号显示信息
+ #[command(about = "Customize how the player appears")]
+ Customize(CustomizeAccountArgs)
+}
+
+/// 列出所有账号 参数
+#[derive(Args, Debug)]
+struct ListAccountArgs { }
+
+/// 添加账号 参数
+#[derive(Args, Debug)]
+struct AddAccountArgs {
+
+ // 注册的角色 ID
+ #[arg(value_name = "NAME")]
+ id: String
+}
+
+/// 移除账号 参数
+#[derive(Args, Debug)]
+struct RemoveAccountArgs {
+
+ // 删除的角色 ID
+ #[arg(value_name = "NAME")]
+ id: String
+}
+
+/// 自定义账号显示信息 参数
+#[derive(Args, Debug)]
+struct CustomizeAccountArgs {
+
+ // 定制的角色 ID
+ #[arg(value_name = "WHO")]
+ id: String,
+
+ // 昵称
+ #[arg(short, long, help = "Nickname")]
+ name: Option,
+
+ // 颜色
+ #[arg(short = 'H', long = "hsv", num_args = 3, value_names = ["H", "S", "V"], help = "Player color, h(0 - 360), s(0 - 1), v(0 - 1)")]
+ hsv: Option>
+}
+
+/// 连接到服务器
+#[derive(Args, Debug)]
+struct ConnectArgs {
+
+ // 连接的玩家
+ #[arg(value_name = "WHO")]
+ id: String,
+
+ // 目标服务器
+ #[arg(value_name = "WHERE", default_value = "127.0.0.1")]
+ target: String,
+
+ // 目标端口
+ #[arg(short, long, default_value = "5989")] // <---- DEFAULT_PORT
+ port: Option,
+
+ // 启用调试
+ #[arg(long)]
+ debug: bool,
+}
+
+/// 配置文件后缀名称
+const EXTENSION_NAME : &str = "yaml";
+
+fn main() {
+
+ // 初始化
+ let root = get_config_folder_path();
+ if ! root.exists() {
+ create_dir_all(root.as_path()).unwrap();
+ }
+
+ // 命令行
+ let cli = NoGamepadClientCli::parse();
+ match cli.command {
+
+ // 账户设置部分
+ Account(commands) => {
+ match commands {
+
+ // 添加账号
+ Add(args) => {
+ if is_account_exist(&args.id) {
+ println!("Account already exists!");
+ } else {
+ // 账号 ID 和 配置路径
+ let account_id = process_inputted_text(args.id);
+ let account_config_path = get_account_config_path(&account_id);
+
+ // 为新号准备的配置文件
+ let mut new_info = PlayerInfo::new();
+
+ // 密码输入和密码验证
+ let password = rpassword::prompt_password("Type password: ").unwrap();
+ let password_confirm = rpassword::prompt_password("Confirm password: ").unwrap();
+ if ! password_confirm.eq(&password) {
+ println!("Password does not match!");
+ exit(1);
+ }
+
+ // 随机生成 色调 值
+ let mut rng = rand::rng();
+ let random_hue: i32 = rng.random_range(0..=360);
+
+ // 建立账号信息并预填入信息
+ new_info.setup_account_info(account_id.as_str(), password.as_str());
+ new_info.set_nickname(account_id.as_str());
+ new_info.set_customize_color_hsv(random_hue, 0.8, 0.8);
+
+ // 新配置信息的文本
+ let new_info_yaml = serde_yaml::to_string(&new_info);
+
+ // 将信息写入文件系统
+ let mut buffer = File::create(account_config_path).unwrap();
+ buffer.write_all(new_info_yaml.unwrap().as_bytes()).unwrap();
+
+ println!("Account created.");
+ }
+ },
+
+ // 移除账号
+ Remove(args) => {
+ if ! is_account_exist(&args.id) {
+ println!("Account not found!");
+ } else {
+ // 账号 ID 和 配置路径
+ let account_id = process_inputted_text(args.id);
+ let account_config_path = get_account_config_path(&account_id);
+
+ // 删除文件
+ remove_file(account_config_path).unwrap();
+
+ println!("Account removed.");
+ }
+ },
+
+ // 列出所有账号
+ List(_args) => {
+ // 账号信息文件夹
+ let folder_path = get_config_folder_path();
+
+ // 输出表格
+ let mut info_table = Table::new();
+
+ // 表头
+ info_table.add_row(row!["ACCOUNT_ID", "NICKNAME", "COLOR", "HASH"]);
+
+ // 遍历目录下文件,将信息逐一填入表格
+ for item in folder_path.read_dir().unwrap() {
+ if let Ok(path) = item {
+ // 文件名
+ let file_name = path.file_name().into_string().unwrap();
+ let ext = format!(".{}", EXTENSION_NAME);
+
+ // 判断是否为指定后缀
+ if file_name.contains(ext.as_str()) {
+
+ // 去除后缀内容,截取为 ID
+ let id = file_name.replace(ext.as_str(), "");
+
+ // 读取并加载其中的玩家信息
+ let file = File::open(get_account_config_path(&id)).unwrap();
+ let reader = BufReader::new(file);
+ let info: PlayerInfo = serde_yaml::from_reader(reader).unwrap();
+
+ // 填入表格
+ info_table.add_row(row![
+ &id, // ACCOUNT_ID
+ info.customize.nickname, // NICKNAME
+ hsv_to_hex( // COLOR
+ info.customize.color_hue,
+ info.customize.color_saturation,
+ info.customize.color_value),
+ info.account.player_hash // HASH
+ ]);
+ }
+ }
+ }
+ println!("{}", info_table.to_string())
+ },
+
+ // 自定义账号显示信息
+ Customize(args) => {
+
+ // 加载配置文件
+ let file = File::open(get_account_config_path(&args.id)).unwrap();
+ let reader = BufReader::new(file);
+ let mut info: PlayerInfo = serde_yaml::from_reader(reader).unwrap();
+
+ // HSV 参数
+ if args.hsv.is_some() {
+ let hsv = args.hsv.unwrap();
+ let hue = hsv[0].round().clamp(0.0, 360.0);
+ let sat = hsv[1].clamp(0.0, 1.0);
+ let val = hsv[2].clamp(0.0, 1.0);
+
+ info.customize.color_hue = hue as i32;
+ info.customize.color_saturation = sat;
+ info.customize.color_value = val;
+
+ println!("Set {}'s HSV color to: {}, {}, {}.", &args.id, hue, sat, val);
+ }
+
+ // 昵称 参数
+ if args.name.is_some() {
+ let name = args.name.unwrap();
+ info.customize.nickname = name.clone();
+
+ println!("Set {}'s display name to: {}.", &args.id, name);
+ }
+
+ // 写入配置文件
+ let yaml_content = serde_yaml::to_string(&info);
+ let mut buffer = File::create(get_account_config_path(&args.id)).unwrap();
+ buffer.write_all(yaml_content.unwrap().as_bytes()).unwrap();
+ },
+ }
+ },
+
+ // 连接到服务器
+ Connect(args) => {
+ if ! is_account_exist(&args.id) {
+ println!("Player not found!");
+ exit(1);
+ }
+
+ // 加载配置文件
+ let file = File::open(get_account_config_path(&args.id)).unwrap();
+ let reader = BufReader::new(file);
+ let info: PlayerInfo = serde_yaml::from_reader(reader).unwrap();
+
+ // 从参数获得 Ip 地址 (或默认)
+ let addr : IpAddr;
+ match IpAddr::from_str(&args.target) {
+ Ok(result) => { addr = result; }
+ Err(_err) => {
+ addr = IpAddr::from(Ipv4Addr::new(127, 0, 0, 1));
+ }
+ }
+
+ // 从参数获得端口地址 (或默认)
+ let port : u16 = if args.port.is_some() { args.port.unwrap() } else { DEFAULT_PORT }
+ .clamp(0, 65535);
+
+ // 绑定目标地址
+ let mut client = PadClient::bind_addr_with_port(addr, port);
+
+ // 启动调试模式 ?
+ if args.debug {
+ println!("- DEBUG MODE -");
+ client.enable_console();
+ }
+
+ // 写入玩家信息
+ client.bind_player(info);
+
+ // 连接
+ client.connect();
+ println!("Connected {} to {}:{}", args.id, addr.to_string(), port.to_string());
+ }
+ }
+}
+
+/// # 处理输入的文本
+///
+/// 将输入的文本进行初步处理,以适合文件名称显示
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | input | String | 输入原始文本 |
+/// | -> | String | 处理后的结果 |
+fn process_inputted_text(input: String) -> String {
+ // 截取前后文本,并转换为小写
+ let s = input.trim().to_lowercase();
+ let mut result = String::new();
+
+ // 处理其中的特殊符号,部分用于分割的符号需要转换为下划线
+ for c in s.chars() {
+ match c {
+ '\n' | '_' => continue,
+ '-' | '.' | ',' | ' ' => result.push('_'),
+ _ => result.push(c),
+ }
+ }
+
+ // 仅保留 ASCII 字符
+ result.chars()
+ .filter(|&c| c.is_ascii_alphanumeric() || c == '_')
+ .collect()
+}
+
+/// # 将 HSV 数值转换为 HEX 颜色码字符串
+///
+/// 在显示玩家信息时,因 HSV 不如 RGB 直观,便转换为 HEX 字符串
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | h | i32 | 色相值 (0 - 360) |
+/// | s | f64 | 饱和度 (0 - 1) |
+/// | v | f64 | 明亮度 (0 - 1) |
+/// | -> | String | HEX 字符串 |
+fn hsv_to_hex(h: i32, s: f64, v: f64) -> String {
+ let h = (h as f64).clamp(0.0, 360.0);
+ let s = s.clamp(0.0, 1.0);
+ let v = v.clamp(0.0, 1.0);
+
+ let c = v * s;
+ let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
+ let m = v - c;
+
+ let (r, g, b) = match (h / 60.0) as usize {
+ 0 => (c, x, 0.0),
+ 1 => (x, c, 0.0),
+ 2 => (0.0, c, x),
+ 3 => (0.0, x, c),
+ 4 => (x, 0.0, c),
+ 5 => (c, 0.0, x),
+ _ => (0.0, 0.0, 0.0),
+ };
+
+ let r = ((r + m) * 255.0).round() as u8;
+ let g = ((g + m) * 255.0).round() as u8;
+ let b = ((b + m) * 255.0).round() as u8;
+ format!("#{:02X}{:02X}{:02X}", r, g, b)
+}
+
+/// # 获得配置文件目录地址
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | -> | PathBuf | 地址 |
+fn get_config_folder_path() -> PathBuf {
+ current_dir().unwrap().join(".nogpadc")
+}
+
+/// # 获得账户配置文件地址
+///
+/// 输入指定的账户ID,获得其配置文件的目录
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | id | &str | 账户 ID |
+/// | -> | PathBuf | 地址 |
+fn get_account_config_path(id: &str) -> PathBuf {
+ get_config_folder_path().join(format!("{}.{}", process_inputted_text(id.to_string()), EXTENSION_NAME))
+}
+
+/// # 判断账户是否存在
+///
+/// 输入指定的账户ID,获得其配置文件的目录
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | id | &str | 账户 ID |
+/// | -> | bool | 是否存在 |
+fn is_account_exist(id: &str) -> bool {
+ let id = process_inputted_text(id.to_string());
+ let path = get_config_folder_path();
+ let dir = path.as_path().read_dir().unwrap();
+ let mut found = false;
+ for item in dir {
+ if let Ok(path) = item {
+ if path.file_name().eq(format!("{}.{}", id, EXTENSION_NAME).as_str()) {
+ found = true;
+ }
+ }
+ }
+ found
+}
\ No newline at end of file
diff --git a/console/src/bin/nogpads.rs b/console/src/bin/nogpads.rs
new file mode 100644
index 0000000..95f5ec7
--- /dev/null
+++ b/console/src/bin/nogpads.rs
@@ -0,0 +1,257 @@
+use clap::{arg, Args, Parser, Subcommand};
+use nogamepads_core::pad_io::server::nogamepads_server::{PadServer};
+use nogamepads_core::DEFAULT_PORT;
+use serde::{Deserialize, Serialize};
+use std::env::current_dir;
+use std::fs::{create_dir, File};
+use std::io::{BufReader, Write};
+use std::net::{IpAddr, Ipv4Addr};
+use std::path::PathBuf;
+use nogamepads_core::pad_data::game_profile::game_profile::GameProfile;
+
+/// NoGamePads Console - Server Cli
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+struct NoGamepadServerCli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+/// 主要命令
+#[derive(Subcommand, Debug)]
+enum Commands {
+
+ // 服务端配置
+ #[command(about = "Configure the server")]
+ Config(ConfigArgs),
+
+ // 运行服务端
+ #[command(about = "Run the server")]
+ Run(RunArgs)
+}
+
+/// 服务端配置 参数
+#[derive(Args, Debug)]
+struct ConfigArgs {
+
+ // 绑定的端口号
+ #[arg(short, long, help = "Server port (0 = Default)")] // <---- DEFAULT_PORT
+ port: Option,
+
+ // 游戏名称
+ #[arg(short ='n', long = "name")]
+ game_name: Option,
+
+ // 游戏描述
+ #[arg(short = 'd', long = "description")]
+ game_description: Option,
+
+ // 游戏组织
+ #[arg(short = 'o', long = "organization")]
+ organization: Option,
+
+ // 游戏版本
+ #[arg(short = 'v', long = "version")]
+ version: Option,
+
+ // 工作室 & 游戏 主页
+ #[arg(short = 'w', long = "website")]
+ website: Option,
+
+ // 交流邮箱
+ #[arg(short = 'e', long = "email")]
+ email: Option
+}
+
+/// 运行服务端 参数
+#[derive(Args, Debug)]
+struct RunArgs {
+
+ // 调试模式
+ #[arg(long)]
+ debug: bool,
+}
+
+/// 本地存储的配置信息
+#[derive(Serialize, Deserialize, PartialEq, Debug)]
+struct ServerConfig {
+ port: u16,
+ profile: GameProfile,
+}
+
+impl Default for ServerConfig {
+ fn default() -> Self {
+ ServerConfig {
+ port: DEFAULT_PORT,
+ profile: GameProfile::default(),
+ }
+ }
+}
+
+/// # 快速生成 更新服务端信息 的宏
+macro_rules! update_config {
+ ($config:expr, $args:expr, $($field:ident),+) => {
+ $(
+ if let Some(ref value) = $args.$field {
+ $config.profile.$field = value.clone();
+ println!("Changed profile \"{}\" to \"{}\"",
+ stringify!($field),
+ value
+ );
+ }
+ )+
+ };
+}
+
+fn main () {
+
+ // 命令行
+ let cli = NoGamepadServerCli::parse();
+ match cli.command {
+
+ // 服务端配置
+ Commands::Config(args) => {
+ // 读取服务端配置
+ let mut config = read_config();
+
+ // 端口信息配置:
+ // 端口数值被限定在 0 - 65535,但是若端口参数为 0,则会被设置为默认端口
+ if args.port.is_some() {
+ let port = args.port.unwrap_or(DEFAULT_PORT).clamp(0, 65535);
+ config.port = if port == 0 { DEFAULT_PORT } else { port };
+ }
+
+ // 其他信息配置
+ update_config!(
+ config, args,
+ game_name,
+ game_description,
+ organization,
+ version,
+ website,
+ email
+ );
+
+ // 写入配置
+ write_config(config);
+ },
+
+ // 运行服务端
+ Commands::Run(args) => {
+ // 读取服务端配置
+ let config = read_config();
+
+ // 根据调试选项启动服务端
+ if args.debug {
+ println!("- DEBUG MODE -");
+ println!("Server started!");
+ PadServer::default()
+ .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), config.port)
+ .put_profile(config.profile)
+ .enable_console()
+ .build()
+ .start_listening();
+ } else {
+ println!("Server started!");
+ PadServer::default()
+ .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), config.port)
+ .put_profile(config.profile)
+ .build()
+ .start_listening();
+ }
+ }
+ }
+}
+
+/// # 获得配置文件目录地址
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | -> | PathBuf | 地址 |
+#[allow(dead_code)]
+fn get_config_folder_path () -> PathBuf {
+ current_dir().unwrap().join(".nogpads")
+}
+
+/// # 获得配置文件地址
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | -> | PathBuf | 地址 |
+#[allow(dead_code)]
+fn get_config_file_path () -> PathBuf {
+ get_config_folder_path().join("config.yaml")
+}
+
+/// # 获得布局文件地址
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | -> | PathBuf | 地址 |
+#[allow(dead_code)]
+fn get_layout_file_path () -> PathBuf {
+ get_config_folder_path().join("layout.yaml")
+}
+
+/// # 获得皮肤资源地址
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | -> | PathBuf | 地址 |
+#[allow(dead_code)]
+fn get_assets_package_path () -> PathBuf {
+ get_config_folder_path().join("assets.zip")
+}
+
+/// # 读取配置信息
+///
+/// 读取 服务端控制台 的配置信息
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | -> | ServerConfig | 配置信息 |
+fn read_config () -> ServerConfig {
+ let config_folder_path = get_config_folder_path();
+ let config_file_path = get_config_file_path();
+
+ if ! config_folder_path.exists() {
+ create_dir(&config_folder_path).unwrap();
+ }
+
+ if ! config_file_path.exists() {
+ let config = ServerConfig::default();
+ let config_text = serde_yaml::to_string(&config).unwrap();
+ File::create(&config_file_path).unwrap().write_all(config_text.as_bytes()).unwrap();
+ config
+ } else {
+ let config_file = File::open(&config_file_path).unwrap();
+ let config_reader = BufReader::new(config_file);
+ serde_yaml::from_reader(config_reader).unwrap()
+ }
+}
+
+/// # 写入配置信息
+///
+/// 将 服务端控制台 的配置信息写入本地
+///
+/// ## 参数 - Parameters
+///
+/// | Field | Type | Description |
+/// | ------ | --------------------- | ----------- |
+/// | config | ServerConfig | 配置信息 |
+fn write_config (config: ServerConfig) {
+ let config_file_path = get_config_file_path();
+
+ let config_text = serde_yaml::to_string(&config).unwrap();
+ File::create(config_file_path).unwrap().write_all(config_text.as_bytes()).unwrap();
+}
\ No newline at end of file
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 = 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> {
+ fn err_result_decode () -> Message;
+ fn err_result_encode () -> Vec {
+ BINCODE_CONVERT_FAILED
+ }
+
+ fn en(&self) -> Vec where Self : Encode {
+ bincode::encode_to_vec(self, BINCODE_CONFIG)
+ .unwrap_or_else(|_| Self::err_result_encode())
+ }
+
+ fn de(encoded : Vec) -> Message {
+ match bincode::decode_from_slice(&encoded[..], BINCODE_CONFIG) {
+ Ok((decoded, _)) => decoded,
+ Err(_) => Self::err_result_decode()
+ }
+ }
+ }
+
+ impl NgpdMessageEncoder for ControlMessage {
+ fn err_result_decode() -> ControlMessage {
+ ControlMessage::Err
+ }
+ }
+
+ impl NgpdMessageEncoder for GameMessage {
+ fn err_result_decode() -> GameMessage {
+ GameMessage::Err
+ }
+ }
+
+ impl NgpdMessageEncoder for ConnectionMessage {
+ fn err_result_decode() -> ConnectionMessage {
+ ConnectionMessage::Err
+ }
+ }
+
+ impl NgpdMessageEncoder 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 (stream: &mut TcpStream, msg: impl NgpdMessageEncoder + Decode<()> + Encode)
+ where Message: NgpdMessageEncoder + Decode<()> + Encode {
+ match stream.write_all(NgpdMessageEncoder::en(&msg).as_slice()).await {
+ Ok(_) => {}
+ Err(_) => {
+ error!("Failed to send message.");
+ }
+ }
+ }
+
+ pub async fn read_msg(buffer: &mut [u8], stream: &mut TcpStream) -> Message
+ where Message: NgpdMessageEncoder + Decode<()> + Encode {
+ match stream.read(buffer).await {
+ Ok(read) => {
+ let received = &buffer[..read];
+ >::de(Vec::from(received))
+ }
+ Err(err) => {
+ error!("Error reading from socket: {}", err);
+ >::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>>;
+ type ReadList = Arc>>;
+
+ 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 {
+ {
+ 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 {
+ 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) {
+ 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, buffer: &mut [u8], addr_str: String) -> Option {
+ 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, 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, 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, mut reader: ReadHalf) {
+ 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, mut writer: WriteHalf) {
+ loop {
+ let msg : Option;
+ {
+ 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) {
+ 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) {
+ loop {
+ if self.exit.load(SeqCst) {
+ info!("Debug console exited");
+ break
+ }
+ tokio::time::sleep(Duration::from_secs_f64(0.2)).await;
+ let option: Option = 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) {
+ 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, 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>>;
+ type WriteList = Arc>>>;
+ type ReadList = Arc>>>;
+
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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, PoisonError>>> {
+ match self.online_players.lock() {
+ Ok(guard) => {
+ Ok(guard.values().cloned().collect())
+ }
+ Err(err) => Err(err)
+ }
+ }
+
+ pub fn list_players_banned(&self) -> Result, PoisonError>>> {
+ match self.banned_players.lock() {
+ Ok(guard) => {
+ Ok(guard.values().cloned().collect())
+ }
+ Err(err) => Err(err)
+ }
+ }
+
+ fn convert_deque_to_vec (deque: &VecDeque) -> Vec {
+ 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) {
+
+ // 构建 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, debug: bool) -> impl Future