@@ -1201,8 +1201,25 @@ function sendConsoleMessageToUI(message, color) {
12011201 msg . startsWith ( "steam-achievements:request" ) ||
12021202 msg . startsWith ( "steam-achievements:success" ) ;
12031203 if ( suppress ) return ;
1204- if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
1205- mainWindow . webContents . send ( "notify" , { message, color } ) ;
1204+ if ( ! mainWindow || mainWindow . isDestroyed ( ) ) return ;
1205+ const wc = mainWindow . webContents ;
1206+ if ( ! wc || wc . isDestroyed ?. ( ) || wc . isCrashed ?. ( ) ) return ;
1207+ // Rate-limit UI notifications to avoid renderer OOM on log storms.
1208+ const now = Date . now ( ) ;
1209+ if ( ! sendConsoleMessageToUI . _bucket ) {
1210+ sendConsoleMessageToUI . _bucket = { ts : now , count : 0 } ;
1211+ }
1212+ const bucket = sendConsoleMessageToUI . _bucket ;
1213+ if ( now - bucket . ts > 2000 ) {
1214+ bucket . ts = now ;
1215+ bucket . count = 0 ;
1216+ }
1217+ bucket . count += 1 ;
1218+ if ( bucket . count > 15 ) return ;
1219+ try {
1220+ wc . send ( "notify" , { message : msg , color } ) ;
1221+ } catch {
1222+ // avoid recursive console error loops
12061223 }
12071224}
12081225
@@ -4121,6 +4138,8 @@ function processNextProgressNotification() {
41214138let currentAchievementsFilePath = null ;
41224139let achievementsWatcher = null ;
41234140let extraAchievementFiles = new Set ( ) ;
4141+ let achievementMonitorToken = 0 ;
4142+ let achievementMonitorTimer = null ;
41244143
41254144if ( ! fs . existsSync ( cacheDir ) ) {
41264145 fs . mkdirSync ( cacheDir , { recursive : true } ) ;
@@ -4397,6 +4416,11 @@ function findAchievementFileDeepForAppId(saveBase, appid, maxDepth = 2) {
43974416
43984417function monitorAchievementsFile ( filePath ) {
43994418 if ( ! filePath ) {
4419+ achievementMonitorToken += 1 ;
4420+ if ( achievementMonitorTimer ) {
4421+ clearTimeout ( achievementMonitorTimer ) ;
4422+ achievementMonitorTimer = null ;
4423+ }
44004424 if ( extraAchievementFiles . size ) {
44014425 for ( const fp of extraAchievementFiles ) {
44024426 try {
@@ -4413,10 +4437,23 @@ function monitorAchievementsFile(filePath) {
44134437 return ;
44144438 }
44154439
4416- if ( currentAchievementsFilePath === filePath && achievementsWatcher ) {
4440+ const samePathActive =
4441+ currentAchievementsFilePath === filePath && achievementsWatcher ;
4442+ if ( samePathActive && fs . existsSync ( filePath ) ) {
4443+ if ( achievementMonitorTimer ) {
4444+ clearTimeout ( achievementMonitorTimer ) ;
4445+ achievementMonitorTimer = null ;
4446+ }
44174447 return ;
44184448 }
44194449
4450+ achievementMonitorToken += 1 ;
4451+ if ( achievementMonitorTimer ) {
4452+ clearTimeout ( achievementMonitorTimer ) ;
4453+ achievementMonitorTimer = null ;
4454+ }
4455+ const monitorToken = achievementMonitorToken ;
4456+
44204457 if ( achievementsWatcher && currentAchievementsFilePath ) {
44214458 fs . unwatchFile ( currentAchievementsFilePath , achievementsWatcher ) ;
44224459 achievementsWatcher = null ;
@@ -4836,6 +4873,7 @@ function monitorAchievementsFile(filePath) {
48364873 } ;
48374874 achievementsWatcher = ( ) => processSnapshot ( false ) ;
48384875 const checkFileLoop = ( ) => {
4876+ if ( monitorToken !== achievementMonitorToken ) return ;
48394877 if ( fs . existsSync ( filePath ) ) {
48404878 processSnapshot ( false ) ;
48414879
@@ -4852,6 +4890,7 @@ function monitorAchievementsFile(filePath) {
48524890 fs . unwatchFile ( filePath ) ;
48534891 } catch { }
48544892 fs . watchFile ( filePath , { interval : 1000 } , achievementsWatcher ) ;
4893+ achievementMonitorTimer = null ;
48554894 } else {
48564895 const baseDir = path . dirname ( filePath ) ;
48574896 const tenokePath = path . join ( baseDir , "SteamData" , "user_stats.ini" ) ;
@@ -4903,7 +4942,7 @@ function monitorAchievementsFile(filePath) {
49034942 }
49044943
49054944 // Retry discovery later in case the save file appears after config load
4906- setTimeout ( checkFileLoop , 1000 ) ;
4945+ achievementMonitorTimer = setTimeout ( checkFileLoop , 1000 ) ;
49074946 }
49084947 } ;
49094948
@@ -5047,32 +5086,7 @@ ipcMain.on(
50475086 const trophyDir = config . save_path || "" ;
50485087 const xmlRoot = path . join ( trophyDir , "Xml" ) ;
50495088 const xmlMain = path . join ( xmlRoot , "TROP.XML" ) ;
5050- const xmlFiles = [
5051- xmlMain ,
5052- path . join ( xmlRoot , "TROP_00.XML" ) ,
5053- path . join ( xmlRoot , "TROP_01.XML" ) ,
5054- path . join ( xmlRoot , "TROP_02.XML" ) ,
5055- path . join ( xmlRoot , "TROP_03.XML" ) ,
5056- path . join ( xmlRoot , "TROP_04.XML" ) ,
5057- path . join ( xmlRoot , "TROP_05.XML" ) ,
5058- path . join ( xmlRoot , "TROP_06.XML" ) ,
5059- path . join ( xmlRoot , "TROP_07.XML" ) ,
5060- path . join ( xmlRoot , "TROP_08.XML" ) ,
5061- path . join ( xmlRoot , "TROP_09.XML" ) ,
5062- path . join ( xmlRoot , "TROP_10.XML" ) ,
5063- path . join ( xmlRoot , "TROP_11.XML" ) ,
5064- path . join ( xmlRoot , "TROP_12.XML" ) ,
5065- path . join ( xmlRoot , "TROP_13.XML" ) ,
5066- path . join ( xmlRoot , "TROP_14.XML" ) ,
5067- path . join ( xmlRoot , "TROP_15.XML" ) ,
5068- path . join ( xmlRoot , "TROP_16.XML" ) ,
5069- path . join ( xmlRoot , "TROP_17.XML" ) ,
5070- path . join ( xmlRoot , "TROP_18.XML" ) ,
5071- path . join ( xmlRoot , "TROP_19.XML" ) ,
5072- path . join ( xmlRoot , "TROP_20.XML" ) ,
5073- ] . filter ( ( p ) => fs . existsSync ( p ) ) ;
5074- achievementsFilePath = xmlFiles . length ? xmlFiles [ 0 ] : null ;
5075- const monitorList = xmlFiles . length ? xmlFiles : null ;
5089+ achievementsFilePath = fs . existsSync ( xmlMain ) ? xmlMain : null ;
50765090 if ( ! achievementsFilePath ) {
50775091 monitorAchievementsFile ( null ) ;
50785092 achievementsFilePath = null ;
@@ -5093,15 +5107,6 @@ ipcMain.on(
50935107 pendingMissingAchievementFiles . delete ( configName ) ;
50945108 }
50955109 monitorAchievementsFile ( achievementsFilePath ) ;
5096- if ( monitorList && achievementsWatcher ) {
5097- for ( const extra of monitorList . slice ( 1 ) ) {
5098- try {
5099- fs . unwatchFile ( extra ) ;
5100- fs . watchFile ( extra , { interval : 500 } , achievementsWatcher ) ;
5101- extraAchievementFiles . add ( extra ) ;
5102- } catch { }
5103- }
5104- }
51055110 if ( overlayWindow && ! overlayWindow . isDestroyed ( ) ) {
51065111 overlayWindow . webContents . send ( "load-overlay-data" , selectedConfig ) ;
51075112 overlayWindow . webContents . send ( "set-language" , {
@@ -5139,26 +5144,6 @@ ipcMain.on(
51395144 pendingMissingAchievementFiles . delete ( configName ) ;
51405145 }
51415146 monitorAchievementsFile ( achievementsFilePath ) ;
5142- if ( achievementsWatcher && statsDir && appid ) {
5143- try {
5144- const extras = fs
5145- . readdirSync ( statsDir )
5146- . filter (
5147- ( f ) =>
5148- / ^ u s e r g a m e s t a t s _ .* _ [ 0 - 9 ] + \. b i n $ / i. test ( f ) &&
5149- f . toLowerCase ( ) . endsWith ( `_${ appid . toLowerCase ( ) } .bin` ) ,
5150- )
5151- . map ( ( f ) => path . join ( statsDir , f ) )
5152- . filter ( ( p ) => p !== achievementsFilePath ) ;
5153- for ( const extra of extras ) {
5154- try {
5155- fs . unwatchFile ( extra ) ;
5156- fs . watchFile ( extra , { interval : 500 } , achievementsWatcher ) ;
5157- extraAchievementFiles . add ( extra ) ;
5158- } catch { }
5159- }
5160- } catch { }
5161- }
51625147 if ( overlayWindow && ! overlayWindow . isDestroyed ( ) ) {
51635148 overlayWindow . webContents . send ( "load-overlay-data" , selectedConfig ) ;
51645149 overlayWindow . webContents . send ( "set-language" , {
0 commit comments