diff --git a/.gitignore b/.gitignore index df9ac71..45fe8c3 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,8 @@ yarn-error.log* *.tsbuildinfo .env -.env.* \ No newline at end of file +.env.* + +temp/ +src-tauri/temp/ +log/ \ No newline at end of file diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore index fd29446..f8248e3 100644 --- a/src-tauri/.gitignore +++ b/src-tauri/.gitignore @@ -1,3 +1,3 @@ # Generated by Cargo # will have compiled files and executables -/target/ +/target/ \ No newline at end of file diff --git a/src-tauri/gen/schemas/macOS-schema.json b/src-tauri/gen/schemas/macOS-schema.json index 5d1e84d..5a259ba 100644 --- a/src-tauri/gen/schemas/macOS-schema.json +++ b/src-tauri/gen/schemas/macOS-schema.json @@ -2039,6 +2039,21 @@ "type": "string", "const": "autostart:deny-is-enabled" }, + { + "description": "Allows reading the CLI matches", + "type": "string", + "const": "cli:default" + }, + { + "description": "Enables the cli_matches command without any pre-configured scope.", + "type": "string", + "const": "cli:allow-cli-matches" + }, + { + "description": "Denies the cli_matches command without any pre-configured scope.", + "type": "string", + "const": "cli:deny-cli-matches" + }, { "description": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n", "type": "string", diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 45d4dd9..8264f72 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,6 +1,7 @@ use crate::steam; use crate::tool::*; use crate::vdf::preset; +use crate::vdf::preset::VideoConfig; use crate::wrap_err; use anyhow::Result; @@ -55,7 +56,7 @@ pub fn get_powerplan() -> Result { let powerplan = powerplan::get_powerplan()?; #[cfg(not(target_os = "windows"))] - let powerplan = powerplan::PowerPlanMode::Other.into(); + let powerplan = powerplan::PowerPlanMode::Other as i32; Ok(powerplan) } @@ -81,6 +82,31 @@ pub fn set_auto_login_user(user: &str) -> Result { Ok(format!("Set auto login user to {}", user)) } +#[tauri::command] +pub fn get_cs2_video_config(steam_dir: &str, steam_id32: u32) -> Result { + let p = format!( + "{}/userdata/{}/730/local/cfg/cs2_video.txt", + steam_dir, steam_id32 + ); + let video = preset::get_cs2_video(p.as_str()).map_err(|e| e.to_string())?; + Ok(video) +} + +#[tauri::command] +pub fn set_cs2_video_config( + steam_dir: &str, + steam_id32: u32, + video_config: VideoConfig, +) -> Result<(), String> { + let p = format!( + "{}/userdata/{}/730/local/cfg/cs2_video.txt", + steam_dir, steam_id32 + ); + preset::set_cs2_video(p.as_str(), video_config).map_err(|e| e.to_string())?; + + Ok(()) +} + #[tauri::command] pub fn check_path(path: &str) -> Result { Ok(std::path::Path::new(&path).exists()) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b66574f..55a64ec 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -129,6 +129,8 @@ fn main() { cmds::set_powerplan, cmds::get_steam_users, cmds::set_auto_login_user, + cmds::get_cs2_video_config, + cmds::set_cs2_video_config, cmds::check_path, on_button_clicked ]) diff --git a/src-tauri/src/tool/common.rs b/src-tauri/src/tool/common.rs index 0427f1c..8b7d142 100644 --- a/src-tauri/src/tool/common.rs +++ b/src-tauri/src/tool/common.rs @@ -1,10 +1,12 @@ -use std::os::windows::process::CommandExt; use std::process::Command; +#[cfg(windows)] +use std::os::windows::process::CommandExt; const CREATE_NO_WINDOW: u32 = 0x08000000; // const DETACHED_PROCESS: u32 = 0x00000008; pub fn kill(name: &str) -> String { + #[cfg(windows)] Command::new("taskkill") .args(&["/IM", name, "/F"]) .creation_flags(CREATE_NO_WINDOW) @@ -15,10 +17,16 @@ pub fn kill(name: &str) -> String { } pub fn run_steam() -> std::io::Result { - Command::new("cmd") + #[cfg(target_os = "windows")] + return Command::new("cmd") .args(&["/C", "start", "steam://run"]) .creation_flags(CREATE_NO_WINDOW) - .output() + .output(); + + #[cfg(target_os = "macos")] + Command::new("open") + .args(&["-a", "Steam"]) + .output() } pub fn get_exe_path(name: &str) -> Result { @@ -30,11 +38,17 @@ pub fn get_exe_path(name: &str) -> Result { // 进程路径 let command = format!("Get-Process {} | Select-Object path", name); let args = command.split_whitespace().collect::>(); + #[cfg(windows)] let output = Command::new("powershell.exe") .args(&args) .creation_flags(CREATE_NO_WINDOW) .output()?; + #[cfg(target_os = "macos")] + let output = Command::new("osascript") + .args(&["-e", &format!("tell application \"{}\" to get path to me", name)]) + .output()?; + let out = String::from_utf8_lossy(&output.stdout).to_string(); if out.contains("Path") { @@ -54,6 +68,7 @@ pub fn get_exe_path(name: &str) -> Result { pub fn open_path(path: &str) -> Result<(), std::io::Error> { // path中所有/ 转换为 \ let path = path.replace("/", "\\"); + #[cfg(windows)] Command::new("cmd.exe") .args(["/c", "start", "", &path]) .creation_flags(CREATE_NO_WINDOW) diff --git a/src-tauri/src/tool/powerplan.rs b/src-tauri/src/tool/powerplan.rs index 36bc362..c6c8395 100644 --- a/src-tauri/src/tool/powerplan.rs +++ b/src-tauri/src/tool/powerplan.rs @@ -39,6 +39,7 @@ impl PowerPlan { .get(&mode) .ok_or("Invalid power plan number (expect from 1 to 4)")?; + #[cfg(target_os = "windows")] let output = Command::new("powercfg") .arg("/S") .arg(guid) @@ -46,6 +47,14 @@ impl PowerPlan { .output() .map_err(|e| format!("Failed to execute powercfg command: {}", e))?; + #[cfg(target_os = "macos")] + let output = Command::new("pmset") + .arg("-g") + .arg("plan") + .output() + .map_err(|e| format!("Failed to execute pmset command: {}", e))?; + + if !output.status.success() { return Err(format!( "Powercfg command failed: {}", @@ -57,12 +66,20 @@ impl PowerPlan { } pub fn get(&self) -> Result { + #[cfg(windows)] let output = Command::new("powercfg") .arg("/L") .creation_flags(CREATE_NO_WINDOW) .output() .map_err(|e| format!("Failed to execute powercfg command: {}", e))?; + #[cfg(target_os = "macos")] + let output = Command::new("pmset") + .arg("-g") + .arg("plan") + .output() + .map_err(|e| format!("Failed to execute pmset command: {}", e))?; + let output_str = String::from_utf8_lossy(&output.stdout); let re = regex::Regex::new(r"GUID:\s+(\S+)\s+\(\S+\)\s+\*") .map_err(|e| format!("Failed to compile regex: {}", e))?; diff --git a/src-tauri/src/vdf/mod.rs b/src-tauri/src/vdf/mod.rs index 28ac29e..294d73f 100644 --- a/src-tauri/src/vdf/mod.rs +++ b/src-tauri/src/vdf/mod.rs @@ -1,2 +1,2 @@ pub mod parse; -pub mod preset; +pub mod preset; \ No newline at end of file diff --git a/src-tauri/src/vdf/parse.rs b/src-tauri/src/vdf/parse.rs index 35c994e..1173a2c 100644 --- a/src-tauri/src/vdf/parse.rs +++ b/src-tauri/src/vdf/parse.rs @@ -1,11 +1,12 @@ pub fn to_json(vdf_data: &str) -> String { let linebreak = match std::env::consts::OS { - "macos" => "\r", + "macos" => "\n", //"\r", "windows" => "\n", "linux" => "\n", _ => "\n", }; + // NOTE: 这样会跳过顶层{} let startpoint = vdf_data.find('{').unwrap_or(0); let vdf_data = &vdf_data[startpoint..]; @@ -31,49 +32,108 @@ pub fn to_json(vdf_data: &str) -> String { json_data.push_str(&line); } + // let json_str = json_data json_data = json_data .replace(",}", "}") .trim_start_matches(": ") .trim_end_matches(',') .to_string(); + // json_data = format!("{{{}}}", json_str); - json_data + return json_data; +} + +pub fn to_vdf(json_data: &str) -> String { + let json_value: serde_json::Value = serde_json::from_str(json_data).unwrap(); + let mut vdf_data = String::new(); + build_vdf(&json_value, &mut vdf_data, 0); + vdf_data +} + +fn build_vdf(json_value: &serde_json::Value, vdf_data: &mut String, indent_level: usize) { + match json_value { + serde_json::Value::Object(obj) => { + for (key, value) in obj { + vdf_data.push_str(&"\t".repeat(indent_level)); + vdf_data.push_str(&format!("\"{}\"\n", key)); + vdf_data.push_str(&"\t".repeat(indent_level)); + vdf_data.push_str("{\n"); + build_vdf(value, vdf_data, indent_level + 1); + vdf_data.push_str(&"\t".repeat(indent_level)); + vdf_data.push_str("}\n"); + } + } + serde_json::Value::String(s) => { + vdf_data.push_str(&"\t".repeat(indent_level)); + vdf_data.push_str(&format!("\"{}\"\t\t\"{}\"\n", s, s)); + } + _ => { + vdf_data.push_str(&"\t".repeat(indent_level)); + vdf_data.push_str(&format!("\"{}\"\t\t\"{}\"\n", json_value, json_value)); + } + } } mod tests { use super::*; + static VDF_DATA: &str = r#""users" + { + "76561198315078806" + { + "AccountName" "_jerry_dota2" + "PersonaName" "Rop紫(已黑化)" + "RememberPassword" "1" + "WantsOfflineMode" "0" + "SkipOfflineModeWarning" "0" + "AllowAutoLogin" "1" + "MostRecent" "1" + "Timestamp" "1742706884" + } + "76561198107125441" + { + "AccountName" "_im_ai_" + "PersonaName" "Buongiorno" + "RememberPassword" "1" + "WantsOfflineMode" "0" + "SkipOfflineModeWarning" "0" + "AllowAutoLogin" "1" + "MostRecent" "0" + "Timestamp" "1739093763" + } + } + "#; + + static JSON_DATA: &str = r#"{ + "users": { + "76561198315078806": { + "AccountName": "_jerry_dota2", + "PersonaName": "Rop紫(已黑化)", + "RememberPassword": "1", + "WantsOfflineMode": "0", + "SkipOfflineModeWarning": "0", + "AllowAutoLogin": "1", + "MostRecent": "1", + "Timestamp": "1742706884" + }, + "76561198107125441": { + "AccountName": "_im_ai_", + "PersonaName": "Buongiorno", + "RememberPassword": "1", + "WantsOfflineMode": "0", + "SkipOfflineModeWarning": "0", + "AllowAutoLogin": "1", + "MostRecent": "0", + "Timestamp": "1739093763" + } + } + }"#; + #[test] fn test_to_json() { - let vdf_data = "\"users\" - { - \"76561198315078806\" - { - \"AccountName\" \"_jerry_dota2\" - \"PersonaName\" \"Rop紫(已黑化)\" - \"RememberPassword\" \"1\" - \"WantsOfflineMode\" \"0\" - \"SkipOfflineModeWarning\" \"0\" - \"AllowAutoLogin\" \"1\" - \"MostRecent\" \"1\" - \"Timestamp\" \"1742706884\" - } - \"76561198107125441\" - { - \"AccountName\" \"_im_ai_\" - \"PersonaName\" \"Buongiorno\" - \"RememberPassword\" \"1\" - \"WantsOfflineMode\" \"0\" - \"SkipOfflineModeWarning\" \"0\" - \"AllowAutoLogin\" \"1\" - \"MostRecent\" \"0\" - \"Timestamp\" \"1739093763\" - } - } -"; - // let expected_json = r#"{"key1": "value1","key2": "value2","subkey": {"key3": "value3"}}"#; - let json_data = to_json(vdf_data); + let json_data = to_json(VDF_DATA); + println!("{}", json_data); // 解析json let json_value: serde_json::Value = serde_json::from_str(&json_data).unwrap(); @@ -81,4 +141,12 @@ mod tests { println!("{}", json_value) // assert_eq!(to_json(vdf_data), expected_json); } + + #[test] + fn test_to_vdf() { + // let json_data = r#"{"key1": "value1","key2": "value2","subkey": {"key3": "value3"}}"#; + let vdf_data = to_vdf(JSON_DATA); + + println!("{}", vdf_data); + } } diff --git a/src-tauri/src/vdf/preset.rs b/src-tauri/src/vdf/preset.rs index 37e9932..902418b 100644 --- a/src-tauri/src/vdf/preset.rs +++ b/src-tauri/src/vdf/preset.rs @@ -1,6 +1,7 @@ use anyhow::Result; use base64::engine::general_purpose::STANDARD; use base64::Engine; +use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; @@ -44,6 +45,81 @@ pub struct LocalUser { avatar_key: String, } +#[derive(Serialize, Deserialize, Debug)] +pub struct VideoConfig { + version: String, + vendor_id: String, + device_id: String, + cpu_level: String, + gpu_mem_level: String, + gpu_level: String, + knowndevice: String, + defaultres: String, + defaultresheight: String, + refreshrate_numerator: String, + refreshrate_denominator: String, + fullscreen: String, + coop_fullscreen: String, + nowindowborder: String, + mat_vsync: String, + fullscreen_min_on_focus_loss: String, + high_dpi: String, + auto_config: String, + shaderquality: String, + r_texturefilteringquality: String, + msaa_samples: String, + r_csgo_cmaa_enable: String, + videocfg_shadow_quality: String, + videocfg_dynamic_shadows: String, + videocfg_texture_detail: String, + videocfg_particle_detail: String, + videocfg_ao_detail: String, + videocfg_hdr_detail: String, + videocfg_fsr_detail: String, + monitor_index: String, + r_low_latency: String, + aspectratiomode: String, +} + +impl Default for VideoConfig { + fn default() -> Self { + VideoConfig { + version: "15".to_string(), + vendor_id: "0".to_string(), + device_id: "0".to_string(), + cpu_level: "3".to_string(), + gpu_mem_level: "3".to_string(), + gpu_level: "3".to_string(), + knowndevice: "0".to_string(), + defaultres: "1920".to_string(), + defaultresheight: "1080".to_string(), + refreshrate_numerator: "144".to_string(), + refreshrate_denominator: "1".to_string(), + fullscreen: "1".to_string(), + coop_fullscreen: "0".to_string(), + nowindowborder: "1".to_string(), + mat_vsync: "0".to_string(), + fullscreen_min_on_focus_loss: "1".to_string(), + high_dpi: "0".to_string(), + auto_config: "2".to_string(), + shaderquality: "0".to_string(), + r_texturefilteringquality: "3".to_string(), + msaa_samples: "2".to_string(), + r_csgo_cmaa_enable: "0".to_string(), + videocfg_shadow_quality: "0".to_string(), + videocfg_dynamic_shadows: "1".to_string(), + videocfg_texture_detail: "1".to_string(), + videocfg_particle_detail: "0".to_string(), + videocfg_ao_detail: "0".to_string(), + videocfg_hdr_detail: "3".to_string(), + videocfg_fsr_detail: "0".to_string(), + monitor_index: "0".to_string(), + r_low_latency: "1".to_string(), + aspectratiomode: "0".to_string(), + } + } +} + pub fn parse_login_users(steam_dir: &str) -> Result> { let t_path = Path::new(steam_dir).join("config/loginusers.vdf"); if !t_path.exists() { @@ -151,6 +227,9 @@ pub fn parse_local_users(steam_dir: &str) -> Result> { let json_data = super::parse::to_json(&data); let kv: HashMap = serde_json::from_str(&json_data)?; + // 剥离顶层 UserLocalConfigStore + // let kv = kv.get("UserLocalConfigStore").and_then(|v| v.as_object()).unwrap(); + // 获取 friends 节点 let friends = kv.get("friends").and_then(|v| v.as_object()); if friends.is_none() { @@ -270,6 +349,184 @@ fn read_avatar(steam_dir: &str, steam_id64: &str) -> Option { } } +pub fn get_cs2_video(file_path: &str) -> Result { + // TODO: no kv + let data = fs::read_to_string(file_path)?; + let json_data = super::parse::to_json(&data); + let kv: HashMap = serde_json::from_str(&json_data)?; + let video_config = VideoConfig { + version: kv.get("Version").unwrap_or(&"".to_string()).to_string(), + vendor_id: kv.get("VendorID").unwrap_or(&"".to_string()).to_string(), + device_id: kv.get("DeviceID").unwrap_or(&"".to_string()).to_string(), + cpu_level: kv + .get("setting.cpu_level") + .unwrap_or(&"".to_string()) + .to_string(), + gpu_mem_level: kv + .get("setting.gpu_mem_level") + .unwrap_or(&"".to_string()) + .to_string(), + gpu_level: kv + .get("setting.gpu_level") + .unwrap_or(&"".to_string()) + .to_string(), + knowndevice: kv + .get("setting.knowndevice") + .unwrap_or(&"".to_string()) + .to_string(), + defaultres: kv + .get("setting.defaultres") + .unwrap_or(&"".to_string()) + .to_string(), + defaultresheight: kv + .get("setting.defaultresheight") + .unwrap_or(&"".to_string()) + .to_string(), + refreshrate_numerator: kv + .get("setting.refreshrate_numerator") + .unwrap_or(&"".to_string()) + .to_string(), + refreshrate_denominator: kv + .get("setting.refreshrate_denominator") + .unwrap_or(&"".to_string()) + .to_string(), + fullscreen: kv + .get("setting.fullscreen") + .unwrap_or(&"".to_string()) + .to_string(), + coop_fullscreen: kv + .get("setting.coop_fullscreen") + .unwrap_or(&"".to_string()) + .to_string(), + nowindowborder: kv + .get("setting.nowindowborder") + .unwrap_or(&"".to_string()) + .to_string(), + mat_vsync: kv + .get("setting.mat_vsync") + .unwrap_or(&"".to_string()) + .to_string(), + fullscreen_min_on_focus_loss: kv + .get("setting.fullscreen_min_on_focus_loss") + .unwrap_or(&"".to_string()) + .to_string(), + high_dpi: kv + .get("setting.high_dpi") + .unwrap_or(&"".to_string()) + .to_string(), + auto_config: kv.get("AutoConfig").unwrap_or(&"".to_string()).to_string(), + shaderquality: kv + .get("setting.shaderquality") + .unwrap_or(&"".to_string()) + .to_string(), + r_texturefilteringquality: kv + .get("setting.r_texturefilteringquality") + .unwrap_or(&"".to_string()) + .to_string(), + msaa_samples: kv + .get("setting.msaa_samples") + .unwrap_or(&"".to_string()) + .to_string(), + r_csgo_cmaa_enable: kv + .get("setting.r_csgo_cmaa_enable") + .unwrap_or(&"".to_string()) + .to_string(), + videocfg_shadow_quality: kv + .get("setting.videocfg_shadow_quality") + .unwrap_or(&"".to_string()) + .to_string(), + videocfg_dynamic_shadows: kv + .get("setting.videocfg_dynamic_shadows") + .unwrap_or(&"".to_string()) + .to_string(), + videocfg_texture_detail: kv + .get("setting.videocfg_texture_detail") + .unwrap_or(&"".to_string()) + .to_string(), + videocfg_particle_detail: kv + .get("setting.videocfg_particle_detail") + .unwrap_or(&"".to_string()) + .to_string(), + videocfg_ao_detail: kv + .get("setting.videocfg_ao_detail") + .unwrap_or(&"".to_string()) + .to_string(), + videocfg_hdr_detail: kv + .get("setting.videocfg_hdr_detail") + .unwrap_or(&"".to_string()) + .to_string(), + videocfg_fsr_detail: kv + .get("setting.videocfg_fsr_detail") + .unwrap_or(&"".to_string()) + .to_string(), + monitor_index: kv + .get("setting.monitor_index") + .unwrap_or(&"".to_string()) + .to_string(), + r_low_latency: kv + .get("setting.r_low_latency") + .unwrap_or(&"".to_string()) + .to_string(), + aspectratiomode: kv + .get("setting.aspectratiomode") + .unwrap_or(&"".to_string()) + .to_string(), + }; + Ok(video_config) +} + +pub fn set_cs2_video(file_path: &str, data: VideoConfig) -> Result<()> { + // 读取文件内容 + let file_content = fs::read_to_string(file_path)?; + + // 定义正则表达式匹配模式 + let re = Regex::new(r#""(setting\.\w+)"\s+"-?\d+""#).unwrap(); + + // 替换字段值 + let updated_content = re.replace_all(&file_content, |caps: ®ex::Captures| { + let key = &caps[1]; // 捕获的键名 + let value = match key { + "Version" => &data.version, + "VendorID" => &data.vendor_id, + "DeviceID" => &data.device_id, + "setting.cpu_level" => &data.cpu_level, + "setting.gpu_mem_level" => &data.gpu_mem_level, + "setting.gpu_level" => &data.gpu_level, + "setting.knowndevice" => &data.knowndevice, + "setting.defaultres" => &data.defaultres, + "setting.defaultresheight" => &data.defaultresheight, + "setting.refreshrate_numerator" => &data.refreshrate_numerator, + "setting.refreshrate_denominator" => &data.refreshrate_denominator, + "setting.fullscreen" => &data.fullscreen, + "setting.coop_fullscreen" => &data.coop_fullscreen, + "setting.nowindowborder" => &data.nowindowborder, + "setting.mat_vsync" => &data.mat_vsync, + "setting.fullscreen_min_on_focus_loss" => &data.fullscreen_min_on_focus_loss, + "setting.high_dpi" => &data.high_dpi, + "AutoConfig" => &data.auto_config, + "setting.shaderquality" => &data.shaderquality, + "setting.r_texturefilteringquality" => &data.r_texturefilteringquality, + "setting.msaa_samples" => &data.msaa_samples, + "setting.r_csgo_cmaa_enable" => &data.r_csgo_cmaa_enable, + "setting.videocfg_shadow_quality" => &data.videocfg_shadow_quality, + "setting.videocfg_dynamic_shadows" => &data.videocfg_dynamic_shadows, + "setting.videocfg_texture_detail" => &data.videocfg_texture_detail, + "setting.videocfg_particle_detail" => &data.videocfg_particle_detail, + "setting.videocfg_ao_detail" => &data.videocfg_ao_detail, + "setting.videocfg_hdr_detail" => &data.videocfg_hdr_detail, + "setting.videocfg_fsr_detail" => &data.videocfg_fsr_detail, + "setting.monitor_index" => &data.monitor_index, + "setting.r_low_latency" => &data.r_low_latency, + "setting.aspectratiomode" => &data.aspectratiomode, + _ => "", // 默认情况 + }; + format!(r#""{}" "{}""#, key, value) + }); + + fs::write(file_path, updated_content.as_ref())?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -279,4 +536,25 @@ mod tests { let users = get_users("D:\\Programs\\Steam").unwrap(); println!("{:?}", users); } + + #[test] + fn test_get_cs2_video() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let file_path = format!("{}/src/vdf/tests/cs2_video.txt", manifest_dir); + let video_config = get_cs2_video(&file_path).unwrap(); + println!("{:?}", video_config); + } + + #[test] + fn test_set_cs2_video() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let file_path = format!("{}/temp/cs2_video.txt", manifest_dir); + fs::copy( + format!("{}/src/vdf/tests/cs2_video.txt", manifest_dir), + file_path.clone(), + ) + .unwrap(); + let video_config = VideoConfig::default(); + set_cs2_video(&file_path, video_config).unwrap(); + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 45a2cc1..116502d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -42,7 +42,7 @@ }, "productName": "CS工具箱", "mainBinaryName": "cstb", - "version": "0.0.5-beta.4", + "version": "0.0.5", "identifier": "upup.cool", "plugins": { "deep-link": { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f718222..7f92ba3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -45,7 +45,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) addToast({ title: `电源计划已切换 → ${PowerPlans[current].title}` }) }) - }) + }, []) // 检测steam路径和游戏路径是否有效 const debounceSteamDir = useDebounce(steam.state.steamDir, { diff --git a/src/components/cstb/VideoSetting.tsx b/src/components/cstb/VideoSetting.tsx index 6934b6f..94ae728 100644 --- a/src/components/cstb/VideoSetting.tsx +++ b/src/components/cstb/VideoSetting.tsx @@ -1,178 +1,452 @@ import { CloseSmall, Down, Edit, Plus, SettingConfig, Up } from "@icon-park/react" -import { useState } from "react" +import { useEffect, useState } from "react" import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card" import { ToolButton } from "../window/ToolButton" import { addToast, NumberInput, Tab, Tabs, Tooltip } from "@heroui/react" import { motion } from "framer-motion" -import { useToolStore } from "@/store/tool" +import { useToolStore, VideoSetting as VideoConfig, VideoSettingTemplate } from "@/store/tool" +import { useSteamStore } from "@/store/steam" const VideoSetting = () => { const [hide, setHide] = useState(false) const [edit, setEdit] = useState(false) const tool = useToolStore() - // const [launchOpt, setLaunchOpt] = useState(tool.state.VideoSettings[tool.state.launchIndex] || "") + const steam = useSteamStore() + const videoSettings = (video: VideoConfig) => { + return [ + { + type: "fullscreen", + title: "全屏", + value: video.fullscreen === "1" ? "全屏" : "窗口", + options: ["窗口", "全屏"], + mapping: (value: string) => { + return ( + { + 窗口: "0", + 全屏: "1", + }[value] || "0" + ) + }, + }, + { + type: "mat_vsync", + title: "垂直同步", + value: video.mat_vsync === "1" ? "开启" : "关闭", + options: ["关闭", "开启"], + mapping: (value: string) => { + return ( + { + 关闭: "0", + 开启: "1", + }[value] || "0" + ) + }, + }, + { + type: "r_low_latency", + title: "低延迟模式", + value: video.r_low_latency === "1" ? "开启" : "关闭", + options: ["关闭", "开启"], + mapping: (value: string) => { + return ( + { + 关闭: "0", + 开启: "1", + }[value] || "0" + ) + }, + }, + // TODO: 改选项不在 cs2_video.txt 中 + // { + // type: "r_player_visible_mode", + // title: "增强角色对比度", + // value: video.r_csgo_cmaa_enable === "1" ? "启用" : "禁用", + // options: ["禁用", "启用"], + // }, + { + type: "r_csgo_cmaa_enable", + title: "CMAA2抗锯齿", + value: video.r_csgo_cmaa_enable === "1" ? "开启" : "关闭", + options: ["关闭", "开启"], + mapping: (value: string) => { + return ( + { + 关闭: "0", + 开启: "1", + }[value] || "0" + ) + }, + }, + { + type: "msaa_samples", + title: "多重采样抗锯齿", + value: + { + 0: "无", + 2: "2X MSAA", + 4: "4X MSAA", + 8: "8X MSAA", + }[parseInt(video.msaa_samples, 10)] || "无", + options: ["无", "2X MSAA", "4X MSAA", "8X MSAA"], + mapping: (value: string) => { + return ( + { + 无: "0", + "2X MSAA": "2", + "4X MSAA": "4", + "8X MSAA": "8", + }[value] || "0" + ) + }, + }, + { + type: "videocfg_shadow_quality", + title: "全局阴影效果", + value: ["低", "中", "高", "非常高"][parseInt(video.videocfg_shadow_quality, 10)] || "低", + options: ["低", "中", "高", "非常高"], + mapping: (value: string) => { + return ( + { + 低: "0", + 中: "1", + 高: "2", + 非常高: "3", + }[value] || "0" + ) + }, + }, + { + type: "videocfg_dynamic_shadows", + title: "动态阴影", + value: ["仅限日光", "全部"][parseInt(video.videocfg_dynamic_shadows, 10)] || "仅限日光", + options: ["仅限日光", "全部"], + mapping: (value: string) => { + return ( + { + 仅限日光: "0", + 全部: "1", + }[value] || "0" + ) + }, + }, + { + type: "videocfg_texture_detail", + title: "模型/贴图细节", + value: ["低", "中", "高"][parseInt(video.videocfg_texture_detail, 10)] || "低", + options: ["低", "中", "高"], + mapping: (value: string) => { + return ( + { + 低: "0", + 中: "1", + 高: "2", + }[value] || "0" + ) + }, + }, + { + type: "r_texturefilteringquality", + title: "贴图过滤模式", + value: + ["双线性", "三线性", "异向 2X", "异向 4X", "异向 8X", "异向 16X"][ + parseInt(video.r_texturefilteringquality, 10) + ] || "双线性", + options: ["双线性", "三线性", "异向 2X", "异向 4X", "异向 8X", "异向 16X"], + mapping: (value: string) => { + return ( + { + 双线性: "0", + 三线性: "1", + "异向 2X": "2", + "异向 4X": "3", + "异向 8X": "4", + "异向 16X": "5", + }[value] || "0" + ) + }, + }, + { + type: "shaderquality", + title: "光影细节", + value: video.shaderquality === "1" ? "高" : "低", + options: ["低", "高"], + mapping: (value: string) => { + return ( + { + 低: "0", + 高: "1", + }[value] || "0" + ) + }, + }, + { + type: "videocfg_particle_detail", + title: "粒子细节", + value: ["低", "中", "高", "非常高"][parseInt(video.videocfg_particle_detail, 10)] || "低", + options: ["低", "中", "高", "非常高"], + mapping: (value: string) => { + return ( + { + 低: "0", + 中: "1", + 高: "2", + 非常高: "3", + }[value] || "低" + ) + }, + }, + { + type: "videocfg_ao_detail", + title: "环境光遮蔽", + value: ["已禁用", "中", "高"][parseInt(video.videocfg_ao_detail, 10)] || "已禁用", + options: ["已禁用", "中", "高"], + mapping: (value: string) => { + return ( + { + 已禁用: "0", + 中: "1", + 高: "2", + }[value] || "已禁用" + ) + }, + }, + { + type: "videocfg_hdr_detail", + title: "高动态范围", + value: video.videocfg_hdr_detail === "-1" ? "品质" : "性能", + options: ["性能", "品质"], + mapping: (value: string) => { + return ( + { + 性能: "3", + 品质: "-1", + }[value] || "3" + ) + }, + }, + { + type: "videocfg_fsr_detail", + title: "Fidelity FX 超级分辨率", + value: + ["已禁用", "超高品质", "品质", "均衡", "性能"][parseInt(video.videocfg_fsr_detail, 10)] || + "性能", + options: ["性能", "均衡", "品质", "超高品质", "已禁用"], + mapping: (value: string) => { + return ( + { + 已禁用: "0", + 超高品质: "1", + 品质: "2", + 均衡: "3", + 性能: "4", + }[value] || "已禁用" + ) + }, + }, + ] + } - // useEffect(() => { - // setLaunchOpt(tool.state.VideoSettings[tool.state.launchIndex] || "") - // }, [tool.state.launchIndex, tool.state.VideoSettings]) + const [vconfig, setVconfig] = useState(tool.state.videoSetting) - // 设置对应关系 - // TODO Value通过实际数值映射 - const videoSettings = [ - { type: "", title: "全屏", value: "全屏", options: ["窗口", "全屏"] }, - { type: "", title: "垂直同步", value: "关闭", options: ["关闭", "开启"] }, - { type: "", title: "低延迟模式", value: "关闭", options: ["关闭", "开启"] }, - { type: "", title: "增强角色对比度", value: "禁用", options: ["禁用", "启用"] }, - { type: "", title: "CMAA2抗锯齿", value: "关闭", options: ["关闭", "开启"] }, - { - type: "", - title: "多重采样抗锯齿", - value: "2X MSAA", - options: ["无", "2X MSAA", "4X MSAA", "8X MSAA"], - }, - { type: "", title: "全局阴影效果", value: "低", options: ["低", "中", "高", "非常高"] }, - { type: "", title: "动态阴影", value: "全部", options: ["仅限日光", "全部"] }, - { type: "", title: "模型/贴图细节", value: "中", options: ["低", "中", "高"] }, - { - type: "", - title: "贴图过滤模式", - value: "异向 4X", - options: ["双线性", "三线性", "异向 2X", "异向 4X", "异向 8X", "异向 16X"], - }, - { type: "", title: "光影细节", value: "低", options: ["低", "高"] }, - { type: "", title: "粒子细节", value: "低", options: ["低", "中", "高", "非常高"] }, - { type: "", title: "环境光遮蔽", value: "已禁用", options: ["已禁用", "中", "高"] }, - { type: "", title: "高动态范围", value: "性能", options: ["性能", "品质"] }, - { - type: "", - title: "Fidelity FX 超级分辨率", - value: "已禁用", - options: ["性能", "均衡", "品质", "超高品质", "已禁用"], - }, - ] + useEffect(() => { + if (steam.state.steamDirValid && steam.currentUser()) + void tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0) + }, []) return ( - - - - - 视频设置 - - - {/* {tool.state.VideoSettings.map((option, index) => ( + + + + 视频设置 + + + {/* {tool.state.VideoSettings.map((option, index) => ( tool.setLaunchIndex(index)}> {index + 1} ))} */} - {edit && ( + {edit && ( + <> + setVconfig({ ...vconfig, ...VideoSettingTemplate.low })}> + 低 + + setVconfig({ ...vconfig, ...VideoSettingTemplate.middle })} + > + 中 + + setVconfig({ ...vconfig, ...VideoSettingTemplate.high })}> + 高 + + setVconfig({ ...vconfig, ...VideoSettingTemplate.veryhigh })} + > + 非常高 + + setVconfig({ ...vconfig, ...VideoSettingTemplate.recommend })} + > + 推荐 + + { + await tool.setVideoConfig( + steam.state.steamDir, + steam.currentUser()?.steam_id32 || 0, + vconfig + ) + await tool.getVideoConfig( + steam.state.steamDir, + steam.currentUser()?.steam_id32 || 0 + ) + setEdit(false) + addToast({ title: "应用设置成功" }) + }} + > + + 应用 + + + )} + { + if (steam.state.steamDirValid && steam.currentUser()) + await tool.getVideoConfig( + steam.state.steamDir, + steam.currentUser()?.steam_id32 || 0 + ) + setVconfig(tool.state.videoSetting) + setEdit(!edit) + }} + > + {edit ? ( <> - - - - 非常高 - 推荐 - { - addToast({ title: "测试中 功能完成后可应用设置到游戏" }) - }} - > - - 应用 - + + 取消编辑 + + ) : ( + <> + + 编辑 )} - setEdit(!edit)}> - {edit ? ( - <> - - 取消编辑 - - ) : ( - <> - - 编辑 - - )} - - setHide(!hide)}> - {hide ? ( - <> - - 显示 - - ) : ( - <> - - 隐藏 - - )} - - - - {!hide && ( - + + { + if (steam.state.steamDirValid && steam.currentUser()) { + await tool.getVideoConfig( + steam.state.steamDir, + steam.currentUser()?.steam_id32 || 0 + ) + addToast({ title: "读取成功" }) + } else addToast({ title: "请先选择用户", color: "danger" }) + }} > - -
    -
  • - 分辨率 - - { - tool.setVideoSetting({ - ...tool.state.videoSetting, - width: value, - }) - }} - radius="full" - step={10} - className="max-w-28" - classNames={{ inputWrapper: "h-10" }} - /> - { - tool.setVideoSetting({ - ...tool.state.videoSetting, - height: value, - }) - }} - radius="full" - step={10} - className="max-w-28" - classNames={{ inputWrapper: "h-10" }} - /> - + 读取 + + setHide(!hide)}> + {hide ? ( + <> + + 显示 + + ) : ( + <> + + 隐藏 + + )} + + + + {!hide && ( + + +
      +
    • + 分辨率 + + { + const _ = edit + ? setVconfig({ + ...vconfig, + defaultres: value.toString(), + }) + : tool.setVideoSetting({ + ...tool.state.videoSetting, + defaultres: value.toString(), + }) + }} + radius="full" + step={10} + className="max-w-28" + classNames={{ inputWrapper: "h-10" }} + /> + { + const _ = edit + ? setVconfig({ + ...vconfig, + defaultresheight: value.toString(), + }) + : tool.setVideoSetting({ + ...tool.state.videoSetting, + defaultresheight: value.toString(), + }) + }} + radius="full" + step={10} + className="max-w-28" + classNames={{ inputWrapper: "h-10" }} + /> + +
    • + {videoSettings(edit ? vconfig : tool.state.videoSetting).map((vid, index) => ( +
    • + {vid.title} + { + // console.log(vid.type, key) + // 修改 vconfig 名为 vid.type 的 value为 key + const _ = + edit && key + ? setVconfig({ + ...vconfig, + [vid.type]: vid.mapping(key.toString()), + }) + : null + }} + > + {vid.options.map((opt, _) => ( + + ))} +
    • - {videoSettings.map((vid, index) => ( -
    • - {vid.title} - - {vid.options.map((opt, _) => ( - - ))} - -
    • - ))} -
    -
    -
    - )} - - + ))} +
