@@ -15,6 +15,7 @@ import require$$5 from "assert";
1515import WebSocket from "ws" ;
1616import https from "https" ;
1717import axios from "axios" ;
18+ import * as crypto from "crypto" ;
1819import Store from "electron-store" ;
1920import { is , optimizer } from "@electron-toolkit/utils" ;
2021import __cjs_mod__ from "node:module" ;
@@ -5695,6 +5696,16 @@ class GameConfigHelper {
56955696 tftConfigPath ;
56965697 // 预设的云顶设置
56975698 isTFTConfig = false ;
5699+ /** 文件监听器实例,用于守护恢复后的配置不被 LOL 客户端覆盖 */
5700+ configWatcher = null ;
5701+ /** 防抖定时器,避免短时间内触发多次恢复 */
5702+ watcherDebounceTimer = null ;
5703+ /** 守护超时定时器,到期后自动停止监听 */
5704+ guardTimeoutTimer = null ;
5705+ /** 守护期间允许的最大自动恢复次数,防止无限循环 */
5706+ MAX_GUARD_RESTORES = 5 ;
5707+ /** 守护期间已执行的自动恢复次数 */
5708+ guardRestoreCount = 0 ;
56985709 constructor ( installPath ) {
56995710 if ( ! installPath ) {
57005711 throw new Error ( "初始化失败,必须提供一个有效的游戏安装路径!" ) ;
@@ -5831,7 +5842,17 @@ class GameConfigHelper {
58315842 try {
58325843 await fs . copy ( backupPath , instance . gameConfigPath ) ;
58335844 instance . isTFTConfig = false ;
5834- logger . info ( `[GameConfigHelper] 设置恢复成功!` ) ;
5845+ const verified = await instance . verifyRestore ( backupPath ) ;
5846+ if ( verified ) {
5847+ logger . info ( `[GameConfigHelper] 设置恢复成功,文件验证通过!` ) ;
5848+ } else {
5849+ logger . warn ( `[GameConfigHelper] 设置恢复完成,但文件验证不一致!可能被外部程序覆盖` ) ;
5850+ if ( attempt < retryCount ) {
5851+ logger . info ( `[GameConfigHelper] 将在 ${ retryDelay } ms 后重试...` ) ;
5852+ await sleep ( retryDelay ) ;
5853+ continue ;
5854+ }
5855+ }
58355856 return true ;
58365857 } catch ( err ) {
58375858 const errMsg = err instanceof Error ? err . message : String ( err ) ;
@@ -5849,6 +5870,146 @@ class GameConfigHelper {
58495870 }
58505871 return false ;
58515872 }
5873+ /**
5874+ * 验证恢复结果:对比备份目录和游戏配置目录中的关键文件哈希值
5875+ *
5876+ * 只对比最关键的 game.cfg 文件,因为它包含分辨率、画质等核心设置
5877+ * 使用 MD5 哈希快速比较文件内容是否一致
5878+ *
5879+ * @param backupPath 备份目录路径
5880+ * @returns true 表示恢复后的文件与备份一致
5881+ */
5882+ async verifyRestore ( backupPath ) {
5883+ const keyFile = "game.cfg" ;
5884+ const backupFile = path__default . join ( backupPath , keyFile ) ;
5885+ const gameFile = path__default . join ( this . gameConfigPath , keyFile ) ;
5886+ try {
5887+ const [ backupExists , gameExists ] = await Promise . all ( [
5888+ fs . pathExists ( backupFile ) ,
5889+ fs . pathExists ( gameFile )
5890+ ] ) ;
5891+ if ( ! backupExists || ! gameExists ) {
5892+ logger . warn ( `[ConfigGuard] 验证跳过:文件不存在 (备份: ${ backupExists } , 游戏: ${ gameExists } )` ) ;
5893+ return true ;
5894+ }
5895+ const [ backupHash , gameHash ] = await Promise . all ( [
5896+ this . getFileHash ( backupFile ) ,
5897+ this . getFileHash ( gameFile )
5898+ ] ) ;
5899+ const match = backupHash === gameHash ;
5900+ if ( ! match ) {
5901+ logger . warn ( `[ConfigGuard] game.cfg 哈希不匹配!备份: ${ backupHash } , 游戏: ${ gameHash } ` ) ;
5902+ }
5903+ return match ;
5904+ } catch ( err ) {
5905+ logger . warn ( `[ConfigGuard] 验证过程出错: ${ err } ` ) ;
5906+ return true ;
5907+ }
5908+ }
5909+ /**
5910+ * 计算文件的 MD5 哈希值
5911+ *
5912+ * crypto.createHash('md5') 创建一个哈希计算器
5913+ * digest('hex') 将计算结果转为十六进制字符串(如 "d41d8cd98f00b204e9800998ecf8427e")
5914+ *
5915+ * @param filePath 文件路径
5916+ * @returns 文件的 MD5 哈希字符串
5917+ */
5918+ async getFileHash ( filePath ) {
5919+ const content = await fs . readFile ( filePath ) ;
5920+ return crypto . createHash ( "md5" ) . update ( content ) . digest ( "hex" ) ;
5921+ }
5922+ /**
5923+ * 启动配置守护监听器
5924+ *
5925+ * 在 restore 成功后调用,监听游戏配置目录的文件变化。
5926+ * 如果检测到 LOL 客户端在恢复后又改写了配置文件,会自动重新恢复。
5927+ *
5928+ * 守护机制会在指定时间后自动停止,防止长期占用资源。
5929+ * 同时有最大恢复次数限制,防止与 LOL 客户端无限互相覆盖。
5930+ *
5931+ * @param guardDuration 守护持续时间(毫秒),默认 30 秒
5932+ */
5933+ static startConfigGuard ( guardDuration = 3e4 ) {
5934+ const instance = GameConfigHelper . getInstance ( ) ;
5935+ if ( ! instance ) return ;
5936+ GameConfigHelper . stopConfigGuard ( ) ;
5937+ instance . guardRestoreCount = 0 ;
5938+ logger . info ( `[ConfigGuard] 启动配置守护,持续 ${ guardDuration / 1e3 } 秒` ) ;
5939+ try {
5940+ instance . configWatcher = fs . watch (
5941+ instance . gameConfigPath ,
5942+ { recursive : true } ,
5943+ ( eventType , filename ) => {
5944+ if ( ! filename || ! filename . toLowerCase ( ) . includes ( "game.cfg" ) ) return ;
5945+ if ( instance . isTFTConfig ) return ;
5946+ if ( instance . guardRestoreCount >= instance . MAX_GUARD_RESTORES ) {
5947+ logger . warn ( `[ConfigGuard] 已达最大自动恢复次数 (${ instance . MAX_GUARD_RESTORES } ),停止守护` ) ;
5948+ GameConfigHelper . stopConfigGuard ( ) ;
5949+ return ;
5950+ }
5951+ if ( instance . watcherDebounceTimer ) {
5952+ clearTimeout ( instance . watcherDebounceTimer ) ;
5953+ }
5954+ instance . watcherDebounceTimer = setTimeout ( async ( ) => {
5955+ logger . info ( `[ConfigGuard] 检测到 ${ filename } 被外部修改,正在验证...` ) ;
5956+ let backupPath = instance . currentBackupPath ;
5957+ if ( ! await fs . pathExists ( backupPath ) ) {
5958+ backupPath = instance . primaryBackupPath ;
5959+ }
5960+ if ( ! await fs . pathExists ( backupPath ) ) {
5961+ backupPath = instance . fallbackBackupPath ;
5962+ }
5963+ const isConsistent = await instance . verifyRestore ( backupPath ) ;
5964+ if ( ! isConsistent ) {
5965+ instance . guardRestoreCount ++ ;
5966+ logger . warn ( `[ConfigGuard] 配置被篡改!自动恢复中... (第 ${ instance . guardRestoreCount } 次)` ) ;
5967+ try {
5968+ await fs . copy ( backupPath , instance . gameConfigPath ) ;
5969+ const verified = await instance . verifyRestore ( backupPath ) ;
5970+ if ( verified ) {
5971+ logger . info ( `[ConfigGuard] 自动恢复成功,验证通过` ) ;
5972+ } else {
5973+ logger . warn ( `[ConfigGuard] 自动恢复后验证仍不一致` ) ;
5974+ }
5975+ } catch ( err ) {
5976+ logger . error ( `[ConfigGuard] 自动恢复失败: ${ err } ` ) ;
5977+ }
5978+ } else {
5979+ logger . debug ( `[ConfigGuard] ${ filename } 变更但内容验证一致,无需恢复` ) ;
5980+ }
5981+ } , 500 ) ;
5982+ }
5983+ ) ;
5984+ instance . guardTimeoutTimer = setTimeout ( ( ) => {
5985+ logger . info ( `[ConfigGuard] 守护时间到,停止监听` ) ;
5986+ GameConfigHelper . stopConfigGuard ( ) ;
5987+ } , guardDuration ) ;
5988+ } catch ( err ) {
5989+ logger . error ( `[ConfigGuard] 启动监听失败: ${ err } ` ) ;
5990+ }
5991+ }
5992+ /**
5993+ * 停止配置守护监听器
5994+ * 清理所有定时器和文件监听器,释放资源
5995+ */
5996+ static stopConfigGuard ( ) {
5997+ const instance = GameConfigHelper . getInstance ( ) ;
5998+ if ( ! instance ) return ;
5999+ if ( instance . configWatcher ) {
6000+ instance . configWatcher . close ( ) ;
6001+ instance . configWatcher = null ;
6002+ logger . debug ( `[ConfigGuard] 文件监听器已关闭` ) ;
6003+ }
6004+ if ( instance . watcherDebounceTimer ) {
6005+ clearTimeout ( instance . watcherDebounceTimer ) ;
6006+ instance . watcherDebounceTimer = null ;
6007+ }
6008+ if ( instance . guardTimeoutTimer ) {
6009+ clearTimeout ( instance . guardTimeoutTimer ) ;
6010+ instance . guardTimeoutTimer = null ;
6011+ }
6012+ }
58526013}
58536014var IpcChannel = /* @__PURE__ */ ( ( IpcChannel2 ) => {
58546015 IpcChannel2 [ "CONFIG_BACKUP" ] = "config-backup" ;
@@ -10636,6 +10797,7 @@ app.on("will-quit", async (event) => {
1063610797 if ( hexService && hexService . isRunning ) {
1063710798 event . preventDefault ( ) ;
1063810799 console . log ( "🔄 [Main] 检测到程序正在运行,正在恢复游戏设置..." ) ;
10800+ GameConfigHelper . stopConfigGuard ( ) ;
1063910801 try {
1064010802 await GameConfigHelper . restore ( ) ;
1064110803 console . log ( "✅ [Main] 游戏设置已恢复" ) ;
@@ -10679,11 +10841,11 @@ app.whenReady().then(async () => {
1067910841 console . log ( "✅ [Main] 原生模块检查通过" ) ;
1068010842 console . log ( "🚀 [Main] 正在加载业务模块..." ) ;
1068110843 try {
10682- const ServicesModule = await import ( "./chunks/index-B2eu_A9a .js" ) ;
10844+ const ServicesModule = await import ( "./chunks/index-BqMcWydW .js" ) ;
1068310845 hexService = ServicesModule . hexService ;
1068410846 const TftOperatorModule = await import ( "./chunks/TftOperator-Bv5E9wfl.js" ) . then ( ( n ) => n . T ) ;
1068510847 tftOperator = TftOperatorModule . tftOperator ;
10686- const LineupModule = await import ( "./chunks/index-DViKavGK .js" ) ;
10848+ const LineupModule = await import ( "./chunks/index-BkP-NETh .js" ) ;
1068710849 lineupLoader = LineupModule . lineupLoader ;
1068810850 const GlobalHotkeyManagerModule = await import ( "./chunks/GlobalHotkeyManager-Cbcy0EP4.js" ) ;
1068910851 globalHotkeyManager = GlobalHotkeyManagerModule . globalHotkeyManager ;
0 commit comments