1111using System . IO ;
1212using System . Linq ;
1313using System . Management ;
14- using System . Net ;
1514using System . Net . Http ;
1615using System . Net . Http . Headers ;
1716using System . Reflection ;
18- using System . Runtime . Intrinsics . Arm ;
1917using System . Text ;
2018using System . Text . Json ;
2119using System . Text . Json . Serialization ;
@@ -38,6 +36,7 @@ internal static class CustomAnalyticsEventKeys
3836 internal static string SERVER_USER_COUNT => "SERVER_USER_COUNT" ;
3937 internal static string USER_SETTINGS => "USER_SETTINGS" ;
4038 }
39+
4140 internal class AnalyticsHelper
4241 {
4342 #region Singleton
@@ -59,11 +58,13 @@ public static AnalyticsHelper Instance
5958 }
6059 }
6160 #endregion
61+
6262 private Timer _heartBeatTimer ;
63- private string timeZone ;
64- private string language ;
65- private HttpClient client ;
66- private bool trackingEnabled = false ;
63+ private readonly string timeZone ;
64+ private readonly string language ;
65+ private readonly HttpClient client ;
66+ private readonly bool trackingEnabled = false ;
67+
6768 internal AnalyticsHelper ( )
6869 {
6970 trackingEnabled = SettingsViewModel . Instance . SendAnonymousAnalytics ;
@@ -75,8 +76,8 @@ internal AnalyticsHelper()
7576
7677 client = new HttpClient ( ) ;
7778 client . DefaultRequestHeaders . UserAgent . Clear ( ) ;
78- string plattform = IsWineRunning ( ) ? "Linux" : "Windows NT" ;
79- var userAgent = $ "GameVault/{ SettingsViewModel . Instance . Version } ({ plattform } )";
79+ string platform = IsWineRunning ( ) ? "Linux" : "Windows NT" ;
80+ var userAgent = $ "GameVault/{ SettingsViewModel . Instance . Version } ({ platform } )";
8081 client . DefaultRequestHeaders . UserAgent . ParseAdd ( userAgent ) ;
8182 try
8283 {
@@ -86,17 +87,20 @@ internal AnalyticsHelper()
8687 catch { }
8788
8889 }
90+
8991 internal void InitHeartBeat ( )
9092 {
9193 if ( ! trackingEnabled )
9294 return ;
9395
9496 _heartBeatTimer = new Timer ( HeartBeat , null , TimeSpan . Zero , TimeSpan . FromSeconds ( 30 ) ) ;
9597 }
98+
9699 private void HeartBeat ( object state )
97100 {
98101 SendHeartBeat ( AnalyticsTargets . HB ) ;
99102 }
103+
100104 internal void RegisterGlobalEvents ( )
101105 {
102106 if ( ! trackingEnabled )
@@ -106,6 +110,7 @@ internal void RegisterGlobalEvents()
106110
107111 EventManager . RegisterClassHandler ( typeof ( IconButton ) , IconButton . ClickEvent , new RoutedEventHandler ( GlobalButton_Click ) ) ;
108112 }
113+
109114 private async void GlobalButton_Click ( object sender , RoutedEventArgs e )
110115 {
111116 try
@@ -119,33 +124,35 @@ private async void GlobalButton_Click(object sender, RoutedEventArgs e)
119124 }
120125 catch { }
121126 }
127+
122128 private string ParseMethodName ( ButtonBase buttonBase )
123129 {
124130 var type = buttonBase . GetType ( ) ;
125- var eventHandlersStore = typeof ( UIElement ) . GetProperty ( "EventHandlersStore" , System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Instance ) ;
126- var eventHandlersStoreValue = eventHandlersStore . GetValue ( buttonBase , null ) ;
131+ var eventHandlersStore = typeof ( UIElement ) . GetProperty ( "EventHandlersStore" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
132+ var eventHandlersStoreValue = eventHandlersStore ? . GetValue ( buttonBase ) ;
127133
128134 if ( eventHandlersStoreValue != null )
129135 {
130136 var entriesField = eventHandlersStoreValue . GetType ( ) . GetField ( "_entries" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
131- var entriesValue = entriesField . GetValue ( eventHandlersStoreValue ) ;
137+ var entriesValue = entriesField ? . GetValue ( eventHandlersStoreValue ) ;
132138
133- var mapStoreField = entriesValue . GetType ( ) . GetField ( "_mapStore" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
134- var mapStoreValue = mapStoreField . GetValue ( entriesValue ) ;
139+ var mapStoreField = entriesValue ? . GetType ( ) . GetField ( "_mapStore" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
140+ var mapStoreValue = mapStoreField ? . GetValue ( entriesValue ) ;
135141
136- var entry0Field = mapStoreValue . GetType ( ) . GetField ( "_entry0" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
137- var entry0Value = entry0Field . GetValue ( mapStoreValue ) ;
142+ var entry0Field = mapStoreValue ? . GetType ( ) . GetField ( "_entry0" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
143+ var entry0Value = entry0Field ? . GetValue ( mapStoreValue ) ;
138144
139- var Value = entry0Value . GetType ( ) . GetField ( "Value" ) . GetValue ( entry0Value ) ;
145+ var valueField = entry0Value ? . GetType ( ) . GetField ( "Value" ) ;
146+ var value = valueField ? . GetValue ( entry0Value ) ;
140147
141- var listStoreField = Value . GetType ( ) . GetField ( "_listStore" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
148+ var listStoreField = value ? . GetType ( ) . GetField ( "_listStore" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
142149 if ( listStoreField == null )
143150 {
144151 string methodName = ( ( EventSetter ) buttonBase . Style . Setters [ 0 ] ) . Handler . Method . Name ;
145152 string className = ( ( EventSetter ) buttonBase . Style . Setters [ 0 ] ) . Handler . Method . DeclaringType ? . Name ?? "UnknownClass" ;
146153 return $ "{ className } .{ methodName } ";
147154 }
148- var listStoreValue = listStoreField . GetValue ( Value ) ;
155+ var listStoreValue = listStoreField . GetValue ( value ) ;
149156 var loneEntryField = listStoreValue . GetType ( ) . GetField ( "_loneEntry" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
150157 var loneEntryValue = loneEntryField . GetValue ( listStoreValue ) ;
151158
@@ -170,16 +177,27 @@ private string ParseMethodName(ButtonBase buttonBase)
170177 }
171178 return string . Empty ;
172179 }
180+
173181 private async Task SendHeartBeat ( string url )
174182 {
175183 try
176184 {
177185 var jsonContent = new StringContent ( JsonSerializer . Serialize ( new AnalyticsData ( ) ) , Encoding . UTF8 , "application/json" ) ;
178- await client . PostAsync ( url , jsonContent ) ;
186+ await client . PostAsync ( url , jsonContent ) ;
179187 }
180- catch ( Exception e ) { }
188+ catch ( Exception )
189+ {
190+ // swallow
191+ }
192+
193+ }
181194
195+ private string Truncate ( string ? value , int maxLength )
196+ {
197+ if ( string . IsNullOrEmpty ( value ) ) return string . Empty ;
198+ return value . Length <= maxLength ? value : value . Substring ( 0 , maxLength ) ;
182199 }
200+
183201 public async Task SendPageView ( UserControl page )
184202 {
185203 if ( ! trackingEnabled )
@@ -191,9 +209,14 @@ public async Task SendPageView(UserControl page)
191209 var jsonContent = new StringContent ( JsonSerializer . Serialize ( new AnalyticsData ( ) { Timezone = timeZone , CurrentPage = pageString , Language = language } ) , Encoding . UTF8 , "application/json" ) ;
192210 await client . PostAsync ( AnalyticsTargets . LG , jsonContent ) ;
193211 }
194- catch ( Exception e ) { }
212+ catch ( Exception )
213+ {
214+ // swallow
215+ }
195216
196217 }
218+
219+ #region ** New Swetrix Error Structure **
197220 public void SendErrorLog ( Exception ex )
198221 {
199222 if ( ! trackingEnabled )
@@ -203,130 +226,180 @@ public void SendErrorLog(Exception ex)
203226 {
204227 try
205228 {
206- var jsonContent = new StringContent ( JsonSerializer . Serialize ( new AnalyticsData ( ) { ExceptionType = ex . GetType ( ) . ToString ( ) , ExceptionMessage = $ "Message:{ ex . Message } | InnerException:{ ex . InnerException ? . Message } | StackTrace:{ ex . StackTrace ? . Substring ( 0 , Math . Min ( 2000 , ex . StackTrace . Length ) ) } | Is Windows Package: { ( App . IsWindowsPackage == true ? "True" : "False" ) } | Version: { SettingsViewModel . Instance . Version } ", Timezone = timeZone , Language = language } ) , Encoding . UTF8 , "application/json" ) ;
229+ // Extract first meaningful frame with file info if available
230+ int ? line = null ;
231+ int ? column = null ;
232+ string ? file = null ;
233+
234+ try
235+ {
236+ var st = new StackTrace ( ex , true ) ;
237+ var frame = st . GetFrames ( ) ? . FirstOrDefault ( f => f . GetFileLineNumber ( ) > 0 ) ?? st . GetFrame ( 0 ) ;
238+ if ( frame != null )
239+ {
240+ line = frame . GetFileLineNumber ( ) ;
241+ column = frame . GetFileColumnNumber ( ) ;
242+ file = frame . GetFileName ( ) ;
243+ }
244+ }
245+ catch { /* ignore parsing issues */ }
246+
247+ var data = new AnalyticsData
248+ {
249+ Name = Truncate ( ex . GetType ( ) . Name , 200 ) ,
250+ Message = Truncate ( ex . Message , 2000 ) ,
251+ LineNo = line ,
252+ ColNo = column ,
253+ FileName = Truncate ( file , 1000 ) ,
254+ StackTrace = Truncate ( ex . StackTrace , 7500 ) ,
255+ Timezone = timeZone ,
256+ Language = language ,
257+ Metadata = new
258+ {
259+ InnerException = ex . InnerException ? . Message ,
260+ IsWindowsPackage = App . IsWindowsPackage == true ,
261+ Version = SettingsViewModel . Instance . Version
262+ }
263+ } ;
264+
265+ var jsonContent = new StringContent ( JsonSerializer . Serialize ( data ) , Encoding . UTF8 , "application/json" ) ;
207266 await client . PostAsync ( AnalyticsTargets . ER , jsonContent ) ;
208267 }
209- catch ( Exception ex ) { }
268+ catch ( Exception )
269+ {
270+ // swallow
271+ }
210272 } ) ;
211273 }
274+ #endregion
275+
212276 public void SendCustomEvent ( string eventName , object meta )
213277 {
214- if ( ! trackingEnabled ) return ;
278+ if ( ! trackingEnabled )
279+ return ;
215280 Task . Run ( async ( ) =>
216281 {
217282 try
218283 {
219284 var jsonContent = new StringContent ( JsonSerializer . Serialize ( new AnalyticsData ( ) { Event = eventName , Metadata = meta , Timezone = timeZone , Language = language } ) , Encoding . UTF8 , "application/json" ) ;
220- HttpResponseMessage res =
221- await client . PostAsync ( AnalyticsTargets . CU , jsonContent ) ;
285+ await client . PostAsync ( AnalyticsTargets . CU , jsonContent ) ;
222286 }
223287 catch { }
224288 } ) ;
225289 }
290+
226291 private string ? ParseUserControl ( UserControl page )
227292 {
228293 switch ( page )
229294 {
230295 case LibraryUserControl :
231- {
232- return "/library" ;
233-
234- }
296+ return "/library" ;
235297 case DownloadsUserControl :
236- {
237- return "/downloads" ;
238-
239- }
298+ return "/downloads" ;
240299 case CommunityUserControl :
241- {
242- return "/community" ;
243-
244- }
300+ return "/community" ;
245301 case SettingsUserControl :
246- {
247- return "/settings" ;
248-
249- }
302+ return "/settings" ;
250303 case AdminConsoleUserControl :
251- {
252- return "/admin" ;
253- }
304+ return "/admin" ;
254305 case GameViewUserControl :
255- {
256- return "/game" ;
257- }
306+ return "/game" ;
258307 default :
259- {
260- return null ;
261- }
308+ return null ;
262309 }
263310 }
311+
264312 public object GetSysInfo ( )
265313 {
266314 try
267315 {
268316 var OS = new ManagementObjectSearcher ( "select * from Win32_OperatingSystem" ) . Get ( ) . Cast < ManagementObject > ( ) . First ( ) ;
269- string os = $ "{ OS [ "Caption" ] } - { OS [ "OSArchitecture" ] } - Version.{ OS [ "Version" ] } "; os = os . Replace ( "NT 5.1.2600" , "XP" ) ; os = os . Replace ( "NT 5.2.3790" , "Server 2003" ) ;
317+ string os = $ "{ OS [ "Caption" ] } - { OS [ "OSArchitecture" ] } - Version.{ OS [ "Version" ] } ". Replace ( "NT 5.1.2600" , "XP" ) . Replace ( "NT 5.2.3790" , "Server 2003" ) ;
270318 string ram = $ "{ OS [ "TotalVisibleMemorySize" ] } KB";
271319 var CPU = new ManagementObjectSearcher ( "select * from Win32_Processor" ) . Get ( ) . Cast < ManagementObject > ( ) . First ( ) ;
272320 string cpu = $ "{ CPU [ "Name" ] } - { CPU [ "MaxClockSpeed" ] } MHz - { CPU [ "NumberOfCores" ] } Core";
273- return new { app_version = SettingsViewModel . Instance . Version , hardware_os = os , hardware_ram = ram , hardware_cpu = cpu , } ;
321+ return new { app_version = SettingsViewModel . Instance . Version , hardware_os = os , hardware_ram = ram , hardware_cpu = cpu } ;
274322 }
275323 catch ( Exception ex )
276324 {
277325 return new { app_version = SettingsViewModel . Instance . Version , hardware_os = $ "The system information could not be loaded due to an { ex . GetType ( ) . Name } " } ;
278326 }
279327 }
328+
280329 public Dictionary < string , string > PrepareSettingsForAnalytics ( )
281330 {
282331 try
283332 {
284333 var propertiesToExclude = new [ ] { "Instance" , "UserName" , "RootPath" , "ServerUrl" , "License" , "RegistrationUser" , "SendAnonymousAnalytics" , "IgnoreList" , "Themes" , "CommunityThemes" } ;
285334 var trimmedObject = SettingsViewModel . Instance . GetType ( )
286- . GetProperties ( )
287- . Where ( prop => ! propertiesToExclude . Contains ( prop . Name ) )
288- . ToDictionary ( prop => prop . Name , prop => prop . GetValue ( SettingsViewModel . Instance ) ? . ToString ( ) ) ;
335+ . GetProperties ( )
336+ . Where ( prop => ! propertiesToExclude . Contains ( prop . Name ) )
337+ . ToDictionary ( prop => prop . Name , prop => prop . GetValue ( SettingsViewModel . Instance ) ? . ToString ( ) ) ;
289338
290339 trimmedObject . Add ( "HasLicence" , ( SettingsViewModel . Instance . License ? . IsActive ( ) == true ) . ToString ( ) ) ;
291340 return trimmedObject ;
292341 }
293342 catch { }
294343 return null ;
295344 }
345+
296346 private bool IsWineRunning ( )
297347 {
298348 // Search for WINLOGON process
299349 int p = Process . GetProcessesByName ( "winlogon" ) . Length ;
300350 return p == 0 ;
301351 }
352+
353+ #region ** DTOs & Helpers **
302354 private class AnalyticsData
303355 {
304356 [ JsonPropertyName ( "pid" ) ]
305357 public string ProjectID => "N2kuL4i8qmOQ" ;
306358
359+ // General Event Fields
307360 [ JsonPropertyName ( "ev" ) ]
308361 public string ? Event { get ; set ; }
362+
309363 [ JsonPropertyName ( "tz" ) ]
310364 public string ? Timezone { get ; set ; }
365+
311366 [ JsonPropertyName ( "pg" ) ]
312367 public string ? CurrentPage { get ; set ; }
313368
314369 [ JsonPropertyName ( "lc" ) ]
315370 public string ? Language { get ; set ; }
371+
316372 [ JsonPropertyName ( "meta" ) ]
317- public object ? Metadata { get ; set ; } //Properties of type string only
318- //Error
373+ public object ? Metadata { get ; set ; }
374+
375+ // Error specific fields
319376 [ JsonPropertyName ( "name" ) ]
320- public string ? ExceptionType { get ; set ; }
377+ public string ? Name { get ; set ; }
378+
321379 [ JsonPropertyName ( "message" ) ]
322- public string ? ExceptionMessage { get ; set ; }
380+ public string ? Message { get ; set ; }
381+
382+ [ JsonPropertyName ( "lineno" ) ]
383+ public int ? LineNo { get ; set ; }
384+
385+ [ JsonPropertyName ( "colno" ) ]
386+ public int ? ColNo { get ; set ; }
387+
388+ [ JsonPropertyName ( "stackTrace" ) ]
389+ public string ? StackTrace { get ; set ; }
390+
391+ [ JsonPropertyName ( "filename" ) ]
392+ public string ? FileName { get ; set ; }
323393 }
394+
324395 private static class AnalyticsTargets
325396 {
326397 public static string HB => Encoding . UTF8 . GetString ( Convert . FromBase64String ( "aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZy9oYg==" ) ) ;
327398 public static string LG => Encoding . UTF8 . GetString ( Convert . FromBase64String ( "aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZw==" ) ) ;
328399 public static string CU => Encoding . UTF8 . GetString ( Convert . FromBase64String ( "aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZy9jdXN0b20=" ) ) ;
329400 public static string ER => Encoding . UTF8 . GetString ( Convert . FromBase64String ( "aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZy9lcnJvcg==" ) ) ;
330401 }
402+ #endregion
331403 }
332404}
405+
0 commit comments