// Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod commands; mod steam_auth; mod browser_automation; mod api_client; mod config; mod key_storage; mod machine; mod storage; mod updater; mod auto_updater; mod error; mod security; mod heartbeat; mod app_logger; use commands::*; use browser_automation::*; use api_client::{ApiConfig as BackendApiConfig, initialize_api_client}; use config::*; use tauri::Manager; // 在最开头初始化日志系统(任何 panic 都要捕获) fn init_logging() { // 初始化应用日志系统(每日一个日志文件,写在 exe 同目录下) if let Err(e) = app_logger::init_app_logger() { eprintln!("Failed to init app logger: {}", e); } // 设置 panic hook + Windows 顶层异常处理器,确保程序崩溃时把堆栈信息写入日志文件 app_logger::setup_all_crash_handlers(); } fn main() { // 最优先初始化日志(panic hook 必须在任何可能 panic 之前设置) init_logging(); app_logger::info("应用主程序开始启动..."); // --- 单实例检测:防止同一台电脑启动多个窗口 --- #[cfg(target_os = "windows")] { use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::ptr; const MUTEX_NAME: &str = "SteamSH_SingleInstance_Mutex_v1"; let mutex_name_wide: Vec = OsStr::new(MUTEX_NAME) .encode_wide() .chain(std::iter::once(0)) .collect(); unsafe { let handle = winapi::um::synchapi::CreateMutexW( ptr::null_mut(), 0, // bInitialOwner = false,新实例不会获得锁的拥有权 mutex_name_wide.as_ptr(), ); if !handle.is_null() { // 检查是否已有互斥量(即已有实例运行) let already_exists = winapi::um::errhandlingapi::GetLastError() == winapi::shared::winerror::ERROR_ALREADY_EXISTS; if already_exists { // 已有实例运行,尝试激活它的主窗口,然后退出当前进程 app_logger::warn("检测到已有实例正在运行,激活已有窗口并退出当前进程"); // 尝试通过 FindWindow 找到并激活已有窗口 let class_name: Vec = OsStr::new("SteamSH") .encode_wide() .chain(std::iter::once(0)) .collect(); let title: Vec = OsStr::new("steam扫货(磨损版)") .encode_wide() .chain(std::iter::once(0)) .collect(); // 先尝试通过窗口类名找 let hwnd = winapi::um::winuser::FindWindowW(class_name.as_ptr(), ptr::null()); let hwnd = if hwnd.is_null() { // 再尝试通过窗口标题找 winapi::um::winuser::FindWindowW(ptr::null(), title.as_ptr()) } else { hwnd }; if !hwnd.is_null() { // 激活窗口并置于前台 winapi::um::winuser::SetForegroundWindow(hwnd); winapi::um::winuser::ShowWindow(hwnd, winapi::um::winuser::SW_RESTORE); winapi::um::winuser::BringWindowToTop(hwnd); } // 关闭 mutex 句柄并退出 winapi::um::handleapi::CloseHandle(handle); std::process::exit(0); } // 记录 mutex 句柄,在程序结束时关闭(通过 drop 或 at_exit) // 为确保程序结束时释放 mutex,这里用 Box::leak 存起来, // 让它在进程结束时由 OS 自动释放(进程退出时内核自动关闭所有句柄) Box::leak(Box::new(handle)); } } } // --- 单实例检测结束 --- // 阶段二:安全检查 if let Err(e) = security::perform_all_security_checks() { app_logger::error(&format!("安全检查失败: {}", e)); // 在生产环境中,安全检查失败应该显示错误信息后退出 if !cfg!(debug_assertions) { // 显示友好的错误对话框 #[cfg(target_os = "windows")] { use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; let msg: Vec = OsStr::new(&format!("安全检查失败:\n{}\n\n请检查系统环境后重试。\n\n详细信息请查看日志文件。", e)) .encode_wide() .chain(Some(0)) .collect(); let title: Vec = OsStr::new("SteamSH 启动失败") .encode_wide() .chain(Some(0)) .collect(); unsafe { extern "system" { fn MessageBoxW(hwnd: *mut std::ffi::c_void, text: *const u16, caption: *const u16, utype: u32) -> i32; } const MB_ICONERROR: u32 = 0x10; const MB_OK: u32 = 0x0; const MB_TOPMOST: u32 = 0x40000; MessageBoxW(std::ptr::null_mut(), msg.as_ptr(), title.as_ptr(), MB_ICONERROR | MB_OK | MB_TOPMOST); } } std::process::exit(1); } } app_logger::info("安全检查通过"); // 初始化存储系统 if let Err(e) = storage::init_storage() { app_logger::error(&format!("Failed to initialize storage: {}", e)); // 存储初始化失败不应该阻止程序启动 } // 初始化应用配置和API客户端 let app_config = AppConfig::load().unwrap_or_else(|e| { app_logger::error(&format!("Failed to load config: {}, using defaults", e)); AppConfig::default() }); // 初始化API客户端 let api_config = BackendApiConfig { base_url: app_config.api.base_url.clone(), timeout: app_config.api.timeout, software_code: app_config.api.software_code.clone(), }; if let Err(e) = initialize_api_client(api_config) { app_logger::error(&format!("Failed to initialize API client: {}", e)); // API客户端初始化失败也不阻止程序启动,用户可以在运行时重新配置 } tauri::Builder::default() .invoke_handler(tauri::generate_handler![ // Steam本地功能 generate_steam_code, fetch_confirmations, accept_confirmation, deny_confirmation, save_steam_account, load_steam_accounts, launch_browser_session, execute_automated_shopping, // project-local steam-msg commands save_steam_guard_file, save_steam_account_to_project, list_project_steam_accounts, delete_project_steam_account, update_account_is_us_account, export_mafile_to_path, get_steam_wallet_balance, read_proxy_config, write_proxy_config, test_proxy_configuration, invalidate_proxy, get_proxy_pool_status, refill_proxy_pool, verify_steam_login, // 后端API调用 initialize_backend_api, user_register, user_login, user_unbind, user_recharge, user_heartbeat, secure_heartbeat, user_logout, activate_card, get_steam_account_limit, get_steam_accounts, create_steam_account, bind_steam_account, update_steam_account, delete_steam_account, get_announcements, health_check, get_public_key, get_client_configs, // 配置管理 get_app_config, update_api_url, get_api_base_url, // 机器码和存储 get_machine_code, store_user_info, get_stored_user_info, get_current_username, get_user_profile, get_system_username, clear_stored_user_info, minimize_window, close_window, stop_all_processes, rsa_encrypt_aes_key, secure_get_announcements, secure_user_login, secure_user_register, forgot_password_request, secure_user_unbind, secure_user_change_password, secure_user_recharge, secure_client_version_check, store_rsa_public_key, has_stored_rsa_key, clear_stored_rsa_key, rsa_encrypt_aes_key_by_stored_key, get_rsa_key_version, // 安全通信模块命令 initialize_security_client, check_security_initialized, get_security_status, refresh_security_keys, reset_security_client, sync_hmac_key, secure_buff_filter_options, secure_buff_goods_attr_list, secure_buff_selection_config, secure_buff_selection_start, secure_buff_selection_log, secure_buff_selection_result, secure_buff_selection_item_delete, secure_buff_selection_stop, batch_import_buff_selection_items, secure_buff_sweep_bound_steam_usernames, secure_buff_sweep_start, spawn_buff_sweep_local_runner, secure_buff_sweep_stop, secure_buff_sweep_log, secure_buff_sweep_result, secure_buff_sweep_append_success_item, // 本地扫货成功数据存储 read_local_sweep_logs, read_sweep_worker_log, read_sweep_worker_backup_log, get_sweep_worker_log_files, read_local_sweep_success_items, read_steam_account_sweep_items, add_local_sweep_success_item, add_demo_sweep_success_item, secure_keys_hmac, // 密钥管理命令 get_key_manager_status, get_hmac_key_status, // 心跳管理命令 get_heartbeat_state_command, stop_heartbeat_command, get_heartbeat_config_command, read_text_file, // 更新相关 updater::check_for_updates, updater::manual_update_check, updater::start_manual_update, // 自动更新相关(自定义实现) auto_updater::get_current_exe_path, auto_updater::get_exe_directory, auto_updater::download_update, auto_updater::verify_exe, auto_updater::execute_update_and_restart, auto_updater::check_pending_update, auto_updater::cleanup_update_files, // 日志相关 get_app_log_path ]) .setup(|app| { heartbeat::set_app_handle(app.handle().clone()); // 开发者工具在发布版本中禁用 // 如需调试,可通过环境变量 TAURI_DEVTOOLS=1 在 release 中启用 Ok(()) }) .run(tauri::generate_context!()) .unwrap_or_else(|e| { let err_msg = format!("Tauri application error: {}", e); app_logger::error(&err_msg); // 显示友好的错误对话框 #[cfg(target_os = "windows")] { use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; let msg: Vec = OsStr::new(&format!("应用启动失败:\n{}\n\n详细信息请查看日志文件。", e)) .encode_wide() .chain(Some(0)) .collect(); let title: Vec = OsStr::new("SteamSH 启动失败") .encode_wide() .chain(Some(0)) .collect(); unsafe { extern "system" { fn MessageBoxW(hwnd: *mut std::ffi::c_void, text: *const u16, caption: *const u16, utype: u32) -> i32; } const MB_ICONERROR: u32 = 0x10; const MB_OK: u32 = 0x0; const MB_TOPMOST: u32 = 0x40000; MessageBoxW(std::ptr::null_mut(), msg.as_ptr(), title.as_ptr(), MB_ICONERROR | MB_OK | MB_TOPMOST); } } std::process::exit(1); }); }