From e88876a62078780d74c30cd126b90cca11d4668b Mon Sep 17 00:00:00 2001
From: Heavy Bob <ferrettclay@gmail.com>
Date: Sat, 12 Apr 2025 07:43:28 +1000
Subject: [PATCH 1/2] Hash Kills

This change updates the .csv to store kill hashes and also stores hashes made in memory. This should prevent reporting the same kill twice or saving it to the .csv twice. This will break the old .csv however it is renamed to .old with the previous change.
---
 AutoTrackR2/HomePage.xaml.cs      | 10 +++++++++-
 AutoTrackR2/KillHistoryManager.cs | 11 ++++++-----
 AutoTrackR2/Util.cs               |  1 +
 AutoTrackR2/WebHandler.cs         | 24 ++++++++++++++++++++++--
 4 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs
index 84ee0f7..165dffe 100644
--- a/AutoTrackR2/HomePage.xaml.cs
+++ b/AutoTrackR2/HomePage.xaml.cs
@@ -192,7 +192,8 @@ public partial class HomePage : UserControl
                         TrackRver = "2.10",
                         Enlisted = playerData?.JoinDate,
                         KillTime = ((DateTimeOffset)DateTime.ParseExact(actorDeathData.Timestamp, "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)).ToUnixTimeSeconds().ToString(),
-                        PFP = playerData?.PFPURL ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg"
+                        PFP = playerData?.PFPURL ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg",
+                        Hash = WebHandler.GenerateKillHash(actorDeathData.VictimPilot, ((DateTimeOffset)DateTime.ParseExact(actorDeathData.Timestamp, "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)).ToUnixTimeSeconds())
                     };
 
                     switch (LocalPlayerData.CurrentGameMode)
@@ -205,6 +206,13 @@ public partial class HomePage : UserControl
                             break;
                     }
 
+                    // Check if this is a duplicate kill
+                    if (WebHandler.IsDuplicateKill(killData.Hash))
+                    {
+                        Console.WriteLine("Duplicate kill detected, skipping...");
+                        return;
+                    }
+
                     // Add kill to UI
                     Dispatcher.Invoke(() =>
                     {
diff --git a/AutoTrackR2/KillHistoryManager.cs b/AutoTrackR2/KillHistoryManager.cs
index eb6c3a2..65f33ad 100644
--- a/AutoTrackR2/KillHistoryManager.cs
+++ b/AutoTrackR2/KillHistoryManager.cs
@@ -7,8 +7,8 @@ namespace AutoTrackR2;
 public class KillHistoryManager
 {
     private string _killHistoryPath;
-    private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP\n";
-    
+    private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP,Hash\n";
+
     public KillHistoryManager(string logPath)
     {
         _killHistoryPath = logPath;
@@ -89,8 +89,8 @@ public class KillHistoryManager
         
         // Append the new kill data to the CSV file
         var csv = new StringBuilder();
-        csv.AppendLine($"\"{killData.KillTime}\",\"{killData.EnemyPilot}\",\"{killData.EnemyShip}\",\"{killData.Enlisted}\",\"{killData.RecordNumber}\",\"{killData.OrgAffiliation}\",\"{killData.Player}\",\"{killData.Weapon}\",\"{killData.Ship}\",\"{killData.Method}\",\"{killData.Mode}\",\"{killData.GameVersion}\",\"{killData.TrackRver}\",\"{killData.Logged}\",\"{killData.PFP}\"");
-        
+        csv.AppendLine($"\"{killData.KillTime}\",\"{killData.EnemyPilot}\",\"{killData.EnemyShip}\",\"{killData.Enlisted}\",\"{killData.RecordNumber}\",\"{killData.OrgAffiliation}\",\"{killData.Player}\",\"{killData.Weapon}\",\"{killData.Ship}\",\"{killData.Method}\",\"{killData.Mode}\",\"{killData.GameVersion}\",\"{killData.TrackRver}\",\"{killData.Logged}\",\"{killData.PFP}\",\"{killData.Hash}\"");
+
         // Check file can be written to
         try
         {
@@ -138,7 +138,8 @@ public class KillHistoryManager
                 GameVersion = data?[11],
                 TrackRver = data?[12],
                 Logged = data?[13],
-                PFP = data?[14]
+                PFP = data?[14],
+                Hash = data?[15]
             });
         }
 
diff --git a/AutoTrackR2/Util.cs b/AutoTrackR2/Util.cs
index f783330..1ba1ada 100644
--- a/AutoTrackR2/Util.cs
+++ b/AutoTrackR2/Util.cs
@@ -29,4 +29,5 @@ public struct KillData
     public string? TrackRver;
     public string? Logged;
     public string? PFP;
+    public string? Hash;
 }
\ No newline at end of file
diff --git a/AutoTrackR2/WebHandler.cs b/AutoTrackR2/WebHandler.cs
index 0d93b48..244d24b 100644
--- a/AutoTrackR2/WebHandler.cs
+++ b/AutoTrackR2/WebHandler.cs
@@ -6,11 +6,19 @@ using System.Text.RegularExpressions;
 using AutoTrackR2.LogEventHandlers;
 using System.Globalization;
 using System.Security.Cryptography;
+using System.Collections.Generic;
 
 namespace AutoTrackR2;
 
 public static class WebHandler
 {
+    private static HashSet<string> _recordedKillHashes = new HashSet<string>();
+
+    public static bool IsDuplicateKill(string hash)
+    {
+        return _recordedKillHashes.Contains(hash);
+    }
+
     class APIKillData
     {
         public string? victim_ship { get; set; }
@@ -28,7 +36,7 @@ public static class WebHandler
         public string hash { get; set; } = string.Empty;
     }
 
-    private static string GenerateKillHash(string victimName, long timestamp)
+    public static string GenerateKillHash(string victimName, long timestamp)
     {
         // Combine victim name and timestamp
         string combined = $"{victimName}_{timestamp}";
@@ -105,6 +113,15 @@ public static class WebHandler
     public static async Task SubmitKill(KillData killData)
     {
         var timestamp = long.Parse(killData.KillTime!);
+        var hash = GenerateKillHash(killData.EnemyPilot!, timestamp);
+
+        // Check if this kill has already been recorded
+        if (_recordedKillHashes.Contains(hash))
+        {
+            Console.WriteLine("Duplicate kill detected, skipping...");
+            return;
+        }
+
         var apiKillData = new APIKillData
         {
             victim_ship = killData.EnemyShip,
@@ -119,7 +136,7 @@ public static class WebHandler
             trackr_version = killData.TrackRver,
             location = killData.Location,
             time = timestamp,
-            hash = GenerateKillHash(killData.EnemyPilot!, timestamp)
+            hash = hash
         };
 
         if (string.IsNullOrEmpty(apiKillData.rsi))
@@ -175,6 +192,9 @@ public static class WebHandler
             var responseContent = await response.Content.ReadAsStringAsync();
             Console.WriteLine($"Response: {responseContent}");
 
+            // Add the hash to our recorded hashes
+            _recordedKillHashes.Add(hash);
+
             // Only process streamer data if streamlink is enabled
             if (ConfigManager.StreamlinkEnabled == 1)
             {

From ad773f6790d0e7252dfc9389d70cf9a87b774c10 Mon Sep 17 00:00:00 2001
From: Heavy Bob <ferrettclay@gmail.com>
Date: Sat, 12 Apr 2025 10:29:56 +1000
Subject: [PATCH 2/2] Added Initialization Waits

This will help prevent reading the old game.log on boot.
---
 AutoTrackR2/HomePage.xaml.cs | 74 ++++++++++++++++++++++++++++--------
 AutoTrackR2/LogHandler.cs    | 63 +++++++++++++++++++++++++++---
 2 files changed, 115 insertions(+), 22 deletions(-)

diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs
index 165dffe..80bbd06 100644
--- a/AutoTrackR2/HomePage.xaml.cs
+++ b/AutoTrackR2/HomePage.xaml.cs
@@ -25,6 +25,9 @@ public partial class HomePage : UserControl
     private bool _isLogHandlerRunning = false;
     private int _counter = 1;
     private System.Timers.Timer _counterTimer;
+    private bool _isInitializing = false;
+    private System.Timers.Timer? _initializationTimer;
+    private bool _wasStarCitizenRunningOnStart = false;
 
     public HomePage()
     {
@@ -42,6 +45,18 @@ public partial class HomePage : UserControl
         AdjustFontSize(KillTallyTextBox);
         AddKillHistoryKillsToUI();
 
+        // Check if Star Citizen is already running
+        _wasStarCitizenRunningOnStart = IsStarCitizenRunning();
+        if (_wasStarCitizenRunningOnStart)
+        {
+            UpdateStatusIndicator(true);
+            InitializeLogHandler();
+        }
+        else
+        {
+            UpdateStatusIndicator(false);
+        }
+
         // Initialize and start the status check timer
         _statusCheckTimer = new System.Timers.Timer(1000); // Check every second
         _statusCheckTimer.Elapsed += CheckStarCitizenStatus;
@@ -51,16 +66,6 @@ public partial class HomePage : UserControl
         _counterTimer = new System.Timers.Timer(1000); // Update every second
         _counterTimer.Elapsed += UpdateCounter;
         _counterTimer.Start();
-
-        // Check if Star Citizen is already running and initialize accordingly
-        if (IsStarCitizenRunning())
-        {
-            Dispatcher.Invoke(() =>
-            {
-                UpdateStatusIndicator(true);
-                InitializeLogHandler(); // Then initialize the log handler
-            });
-        }
     }
 
     private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e)
@@ -68,14 +73,44 @@ public partial class HomePage : UserControl
         bool isRunning = IsStarCitizenRunning();
         Dispatcher.Invoke(() =>
         {
-            UpdateStatusIndicator(isRunning);
+            if (_isInitializing)
+            {
+                return; // Don't update status while initializing
+            }
 
             if (isRunning)
             {
                 if (!_isLogHandlerRunning)
                 {
-                    // Game is running, start log monitoring and read initial states
-                    InitializeLogHandler();
+                    if (_wasStarCitizenRunningOnStart)
+                    {
+                        // Game was already running on start, initialize immediately
+                        UpdateStatusIndicator(true);
+                        InitializeLogHandler();
+                    }
+                    else
+                    {
+                        // Game started after app launch, use initialization delay
+                        _isInitializing = true;
+                        UpdateStatusIndicator(false, true); // Set to yellow for initialization
+
+                        _initializationTimer = new System.Timers.Timer(20000); // 20 seconds
+                        _initializationTimer.Elapsed += (sender, e) =>
+                        {
+                            _isInitializing = false;
+                            _initializationTimer?.Stop();
+                            _initializationTimer?.Dispose();
+                            _initializationTimer = null;
+
+                            Dispatcher.Invoke(() =>
+                            {
+                                // Game is running, start log monitoring and read initial states
+                                UpdateStatusIndicator(true);
+                                InitializeLogHandler();
+                            });
+                        };
+                        _initializationTimer.Start();
+                    }
                 }
             }
             else
@@ -96,16 +131,23 @@ public partial class HomePage : UserControl
                     _logHandler?.StopMonitoring();
                     _isLogHandlerRunning = false;
                 }
+
+                UpdateStatusIndicator(false);
             }
         });
     }
 
-    private void UpdateStatusIndicator(bool isRunning)
+    private void UpdateStatusIndicator(bool isRunning, bool isInitializing = false)
     {
-        if (isRunning)
+        if (isInitializing)
+        {
+            StatusLight.Fill = new SolidColorBrush(Colors.Yellow);
+            StatusText.Text = "TrackR\nInitializing";
+        }
+        else if (isRunning)
         {
             StatusLight.Fill = new SolidColorBrush(Colors.Green);
-            StatusText.Text = "TrackR\nActive";
+            StatusText.Text = "TrackR\nRunning";
         }
         else
         {
diff --git a/AutoTrackR2/LogHandler.cs b/AutoTrackR2/LogHandler.cs
index 2e2ab91..73c0f7f 100644
--- a/AutoTrackR2/LogHandler.cs
+++ b/AutoTrackR2/LogHandler.cs
@@ -30,8 +30,11 @@ public class LogHandler
     private CancellationTokenSource? _cancellationTokenSource;
     private GameProcessState _gameProcessState = GameProcessState.NotRunning;
     private bool _isMonitoring = false;
+    private bool _isInitializing = false;
+    private System.Timers.Timer? _initializationTimer;
 
     public bool IsMonitoring => _isMonitoring;
+    public bool IsInitializing => _isInitializing;
 
     // Handlers that should be run on every log entry
     // Overlap with _startupEventHandlers is fine
@@ -62,6 +65,37 @@ public class LogHandler
             throw new FileNotFoundException("Log file not found", _logPath);
         }
 
+        // Check if Star Citizen is running
+        if (!IsStarCitizenRunning())
+        {
+            StartInitializationDelay();
+            return;
+        }
+
+        InitializeLogHandler();
+    }
+
+    private void StartInitializationDelay()
+    {
+        _isInitializing = true;
+        _initializationTimer = new System.Timers.Timer(20000); // 20 seconds
+        _initializationTimer.Elapsed += (sender, e) =>
+        {
+            _isInitializing = false;
+            _initializationTimer?.Stop();
+            _initializationTimer?.Dispose();
+            _initializationTimer = null;
+
+            if (IsStarCitizenRunning())
+            {
+                InitializeLogHandler();
+            }
+        };
+        _initializationTimer.Start();
+    }
+
+    private void InitializeLogHandler()
+    {
         _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
         _reader = new StreamReader(_fileStream);
 
@@ -75,6 +109,11 @@ public class LogHandler
         StartMonitoring();
     }
 
+    private bool IsStarCitizenRunning()
+    {
+        return Process.GetProcessesByName("StarCitizen").Length > 0;
+    }
+
     public void StartMonitoring()
     {
         if (_isMonitoring) return;
@@ -156,14 +195,26 @@ public class LogHandler
         GameProcessState newGameProcessState = process != null ? GameProcessState.Running : GameProcessState.NotRunning;
         if (newGameProcessState == GameProcessState.Running && _gameProcessState == GameProcessState.NotRunning)
         {
-            // Game process went from NotRunning to Running, so reload the Game.log file
-            Console.WriteLine("Game process started, reloading log file");
+            // Game process went from NotRunning to Running, wait 20 seconds before reloading
+            Console.WriteLine("Game process started, waiting 20 seconds before initializing...");
+            _isInitializing = true;
 
-            _reader?.Close();
-            _fileStream?.Close();
+            _initializationTimer = new System.Timers.Timer(20000); // 20 seconds
+            _initializationTimer.Elapsed += (sender, e) =>
+            {
+                _isInitializing = false;
+                _initializationTimer?.Stop();
+                _initializationTimer?.Dispose();
+                _initializationTimer = null;
 
-            _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
-            _reader = new StreamReader(_fileStream);
+                Console.WriteLine("Initialization delay complete, reloading log file");
+                _reader?.Close();
+                _fileStream?.Close();
+
+                _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+                _reader = new StreamReader(_fileStream);
+            };
+            _initializationTimer.Start();
         }
         _gameProcessState = newGameProcessState;
     }