aboutsummaryrefslogtreecommitdiff
path: root/console/src
diff options
context:
space:
mode:
author1992414357@qq.com <1992414357@qq.com>2025-06-09 01:44:36 +0800
committer1992414357@qq.com <1992414357@qq.com>2025-06-09 01:44:36 +0800
commit49192dbb98e0ab1f2a66b4786fac86d63f69be64 (patch)
tree7cdf27c7cab5fb6b1aabc2ac7c44b7b492558945 /console/src
parentc9dbba0d288becb7f05cebe526be25c76e5a850a (diff)
重构所有部分
Diffstat (limited to 'console/src')
-rw-r--r--console/src/bin/nogpadc.rs387
-rw-r--r--console/src/bin/nogpads.rs299
-rw-r--r--console/src/bin/padc.rs684
-rw-r--r--console/src/lib.rs1
-rw-r--r--console/src/utils.rs107
5 files changed, 792 insertions, 686 deletions
diff --git a/console/src/bin/nogpadc.rs b/console/src/bin/nogpadc.rs
deleted file mode 100644
index 9a5418e..0000000
--- a/console/src/bin/nogpadc.rs
+++ /dev/null
@@ -1,387 +0,0 @@
-use crate::AccountCommands::{Add, Customize, List, Remove};
-use crate::Commands::{Account, Connect};
-use clap::{Args, Parser, Subcommand};
-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;
-use nogamepads_lib_rs::DEFAULT_PORT;
-use nogamepads_lib_rs::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo;
-use nogamepads_lib_rs::pad_service::client::nogamepads_client::PadClient;
-
-/// 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<String>,
-
- // 颜色
- #[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<Vec<f64>>
-}
-
-/// 连接到服务器
-#[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<u16>,
-
- // 启用调试
- #[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());
- }
- }
-}
-
-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()
-}
-
-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)
-}
-
-fn get_config_folder_path() -> PathBuf {
- current_dir().unwrap().join(".nogpadc")
-}
-
-fn get_account_config_path(id: &str) -> PathBuf {
- get_config_folder_path().join(format!("{}.{}", process_inputted_text(id.to_string()), EXTENSION_NAME))
-}
-
-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
deleted file mode 100644
index 14df488..0000000
--- a/console/src/bin/nogpads.rs
+++ /dev/null
@@ -1,299 +0,0 @@
-use std::collections::HashMap;
-use clap::{arg, Args, Parser, Subcommand};
-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_lib_rs::DEFAULT_PORT;
-use nogamepads_lib_rs::pad_data::game_profile::game_profile::GameProfile;
-use nogamepads_lib_rs::pad_data::layout::layout_data::LayoutKeyRegisters;
-use nogamepads_lib_rs::pad_service::server::nogamepads_server::PadServer;
-
-/// 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(subcommand, about = "Manage buttons")]
- Button(ManageCommands),
-
- #[command(subcommand, about = "Manage directions")]
- Direction(ManageCommands),
-
- #[command(subcommand, about = "Manage axes")]
- Axis(ManageCommands),
-
- // 服务端配置
- #[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<u16>,
-
- // 游戏名称
- #[arg(short ='n', long = "name")]
- game_name: Option<String>,
-
- // 游戏描述
- #[arg(short = 'd', long = "description")]
- game_description: Option<String>,
-
- // 游戏组织
- #[arg(short = 'o', long = "organization")]
- organization: Option<String>,
-
- // 游戏版本
- #[arg(short = 'v', long = "version")]
- version: Option<String>,
-
- // 工作室 & 游戏 主页
- #[arg(short = 'w', long = "website")]
- website: Option<String>,
-
- // 交流邮箱
- #[arg(short = 'e', long = "email")]
- email: Option<String>
-}
-
-/// 运行服务端 参数
-#[derive(Args, Debug)]
-struct RunArgs {
-
- // 调试模式
- #[arg(long)]
- debug: bool,
-}
-
-/// 管理键
-#[derive(Subcommand, Debug)]
-enum ManageCommands {
-
- #[command(about = "Add or rename a key")]
- Add(AddKeyArgs),
-
- #[command(about = "Remove a key")]
- Remove(RemoveKeyArgs),
-
- #[command(about = "List all")]
- List
-}
-
-/// 添加键
-#[derive(Args, Debug)]
-struct AddKeyArgs{
-
- // 添加的键
- #[arg(value_name = "KEY")]
- key: u8,
-
- // 事件编号
- #[arg(value_name = "NAME")]
- name: String
-}
-
-/// 移除键
-#[derive(Args, Debug)]
-struct RemoveKeyArgs{
-
- // 删除的键
- #[arg(value_name = "KEY")]
- key: u8,
-}
-
-/// 本地存储的配置信息
-#[derive(Serialize, Deserialize, PartialEq, Debug)]
-struct ServerConfig {
- port: u16,
- registered_keys: LayoutKeyRegisters,
- profile: GameProfile,
-}
-
-impl Default for ServerConfig {
- fn default() -> Self {
- ServerConfig {
- port: DEFAULT_PORT,
- registered_keys: Default::default(),
- 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();
-
- // 读取服务端配置
- let mut config = read_config();
-
- match cli.command {
-
- // 服务端配置
- Commands::Config(args) => {
-
- // 端口信息配置:
- // 端口数值被限定在 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
- );
- },
-
- // 运行服务端
- 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)
- .register_keys(config.registered_keys)
- .enable_console()
- .build()
- .start_server();
- } else {
- println!("Server started!");
- PadServer::default()
- .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), config.port)
- .put_profile(config.profile)
- .register_keys(config.registered_keys)
- .build()
- .start_server();
- }
- }
-
- Commands::Button(manage) => {
- process_manage_command("btn", manage, &mut config.registered_keys.button_keys);
- }
-
- Commands::Direction(manage) => {
- process_manage_command("dir", manage, &mut config.registered_keys.direction_keys);
- }
-
- Commands::Axis(manage) => {
- process_manage_command("ax", manage, &mut config.registered_keys.axis_keys);
- }
- }
-
- // 写入配置
- write_config(config);
-}
-
-fn process_manage_command(prefix: &str, manage: ManageCommands, map: &mut HashMap<u8, String>) {
- match manage {
- ManageCommands::Add(args) => {
- map.entry(args.key)
- .or_insert_with(|| args.name.clone());
- println!("Added(Renamed) key {}_{} : \"{}\".", prefix, args.key, args.name);
- }
- ManageCommands::Remove(args) => {
- let removed = map.remove(&args.key);
- if removed.is_some() {
- println!("Removed key {}_{}.", prefix, removed.is_some())
- } else {
- println!("Removed key failed: Cannot found key {}", args.key);
- }
- }
- ManageCommands::List => {
- for button_key in map {
- println!("{}_{} : \"{}\"", prefix, button_key.0, button_key.1)
- }
- }
- }
-}
-
-#[allow(dead_code)]
-fn get_config_folder_path () -> PathBuf {
- current_dir().unwrap().join(".nogpads")
-}
-
-#[allow(dead_code)]
-fn get_config_file_path () -> PathBuf {
- get_config_folder_path().join("config.yaml")
-}
-
-#[allow(dead_code)]
-fn get_layout_file_path () -> PathBuf {
- get_config_folder_path().join("layout.yaml")
-}
-
-#[allow(dead_code)]
-fn get_assets_package_path () -> PathBuf {
- get_config_folder_path().join("assets.zip")
-}
-
-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()
- }
-}
-
-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/console/src/bin/padc.rs b/console/src/bin/padc.rs
new file mode 100644
index 0000000..742b6c2
--- /dev/null
+++ b/console/src/bin/padc.rs
@@ -0,0 +1,684 @@
+use clap::{Args, CommandFactory, Parser, Subcommand};
+use nogamepads::string_utils::process_id_text;
+use nogamepads_console::utils::{confirm, read_password, read_password_and_confirm};
+use nogamepads_core::data::game::structs::GameData;
+use nogamepads_core::data::player::structs::Player;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::env::current_dir;
+use std::fs::File;
+use std::io::{BufReader, Write};
+use std::net::SocketAddr;
+use std::path::PathBuf;
+use std::process::exit;
+use std::str::FromStr;
+use std::sync::Arc;
+use log::LevelFilter;
+use nogamepads::logger_utils::logger_build;
+use nogamepads_core::data::controller::cli::cli_command::{process_controller_cli, ControllerCli};
+use nogamepads_core::data::controller::structs::ControllerData;
+use nogamepads_core::data::game::cli::cli_command::{process_game_cli, GameCli};
+use nogamepads_core::run_services;
+use nogamepads_core::service::cli_addition::runtime_consoles::RuntimeConsole;
+use nogamepads_core::service::service_runner::{NoGamepadsService, ServiceRunner};
+use nogamepads_core::service::tcp_network::DEFAULT_PORT;
+use nogamepads_core::service::tcp_network::pad_client::structs::PadClientNetwork;
+use nogamepads_core::service::tcp_network::pad_server::structs::PadServerNetwork;
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+struct ConsoleCli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+
+ #[command(subcommand, about = "Manage accounts")]
+ Account(AccountCommands),
+
+ #[command(about = "List accounts")]
+ Accounts,
+
+ #[command(subcommand, about = "Manage game infos")]
+ Game(GameCommands),
+
+ #[command(about = "List all games")]
+ Games,
+
+ #[command(about = "Connect to a service")]
+ Connect(ConnectArgs),
+
+ #[command(about = "Start a service")]
+ Listen(ListenArgs)
+}
+
+#[derive(Subcommand, Debug)]
+enum AccountCommands {
+
+ #[command(about = "Add a account")]
+ Add(AccountArgs),
+
+ #[command(about = "Remove a account")]
+ Remove(AccountArgs),
+
+ #[command(about = "Edit the profile of account")]
+ Edit(EditAccountArgs)
+}
+
+#[derive(Args, Debug)]
+struct AccountArgs{
+
+ #[arg(value_name = "Account")]
+ account: String,
+
+ #[arg(short, long, value_name = "Password")]
+ password: Option<String>
+}
+
+#[derive(Args, Debug)]
+struct EditAccountArgs{
+
+ #[arg(value_name = "Account")]
+ account: String,
+
+ #[arg(short, long, help = "Nickname")]
+ nickname: Option<String>,
+
+ #[arg(short = 'c', long = "color", num_args = 3, value_names = ["H", "S", "V"], help = "Player color, h(0 - 360), s(0 - 1), v(0 - 1)")]
+ color: Option<Vec<f64>>
+}
+
+#[derive(Subcommand, Debug)]
+enum GameCommands {
+
+ #[command(about = "Add a game")]
+ Add(GameArgs),
+
+ #[command(about = "Remove a game")]
+ Remove(GameArgs),
+
+ #[command(about = "Edit the profile of game")]
+ Edit(EditGameArgs),
+
+ #[command(subcommand, about = "Register keys to game")]
+ Register(RegisterKeysCommands)
+}
+
+#[derive(Args, Debug)]
+struct GameArgs {
+
+ #[arg(value_name = "Game Name")]
+ name: String,
+
+ #[arg(short, long)]
+ confirm: bool
+}
+
+#[derive(Args, Debug)]
+struct EditGameArgs {
+
+ #[arg(value_name = "Game Name")]
+ name: String,
+
+ #[arg(value_name = "Key")]
+ key: String,
+
+ #[arg(value_name = "Value")]
+ value: String,
+}
+
+#[derive(Subcommand, Debug)]
+enum RegisterKeysCommands {
+
+ #[command(subcommand, about = "Register a button")]
+ Button(KeyManageCommands),
+
+ #[command(subcommand, about = "Register a axis")]
+ Axis(KeyManageCommands),
+
+ #[command(subcommand, about = "Register a direction")]
+ Direction(KeyManageCommands)
+}
+
+#[derive(Subcommand, Debug)]
+enum KeyManageCommands {
+
+ #[command(about = "Add or rename a key")]
+ Add(AddKeyArgs),
+
+ #[command(about = "Remove a key")]
+ Remove(RemoveKeyArgs),
+
+ #[command(about = "List all")]
+ List(ListKeyArgs)
+}
+
+#[derive(Args, Debug)]
+struct AddKeyArgs{
+
+ #[arg(value_name = "Game Name")]
+ name: String,
+
+ #[arg(value_name = "Key")]
+ key: u8,
+
+ #[arg(value_name = "Name")]
+ key_name: String
+}
+
+#[derive(Args, Debug)]
+struct RemoveKeyArgs{
+
+ #[arg(value_name = "Game Name")]
+ name: String,
+
+ #[arg(value_name = "Key")]
+ key: u8,
+}
+
+#[derive(Args, Debug)]
+struct ListKeyArgs{
+
+ #[arg(value_name = "Game Name")]
+ name: String
+}
+
+#[derive(Args, Debug)]
+struct ConnectArgs {
+
+ #[arg(short, long, value_name = "Account")]
+ account: Option<String>,
+
+ #[arg(short, long, value_name = "Address")]
+ tcp_addr: Option<String>,
+
+ #[arg(short, long, value_name = "Methods")]
+ method: Option<String>,
+
+ #[arg(long)]
+ cmd: bool,
+
+ #[arg(long)]
+ gui: bool,
+
+ #[arg(long)]
+ debug: bool,
+}
+
+#[derive(Args, Debug)]
+struct ListenArgs {
+
+ #[arg(value_name = "Game")]
+ game: String,
+
+ #[arg(short = 'a', long, value_name = "Address")]
+ tcp_addr: Option<String>,
+
+ #[arg(long)]
+ cmd: bool,
+
+ #[arg(long)]
+ gui: bool,
+
+ #[arg(long)]
+ tcp: bool,
+
+ #[arg(long)]
+ bluetooth: bool,
+
+ #[arg(long)]
+ usb: bool,
+
+ #[arg(long)]
+ debug: bool,
+}
+
+#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
+struct LocalData {
+ game_data: LocalGameData,
+ controller_data: LocalControllerData,
+}
+
+#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
+struct LocalGameData {
+ games: HashMap<String, GameData>
+}
+
+#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
+struct LocalControllerData {
+ players: HashMap<String, Player>
+}
+
+fn main () {
+ let cli = ConsoleCli::parse();
+ let mut data = read();
+
+ match cli.command {
+ Commands::Account(cmds) => {
+ match cmds {
+ AccountCommands::Add(args) => {
+ add_player(&mut data, args.account, args.password);
+ }
+
+ AccountCommands::Remove(args) => {
+ remove_player(&mut data, args.account, args.password);
+ }
+
+ AccountCommands::Edit(args) => {
+ edit_player(&mut data, args);
+ }
+ }
+ }
+
+ Commands::Accounts => {
+ for player in data.controller_data.players.values() {
+ let id = &player.account.id;
+ if player.customize.is_some() {
+ let nickname = &player.clone().customize.unwrap().nickname;
+ println!("{}({})", id, nickname);
+ } else {
+ println!("{}", id);
+ }
+ }
+ }
+
+ Commands::Game(cmds) => {
+ match cmds {
+ GameCommands::Add(args) => {
+ let name = process_id_text(args.name);
+ data.game_data.games.entry(name.clone())
+ .or_insert_with(GameData::default);
+ println!("Game configuration added or reset: \"{}\"", name.clone());
+ }
+
+ GameCommands::Remove(args) => {
+ if ! args.confirm {
+ if ! confirm("Confirm ") {
+ exit(1);
+ }
+ }
+
+ let name = process_id_text(args.name);
+ let game = data.game_data.games.remove(&name);
+ if game.is_none() {
+ eprintln!("Removal of the game \"{}\" failed: game not found.", name.clone());
+ } else {
+ println!("The game \"{}\" has been removed.", name.clone());
+ }
+ }
+
+ GameCommands::Edit(args) => {
+ let name = process_id_text(args.name);
+ let game = data.game_data.games.get_mut(&name);
+ if game.is_none() {
+ eprintln!("Edit the game \"{}\" failed: game not found.", name.clone());
+ } else {
+ let mut game = game.unwrap();
+ game.info(args.key.clone(), args.value.clone());
+ println!("Set the game info \"{}\" to \"{}\".", args.key, args.value);
+ }
+ }
+
+ GameCommands::Register(cmds) => {
+ match cmds {
+ RegisterKeysCommands::Button(cmds) => {
+ manage_keys(&mut data, cmds,
+ |game| &mut game.control.button_keys);
+ }
+
+ RegisterKeysCommands::Axis(cmds) => {
+ manage_keys(&mut data, cmds,
+ |game| &mut game.control.axis_keys);
+ }
+
+ RegisterKeysCommands::Direction(cmds) => {
+ manage_keys(&mut data, cmds,
+ |game| &mut game.control.direction_keys);
+ }
+ }
+ }
+ }
+ }
+
+ Commands::Games => {
+ for (key, _) in data.game_data.games.iter() {
+ println!("{}", key);
+ }
+ }
+
+ Commands::Connect(args) => {
+ connect(&mut data, args);
+ }
+
+ Commands::Listen(args) => {
+ listen(&mut data, args);
+ }
+ }
+
+ write(data);
+}
+
+fn connect(data: &mut LocalData, args: ConnectArgs) {
+
+ let mut result: Option<&Player> = None;
+ if args.account.is_some() {
+ // Account specified
+ let id = &args.account.unwrap();
+ let player = data.controller_data.players.get(id);
+
+ // Account not found
+ if player.is_none() {
+ eprintln!("Account not found: \"{}\"", id);
+ exit(1);
+ } else {
+ // Account exists
+ result = Some(player.unwrap());
+ }
+ } else {
+ // Account not specified
+ for found in data.controller_data.players.values() {
+ // Found a replaceable account
+ result = Some(found);
+ println!("Account not specified! Using account \"{}\" instead!", found.account.id);
+ break;
+ }
+ // No replaceable account found
+ if result.is_none() {
+ eprintln!("Cannot find any replaceable account! Please ensure at least one account exists locally!");
+ exit(1);
+ }
+ }
+
+ let player = result.unwrap().clone();
+
+ let mut controller = ControllerData::default();
+ controller.bind_player(player);
+
+ let runtime = controller.runtime();
+
+ let method = args.method.unwrap_or("tcp".to_string());
+ let mut entry: Option<NoGamepadsService> = None;
+ match method.as_str() {
+ "tcp" => {
+ let mut client = PadClientNetwork::build(Arc::clone(&runtime));
+ let addr = args.tcp_addr.unwrap_or(format!("127.0.0.1:{}", DEFAULT_PORT));
+ client.bind_addr(SocketAddr::from_str(&addr).unwrap_or(
+ SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)),
+ ));
+
+ entry = Some(client.build_entry());
+ },
+
+ "bluetooth" => {
+ // TODO :: BLUETOOTH METHOD
+ },
+
+ "usb" => {
+ // TODO :: USB METHOD
+ },
+
+ _ => {
+ eprintln!("Unknown connection method: {}", method);
+ exit(1);
+ }
+ }
+
+ if let Some(entry) = entry {
+ let mut services = Vec::new();
+ services.push(entry);
+
+ if args.cmd {
+ services.push(RuntimeConsole::build(
+ ControllerCli::command(),
+ "ControllerCli".to_string(),
+ Arc::clone(&runtime),
+ |runtime, cmd| {
+ process_controller_cli(runtime, cmd);
+ }
+ ).build_entry());
+ }
+
+ if args.gui {
+
+ }
+
+ if args.debug {
+ logger_build(LevelFilter::Trace);
+ } else {
+ logger_build(LevelFilter::Info);
+ }
+
+ ServiceRunner::run(services);
+ }
+}
+
+fn listen(data: &mut LocalData, args: ListenArgs) {
+ let id = process_id_text(args.game);
+ let game = data.game_data.games.get(&id);
+ if game.is_none() {
+ eprintln!("Game not found: \"{}\"", id);
+ exit(1);
+ }
+
+ let game_data = game.unwrap().clone();
+
+ let runtime = game_data.runtime();
+
+ let mut services = Vec::new();
+
+ if args.tcp {
+ let mut server = PadServerNetwork::build(Arc::clone(&runtime));
+ if args.tcp_addr.is_some() {
+ let addr = SocketAddr::from_str(&args.tcp_addr.unwrap())
+ .unwrap_or(SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)));
+ server.bind_ip(addr.ip());
+ server.bind_port(addr.port());
+ }
+ services.push(server.build_entry());
+ println!("Setup TCP Service!")
+ }
+
+ if args.bluetooth {
+
+ }
+
+ if args.usb {
+
+ }
+
+ if args.cmd {
+ services.push(RuntimeConsole::build(
+ GameCli::command(),
+ "GameCli".to_string(),
+ Arc::clone(&runtime),
+ |runtime, cmd| {
+ process_game_cli(runtime, cmd);
+ }
+ ).build_entry());
+ println!("Setup Command Line!")
+ }
+
+ if args.gui {
+
+ }
+
+ if args.debug {
+ logger_build(LevelFilter::Trace);
+ } else {
+ logger_build(LevelFilter::Info);
+ }
+
+ ServiceRunner::run(services);
+}
+
+fn add_player(data: &mut LocalData, account_args: String, password_args: Option<String>) {
+ if data.controller_data.players.contains_key(process_id_text(account_args.clone()).as_str()) {
+ eprintln!("This account already exists. Please do not create it again.");
+ exit(1);
+ } else {
+ // Read password
+ let mut password = "".to_string();
+ if password_args.is_none() {
+ let input = read_password_and_confirm("Enter password: ", "Confirm: ");
+ if input.is_some() {
+ password = input.unwrap();
+ }
+ } else if password_args.is_some() {
+ password = password_args.unwrap();
+ }
+
+ // Create player
+ let player = Player::register(account_args, password);
+ let player_key = player.clone().account.id;
+
+ data.controller_data.players.insert(player_key, player.clone());
+ }
+}
+
+fn remove_player(data: &mut LocalData, account_args: String, password_args: Option<String>) {
+ // Read password
+ let mut password = "".to_string();
+ if password_args.is_none() {
+ password = read_password("Enter password: ").unwrap_or("".to_string());
+ } else if password_args.is_some() {
+ password = password_args.unwrap();
+ }
+
+ // Remove
+ let player_id = process_id_text(account_args.clone());
+
+ let player = data.controller_data.players.get(&player_id);
+ if player.is_none() {
+ eprintln!("Failed to remove account \"{}\": Account not found.", account_args.clone());
+ exit(1);
+ }
+ let player = player.unwrap();
+
+ if player.check(password.clone()) {
+ data.controller_data.players.remove(player_id.as_str());
+ println!("The account \"{}\" has been removed!", player_id);
+ } else {
+ eprintln!("Failed to remove account \"{}\": Password is incorrect!", player_id)
+ }
+}
+
+fn edit_player(data: &mut LocalData, args: EditAccountArgs) {
+ let account_id = process_id_text(args.account);
+ if ! data.controller_data.players.contains_key(&account_id) {
+ eprintln!("Edit failed: Account \"{}\" not found!", account_id);
+ exit(1);
+ }
+
+ let player = data.controller_data.players.get_mut(&account_id).unwrap();
+
+ if args.nickname.is_some() {
+ let nickname = args.nickname.unwrap();
+ player.nickname(&nickname);
+ println!("Change the nickname of account \"{}\" to \"{}\"", account_id, &nickname);
+ }
+
+ if args.color.is_some() {
+ let color = args.color.unwrap();
+ let hue = color[0].round().clamp(0.0, 360.0);
+ let sat = color[1].clamp(0.0, 1.0);
+ let val = color[2].clamp(0.0, 1.0);
+ player.hsv(hue as i32, sat, val);
+ println!("Change the color of account \"{}\" to \"{}\"", account_id, hsv_to_hex(hue as i32, sat, val));
+ }
+}
+
+macro_rules! check_game {
+ ($data:expr, $args:expr, |$game:ident| $code:block) => {
+ let name = process_id_text($args.name);
+ let mut game = $data.game_data.games.get_mut(&name);
+ if game.is_none() {
+ eprintln!("Edit the game \"{}\" failed: game not found.", name.clone());
+ exit(1);
+ } else {
+ let mut $game = game.unwrap();
+ $code
+ }
+ };
+}
+
+fn manage_keys(data: &mut LocalData, cmds: KeyManageCommands, get_map: fn(game: &mut GameData) -> &mut HashMap<u8, String>) {
+ match cmds {
+ KeyManageCommands::Add(args) => {
+ check_game!(data, args, |game| {
+ get_map(game).entry(args.key)
+ .or_insert_with(|| args.key_name.clone());
+ println!("Registered key \"{}\"", args.key_name);
+ });
+ }
+
+ KeyManageCommands::Remove(args) => {
+ check_game!(data, args, |game| {
+ let result = get_map(game).remove(&args.key);
+ if result.is_some() {
+ println!("Removed key \"{}\"", result.unwrap());
+ }
+ });
+ }
+
+ KeyManageCommands::List(args) => {
+ check_game!(data, args, |game| {
+ for (key, key_name) in get_map(game).iter() {
+ println!("{} - \"{}\"", key, key_name);
+ }
+ });
+ }
+ }
+}
+
+fn local_config() -> PathBuf {
+ current_dir().unwrap().join("nogamepads.yaml")
+}
+
+fn read() -> LocalData {
+ let file_path = local_config();
+
+ if ! file_path.exists() {
+ let data = LocalData::default();
+ let content = serde_yaml::to_string(&data).unwrap();
+ File::create(&file_path).unwrap().write_all(content.as_bytes()).unwrap();
+ data
+ } else {
+ let file = File::open(&file_path).unwrap();
+ let reader = BufReader::new(file);
+ serde_yaml::from_reader(reader).unwrap()
+ }
+}
+
+fn write(config: LocalData) {
+ let file_path = local_config();
+ let content = serde_yaml::to_string(&config).unwrap();
+
+ File::create(file_path).unwrap().write_all(content.as_bytes()).unwrap();
+}
+
+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)
+}
diff --git a/console/src/lib.rs b/console/src/lib.rs
index e69de29..fab870e 100644
--- a/console/src/lib.rs
+++ b/console/src/lib.rs
@@ -0,0 +1 @@
+pub mod utils; \ No newline at end of file
diff --git a/console/src/utils.rs b/console/src/utils.rs
new file mode 100644
index 0000000..1e65f0e
--- /dev/null
+++ b/console/src/utils.rs
@@ -0,0 +1,107 @@
+use std::io::{self, Read, Write};
+use crossterm::terminal;
+use tokio::task;
+
+pub fn read_password(prompt: &str) -> Option<String> {
+ print!("{}", prompt);
+ io::stdout().flush().unwrap();
+
+ let raw_mode_enabled = terminal::enable_raw_mode().is_ok();
+ if !raw_mode_enabled {
+ eprintln!("Warning: Password will be displayed in plain text");
+ }
+
+ let mut buffer = Vec::new();
+ loop {
+ let mut byte = [0];
+ if io::stdin().read(&mut byte).is_err() || byte[0] == b'\n' || byte[0] == b'\r' {
+ break;
+ }
+
+ match byte[0] {
+ 8 | 127 => {
+ if buffer.pop().is_some() {
+ print!("\x08 \x08");
+ io::stdout().flush().unwrap();
+ }
+ }
+ 3 => {
+ println!();
+ return None;
+ }
+ _ => {
+ buffer.push(byte[0]);
+ print!("*");
+ io::stdout().flush().unwrap();
+ }
+ }
+ }
+
+ if raw_mode_enabled {
+ terminal::disable_raw_mode().unwrap();
+ }
+ println!();
+
+ Some(String::from_utf8_lossy(&buffer).into_owned())
+}
+
+pub fn read_password_and_confirm(
+ prompt: &str,
+ confirm_prompt: &str
+) -> Option<String> {
+ loop {
+ let pw1 = read_password(prompt).unwrap();
+ let pw2 = read_password(confirm_prompt).unwrap();
+
+ if pw1 == pw2 {
+ return Some(pw1);
+ }
+ eprintln!("The passwords entered twice do not match, please try again.");
+ }
+}
+
+pub fn confirm(prompt: &str) -> bool {
+ let prompt = format!("{} [Y/n]: ", prompt);
+ loop {
+ let input = read_password(&prompt).unwrap_or_default();
+ match input.to_lowercase().as_str() {
+ "y" | "yes" | "" => return true,
+ "n" | "no" => return false,
+ _ => eprintln!("Invalid input, please enter Y or n."),
+ }
+ }
+}
+
+pub async fn read_password_async(prompt: &str) -> Option<String> {
+ let prompt = prompt.to_owned();
+ task::spawn_blocking(move || read_password(&prompt))
+ .await
+ .unwrap_or_else(|_| None)
+}
+
+pub async fn read_password_and_confirm_async(
+ prompt: &str,
+ confirm_prompt: &str
+) -> Option<String> {
+ loop {
+ let pw1 = read_password_async(prompt).await.unwrap();
+ let pw2 = read_password_async(confirm_prompt).await.unwrap();
+
+ if pw1 == pw2 {
+ return Some(pw1);
+ }
+ eprintln!("The passwords entered twice do not match, please try again.");
+ }
+}
+
+pub async fn confirm_async(prompt: &str) -> bool {
+ let prompt = format!("{} [Y/n]: ", prompt);
+ loop {
+ let input = read_password_async(&prompt).await.unwrap_or_default();
+ match input.to_lowercase().as_str() {
+ "y" | "yes" | "" => return true,
+ "n" | "no" => return false,
+ _ => eprintln!("Invalid input, please enter Y or n."),
+ }
+ }
+} \ No newline at end of file