diff --git a/AutoTrackR2/App.xaml.cs b/AutoTrackR2/App.xaml.cs index 115d332..e84d792 100644 --- a/AutoTrackR2/App.xaml.cs +++ b/AutoTrackR2/App.xaml.cs @@ -1,6 +1,9 @@ using System.Configuration; using System.Data; using System.Windows; +using System.Threading; +using System.IO; +using System; namespace AutoTrackR2 { @@ -9,5 +12,101 @@ namespace AutoTrackR2 /// </summary> public partial class App : System.Windows.Application { + private static Mutex _mutex = null; + private static bool _mutexOwned = false; + private static readonly string CrashLogPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "AutoTrackR2", + "crash.log" + ); + + protected override void OnStartup(StartupEventArgs e) + { + // Ensure crash log directory exists + Directory.CreateDirectory(Path.GetDirectoryName(CrashLogPath)); + + // Set up unhandled exception handlers + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + DispatcherUnhandledException += App_DispatcherUnhandledException; + + const string appName = "AutoTrackR2"; + bool createdNew; + + _mutex = new Mutex(true, appName, out createdNew); + _mutexOwned = createdNew; + + if (!createdNew) + { + // App is already running, silently exit + Current.Shutdown(); + return; + } + + base.OnStartup(e); + } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + LogCrash(e.ExceptionObject as Exception); + } + + private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + { + LogCrash(e.Exception); + e.Handled = true; // Prevent the application from crashing + } + + private void LogCrash(Exception? ex) + { + try + { + var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var logMessage = $"[{timestamp}] CRASH: {ex?.Message}\n" + + $"Stack Trace:\n{ex?.StackTrace}\n" + + $"Source: {ex?.Source}\n" + + $"Target Site: {ex?.TargetSite}\n" + + "----------------------------------------\n"; + + File.AppendAllText(CrashLogPath, logMessage); + + // Show error message to user + MessageBox.Show( + "AutoTrackR2 has encountered an error. A crash log has been created.\n" + + $"Location: {CrashLogPath}", + "AutoTrackR2 Error", + MessageBoxButton.OK, + MessageBoxImage.Error + ); + } + catch (Exception logEx) + { + // If logging fails, at least show a basic error message + MessageBox.Show( + "AutoTrackR2 has encountered an error and failed to create a crash log.\n" + + $"Error: {ex?.Message}", + "AutoTrackR2 Error", + MessageBoxButton.OK, + MessageBoxImage.Error + ); + } + } + + protected override void OnExit(ExitEventArgs e) + { + if (_mutex != null && _mutexOwned) + { + try + { + _mutex.ReleaseMutex(); + } + catch (Exception ex) + { + // Log the mutex release error but don't throw + LogCrash(ex); + } + _mutex.Dispose(); + } + base.OnExit(e); + } } } diff --git a/AutoTrackR2/ConfigPage.xaml b/AutoTrackR2/ConfigPage.xaml index 9114dfc..70567a9 100644 --- a/AutoTrackR2/ConfigPage.xaml +++ b/AutoTrackR2/ConfigPage.xaml @@ -26,7 +26,7 @@ VerticalAlignment="Center" Height="389"> <!-- Log File --> - <StackPanel Margin="0,10,0,15" + <StackPanel Margin="0,5,0,10" Orientation="Horizontal"> <TextBlock Text="ⓘ" ToolTip="Set this to the Game.log file in your StarCitizen\LIVE directory." @@ -55,7 +55,7 @@ </StackPanel> <!-- API URL --> - <StackPanel Margin="0,0,0,15" + <StackPanel Margin="0,0,0,10" Orientation="Horizontal"> <TextBlock Text="ⓘ" ToolTip="Need a URL? No idea what to do? Contact heavy_bob on Discord!" @@ -83,7 +83,7 @@ </StackPanel> <!-- API Key --> - <StackPanel Margin="0,0,0,15" + <StackPanel Margin="0,0,0,10" Orientation="Horizontal"> <TextBlock Text="ⓘ" ToolTip="Need a key? No idea what to do? Contact heavy_bob on Discord!" @@ -102,7 +102,7 @@ </StackPanel> <!-- Video Path --> - <StackPanel Margin="0,0,0,15" + <StackPanel Margin="0,0,0,10" Orientation="Horizontal"> <TextBlock Text="ⓘ" ToolTip="The directory where your clipping software saves kills. Check the README." @@ -129,18 +129,31 @@ </StackPanel> </StackPanel> - <!-- Visor Wipe Toggle Slider --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> + <!-- Toggle Settings Grid --> + <Grid Margin="0,0,0,10"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + + <!-- Visor Wipe Toggle --> + <StackPanel Grid.Column="0" Grid.Row="0" + Orientation="Horizontal" + Margin="0,0,5,5"> <TextBlock Text="ⓘ" ToolTip="Perform a Visor Wipe animation on player kill. Requires AHKv2." Foreground="{DynamicResource TextBrush}" FontSize="20" - Margin="0,4,3,5"/> + Margin="0,4,3,0"/> <TextBlock Text="Visor Wipe:" Foreground="{DynamicResource TextBrush}" FontSize="16" - Margin="0,7,0,5"/> + Margin="0,7,0,0"/> <Slider Name="VisorWipeSlider" Minimum="0" Maximum="1" @@ -148,22 +161,24 @@ IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" - Margin="27,-4,0,0" + Margin="10,-4,0,0" + Width="100" ValueChanged="VisorWipeSlider_ValueChanged"/> - </StackPanel> + </StackPanel> - <!-- Video Record Toggle Slider --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> + <!-- Video Record Toggle --> + <StackPanel Grid.Column="1" Grid.Row="0" + Orientation="Horizontal" + Margin="5,0,0,5"> <TextBlock Text="ⓘ" ToolTip="Automatically clip your last kill. Check the README for more info." Foreground="{DynamicResource TextBrush}" FontSize="20" - Margin="0,4,3,5"/> + Margin="0,4,3,0"/> <TextBlock Text="Video Record:" Foreground="{DynamicResource TextBrush}" FontSize="16" - Margin="0,7,0,5"/> + Margin="0,7,0,0"/> <Slider Name="VideoRecordSlider" Minimum="0" Maximum="1" @@ -172,21 +187,23 @@ Value="0" Style="{StaticResource ToggleSliderStyle}" Margin="10,-4,0,0" + Width="100" ValueChanged="VideoRecordSlider_ValueChanged"/> - </StackPanel> + </StackPanel> - <!-- Offline Mode Toggle Slider --> - <StackPanel Margin="0,0,0,10" - Orientation="Horizontal"> + <!-- Offline Mode Toggle --> + <StackPanel Grid.Column="0" Grid.Row="1" + Orientation="Horizontal" + Margin="0,0,5,5"> <TextBlock Text="ⓘ" ToolTip="With Offline Mode enabled, kills will not be submitted to the configured API." Foreground="{DynamicResource TextBrush}" FontSize="20" - Margin="0,4,3,5"/> + Margin="0,4,3,0"/> <TextBlock Text="Offline Mode:" Foreground="{DynamicResource TextBrush}" FontSize="16" - Margin="0,7,0,5"/> + Margin="0,7,0,0"/> <Slider Name="OfflineModeSlider" Minimum="0" Maximum="1" @@ -194,17 +211,20 @@ IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" - Margin="12,-4,0,0" + Margin="10,-4,0,0" + Width="100" ValueChanged="OfflineModeSlider_ValueChanged"/> - </StackPanel> + </StackPanel> - <!-- 3-Position Toggle Slider --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> + <!-- Theme Slider --> + <StackPanel Grid.Column="0" Grid.Row="2" + Grid.ColumnSpan="2" + Orientation="Horizontal" + Margin="0,0,0,5"> <TextBlock Text="Theme:" Foreground="{DynamicResource TextBrush}" FontSize="16" - Margin="0,7,0,5"/> + Margin="0,7,10,0"/> <Slider x:Name="ThemeSlider" Minimum="0" Value="0" @@ -213,8 +233,8 @@ ValueChanged="ThemeSlider_ValueChanged" Width="447" Style="{StaticResource ThreePositionSlider}"/> - </StackPanel> - + </StackPanel> + </Grid> </StackPanel> <!-- Save Button --> diff --git a/AutoTrackR2/ConfigPage.xaml.cs b/AutoTrackR2/ConfigPage.xaml.cs index c4397ff..1136f55 100644 --- a/AutoTrackR2/ConfigPage.xaml.cs +++ b/AutoTrackR2/ConfigPage.xaml.cs @@ -39,7 +39,6 @@ public partial class ConfigPage : UserControl ApplyToggleModeStyle(OfflineModeSlider.Value, VisorWipeSlider.Value, VideoRecordSlider.Value); - const string themeJsonPath = "themes.json"; var themeJson = File.ReadAllText(themeJsonPath); _themes = JsonSerializer.Deserialize<Dictionary<string, Theme>>(themeJson); @@ -78,23 +77,19 @@ public partial class ConfigPage : UserControl } // This method will set the loaded config values to the UI controls - public void SetConfigValues(string logFile, string apiUrl, string apiKey, string videoPath, - int visorWipe, int videoRecord, int offlineMode, int theme) + public void SetConfigValues(string logFile, string apiUrl, string apiKey, string videoPath, int visorWipe, int videoRecord, int offlineMode, int theme) { - // Set the textboxes with the loaded values LogFilePath.Text = logFile; ApiUrl.Text = apiUrl; ApiKey.Password = apiKey; VideoPath.Text = videoPath; - - // Set the sliders with the loaded values - VideoRecordSlider.Value = videoRecord; VisorWipeSlider.Value = visorWipe; + VideoRecordSlider.Value = videoRecord; OfflineModeSlider.Value = offlineMode; + ThemeSlider.Value = theme; // Handle themes ApplyTheme(theme); - } private void ApplyToggleModeStyle(double offlineModeValue, double visorWipeValue, double videoRecordValue) @@ -202,18 +197,18 @@ public partial class ConfigPage : UserControl // Video Path Browse Button Handler private void VideoPathBrowseButton_Click(object sender, RoutedEventArgs e) { - var dialog = new OpenFileDialog(); - dialog.CheckFileExists = false; + var dialog = new Microsoft.Win32.OpenFileDialog(); dialog.ValidateNames = false; - dialog.Filter = "All files|*.*"; - - if (dialog.ShowDialog() == true && dialog.FileName != null) + dialog.CheckFileExists = false; + dialog.CheckPathExists = true; + dialog.FileName = "Folder Selection"; + + if (dialog.ShowDialog() == true) { - // Extract only the directory path from the file string? selectedFolder = Path.GetDirectoryName(dialog.FileName); if (selectedFolder != null) { - VideoPath.Text = selectedFolder; // Set the folder path + VideoPath.Text = selectedFolder; } } } @@ -320,10 +315,8 @@ public partial class ConfigPage : UserControl } } - private void SaveButton_Click(object sender, RoutedEventArgs e) { - ConfigManager.ApiKey = ApiKey.Password; ConfigManager.ApiUrl = ApiUrl.Text; ConfigManager.LogFile = LogFilePath.Text; @@ -332,6 +325,7 @@ public partial class ConfigPage : UserControl ConfigManager.VideoRecord = (int)VideoRecordSlider.Value; ConfigManager.OfflineMode = (int)OfflineModeSlider.Value; ConfigManager.Theme = (int)ThemeSlider.Value; + // Save the current config values ConfigManager.SaveConfig(); // Start the flashing effect @@ -404,10 +398,11 @@ public partial class ConfigPage : UserControl { // Set headers client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); - client.DefaultRequestHeaders.UserAgent.ParseAdd("AutoTrackR"); + client.DefaultRequestHeaders.UserAgent.ParseAdd("AutoTrackR2"); - // Empty JSON body - var content = new StringContent("{}", Encoding.UTF8, "application/json"); + // Create JSON body with version + var jsonBody = new { version = "2.10" }; + var content = new StringContent(JsonSerializer.Serialize(jsonBody), Encoding.UTF8, "application/json"); // Send POST var response = await client.PostAsync(modifiedUrl, content); diff --git a/AutoTrackR2/HomePage.xaml b/AutoTrackR2/HomePage.xaml index 9c08122..58c0e95 100644 --- a/AutoTrackR2/HomePage.xaml +++ b/AutoTrackR2/HomePage.xaml @@ -42,19 +42,20 @@ </ScrollViewer> </Border> - <!-- StackPanel for Start and Stop buttons --> + <!-- Border and StackPanel for player info --> <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" Grid.Row="0" Grid.Column="1" BorderThickness="2" CornerRadius="5" - Margin="0,0,0,82"/> - <StackPanel Grid.Column="1" - VerticalAlignment="Center" + Margin="0,0,0,10" + VerticalAlignment="Top"> + <StackPanel + VerticalAlignment="Top" HorizontalAlignment="Center" - Height="269" - Width="152"> + Width="152" + Margin="10,5,10,5"> <TextBlock Name="PilotNameTitle" Text="Pilot" Width="152" @@ -112,6 +113,25 @@ Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> + <TextBlock Name="LocationTitle" + Text="Location" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,5,0,0" + Foreground="{DynamicResource AltTextBrush}" + FontSize="14"/> + <TextBlock Name="LocationTextBox" + Text="Unknown" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,0,0,0" + Foreground="{DynamicResource TextBrush}" + FontSize="10" + TextAlignment="Center"/> <TextBlock Name="KillTallyTitle" Text="Kill Tally" Width="152" @@ -141,7 +161,8 @@ FontSize="8" BorderThickness="0" Margin="0,9,0,0"/> - </StackPanel> + </StackPanel> + </Border> <StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs index b765f0a..f727a62 100644 --- a/AutoTrackR2/HomePage.xaml.cs +++ b/AutoTrackR2/HomePage.xaml.cs @@ -77,9 +77,11 @@ public partial class HomePage : UserControl GameModeTextBox.Text = "Unknown"; PlayerShipTextBox.Text = "Unknown"; PilotNameTextBox.Text = "Unknown"; + LocationTextBox.Text = "Unknown"; LocalPlayerData.CurrentGameMode = GameMode.Unknown; LocalPlayerData.PlayerShip = string.Empty; LocalPlayerData.Username = string.Empty; + LocalPlayerData.LastSeenVehicleLocation = "Unknown"; // Stop log monitoring if it's running if (_isLogHandlerRunning) @@ -139,6 +141,8 @@ public partial class HomePage : UserControl AdjustFontSize(PlayerShipTextBox); LocalPlayerData.PlayerShip = data.ShipName; LocalPlayerData.LastSeenVehicleLocation = data.Location; + LocationTextBox.Text = data.Location; + AdjustFontSize(LocationTextBox); }); }; @@ -216,7 +220,12 @@ public partial class HomePage : UserControl // Vehicle Destruction TrackREventDispatcher.VehicleDestructionEvent += (data) => { - LocalPlayerData.LastSeenVehicleLocation = data.VehicleZone; + Dispatcher.Invoke(() => + { + LocalPlayerData.LastSeenVehicleLocation = data.VehicleZone; + LocationTextBox.Text = data.VehicleZone; + AdjustFontSize(LocationTextBox); + }); }; _UIEventsRegistered = true; @@ -224,67 +233,51 @@ public partial class HomePage : UserControl private void AddKillToScreen(KillData killData) { - // Fetch the dynamic resource for AltTextColor - var altTextColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AltTextColor"]); - var accentColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AccentColor"]); - - // Fetch the Orbitron FontFamily from resources - var orbitronFontFamily = (FontFamily)Application.Current.Resources["Orbitron"]; - var gemunuFontFamily = (FontFamily)Application.Current.Resources["Gemunu"]; - - // Create a new TextBlock for each kill + // Use resource references instead of creating new brushes var killTextBlock = new TextBlock { Margin = new Thickness(0, 10, 0, 10), - Style = (Style)Application.Current.Resources["RoundedTextBlock"], // Apply style for text + Style = (Style)Application.Current.Resources["RoundedTextBlock"], FontSize = 14, FontWeight = FontWeights.Bold, - FontFamily = gemunuFontFamily, + FontFamily = (FontFamily)Application.Current.Resources["Gemunu"], }; - // Add styled content using Run elements - killTextBlock.Inlines.Add(new Run("Victim Name: ") - { - Foreground = altTextColorBrush, - FontFamily = orbitronFontFamily, - }); + // Add styled content using Run elements with resource references + var titleRun = new Run("Victim Name: "); + titleRun.SetResourceReference(TextElement.ForegroundProperty, "AltTextBrush"); + titleRun.FontFamily = (FontFamily)Application.Current.Resources["Orbitron"]; + killTextBlock.Inlines.Add(titleRun); killTextBlock.Inlines.Add(new Run($"{killData.EnemyPilot}\n")); - // Repeat for other lines - killTextBlock.Inlines.Add(new Run("Victim Ship: ") - { - Foreground = altTextColorBrush, - FontFamily = orbitronFontFamily, - }); + titleRun = new Run("Victim Ship: "); + titleRun.SetResourceReference(TextElement.ForegroundProperty, "AltTextBrush"); + titleRun.FontFamily = (FontFamily)Application.Current.Resources["Orbitron"]; + killTextBlock.Inlines.Add(titleRun); killTextBlock.Inlines.Add(new Run($"{killData.EnemyShip}\n")); - killTextBlock.Inlines.Add(new Run("Victim Org: ") - { - Foreground = altTextColorBrush, - FontFamily = orbitronFontFamily, - }); + titleRun = new Run("Victim Org: "); + titleRun.SetResourceReference(TextElement.ForegroundProperty, "AltTextBrush"); + titleRun.FontFamily = (FontFamily)Application.Current.Resources["Orbitron"]; + killTextBlock.Inlines.Add(titleRun); killTextBlock.Inlines.Add(new Run($"{killData.OrgAffiliation}\n")); - killTextBlock.Inlines.Add(new Run("Join Date: ") - { - Foreground = altTextColorBrush, - FontFamily = orbitronFontFamily, - }); + titleRun = new Run("Join Date: "); + titleRun.SetResourceReference(TextElement.ForegroundProperty, "AltTextBrush"); + titleRun.FontFamily = (FontFamily)Application.Current.Resources["Orbitron"]; + killTextBlock.Inlines.Add(titleRun); killTextBlock.Inlines.Add(new Run($"{killData.Enlisted}\n")); - killTextBlock.Inlines.Add(new Run("UEE Record: ") - { - Foreground = altTextColorBrush, - FontFamily = orbitronFontFamily, - }); - + titleRun = new Run("UEE Record: "); + titleRun.SetResourceReference(TextElement.ForegroundProperty, "AltTextBrush"); + titleRun.FontFamily = (FontFamily)Application.Current.Resources["Orbitron"]; + killTextBlock.Inlines.Add(titleRun); killTextBlock.Inlines.Add(new Run($"{killData.RecordNumber}\n")); - killTextBlock.Inlines.Add(new Run("Kill Time: ") - { - Foreground = altTextColorBrush, - FontFamily = orbitronFontFamily, - }); + titleRun = new Run("Kill Time: "); + titleRun.SetResourceReference(TextElement.ForegroundProperty, "AltTextBrush"); + titleRun.FontFamily = (FontFamily)Application.Current.Resources["Orbitron"]; + killTextBlock.Inlines.Add(titleRun); killTextBlock.Inlines.Add(new Run($"{killData.KillTime}")); // Create a Border and apply the RoundedTextBlockWithBorder style