+
+
+ )} +
) } diff --git a/src/store/tool.ts b/src/store/tool.ts index 42018e0..ea1570f 100644 --- a/src/store/tool.ts +++ b/src/store/tool.ts @@ -2,7 +2,8 @@ import { store } from "@tauri-store/valtio" import { useSnapshot } from "valtio" import { DEFAULT_STORE_CONFIG } from "./config" import { emit } from "@tauri-apps/api/event" -import { send } from "process" +import { invoke } from "@tauri-apps/api/core" +import VideoSetting from "@/components/cstb/VideoSetting" interface LaunchOption { option: string @@ -10,24 +11,109 @@ interface LaunchOption { } export interface VideoSetting { - width: number; // 分辨率宽度 - height: number; // 分辨率高度 + version: string; // 版本 + vendor_id: string; // 供应商ID + device_id: string; // 设备ID + cpu_level: string; // CPU等级 + gpu_mem_level: string; // GPU内存等级 + gpu_level: string; // GPU等级 + knowndevice: string; // 已知设备 + defaultres: string; // 默认分辨率宽度 + defaultresheight: string; // 默认分辨率高度 + refreshrate_numerator: string; // 刷新率分子 + refreshrate_denominator: string; // 刷新率分母 fullscreen: string; // 全屏 - vsync: string; // 垂直同步 - enhanceCharacterContrast: string; // 增强角色对比度 - cmaa2AntiAliasing: string; // CMAA2抗锯齿 - msaaAntiAliasing: string; // 多重采样抗锯齿 - globalShadowQuality: string; // 全局阴影效果 - dynamicShadows: string; // 动态阴影 - modelTextureDetail: string; // 模型/贴图细节 - textureFilteringMode: string; // 贴图过滤模式 - lightShadowDetail: string; // 光影细节 - particleDetail: string; // 粒子细节 - ambientOcclusion: string; // 环境光遮蔽 - hdr: string; // 高动态范围 - fidelityFxSuperResolution: string; // Fidelity FX 超级分辨率 + coop_fullscreen: string; // 合作模式全屏 + nowindowborder: string; // 无窗口边框 + mat_vsync: string; // 垂直同步 + fullscreen_min_on_focus_loss: string; // 失去焦点时最小化全屏 + high_dpi: string; // 高DPI + auto_config: string; // 自动配置 + shaderquality: string; // 光影质量 + r_texturefilteringquality: string; // 纹理过滤质量 + msaa_samples: string; // 多重采样抗锯齿样本数 + r_csgo_cmaa_enable: string; // CMAA抗锯齿启用 + videocfg_shadow_quality: string; // 阴影质量 + videocfg_dynamic_shadows: string; // 动态阴影 + videocfg_texture_detail: string; // 纹理细节 + videocfg_particle_detail: string; // 粒子细节 + videocfg_ao_detail: string; // 环境光遮蔽细节 + videocfg_hdr_detail: string; // 高动态范围细节 + videocfg_fsr_detail: string; // FSR细节 + monitor_index: string; // 显示器索引 + r_low_latency: string; // 低延迟 + aspectratiomode: string; // 宽高比模式 } +// 视频设置预设模版 +export const VideoSettingTemplate = { + veryhigh: { + shaderquality: "1", + r_texturefilteringquality: "3", + msaa_samples: "8", + r_csgo_cmaa_enable: "0", + videocfg_shadow_quality: "3", + videocfg_dynamic_shadows: "1", + videocfg_texture_detail: "2", + videocfg_particle_detail: "3", + videocfg_ao_detail: "3", + videocfg_hdr_detail: "-1", + videocfg_fsr_detail: "0", + }, + high: { + shaderquality: "1", + r_texturefilteringquality: "3", + msaa_samples: "4", + r_csgo_cmaa_enable: "0", + videocfg_shadow_quality: "2", + videocfg_dynamic_shadows: "1", + videocfg_texture_detail: "2", + videocfg_particle_detail: "2", + videocfg_ao_detail: "2", + videocfg_hdr_detail: "-1", + videocfg_fsr_detail: "0", + }, + middle: { + shaderquality: "0", + r_texturefilteringquality: "1", + msaa_samples: "2", + r_csgo_cmaa_enable: "0", + videocfg_shadow_quality: "1", + videocfg_dynamic_shadows: "1", + videocfg_texture_detail: "1", + videocfg_particle_detail: "1", + videocfg_ao_detail: "0", + videocfg_fsr_detail: "2", + }, + low: { + shaderquality: "0", + r_texturefilteringquality: "0", + msaa_samples: "0", + r_csgo_cmaa_enable: "0", + videocfg_shadow_quality: "0", + videocfg_dynamic_shadows: "0", + videocfg_texture_detail: "0", + videocfg_particle_detail: "0", + videocfg_ao_detail: "0", + videocfg_hdr_detail: "3", + videocfg_fsr_detail: "3", + }, + recommend: { + shaderquality: "0", + r_texturefilteringquality: "3", + msaa_samples: "2", + r_csgo_cmaa_enable: "0", + videocfg_shadow_quality: "0", + videocfg_dynamic_shadows: "1", + videocfg_texture_detail: "1", + videocfg_particle_detail: "0", + videocfg_ao_detail: "0", + videocfg_hdr_detail: "3", + videocfg_fsr_detail: "0", + }, +} + + const defaultValue = { launchOptions: [ { option: "-novid -high -freq 144 -fullscreen", name: "" }, @@ -37,8 +123,38 @@ const defaultValue = { launchIndex: 0, powerPlan: 0, videoSetting: { - width: 1920, - height: 1080 + version: "15", + vendor_id: "0", + device_id: "0", + cpu_level: "3", + gpu_mem_level: "3", + gpu_level: "3", + knowndevice: "0", + defaultres: "1920", + defaultresheight: "1080", + refreshrate_numerator: "144", + refreshrate_denominator: "1", + fullscreen: "1", + coop_fullscreen: "0", + nowindowborder: "1", + mat_vsync: "0", + fullscreen_min_on_focus_loss: "1", + high_dpi: "0", + auto_config: "2", + shaderquality: "0", + r_texturefilteringquality: "3", + msaa_samples: "2", + r_csgo_cmaa_enable: "0", + videocfg_shadow_quality: "0", + videocfg_dynamic_shadows: "1", + videocfg_texture_detail: "1", + videocfg_particle_detail: "0", + videocfg_ao_detail: "0", + videocfg_hdr_detail: "3", + videocfg_fsr_detail: "0", + monitor_index: "0", + r_low_latency: "1", + aspectratiomode: "0", } as VideoSetting, } @@ -67,6 +183,8 @@ export const useToolStore = () => { setLaunchIndex, setPowerPlan, setVideoSetting, + getVideoConfig, + setVideoConfig, addLaunchOption, resetToolStore, } @@ -104,6 +222,17 @@ const setVideoSetting = (setting: VideoSetting) => { toolStore.state.videoSetting = setting } +const getVideoConfig = async (steam_dir: string, steam_id32: number) => { + const video = await invoke("get_cs2_video_config", { steamDir: steam_dir, steamId32: steam_id32 }) + // console.log(video) + setVideoSetting(video) +} + +const setVideoConfig = async (steam_dir: string, steam_id32: number, video_config: VideoSetting) => { + console.log(video_config.videocfg_hdr_detail) + await invoke("set_cs2_video_config", { steamDir: steam_dir, steamId32: steam_id32, videoConfig: video_config }) +} + const addLaunchOption = (option: LaunchOption) => { // 限制最高10个 if (toolStore.state.launchOptions.length >= 10) {