diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 075fb06..a2fbe5d 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -156,6 +156,22 @@ pub fn check_path(path: &str) -> Result { Ok(std::path::Path::new(&path).exists()) } +#[tauri::command] +pub fn check_steam_dir_valid(steam_dir: &str) -> Result { + use std::path::Path; + + let path = Path::new(steam_dir); + if !path.exists() { + return Ok(false); + } + + // 检查是否存在 steam.exe 或 config 目录(至少有一个即可) + let steam_exe = path.join("steam.exe"); + let config_dir = path.join("config"); + + Ok(steam_exe.exists() || config_dir.exists()) +} + ///// 录像 #[tauri::command] pub async fn analyze_replay(app: tauri::AppHandle, path: &str) -> Result { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a13e5a4..a18afbb 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -162,6 +162,7 @@ fn main() { cmds::get_cs2_video_config, cmds::set_cs2_video_config, cmds::check_path, + cmds::check_steam_dir_valid, cmds::analyze_replay, cmds::get_console_log_path, cmds::read_vprof_report, diff --git a/src-tauri/src/vdf/preset.rs b/src-tauri/src/vdf/preset.rs index c82c029..a6a3adf 100644 --- a/src-tauri/src/vdf/preset.rs +++ b/src-tauri/src/vdf/preset.rs @@ -134,7 +134,10 @@ pub fn parse_login_users(steam_dir: &str) -> Result> { let mut users = Vec::new(); for (k, v) in kv { - let props = v.as_object().unwrap(); + let props = match v.as_object() { + Some(p) => p, + None => continue, // 跳过非对象类型的值 + }; let avatar = if let Some(img) = read_avatar(&steam_dir, &k) { img @@ -142,7 +145,11 @@ pub fn parse_login_users(steam_dir: &str) -> Result> { String::new() }; - let id64 = k.parse::()?; + // 跳过无法解析为 u64 的键 + let id64 = match k.parse::() { + Ok(id) => id, + Err(_) => continue, + }; let user = LoginUser { steam_id32: steam::id::id64_to_32(id64), @@ -216,7 +223,11 @@ pub fn parse_local_users(steam_dir: &str) -> Result> { // 只处理目录 if entry.file_type().is_dir() { - let id = path.file_name().unwrap().to_str().unwrap(); + // 安全获取文件名 + let id = match path.file_name().and_then(|n| n.to_str()) { + Some(id_str) => id_str, + None => continue, // 跳过无法获取文件名的路径 + }; // 检查 localconfig.vdf 文件是否存在 let local_config_path = path.join("config/localconfig.vdf"); @@ -224,21 +235,26 @@ pub fn parse_local_users(steam_dir: &str) -> Result> { continue; } - // 读取并解析 localconfig.vdf 文件 - let data = fs::read_to_string(local_config_path)?; + // 读取并解析 localconfig.vdf 文件,如果失败则跳过 + let data = match fs::read_to_string(&local_config_path) { + Ok(d) => d, + Err(_) => continue, // 跳过无法读取的文件 + }; + let json_data = super::parse::to_json(&data); - let kv: HashMap = serde_json::from_str(&json_data)?; + let kv = match serde_json::from_str::>(&json_data) { + Ok(kv) => kv, + Err(_) => continue, // 跳过无法解析的 JSON + }; // 剥离顶层 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() { - continue; - } - - let friends = friends.unwrap(); + let friends = match kv.get("friends").and_then(|v| v.as_object()) { + Some(f) => f, + None => continue, + }; // 获取 PersonaName let persona_name = friends @@ -256,9 +272,15 @@ pub fn parse_local_users(steam_dir: &str) -> Result> { .unwrap_or("") .to_string(); + // 安全解析 ID,如果失败则跳过 + let steam_id32 = match id.parse::() { + Ok(id) => id, + Err(_) => continue, // 跳过无法解析为 u32 的 ID + }; + // 创建 LocalUser 并加入列表 local_users.push(LocalUser { - steam_id32: id.parse::().unwrap(), + steam_id32, persona_name, avatar_key, }); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9e03466..909d98f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -19,12 +19,31 @@ export default function RootLayout({ children }: { children: React.ReactNode }) void init() void listen("tray://launch_game", async (event) => { - await invoke("launch_game", { - steamPath: `${steamStore.state.steamDir}\\steam.exe`, - launchOption: toolStore.state.launchOptions[toolStore.state.launchIndex].option || "", - server: event.payload || "worldwide", - }) - addToast({ title: `启动${event.payload === "worldwide" ? "国际服" : "国服"}成功` }) + // 验证路径 + if (!steamStore.state.steamDir || !steamStore.state.steamDirValid) { + addToast({ + title: "Steam 路径无效,请先配置路径", + color: "warning" + }) + return + } + try { + await invoke("launch_game", { + steamPath: `${steamStore.state.steamDir}\\steam.exe`, + launchOption: toolStore.state.launchOptions[toolStore.state.launchIndex].option || "", + server: event.payload || "worldwide", + }) + addToast({ + title: `启动${event.payload === "worldwide" ? "国际服" : "国服"}成功`, + color: "success" + }) + } catch (error) { + console.error("启动游戏失败:", error) + addToast({ + title: `启动失败: ${error instanceof Error ? error.message : String(error)}`, + color: "danger" + }) + } }) void listen("tray://kill_steam", async () => { @@ -82,10 +101,13 @@ export default function RootLayout({ children }: { children: React.ReactNode }) void steam.checkCs2DirValid() }, [debounceCs2Dir]) useEffect(() => { - if (debounceSteamDirValid) { + if (debounceSteamDirValid && steam.state.steamDir) { + // 安全地获取用户列表(内部已有错误处理) void steam.getUsers() - // 启动文件监听 - void invoke("start_watch_loginusers", { steamDir: steam.state.steamDir }) + // 启动文件监听,添加错误处理 + void invoke("start_watch_loginusers", { steamDir: steam.state.steamDir }).catch((error) => { + console.error("启动文件监听失败:", error) + }) } }, [debounceSteamDirValid, steam.state.steamDir]) diff --git a/src/components/cstb/FastLaunch.tsx b/src/components/cstb/FastLaunch.tsx index cd0ebf6..91f91e2 100644 --- a/src/components/cstb/FastLaunch.tsx +++ b/src/components/cstb/FastLaunch.tsx @@ -20,13 +20,29 @@ const FastLaunch = () => {
+
+ ) +} + export function FpsTest() { const steam = useSteamStore() const tool = useToolStore() @@ -291,9 +311,7 @@ export function FpsTest() { tool.state.videoSetting.defaultresheight || "" ) } - if (fpsTest.state.config.isFullscreen === true && tool.state.videoSetting.fullscreen !== "1") { - fpsTest.setIsFullscreen(tool.state.videoSetting.fullscreen === "1") - } + // 全屏/窗口化设置不再同步游戏设置,只控制启动项参数 } }, [tool.state.videoSetting, fpsTest]) @@ -360,14 +378,38 @@ export function FpsTest() { try { // 获取 console.log 路径 - const consoleLogPath = await invoke("get_console_log_path", { - csPath: steam.state.cs2Dir, - }) + let consoleLogPath: string + try { + consoleLogPath = await invoke("get_console_log_path", { + csPath: steam.state.cs2Dir, + }) + } catch (error) { + console.error("获取控制台日志路径失败:", error) + if (!silent) { + addToast({ + title: "获取控制台日志路径失败", + color: "warning" + }) + } + return false + } // 读取 VProf 报告 - const report = await invoke("read_vprof_report", { - consoleLogPath: consoleLogPath, - }) + let report: string + try { + report = await invoke("read_vprof_report", { + consoleLogPath: consoleLogPath, + }) + } catch (error) { + console.error("读取性能报告失败:", error) + if (!silent) { + addToast({ + title: "读取性能报告失败", + color: "warning" + }) + } + return false + } if (report && report.trim().length > 0) { const parsed = parseVProfReport(report) @@ -427,7 +469,7 @@ export function FpsTest() { // 添加带批量标识和分辨率信息的备注 let batchNote = "" if (currentResolution) { - batchNote = `[分辨率:${currentResolution.label}]` + batchNote = `[${currentResolution.label}]` } if (testNote) { batchNote = batchNote ? `${testNote} ${batchNote}` : testNote @@ -472,7 +514,7 @@ export function FpsTest() { // 单次测试,添加分辨率信息到备注 let singleNote = testNote if (currentResolution) { - const resolutionNote = `[分辨率:${currentResolution.label}]` + const resolutionNote = `[${currentResolution.label}]` singleNote = singleNote ? `${testNote} ${resolutionNote}` : resolutionNote } @@ -636,7 +678,21 @@ export function FpsTest() { isFirstTest: boolean = false, resolution?: { width: string; height: string; label: string } ): Promise => { + // 验证路径是否存在且有效 if (!steam.state.steamDir || !steam.state.cs2Dir) { + addToast({ + title: "Steam 或 CS2 路径未设置,请先配置路径", + color: "warning" + }) + return false + } + + // 验证 Steam 路径是否有效 + if (!steam.state.steamDirValid) { + addToast({ + title: "Steam 路径无效,请检查路径设置", + color: "warning" + }) return false } @@ -684,14 +740,14 @@ export function FpsTest() { label: `${resolutionWidth}x${resolutionHeight}`, } - // 只有在启用分辨率和全屏设置时才添加相关参数 - if (isResolutionEnabled) { + // 添加分辨率设置(如果启用分辨率功能或分辨率组) + if (isResolutionEnabled || isResolutionGroupEnabled) { // 添加分辨率设置(如果有设置) if (currentResolution.width && currentResolution.height) { baseLaunchOption += ` -w ${currentResolution.width} -h ${currentResolution.height}` } - // 添加全屏/窗口化设置 + // 添加全屏/窗口化设置(独立控制,不依赖游戏设置) if (isFullscreen) { baseLaunchOption += ` -fullscreen` } else { @@ -705,11 +761,20 @@ export function FpsTest() { : baseLaunchOption // 启动游戏(强制使用worldwide国际服) - await invoke("launch_game", { - steamPath: `${steam.state.steamDir}\\steam.exe`, - launchOption: launchOption, - server: "worldwide", - }) + try { + await invoke("launch_game", { + steamPath: `${steam.state.steamDir}\\steam.exe`, + launchOption: launchOption, + server: "worldwide", + }) + } catch (error) { + console.error("启动游戏失败:", error) + addToast({ + title: `启动游戏失败: ${error instanceof Error ? error.message : String(error)}`, + color: "danger" + }) + return false + } // 视频设置将在测试结束后读取(在readResult中) // 保存当前测试的分辨率信息 @@ -859,7 +924,7 @@ export function FpsTest() { const second = String(now.getSeconds()).padStart(2, "0") const testTime = `${month}/${day} ${hour}:${minute}:${second}` - let averageNote = `[分辨率:${currentResolution.label}]` + let averageNote = `[${currentResolution.label}]` if (testNote) { averageNote = `${testNote} ${averageNote}` } @@ -1209,121 +1274,102 @@ export function FpsTest() { {showResultsTable ? ( -
-
- - - 测试时间 - 测试地图 - 平均帧 - P1低帧 - CPU - 系统版本 - GPU - 内存 - 视频设置 - 备注 - 操作 - - - {fpsTest.state.results.map((result) => ( - - {result.testTime} - {result.mapLabel} - - {result.avg !== null ? `${result.avg.toFixed(1)}` : "N/A"} - - - {result.p1 !== null ? `${result.p1.toFixed(1)}` : "N/A"} - - -
- {result.hardwareInfo?.cpu || "N/A"} -
-
- -
- {result.hardwareInfo?.os || "N/A"} -
-
- -
- {result.hardwareInfo?.gpu || "N/A"} -
-
- - {result.hardwareInfo?.memory ? `${result.hardwareInfo.memory}GB` : "N/A"} - - - - - {result.videoSetting - ? `${result.videoSetting.defaultres}x${result.videoSetting.defaultresheight}` - : "N/A"} - - - - -
- - {result.note || "无备注"} - - -
-
- -
- -
-
-
- ))} -
-
-
+
+ + + 测试时间 + 测试地图 + 平均帧 + P1低帧 + CPU + 系统版本 + GPU + 内存 + 视频设置 + 备注 + 操作 + + + {fpsTest.state.results.map((result) => ( + + {result.testTime} + +
+ {result.mapLabel} +
+
+ + {result.avg !== null ? `${result.avg.toFixed(1)}` : "N/A"} + + + {result.p1 !== null ? `${result.p1.toFixed(1)}` : "N/A"} + + +
+ {result.hardwareInfo?.cpu || "N/A"} +
+
+ +
+ {result.hardwareInfo?.os || "N/A"} +
+
+ +
+ {result.hardwareInfo?.gpu || "N/A"} +
+
+ + {result.hardwareInfo?.memory ? `${result.hardwareInfo.memory}GB` : "N/A"} + + + + + {result.videoSetting + ? `${result.videoSetting.defaultres}x${result.videoSetting.defaultresheight}` + : "N/A"} + + + + + handleEditNote(result.id, result.note || "")} + /> + + +
+ +
+
+
+ ))} +
+
) : (
@@ -1554,7 +1600,7 @@ export function FpsTest() { variant={isFullscreen ? "solid" : "flat"} color={isFullscreen ? "primary" : "default"} onPress={() => fpsTest.setIsFullscreen(!isFullscreen)} - isDisabled={!isResolutionEnabled || isMonitoring} + isDisabled={isMonitoring || (!isResolutionEnabled && !isResolutionGroupEnabled)} className="font-medium" > {isFullscreen ? "全屏" : "窗口化"} diff --git a/src/components/cstb/VideoSetting.tsx b/src/components/cstb/VideoSetting.tsx index 11a28fa..576fd01 100644 --- a/src/components/cstb/VideoSetting.tsx +++ b/src/components/cstb/VideoSetting.tsx @@ -331,11 +331,15 @@ const VideoSetting = () => { useEffect(() => { if (steam.state.steamDirValid && steam.currentUser()) { + // 安全地获取视频配置(内部已有错误处理) void tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0) - // 启动文件监听 + // 启动文件监听,添加错误处理 void invoke("start_watch_cs2_video", { steamDir: steam.state.steamDir, steamId32: steam.currentUser()?.steam_id32 || 0, + }).catch((error) => { + console.error("启动视频配置文件监听失败:", error) + // 不显示错误提示,避免干扰用户 }) } // 清理函数:停止后端监听 diff --git a/src/store/steam.ts b/src/store/steam.ts index 462aa09..23cd6fb 100644 --- a/src/store/steam.ts +++ b/src/store/steam.ts @@ -74,11 +74,20 @@ const setCs2DirChecking = (checking: boolean) => { const checkSteamDirValid = async () => { setSteamDirChecking(true) - const pathExist = await invoke("check_path", { path: steamStore.state.steamDir }) - setSteamDirValid(pathExist) - setTimeout(() => { - setSteamDirChecking(false) - }, 500) + try { + // 使用专门的 Steam 路径验证,检查 steam.exe 或 config 目录 + const isValid = await invoke("check_steam_dir_valid", { + steamDir: steamStore.state.steamDir + }) + setSteamDirValid(isValid) + } catch (error) { + console.error("验证 Steam 路径时出错:", error) + setSteamDirValid(false) + } finally { + setTimeout(() => { + setSteamDirChecking(false) + }, 500) + } } const checkCs2DirValid = async () => { @@ -95,8 +104,21 @@ const currentUser = () => { } const getUsers = async () => { - const users = await invoke("get_steam_users", { steamDir: steamStore.state.steamDir }) - setUsers(users) + // 只有在路径有效时才尝试获取用户 + if (!steamStore.state.steamDirValid || !steamStore.state.steamDir) { + return + } + + try { + const users = await invoke("get_steam_users", { + steamDir: steamStore.state.steamDir + }) + setUsers(users) + } catch (error) { + console.error("获取 Steam 用户列表失败:", error) + // 如果获取失败,清空用户列表,避免显示错误数据 + setUsers([]) + } } const selectUser = (index: number) => { diff --git a/src/store/tool.ts b/src/store/tool.ts index 2ff441a..c002450 100644 --- a/src/store/tool.ts +++ b/src/store/tool.ts @@ -262,9 +262,15 @@ const setVideoSetting = (setting: VideoSetting) => { } 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) + try { + const video = await invoke("get_cs2_video_config", { steamDir: steam_dir, steamId32: steam_id32 }) + // console.log(video) + setVideoSetting(video) + } catch (error) { + console.error("读取视频配置失败:", error) + // 如果文件不存在或读取失败,使用默认配置或保持当前配置 + // 不抛出错误,避免影响其他功能 + } } const setVideoConfig = async (steam_dir: string, steam_id32: number, video_config: VideoSetting) => {