main.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. // Prevents additional console window on Windows in release, DO NOT REMOVE!!
  2. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
  3. mod commands;
  4. mod steam_auth;
  5. mod browser_automation;
  6. mod api_client;
  7. mod config;
  8. mod key_storage;
  9. mod machine;
  10. mod storage;
  11. mod updater;
  12. mod auto_updater;
  13. mod error;
  14. mod security;
  15. mod heartbeat;
  16. mod app_logger;
  17. use commands::*;
  18. use browser_automation::*;
  19. use api_client::{ApiConfig as BackendApiConfig, initialize_api_client};
  20. use config::*;
  21. use tauri::Manager;
  22. // 在最开头初始化日志系统(任何 panic 都要捕获)
  23. fn init_logging() {
  24. // 初始化应用日志系统(每日一个日志文件,写在 exe 同目录下)
  25. if let Err(e) = app_logger::init_app_logger() {
  26. eprintln!("Failed to init app logger: {}", e);
  27. }
  28. // 设置 panic hook + Windows 顶层异常处理器,确保程序崩溃时把堆栈信息写入日志文件
  29. app_logger::setup_all_crash_handlers();
  30. }
  31. fn main() {
  32. // 最优先初始化日志(panic hook 必须在任何可能 panic 之前设置)
  33. init_logging();
  34. app_logger::info("应用主程序开始启动...");
  35. // --- 单实例检测:防止同一台电脑启动多个窗口 ---
  36. #[cfg(target_os = "windows")]
  37. {
  38. use std::ffi::OsStr;
  39. use std::os::windows::ffi::OsStrExt;
  40. use std::ptr;
  41. const MUTEX_NAME: &str = "SteamSH_SingleInstance_Mutex_v1";
  42. let mutex_name_wide: Vec<u16> = OsStr::new(MUTEX_NAME)
  43. .encode_wide()
  44. .chain(std::iter::once(0))
  45. .collect();
  46. unsafe {
  47. let handle = winapi::um::synchapi::CreateMutexW(
  48. ptr::null_mut(),
  49. 0, // bInitialOwner = false,新实例不会获得锁的拥有权
  50. mutex_name_wide.as_ptr(),
  51. );
  52. if !handle.is_null() {
  53. // 检查是否已有互斥量(即已有实例运行)
  54. let already_exists = winapi::um::errhandlingapi::GetLastError()
  55. == winapi::shared::winerror::ERROR_ALREADY_EXISTS;
  56. if already_exists {
  57. // 已有实例运行,尝试激活它的主窗口,然后退出当前进程
  58. app_logger::warn("检测到已有实例正在运行,激活已有窗口并退出当前进程");
  59. // 尝试通过 FindWindow 找到并激活已有窗口
  60. let class_name: Vec<u16> = OsStr::new("SteamSH")
  61. .encode_wide()
  62. .chain(std::iter::once(0))
  63. .collect();
  64. let title: Vec<u16> = OsStr::new("steam扫货(磨损版)")
  65. .encode_wide()
  66. .chain(std::iter::once(0))
  67. .collect();
  68. // 先尝试通过窗口类名找
  69. let hwnd = winapi::um::winuser::FindWindowW(class_name.as_ptr(), ptr::null());
  70. let hwnd = if hwnd.is_null() {
  71. // 再尝试通过窗口标题找
  72. winapi::um::winuser::FindWindowW(ptr::null(), title.as_ptr())
  73. } else {
  74. hwnd
  75. };
  76. if !hwnd.is_null() {
  77. // 激活窗口并置于前台
  78. winapi::um::winuser::SetForegroundWindow(hwnd);
  79. winapi::um::winuser::ShowWindow(hwnd, winapi::um::winuser::SW_RESTORE);
  80. winapi::um::winuser::BringWindowToTop(hwnd);
  81. }
  82. // 关闭 mutex 句柄并退出
  83. winapi::um::handleapi::CloseHandle(handle);
  84. std::process::exit(0);
  85. }
  86. // 记录 mutex 句柄,在程序结束时关闭(通过 drop 或 at_exit)
  87. // 为确保程序结束时释放 mutex,这里用 Box::leak 存起来,
  88. // 让它在进程结束时由 OS 自动释放(进程退出时内核自动关闭所有句柄)
  89. Box::leak(Box::new(handle));
  90. }
  91. }
  92. }
  93. // --- 单实例检测结束 ---
  94. // 阶段二:安全检查
  95. if let Err(e) = security::perform_all_security_checks() {
  96. app_logger::error(&format!("安全检查失败: {}", e));
  97. // 在生产环境中,安全检查失败应该显示错误信息后退出
  98. if !cfg!(debug_assertions) {
  99. // 显示友好的错误对话框
  100. #[cfg(target_os = "windows")]
  101. {
  102. use std::ffi::OsStr;
  103. use std::os::windows::ffi::OsStrExt;
  104. let msg: Vec<u16> = OsStr::new(&format!("安全检查失败:\n{}\n\n请检查系统环境后重试。\n\n详细信息请查看日志文件。", e))
  105. .encode_wide()
  106. .chain(Some(0))
  107. .collect();
  108. let title: Vec<u16> = OsStr::new("SteamSH 启动失败")
  109. .encode_wide()
  110. .chain(Some(0))
  111. .collect();
  112. unsafe {
  113. extern "system" {
  114. fn MessageBoxW(hwnd: *mut std::ffi::c_void, text: *const u16, caption: *const u16, utype: u32) -> i32;
  115. }
  116. const MB_ICONERROR: u32 = 0x10;
  117. const MB_OK: u32 = 0x0;
  118. const MB_TOPMOST: u32 = 0x40000;
  119. MessageBoxW(std::ptr::null_mut(), msg.as_ptr(), title.as_ptr(), MB_ICONERROR | MB_OK | MB_TOPMOST);
  120. }
  121. }
  122. std::process::exit(1);
  123. }
  124. }
  125. app_logger::info("安全检查通过");
  126. // 初始化存储系统
  127. if let Err(e) = storage::init_storage() {
  128. app_logger::error(&format!("Failed to initialize storage: {}", e));
  129. // 存储初始化失败不应该阻止程序启动
  130. }
  131. // 初始化应用配置和API客户端
  132. let app_config = AppConfig::load().unwrap_or_else(|e| {
  133. app_logger::error(&format!("Failed to load config: {}, using defaults", e));
  134. AppConfig::default()
  135. });
  136. // 初始化API客户端
  137. let api_config = BackendApiConfig {
  138. base_url: app_config.api.base_url.clone(),
  139. timeout: app_config.api.timeout,
  140. software_code: app_config.api.software_code.clone(),
  141. };
  142. if let Err(e) = initialize_api_client(api_config) {
  143. app_logger::error(&format!("Failed to initialize API client: {}", e));
  144. // API客户端初始化失败也不阻止程序启动,用户可以在运行时重新配置
  145. }
  146. tauri::Builder::default()
  147. .invoke_handler(tauri::generate_handler![
  148. // Steam本地功能
  149. generate_steam_code,
  150. fetch_confirmations,
  151. accept_confirmation,
  152. deny_confirmation,
  153. save_steam_account,
  154. load_steam_accounts,
  155. launch_browser_session,
  156. execute_automated_shopping,
  157. // project-local steam-msg commands
  158. save_steam_guard_file,
  159. save_steam_account_to_project,
  160. list_project_steam_accounts,
  161. delete_project_steam_account,
  162. update_account_is_us_account,
  163. export_mafile_to_path,
  164. get_steam_wallet_balance,
  165. read_proxy_config,
  166. write_proxy_config,
  167. test_proxy_configuration,
  168. invalidate_proxy,
  169. get_proxy_pool_status,
  170. refill_proxy_pool,
  171. verify_steam_login,
  172. // 后端API调用
  173. initialize_backend_api,
  174. user_register,
  175. user_login,
  176. user_unbind,
  177. user_recharge,
  178. user_heartbeat,
  179. secure_heartbeat,
  180. user_logout,
  181. activate_card,
  182. get_steam_account_limit,
  183. get_steam_accounts,
  184. create_steam_account,
  185. bind_steam_account,
  186. update_steam_account,
  187. delete_steam_account,
  188. get_announcements,
  189. health_check,
  190. get_public_key,
  191. get_client_configs,
  192. // 配置管理
  193. get_app_config,
  194. update_api_url,
  195. get_api_base_url,
  196. // 机器码和存储
  197. get_machine_code,
  198. store_user_info,
  199. get_stored_user_info,
  200. get_current_username,
  201. get_user_profile,
  202. get_system_username,
  203. clear_stored_user_info,
  204. minimize_window,
  205. close_window,
  206. stop_all_processes,
  207. rsa_encrypt_aes_key,
  208. secure_get_announcements,
  209. secure_user_login,
  210. secure_user_register,
  211. forgot_password_request,
  212. secure_user_unbind,
  213. secure_user_change_password,
  214. secure_user_recharge,
  215. secure_client_version_check,
  216. store_rsa_public_key,
  217. has_stored_rsa_key,
  218. clear_stored_rsa_key,
  219. rsa_encrypt_aes_key_by_stored_key,
  220. get_rsa_key_version,
  221. // 安全通信模块命令
  222. initialize_security_client,
  223. check_security_initialized,
  224. get_security_status,
  225. refresh_security_keys,
  226. reset_security_client,
  227. sync_hmac_key,
  228. secure_buff_filter_options,
  229. secure_buff_goods_attr_list,
  230. secure_buff_selection_config,
  231. secure_buff_selection_start,
  232. secure_buff_selection_log,
  233. secure_buff_selection_result,
  234. secure_buff_selection_item_delete,
  235. secure_buff_selection_stop,
  236. batch_import_buff_selection_items,
  237. secure_buff_sweep_bound_steam_usernames,
  238. secure_buff_sweep_start,
  239. spawn_buff_sweep_local_runner,
  240. secure_buff_sweep_stop,
  241. secure_buff_sweep_log,
  242. secure_buff_sweep_result,
  243. secure_buff_sweep_append_success_item,
  244. // 本地扫货成功数据存储
  245. read_local_sweep_logs,
  246. read_sweep_worker_log,
  247. read_sweep_worker_backup_log,
  248. get_sweep_worker_log_files,
  249. read_local_sweep_success_items,
  250. read_steam_account_sweep_items,
  251. add_local_sweep_success_item,
  252. add_demo_sweep_success_item,
  253. secure_keys_hmac,
  254. // 密钥管理命令
  255. get_key_manager_status,
  256. get_hmac_key_status,
  257. // 心跳管理命令
  258. get_heartbeat_state_command,
  259. stop_heartbeat_command,
  260. get_heartbeat_config_command,
  261. read_text_file,
  262. // 更新相关
  263. updater::check_for_updates,
  264. updater::manual_update_check,
  265. updater::start_manual_update,
  266. // 自动更新相关(自定义实现)
  267. auto_updater::get_current_exe_path,
  268. auto_updater::get_exe_directory,
  269. auto_updater::download_update,
  270. auto_updater::verify_exe,
  271. auto_updater::execute_update_and_restart,
  272. auto_updater::check_pending_update,
  273. auto_updater::cleanup_update_files,
  274. // 日志相关
  275. get_app_log_path
  276. ])
  277. .setup(|app| {
  278. heartbeat::set_app_handle(app.handle().clone());
  279. // 开发者工具在发布版本中禁用
  280. // 如需调试,可通过环境变量 TAURI_DEVTOOLS=1 在 release 中启用
  281. Ok(())
  282. })
  283. .run(tauri::generate_context!())
  284. .unwrap_or_else(|e| {
  285. let err_msg = format!("Tauri application error: {}", e);
  286. app_logger::error(&err_msg);
  287. // 显示友好的错误对话框
  288. #[cfg(target_os = "windows")]
  289. {
  290. use std::ffi::OsStr;
  291. use std::os::windows::ffi::OsStrExt;
  292. let msg: Vec<u16> = OsStr::new(&format!("应用启动失败:\n{}\n\n详细信息请查看日志文件。", e))
  293. .encode_wide()
  294. .chain(Some(0))
  295. .collect();
  296. let title: Vec<u16> = OsStr::new("SteamSH 启动失败")
  297. .encode_wide()
  298. .chain(Some(0))
  299. .collect();
  300. unsafe {
  301. extern "system" {
  302. fn MessageBoxW(hwnd: *mut std::ffi::c_void, text: *const u16, caption: *const u16, utype: u32) -> i32;
  303. }
  304. const MB_ICONERROR: u32 = 0x10;
  305. const MB_OK: u32 = 0x0;
  306. const MB_TOPMOST: u32 = 0x40000;
  307. MessageBoxW(std::ptr::null_mut(), msg.as_ptr(), title.as_ptr(), MB_ICONERROR | MB_OK | MB_TOPMOST);
  308. }
  309. }
  310. std::process::exit(1);
  311. });
  312. }