This commit is contained in:
DorkNormalize 2025-04-07 01:34:17 +00:00 committed by GitHub
commit 6b327a36a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 2192 additions and 2220 deletions

5
.gitignore vendored
View file

@ -360,4 +360,7 @@ MigrationBackup/
.ionide/ .ionide/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
### Rider ###
.idea/

View file

@ -5,8 +5,6 @@ VisualStudioVersion = 17.12.35521.163
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTrackR2", "AutoTrackR2\AutoTrackR2.csproj", "{31093634-8FBB-4BC6-BEA4-DAD6C11404F3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTrackR2", "AutoTrackR2\AutoTrackR2.csproj", "{31093634-8FBB-4BC6-BEA4-DAD6C11404F3}"
EndProject EndProject
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "AutoTrackR2_Setup", "AutoTrackR2_Setup\AutoTrackR2_Setup.vdproj", "{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -29,13 +27,6 @@ Global
{31093634-8FBB-4BC6-BEA4-DAD6C11404F3}.Test|Any CPU.Build.0 = Test|Any CPU {31093634-8FBB-4BC6-BEA4-DAD6C11404F3}.Test|Any CPU.Build.0 = Test|Any CPU
{31093634-8FBB-4BC6-BEA4-DAD6C11404F3}.Test|x64.ActiveCfg = Test|x64 {31093634-8FBB-4BC6-BEA4-DAD6C11404F3}.Test|x64.ActiveCfg = Test|x64
{31093634-8FBB-4BC6-BEA4-DAD6C11404F3}.Test|x64.Build.0 = Test|x64 {31093634-8FBB-4BC6-BEA4-DAD6C11404F3}.Test|x64.Build.0 = Test|x64
{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}.Debug|Any CPU.ActiveCfg = Debug
{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}.Debug|x64.ActiveCfg = Debug
{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}.Release|Any CPU.ActiveCfg = Release
{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}.Release|x64.ActiveCfg = Release
{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}.Release|x64.Build.0 = Release
{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}.Test|Any CPU.ActiveCfg = Debug
{025CBDCE-DE23-47CF-B75F-8C4C6C539E59}.Test|x64.ActiveCfg = Debug
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -15,20 +15,30 @@
<Color x:Key="TextColor">#FFFFFF</Color> <Color x:Key="TextColor">#FFFFFF</Color>
<Color x:Key="AltTextColor">#A88F2C</Color> <Color x:Key="AltTextColor">#A88F2C</Color>
<SolidColorBrush x:Key="TextBrush" Color="{DynamicResource TextColor}" /> <SolidColorBrush x:Key="TextBrush"
<SolidColorBrush x:Key="AccentBrush" Color="{DynamicResource AccentColor}" /> Color="{DynamicResource TextColor}"/>
<SolidColorBrush x:Key="BackgroundDarkBrush" Color="{DynamicResource BackgroundDarkColor}" /> <SolidColorBrush x:Key="AccentBrush"
<SolidColorBrush x:Key="BackgroundLightBrush" Color="{DynamicResource BackgroundLightColor}" /> Color="{DynamicResource AccentColor}"/>
<SolidColorBrush x:Key="AltTextBrush" Color="{DynamicResource AltTextColor}" /> <SolidColorBrush x:Key="BackgroundDarkBrush"
Color="{DynamicResource BackgroundDarkColor}"/>
<SolidColorBrush x:Key="BackgroundLightBrush"
Color="{DynamicResource BackgroundLightColor}"/>
<SolidColorBrush x:Key="AltTextBrush"
Color="{DynamicResource AltTextColor}"/>
<!-- Define the Style for Window --> <!-- Define the Style for Window -->
<Style TargetType="Window" x:Key="CustomWindowStyle"> <Style TargetType="Window"
x:Key="CustomWindowStyle">
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Window"> <ControlTemplate TargetType="Window">
<Border BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="10" Background="{DynamicResource BackgroundLightBrush}"> <Border BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="2"
CornerRadius="10"
Background="{DynamicResource BackgroundLightBrush}">
<Grid> <Grid>
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> <ContentPresenter HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid> </Grid>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
@ -37,62 +47,99 @@
</Style> </Style>
<!-- Tab Button Style --> <!-- Tab Button Style -->
<Style x:Key="TabButtonStyle" TargetType="Button"> <Style x:Key="TabButtonStyle"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> TargetType="Button">
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> <Setter Property="Foreground"
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> Value="{DynamicResource TextBrush}"/>
<Setter Property="BorderThickness" Value="2"/> <Setter Property="Background"
<Setter Property="Cursor" Value="Hand"/> Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="Padding" Value="10"/> <Setter Property="BorderBrush"
<Setter Property="Margin" Value="5"/> Value="{DynamicResource AccentBrush}"/>
<Setter Property="FontFamily" Value="{StaticResource Orbitron}"/> <Setter Property="BorderThickness"
Value="2"/>
<Setter Property="Cursor"
Value="Hand"/>
<Setter Property="Padding"
Value="10"/>
<Setter Property="Margin"
Value="5"/>
<Setter Property="FontFamily"
Value="{StaticResource Orbitron}"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Button"> <ControlTemplate TargetType="Button">
<Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="5"> <Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<!-- ContentPresenter will automatically inherit Foreground from Button --> <!-- ContentPresenter will automatically inherit Foreground from Button -->
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" /> <ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<!-- General Button Style --> <!-- General Button Style -->
<Style x:Key="DisabledButtonStyle" TargetType="Button"> <Style x:Key="DisabledButtonStyle"
<Setter Property="Foreground" Value="Gray"/> TargetType="Button">
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> <Setter Property="Foreground"
<Setter Property="BorderBrush" Value="Gray"/> Value="Gray"/>
<Setter Property="BorderThickness" Value="2"/> <Setter Property="Background"
<Setter Property="FontWeight" Value="Bold"/> Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="Cursor" Value="Hand"/> <Setter Property="BorderBrush"
<Setter Property="Padding" Value="5"/> Value="Gray"/>
<Setter Property="BorderThickness"
Value="2"/>
<Setter Property="FontWeight"
Value="Bold"/>
<Setter Property="Cursor"
Value="Hand"/>
<Setter Property="Padding"
Value="5"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Button"> <ControlTemplate TargetType="Button">
<Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="5"> <Border Background="{DynamicResource BackgroundDarkBrush}"
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style x:Key="ButtonStyle" TargetType="Button"> <Style x:Key="ButtonStyle"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> TargetType="Button">
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> <Setter Property="Foreground"
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> Value="{DynamicResource TextBrush}"/>
<Setter Property="BorderThickness" Value="2"/> <Setter Property="Background"
<Setter Property="FontWeight" Value="Bold"/> Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="Cursor" Value="Hand"/> <Setter Property="BorderBrush"
<Setter Property="Padding" Value="5"/> Value="{DynamicResource AccentBrush}"/>
<Setter Property="BorderThickness"
Value="2"/>
<Setter Property="FontWeight"
Value="Bold"/>
<Setter Property="Cursor"
Value="Hand"/>
<Setter Property="Padding"
Value="5"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Button"> <ControlTemplate TargetType="Button">
<Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="5"> <Border Background="{DynamicResource BackgroundDarkBrush}"
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
@ -100,69 +147,141 @@
</Style> </Style>
<!-- Title Bar Button Style --> <!-- Title Bar Button Style -->
<Style x:Key="TitleButtonStyle" TargetType="Button"> <Style x:Key="TitleButtonStyle"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> TargetType="Button">
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> <Setter Property="Foreground"
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> Value="{DynamicResource TextBrush}"/>
<Setter Property="BorderThickness" Value="2"/> <Setter Property="Background"
<Setter Property="FontWeight" Value="Bold"/> Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="Cursor" Value="Hand"/> <Setter Property="BorderBrush"
Value="{DynamicResource AccentBrush}"/>
<Setter Property="BorderThickness"
Value="2"/>
<Setter Property="FontWeight"
Value="Bold"/>
<Setter Property="Cursor"
Value="Hand"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Button"> <ControlTemplate TargetType="Button">
<Border Background="{DynamicResource BackgroundDarkBrush}" <Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}" BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="2" BorderThickness="2"
CornerRadius="5" CornerRadius="5"
Margin="0,1,4,1"> Margin="0,1,4,1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> <ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<!-- Custom style for text blocks --> <!-- Custom style for text blocks -->
<Style x:Key="RoundedTextBlock" TargetType="TextBlock"> <Style x:Key="RoundedTextBlock"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource Roboto}" /> <Setter Property="Foreground"
<Setter Property="Background" Value="Transparent" /> Value="{DynamicResource TextBrush}"/>
<Setter Property="FontSize" Value="14" /> <Setter Property="FontFamily"
<Setter Property="Padding" Value="10,0,10,0" /> Value="{StaticResource Roboto}"/>
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="Background"
<Setter Property="HorizontalAlignment" Value="Stretch" /> Value="Transparent"/>
<Setter Property="FontSize"
Value="14"/>
<Setter Property="Padding"
Value="10,0,10,0"/>
<Setter Property="VerticalAlignment"
Value="Center"/>
<Setter Property="HorizontalAlignment"
Value="Stretch"/>
</Style> </Style>
<!-- Wrap TextBlock in Border to apply rounded corners --> <!-- Wrap TextBlock in Border to apply rounded corners -->
<Style x:Key="RoundedTextBlockWithBorder" TargetType="Border"> <Style x:Key="RoundedTextBlockWithBorder"
<Setter Property="Background" Value="{DynamicResource BackgroundLightBrush}"/> TargetType="Border">
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> <Setter Property="Background"
<Setter Property="BorderThickness" Value="2"/> Value="{DynamicResource BackgroundLightBrush}"/>
<Setter Property="CornerRadius" Value="5"/> <Setter Property="BorderBrush"
<Setter Property="Padding" Value="0"/> Value="{DynamicResource AccentBrush}"/>
<Setter Property="Margin" Value="0,10,0,0"/> <Setter Property="BorderThickness"
Value="2"/>
<Setter Property="CornerRadius"
Value="5"/>
<Setter Property="Padding"
Value="0"/>
<Setter Property="Margin"
Value="0,10,0,0"/>
</Style> </Style>
<!-- Custom Style for Rounded TextBox --> <!-- Custom Style for Rounded TextBox -->
<Style x:Key="RoundedTextBox" TargetType="TextBox"> <Style x:Key="RoundedTextBox"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> TargetType="TextBox">
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> <Setter Property="Foreground"
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> Value="{DynamicResource TextBrush}"/>
<Setter Property="FontFamily" Value="{StaticResource Roboto}"/> <Setter Property="Background"
<Setter Property="Height" Value="30"/> Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="Padding" Value="5"/> <Setter Property="BorderBrush"
<Setter Property="VerticalContentAlignment" Value="Center"/> Value="{DynamicResource AccentBrush}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="FontFamily"
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> Value="{StaticResource Roboto}"/>
<Setter Property="BorderThickness" Value="2"/> <Setter Property="Height"
Value="30"/>
<Setter Property="Padding"
Value="5"/>
<Setter Property="VerticalContentAlignment"
Value="Center"/>
<Setter Property="HorizontalAlignment"
Value="Stretch"/>
<Setter Property="BorderBrush"
Value="{DynamicResource AccentBrush}"/>
<Setter Property="BorderThickness"
Value="2"/>
<!-- The actual border with rounded corners --> <!-- The actual border with rounded corners -->
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="TextBox"> <ControlTemplate TargetType="TextBox">
<Border Background="{DynamicResource BackgroundDarkBrush}" <Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}" BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5"> CornerRadius="5">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Custom Style for Rounded PasswordBox -->
<Style x:Key="RoundedPasswordBox"
TargetType="PasswordBox">
<Setter Property="Foreground"
Value="{DynamicResource TextBrush}"/>
<Setter Property="Background"
Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="BorderBrush"
Value="{DynamicResource AccentBrush}"/>
<Setter Property="FontFamily"
Value="{StaticResource Roboto}"/>
<Setter Property="Height"
Value="30"/>
<Setter Property="Padding"
Value="5"/>
<Setter Property="VerticalContentAlignment"
Value="Center"/>
<Setter Property="HorizontalAlignment"
Value="Stretch"/>
<Setter Property="BorderBrush"
Value="{DynamicResource AccentBrush}"/>
<Setter Property="BorderThickness"
Value="2"/>
<!-- The actual border with rounded corners -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="PasswordBox">
<Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<ScrollViewer x:Name="PART_ContentHost"/> <ScrollViewer x:Name="PART_ContentHost"/>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
@ -171,39 +290,57 @@
</Style> </Style>
<!-- Custom Style for Slider --> <!-- Custom Style for Slider -->
<Style x:Key="ThreePositionSlider" TargetType="Slider"> <Style x:Key="ThreePositionSlider"
<Setter Property="Height" Value="40" /> TargetType="Slider">
<Setter Property="Width" Value="160" /> <Setter Property="Height"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> Value="40"/>
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}" /> <Setter Property="Width"
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}" /> Value="160"/>
<Setter Property="BorderThickness" Value="2" /> <Setter Property="Foreground"
Value="{DynamicResource TextBrush}"/>
<Setter Property="Background"
Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="BorderBrush"
Value="{DynamicResource AccentBrush}"/>
<Setter Property="BorderThickness"
Value="2"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Slider"> <ControlTemplate TargetType="Slider">
<Grid Width="200" Height="30" HorizontalAlignment="Left" Margin="58,-6,0,0"> <Grid Width="200"
Height="30"
HorizontalAlignment="Left"
Margin="58,-6,0,0">
<!-- Track Background --> <!-- Track Background -->
<Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="15" Margin="0,0,-5,-4" /> <Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="2"
CornerRadius="15"
Margin="0,0,-5,-4"/>
<!-- Track --> <!-- Track -->
<Track x:Name="PART_Track"> <Track x:Name="PART_Track">
<Track.Thumb> <Track.Thumb>
<Thumb x:Name="PART_Thumb" <Thumb x:Name="PART_Thumb"
Width="22" Width="22"
Height="22" Height="22"
Margin="6,4,1,0"> Margin="6,4,1,0">
<Thumb.Template> <Thumb.Template>
<ControlTemplate TargetType="Thumb"> <ControlTemplate TargetType="Thumb">
<Ellipse Fill="{DynamicResource AccentBrush}" /> <Ellipse Fill="{DynamicResource AccentBrush}"/>
</ControlTemplate> </ControlTemplate>
</Thumb.Template> </Thumb.Template>
</Thumb> </Thumb>
</Track.Thumb> </Track.Thumb>
<Track.DecreaseRepeatButton> <Track.DecreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> <RepeatButton Background="Transparent"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Track.DecreaseRepeatButton> </Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton> <Track.IncreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> <RepeatButton Background="Transparent"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Track.IncreaseRepeatButton> </Track.IncreaseRepeatButton>
</Track> </Track>
</Grid> </Grid>
@ -213,39 +350,56 @@
</Style> </Style>
<!-- Toggle Slider Style --> <!-- Toggle Slider Style -->
<Style x:Key="ToggleSliderStyle" TargetType="Slider"> <Style x:Key="ToggleSliderStyle"
<Setter Property="Height" Value="40" /> TargetType="Slider">
<Setter Property="Width" Value="160" /> <Setter Property="Height"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> Value="40"/>
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}" /> <Setter Property="Width"
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}" /> Value="160"/>
<Setter Property="BorderThickness" Value="2" /> <Setter Property="Foreground"
Value="{DynamicResource TextBrush}"/>
<Setter Property="Background"
Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="BorderBrush"
Value="{DynamicResource AccentBrush}"/>
<Setter Property="BorderThickness"
Value="2"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Slider"> <ControlTemplate TargetType="Slider">
<Grid Width="50" Height="30" HorizontalAlignment="Left" > <Grid Width="50"
Height="30"
HorizontalAlignment="Left">
<!-- Track Background --> <!-- Track Background -->
<Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="15" Margin="0,0,-5,-4" /> <Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="2"
CornerRadius="15"
Margin="0,0,-5,-4"/>
<!-- Track --> <!-- Track -->
<Track x:Name="PART_Track"> <Track x:Name="PART_Track">
<Track.Thumb> <Track.Thumb>
<Thumb x:Name="PART_Thumb" <Thumb x:Name="PART_Thumb"
Width="22" Width="22"
Height="22" Height="22"
Margin="6,4,1,0"> Margin="6,4,1,0">
<Thumb.Template> <Thumb.Template>
<ControlTemplate TargetType="Thumb"> <ControlTemplate TargetType="Thumb">
<Ellipse Fill="{DynamicResource AccentBrush}" /> <Ellipse Fill="{DynamicResource AccentBrush}"/>
</ControlTemplate> </ControlTemplate>
</Thumb.Template> </Thumb.Template>
</Thumb> </Thumb>
</Track.Thumb> </Track.Thumb>
<Track.DecreaseRepeatButton> <Track.DecreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> <RepeatButton Background="Transparent"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Track.DecreaseRepeatButton> </Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton> <Track.IncreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> <RepeatButton Background="Transparent"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Track.IncreaseRepeatButton> </Track.IncreaseRepeatButton>
</Track> </Track>
</Grid> </Grid>
@ -255,39 +409,56 @@
</Style> </Style>
<!-- False toggle theme --> <!-- False toggle theme -->
<Style x:Key="FalseToggleStyle" TargetType="Slider"> <Style x:Key="FalseToggleStyle"
<Setter Property="Height" Value="40" /> TargetType="Slider">
<Setter Property="Width" Value="160" /> <Setter Property="Height"
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> Value="40"/>
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}" /> <Setter Property="Width"
<Setter Property="BorderBrush" Value="Gray" /> Value="160"/>
<Setter Property="BorderThickness" Value="2" /> <Setter Property="Foreground"
Value="{DynamicResource TextBrush}"/>
<Setter Property="Background"
Value="{DynamicResource BackgroundDarkBrush}"/>
<Setter Property="BorderBrush"
Value="Gray"/>
<Setter Property="BorderThickness"
Value="2"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Slider"> <ControlTemplate TargetType="Slider">
<Grid Width="50" Height="30" HorizontalAlignment="Left" > <Grid Width="50"
Height="30"
HorizontalAlignment="Left">
<!-- Track Background --> <!-- Track Background -->
<Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="15" Margin="0,0,-5,-4" /> <Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="2"
CornerRadius="15"
Margin="0,0,-5,-4"/>
<!-- Track --> <!-- Track -->
<Track x:Name="PART_Track"> <Track x:Name="PART_Track">
<Track.Thumb> <Track.Thumb>
<Thumb x:Name="PART_Thumb" <Thumb x:Name="PART_Thumb"
Width="22" Width="22"
Height="22" Height="22"
Margin="6,4,1,0"> Margin="6,4,1,0">
<Thumb.Template> <Thumb.Template>
<ControlTemplate TargetType="Thumb"> <ControlTemplate TargetType="Thumb">
<Ellipse Fill="Gray" /> <Ellipse Fill="Gray"/>
</ControlTemplate> </ControlTemplate>
</Thumb.Template> </Thumb.Template>
</Thumb> </Thumb>
</Track.Thumb> </Track.Thumb>
<Track.DecreaseRepeatButton> <Track.DecreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> <RepeatButton Background="Transparent"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Track.DecreaseRepeatButton> </Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton> <Track.IncreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> <RepeatButton Background="Transparent"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Track.IncreaseRepeatButton> </Track.IncreaseRepeatButton>
</Track> </Track>
</Grid> </Grid>
@ -298,15 +469,21 @@
<!-- Modern Rounded ScrollBar Style --> <!-- Modern Rounded ScrollBar Style -->
<Style TargetType="ScrollBar"> <Style TargetType="ScrollBar">
<Setter Property="Width" Value="6" /> <Setter Property="Width"
Value="6"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="ScrollBar"> <ControlTemplate TargetType="ScrollBar">
<Grid> <Grid>
<Track Name="PART_Track" IsDirectionReversed="true" Width="6" Margin="0,0,0,0"> <Track Name="PART_Track"
IsDirectionReversed="true"
Width="6"
Margin="0,0,0,0">
<!-- Decrease Repeat Button --> <!-- Decrease Repeat Button -->
<Track.DecreaseRepeatButton> <Track.DecreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="0"> <RepeatButton Background="Transparent"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="0">
<RepeatButton.Template> <RepeatButton.Template>
<ControlTemplate TargetType="RepeatButton"> <ControlTemplate TargetType="RepeatButton">
<Grid x:Name="RepeatButtonGrid"> <Grid x:Name="RepeatButtonGrid">
@ -314,12 +491,18 @@
<VisualStateGroup Name="CommonStates"> <VisualStateGroup Name="CommonStates">
<VisualState Name="Normal"> <VisualState Name="Normal">
<Storyboard> <Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="1" Duration="0:0:0"/> <DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="RepeatButtonGrid"
To="1"
Duration="0:0:0"/>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState Name="MouseOver"> <VisualState Name="MouseOver">
<Storyboard> <Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="0" Duration="0:0:0.1"/> <DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="RepeatButtonGrid"
To="0"
Duration="0:0:0.1"/>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
@ -338,10 +521,10 @@
<ControlTemplate TargetType="Thumb"> <ControlTemplate TargetType="Thumb">
<Grid> <Grid>
<Border <Border
Background="{DynamicResource AccentBrush}" Background="{DynamicResource AccentBrush}"
BorderBrush="{DynamicResource AccentBrush}" BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="0" BorderThickness="0"
CornerRadius="3" /> CornerRadius="3"/>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
</Thumb.Template> </Thumb.Template>
@ -350,7 +533,9 @@
<!-- Increase Repeat Button --> <!-- Increase Repeat Button -->
<Track.IncreaseRepeatButton> <Track.IncreaseRepeatButton>
<RepeatButton Background="Transparent" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="0"> <RepeatButton Background="Transparent"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="0">
<RepeatButton.Template> <RepeatButton.Template>
<ControlTemplate TargetType="RepeatButton"> <ControlTemplate TargetType="RepeatButton">
<Grid x:Name="RepeatButtonGrid"> <Grid x:Name="RepeatButtonGrid">
@ -358,12 +543,18 @@
<VisualStateGroup Name="CommonStates"> <VisualStateGroup Name="CommonStates">
<VisualState Name="Normal"> <VisualState Name="Normal">
<Storyboard> <Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="1" Duration="0:0:0"/> <DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="RepeatButtonGrid"
To="1"
Duration="0:0:0"/>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState Name="MouseOver"> <VisualState Name="MouseOver">
<Storyboard> <Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="0" Duration="0:0:0.1"/> <DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="RepeatButtonGrid"
To="0"
Duration="0:0:0.1"/>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>

View file

@ -108,13 +108,4 @@
</Resource> </Resource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="KillTrackR_MainScript.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="update.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View file

@ -1,97 +1,236 @@
<UserControl x:Class="AutoTrackR2.ConfigPage" <UserControl x:Class="AutoTrackR2.ConfigPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="396" Width="626"> Height="410"
Width="626">
<Grid Background="{DynamicResource BackgroundLightBrush}">
<!-- Main Layout Grid -->
<Grid Margin="0,0,5,7">
<Grid.RowDefinitions>
<!-- One row for the content, the other for buttons -->
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid Background="{DynamicResource BackgroundLightBrush}">
<!-- Left column for the main content area --> <!-- Main Layout Grid -->
<ColumnDefinition Width="*" /> <Grid Margin="0,0,5,7">
<!-- Right column for the buttons --> <Grid.RowDefinitions>
<ColumnDefinition Width="Auto" /> <!-- One row for the content, the other for buttons -->
</Grid.ColumnDefinitions> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Section for Config Fields --> <Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center" Height="389"> <!-- Left column for the main content area -->
<!-- Log File --> <ColumnDefinition Width="*"/>
<StackPanel Margin="0,10,0,15" Orientation="Horizontal"> <!-- Right column for the buttons -->
<TextBlock Text="Log File:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5" FontFamily="{StaticResource Roboto}"/> <ColumnDefinition Width="Auto"/>
<StackPanel Orientation="Horizontal" Margin="30,0,0,0"> </Grid.ColumnDefinitions>
<TextBox Name="LogFilePath" Width="340" Height="30" Style="{StaticResource RoundedTextBox}"/>
<Button Content="Browse" Width="75" Height="30" FontFamily="{StaticResource Orbitron}" Margin="5,0" Style="{StaticResource ButtonStyle}" Click="LogFileBrowseButton_Click"/>
</StackPanel>
</StackPanel>
<!-- API URL --> <!-- Section for Config Fields -->
<StackPanel Margin="0,0,0,15" Orientation="Horizontal"> <StackPanel Grid.Column="0"
<TextBlock Text="API URL:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5"/> VerticalAlignment="Center"
<StackPanel Orientation="Horizontal" Margin="30,0,0,0"> Height="389">
<TextBox Name="ApiUrl" Width="340" Height="30" Style="{StaticResource RoundedTextBox}"/> <!-- Log File -->
<Button Content="Test" Width="75" Height="30" FontFamily="{StaticResource Orbitron}" Margin="5,0" Style="{StaticResource ButtonStyle}" Click="TestApiButton_Click"/> <StackPanel Margin="0,10,0,15"
</StackPanel> Orientation="Horizontal">
</StackPanel> <TextBlock Text="ⓘ"
ToolTip="Set this to the Game.log file in your StarCitizen\LIVE directory."
Foreground="{DynamicResource TextBrush}"
FontSize="20"
Margin="0,0,3,5"/>
<TextBlock Text="Log File:"
Foreground="{DynamicResource TextBrush}"
FontSize="16"
Margin="0,5,0,5"
FontFamily="{StaticResource Roboto}"/>
<StackPanel Orientation="Horizontal"
Margin="30,0,0,0">
<TextBox Name="LogFilePath"
Width="330"
Height="30"
Style="{StaticResource RoundedTextBox}"/>
<Button Content="Browse"
Width="75"
Height="30"
FontFamily="{StaticResource Orbitron}"
Margin="5,0"
Style="{StaticResource ButtonStyle}"
Click="LogFileBrowseButton_Click"/>
</StackPanel>
</StackPanel>
<!-- API Key --> <!-- API URL -->
<StackPanel Margin="0,0,0,15" Orientation="Horizontal"> <StackPanel Margin="0,0,0,15"
<TextBlock Text="API Key:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5"/> Orientation="Horizontal">
<TextBox Name="ApiKey" Width="340" Height="30" Margin="33,0,0,0" Style="{StaticResource RoundedTextBox}"/> <TextBlock Text="ⓘ"
</StackPanel> ToolTip="Need a URL? No idea what to do? Contact heavy_bob on Discord!"
Foreground="{DynamicResource TextBrush}"
FontSize="20"
Margin="0,3,3,5"/>
<TextBlock Text="API URL:"
Foreground="{DynamicResource TextBrush}"
FontSize="16"
Margin="0,5,0,5"/>
<StackPanel Orientation="Horizontal"
Margin="30,0,0,0">
<TextBox Name="ApiUrl"
Width="330"
Height="30"
Style="{StaticResource RoundedTextBox}"/>
<Button Content="Test"
Width="75"
Height="30"
FontFamily="{StaticResource Orbitron}"
Margin="5,0"
Style="{StaticResource ButtonStyle}"
Click="TestApiButton_Click"/>
</StackPanel>
</StackPanel>
<!-- Video Path --> <!-- API Key -->
<StackPanel Margin="0,0,0,15" Orientation="Horizontal"> <StackPanel Margin="0,0,0,15"
<TextBlock Text="Video Path:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5"/> Orientation="Horizontal">
<StackPanel Orientation="Horizontal"> <TextBlock Text="ⓘ"
<TextBox Name="VideoPath" Width="340" Height="30" Margin="10,0,0,0" Style="{StaticResource RoundedTextBox}"/> ToolTip="Need a key? No idea what to do? Contact heavy_bob on Discord!"
<Button Content="Browse" Width="75" Height="30" FontFamily="{StaticResource Orbitron}" Margin="5,0" Style="{StaticResource ButtonStyle}" Click="VideoPathBrowseButton_Click"/> Foreground="{DynamicResource TextBrush}"
</StackPanel> FontSize="20"
</StackPanel> Margin="0,3,3,5"/>
<TextBlock Text="API Key:"
Foreground="{DynamicResource TextBrush}"
FontSize="16"
Margin="0,5,0,5"/>
<PasswordBox Name="ApiKey"
Width="330"
Height="30"
Margin="33,0,0,0"
Style="{StaticResource RoundedPasswordBox}"/>
</StackPanel>
<!-- Visor Wipe Toggle Slider --> <!-- Video Path -->
<StackPanel Margin="0,0,0,15" Orientation="Horizontal"> <StackPanel Margin="0,0,0,15"
<TextBlock Text="Visor Wipe:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> Orientation="Horizontal">
<Slider Name="VisorWipeSlider" Minimum="0" Maximum="1" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" Margin="27,-4,0,0" ValueChanged="VisorWipeSlider_ValueChanged"/> <TextBlock Text="ⓘ"
</StackPanel> ToolTip="The directory where your clipping software saves kills. Check the README."
Foreground="{DynamicResource TextBrush}"
FontSize="20"
Margin="0,3,3,5"/>
<TextBlock Text="Video Path:"
Foreground="{DynamicResource TextBrush}"
FontSize="16"
Margin="0,5,0,5"/>
<StackPanel Orientation="Horizontal">
<TextBox Name="VideoPath"
Width="330"
Height="30"
Margin="10,0,0,0"
Style="{StaticResource RoundedTextBox}"/>
<Button Content="Browse"
Width="75"
Height="30"
FontFamily="{StaticResource Orbitron}"
Margin="5,0"
Style="{StaticResource ButtonStyle}"
Click="VideoPathBrowseButton_Click"/>
</StackPanel>
</StackPanel>
<!-- Video Record Toggle Slider --> <!-- Visor Wipe Toggle Slider -->
<StackPanel Margin="0,0,0,15" Orientation="Horizontal"> <StackPanel Margin="0,0,0,15"
<TextBlock Text="Video Record:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> Orientation="Horizontal">
<Slider Name="VideoRecordSlider" Minimum="0" Maximum="1" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" Margin="10,-4,0,0" ValueChanged="VideoRecordSlider_ValueChanged"/> <TextBlock Text="ⓘ"
</StackPanel> ToolTip="Perform a Visor Wipe animation on player kill. Requires AHKv2."
Foreground="{DynamicResource TextBrush}"
FontSize="20"
Margin="0,4,3,5"/>
<TextBlock Text="Visor Wipe:"
Foreground="{DynamicResource TextBrush}"
FontSize="16"
Margin="0,7,0,5"/>
<Slider Name="VisorWipeSlider"
Minimum="0"
Maximum="1"
TickFrequency="1"
IsSnapToTickEnabled="True"
Value="0"
Style="{StaticResource ToggleSliderStyle}"
Margin="27,-4,0,0"
ValueChanged="VisorWipeSlider_ValueChanged"/>
</StackPanel>
<!-- Offline Mode Toggle Slider --> <!-- Video Record Toggle Slider -->
<StackPanel Margin="0,0,0,15" Orientation="Horizontal"> <StackPanel Margin="0,0,0,15"
<TextBlock Text="Offline Mode:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> Orientation="Horizontal">
<Slider Name="OfflineModeSlider" Minimum="0" Maximum="1" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" Margin="12,-4,0,0" ValueChanged="OfflineModeSlider_ValueChanged"/> <TextBlock Text="ⓘ"
</StackPanel> ToolTip="Automatically clip your last kill. Check the README for more info."
Foreground="{DynamicResource TextBrush}"
FontSize="20"
Margin="0,4,3,5"/>
<TextBlock Text="Video Record:"
Foreground="{DynamicResource TextBrush}"
FontSize="16"
Margin="0,7,0,5"/>
<Slider Name="VideoRecordSlider"
Minimum="0"
Maximum="1"
TickFrequency="1"
IsSnapToTickEnabled="True"
Value="0"
Style="{StaticResource ToggleSliderStyle}"
Margin="10,-4,0,0"
ValueChanged="VideoRecordSlider_ValueChanged"/>
</StackPanel>
<!-- 3-Position Toggle Slider --> <!-- Offline Mode Toggle Slider -->
<StackPanel Margin="0,0,0,15" Orientation="Horizontal"> <StackPanel Margin="0,0,0,10"
<TextBlock Text="Theme:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> Orientation="Horizontal">
<Slider x:Name="ThemeSlider" <TextBlock Text="ⓘ"
Minimum="0" ToolTip="With Offline Mode enabled, kills will not be submitted to the configured API."
Maximum="21" Foreground="{DynamicResource TextBrush}"
Value="0" FontSize="20"
TickFrequency="1" Margin="0,4,3,5"/>
IsSnapToTickEnabled="True" <TextBlock Text="Offline Mode:"
ValueChanged="ThemeSlider_ValueChanged" Width="447" Foreground="{DynamicResource TextBrush}"
Style="{StaticResource ThreePositionSlider}" FontSize="16"
/> Margin="0,7,0,5"/>
</StackPanel> <Slider Name="OfflineModeSlider"
Minimum="0"
Maximum="1"
TickFrequency="1"
IsSnapToTickEnabled="True"
Value="0"
Style="{StaticResource ToggleSliderStyle}"
Margin="12,-4,0,0"
ValueChanged="OfflineModeSlider_ValueChanged"/>
</StackPanel>
</StackPanel> <!-- 3-Position Toggle Slider -->
<StackPanel Margin="0,0,0,15"
Orientation="Horizontal">
<TextBlock Text="Theme:"
Foreground="{DynamicResource TextBrush}"
FontSize="16"
Margin="0,7,0,5"/>
<Slider x:Name="ThemeSlider"
Minimum="0"
Maximum="21"
Value="0"
TickFrequency="1"
IsSnapToTickEnabled="True"
ValueChanged="ThemeSlider_ValueChanged"
Width="447"
Style="{StaticResource ThreePositionSlider}"/>
</StackPanel>
<!-- Save Button --> </StackPanel>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0">
<Button x:Name="SaveButton" Content="Save" Width="100" Height="40" Style="{StaticResource ButtonStyle}" FontFamily="{StaticResource Orbitron}" Click="SaveButton_Click"/> <!-- Save Button -->
</StackPanel> <StackPanel Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,0,0,10">
<Button x:Name="SaveButton"
Content="Save"
Width="100"
Height="40"
Style="{StaticResource ButtonStyle}"
FontFamily="{StaticResource Orbitron}"
Click="SaveButton_Click"/>
</StackPanel>
</Grid>
</Grid> </Grid>
</Grid>
</UserControl> </UserControl>

View file

@ -24,10 +24,10 @@ namespace AutoTrackR2
{ {
InitializeComponent(); InitializeComponent();
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
LogFilePath.Text = ConfigManager.LogFile; LogFilePath.Text = ConfigManager.LogFile;
ApiUrl.Text = ConfigManager.ApiUrl; ApiUrl.Text = ConfigManager.ApiUrl;
ApiKey.Text = ConfigManager.ApiKey; ApiKey.Password = ConfigManager.ApiKey;
VideoPath.Text = ConfigManager.VideoPath; VideoPath.Text = ConfigManager.VideoPath;
VisorWipeSlider.Value = ConfigManager.VisorWipe; VisorWipeSlider.Value = ConfigManager.VisorWipe;
VideoRecordSlider.Value = ConfigManager.VideoRecord; VideoRecordSlider.Value = ConfigManager.VideoRecord;
@ -70,7 +70,7 @@ namespace AutoTrackR2
// Set the textboxes with the loaded values // Set the textboxes with the loaded values
LogFilePath.Text = logFile; LogFilePath.Text = logFile;
ApiUrl.Text = apiUrl; ApiUrl.Text = apiUrl;
ApiKey.Text = apiKey; ApiKey.Password = apiKey;
VideoPath.Text = videoPath; VideoPath.Text = videoPath;
// Set the sliders with the loaded values // Set the sliders with the loaded values
@ -123,7 +123,7 @@ namespace AutoTrackR2
// Apply the selected theme // Apply the selected theme
ApplyTheme(themeIndex); ApplyTheme(themeIndex);
mainWindow.UpdateTabVisuals(); mainWindow.UpdateTabVisuals();
} }
@ -293,11 +293,11 @@ namespace AutoTrackR2
break; break;
case 16: // Feezy case 16: // Feezy
UpdateThemeColors( UpdateThemeColors(
(Color)ColorConverter.ConvertFromString("#FFA500"), // Accent/Border - Orange (Color)ColorConverter.ConvertFromString("#FFA500"), // Accent/Border
(Color)ColorConverter.ConvertFromString("#FFE4B5"), // Button - Moccasin (Color)ColorConverter.ConvertFromString("#1B0C04"), // Button
(Color)ColorConverter.ConvertFromString("#FFF8DC"), // Background - Cornsilk (Color)ColorConverter.ConvertFromString("#1B0C04"), // Background
(Color)ColorConverter.ConvertFromString("#8B4513"), // Text - Saddle Brown (Color)ColorConverter.ConvertFromString("#FFE4B5"), // Text
(Color)ColorConverter.ConvertFromString("#FF7F50") // AltText - Coral (Color)ColorConverter.ConvertFromString("#FFE4B5") // AltText
); );
ChangeLogo("/Assets/chibifox.png", (Color)ColorConverter.ConvertFromString("#FFA500")); ChangeLogo("/Assets/chibifox.png", (Color)ColorConverter.ConvertFromString("#FFA500"));
break; break;
@ -399,11 +399,14 @@ namespace AutoTrackR2
dialog.ValidateNames = false; dialog.ValidateNames = false;
dialog.Filter = "All files|*.*"; dialog.Filter = "All files|*.*";
if (dialog.ShowDialog() == true) if (dialog.ShowDialog() == true && dialog.FileName != null)
{ {
// Extract only the directory path from the file // Extract only the directory path from the file
string selectedFolder = Path.GetDirectoryName(dialog.FileName); string? selectedFolder = Path.GetDirectoryName(dialog.FileName);
VideoPath.Text = selectedFolder; // Set the folder path if (selectedFolder != null)
{
VideoPath.Text = selectedFolder; // Set the folder path
}
} }
} }
@ -412,9 +415,13 @@ namespace AutoTrackR2
Slider slider = (Slider)sender; Slider slider = (Slider)sender;
// Build the dynamic file path for the current user // Build the dynamic file path for the current user
if (string.IsNullOrEmpty(ConfigManager.AHKScriptFolder))
{
MessageBox.Show("AHK script folder path is not configured.", "Configuration Error", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
string filePath = Path.Combine( string filePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ConfigManager.AHKScriptFolder,
"AutoTrackR2",
"visorwipe.ahk" "visorwipe.ahk"
); );
@ -508,41 +515,24 @@ namespace AutoTrackR2
private void SaveButton_Click(object sender, RoutedEventArgs e) private void SaveButton_Click(object sender, RoutedEventArgs e)
{ {
// Get the directory for the user's local application data
string appDataDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"AutoTrackR2"
);
// Ensure the directory exists
if (!Directory.Exists(appDataDirectory))
{
Directory.CreateDirectory(appDataDirectory);
}
// Combine the app data directory with the config file name
string configFilePath = Path.Combine(appDataDirectory, "config.ini");
using (StreamWriter writer = new StreamWriter(configFilePath))
{
writer.WriteLine($"LogFile={LogFilePath.Text}");
writer.WriteLine($"ApiUrl={ApiUrl.Text}");
writer.WriteLine($"ApiKey={ApiKey.Text}");
writer.WriteLine($"VideoPath={VideoPath.Text}");
writer.WriteLine($"VisorWipe={(int)VisorWipeSlider.Value}");
writer.WriteLine($"VideoRecord={(int)VideoRecordSlider.Value}");
writer.WriteLine($"OfflineMode={(int)OfflineModeSlider.Value}");
writer.WriteLine($"Theme={(int)ThemeSlider.Value}"); // Assumes you are saving the theme slider value (0, 1, or 2)
}
ConfigManager.ApiKey = ApiKey.Password;
ConfigManager.ApiUrl = ApiUrl.Text;
ConfigManager.LogFile = LogFilePath.Text;
ConfigManager.VideoPath = VideoPath.Text;
ConfigManager.VisorWipe = (int)VisorWipeSlider.Value;
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 // Start the flashing effect
FlashSaveButton(); FlashSaveButton();
ConfigManager.LoadConfig();
} }
private void FlashSaveButton() private void FlashSaveButton()
{ {
string originalText = SaveButton.Content.ToString(); string? originalText = SaveButton.Content?.ToString() ?? string.Empty;
SaveButton.Content = "Saved"; SaveButton.Content = "Saved";
// Save button color change effect // Save button color change effect
@ -591,7 +581,7 @@ namespace AutoTrackR2
{ {
string apiUrl = ApiUrl.Text; string apiUrl = ApiUrl.Text;
string modifiedUrl = Regex.Replace(apiUrl, @"(https?://[^/]+)/?.*", "$1/test"); string modifiedUrl = Regex.Replace(apiUrl, @"(https?://[^/]+)/?.*", "$1/test");
string apiKey = ApiKey.Text; string apiKey = ApiKey.Password;
Debug.WriteLine($"Sending to {modifiedUrl}"); Debug.WriteLine($"Sending to {modifiedUrl}");
try try

View file

@ -1,48 +1,176 @@
<UserControl x:Class="AutoTrackR2.HomePage" <UserControl x:Class="AutoTrackR2.HomePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="396" Width="626"> Height="396"
<Grid Background="{DynamicResource BackgroundLightBrush}"> Width="626">
<!-- Main Layout Grid --> <Grid Background="{DynamicResource BackgroundLightBrush}">
<Grid Margin="0,0,5,7"> <!-- Main Layout Grid -->
<Grid.RowDefinitions> <Grid Margin="0,0,5,7">
<!-- One row for the content, the other for buttons --> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <!-- One row for the content, the other for buttons -->
<RowDefinition Height="Auto" /> <RowDefinition Height="*"/>
</Grid.RowDefinitions> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<!-- Left column for the main content area --> <!-- Left column for the main content area -->
<ColumnDefinition /> <ColumnDefinition/>
<!-- Right column for the buttons --> <!-- Right column for the buttons -->
<ColumnDefinition Width="Auto" MinWidth="173" /> <ColumnDefinition Width="Auto"
</Grid.ColumnDefinitions> MinWidth="173"/>
</Grid.ColumnDefinitions>
<!-- Border for the kill feed section --> <!-- Border for the kill feed section -->
<!--TextBox Name="OutputTextBox" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Height="NaN" Margin="0,0,20,0" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="True" Style="{StaticResource RoundedTextBox}"/--> <!--TextBox Name="OutputTextBox" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Height="NaN" Margin="0,0,20,0" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="True" Style="{StaticResource RoundedTextBox}"/-->
<Border Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="5" Padding="10,0,0,0" Background="{DynamicResource BackgroundDarkBrush}" Margin="0,0,20,0"> <Border Grid.Row="0"
<ScrollViewer VerticalScrollBarVisibility="Auto" Width="419" Margin="0,0,-5,0"> Grid.Column="0"
<StackPanel Name="KillFeedStackPanel" Orientation="Vertical" Margin="0,0,0,0" Width="402" HorizontalAlignment="Left"/> Grid.RowSpan="2"
</ScrollViewer> BorderBrush="{DynamicResource AccentBrush}"
</Border> BorderThickness="2"
CornerRadius="5"
Padding="10,0,0,0"
Background="{DynamicResource BackgroundDarkBrush}"
Margin="0,0,20,0">
<ScrollViewer VerticalScrollBarVisibility="Auto"
Width="419"
Margin="0,0,-5,0">
<StackPanel Name="KillFeedStackPanel"
Orientation="Vertical"
Margin="0,0,0,0"
Width="402"
HorizontalAlignment="Left"/>
</ScrollViewer>
</Border>
<!-- StackPanel for Start and Stop buttons --> <!-- StackPanel for Start and Stop buttons -->
<Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" Grid.Row="0" Grid.Column="1" BorderThickness="2" CornerRadius="5" Margin="0,0,0,82"/> <Border Background="{DynamicResource BackgroundDarkBrush}"
<StackPanel Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Height="269" Width="152"> BorderBrush="{DynamicResource AccentBrush}"
<TextBlock Name="PilotNameTitle" Text="Pilot" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14"/> Grid.Row="0"
<TextBlock Name="PilotNameTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> Grid.Column="1"
<TextBlock Name="PlayerShipTitle" Text="Ship" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14" /> BorderThickness="2"
<TextBlock Name="PlayerShipTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> CornerRadius="5"
<TextBlock Name="GameModeTitle" Text="Game Mode" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14"/> Margin="0,0,0,82"/>
<TextBlock Name="GameModeTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> <StackPanel Grid.Column="1"
<TextBlock Name="KillTallyTitle" Text="Kill Tally" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14"/> VerticalAlignment="Center"
<TextBlock Name="KillTallyTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> HorizontalAlignment="Center"
<TextBox x:Name="DebugPanel" Text="" Width="152" Height="98" Background="Transparent" FontFamily="{StaticResource Orbitron}" Foreground="{DynamicResource TextBrush}" FontSize="8" BorderThickness="0" Margin="0,9,0,0"/> Height="269"
</StackPanel> Width="152">
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Height="120" Width="172" > <TextBlock Name="PilotNameTitle"
<Button Name="StartButton" Content="Start" Width="100" Height="40" Style="{StaticResource ButtonStyle}" FontFamily="{StaticResource Orbitron}" Margin="0,20" Click="StartButton_Click"/> Text="Pilot"
<Button Name="StopButton" Content="Stop" Width="100" Height="40" Style="{StaticResource DisabledButtonStyle}" FontFamily="{StaticResource Orbitron}" IsEnabled="False" Click="StopButton_Click"/> Width="152"
</StackPanel> Height="20"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/>
<TextBlock Name="PilotNameTextBox"
Text=""
Width="152"
Height="20"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}"
FontSize="10"
TextAlignment="Center"/>
<TextBlock Name="PlayerShipTitle"
Text="Ship"
Width="152"
Height="20"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/>
<TextBlock Name="PlayerShipTextBox"
Text=""
Width="152"
Height="20"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}"
FontSize="10"
TextAlignment="Center"/>
<TextBlock Name="GameModeTitle"
Text="Game Mode"
Width="152"
Height="20"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/>
<TextBlock Name="GameModeTextBox"
Text=""
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"
Height="20"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/>
<TextBlock Name="KillTallyTextBox"
Text=""
Width="152"
Height="20"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}"
FontSize="10"
TextAlignment="Center"/>
<TextBox x:Name="DebugPanel"
Text=""
Width="152"
Height="98"
Background="Transparent"
FontFamily="{StaticResource Orbitron}"
Foreground="{DynamicResource TextBrush}"
FontSize="8"
BorderThickness="0"
Margin="0,9,0,0"/>
</StackPanel>
<StackPanel Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Height="120"
Width="172">
<Border Background="{DynamicResource BackgroundDarkBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="2"
CornerRadius="5"
Height="80"
Margin="0,10,0,0">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Ellipse x:Name="StatusLight"
Width="15"
Height="15"
Margin="0,0,10,0"
Fill="Red"/>
<TextBlock x:Name="StatusText"
Text="TrackR&#x0a;Standby"
Foreground="{DynamicResource TextBrush}"
FontFamily="{StaticResource Orbitron}"
FontSize="14"
VerticalAlignment="Center"/>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Grid> </Grid>
</Grid>
</UserControl> </UserControl>

View file

@ -3,309 +3,386 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Effects; using System.Windows.Media.Effects;
using System.IO;
using System.Windows.Documents; using System.Windows.Documents;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using AutoTrackR2.LogEventHandlers;
using System.Timers;
using System.Linq;
namespace AutoTrackR2
namespace AutoTrackR2;
public partial class HomePage : UserControl
{ {
public partial class HomePage : UserControl
private LogHandler? _logHandler;
private KillHistoryManager _killHistoryManager;
private bool _UIEventsRegistered = false;
private System.Timers.Timer _statusCheckTimer;
private bool _isLogHandlerRunning = false;
public HomePage()
{ {
public HomePage() InitializeComponent();
if (string.IsNullOrEmpty(ConfigManager.KillHistoryFile))
{ {
InitializeComponent(); throw new InvalidOperationException("KillHistoryFile path is not configured.");
// Get the current month
string currentMonth = DateTime.Now.ToString("MMMM", CultureInfo.InvariantCulture);
// Set the TextBlock text
KillTallyTitle.Text = $"Kill Tally - {currentMonth}";
} }
_killHistoryManager = new KillHistoryManager(ConfigManager.KillHistoryFile);
private Process runningProcess; // Field to store the running process // Set the TextBlock text
KillTallyTitle.Text = $"Kill Tally - {DateTime.Now.ToString("MMMM")}";
KillTallyTextBox.Text = _killHistoryManager.GetKillsInCurrentMonth().Count.ToString();
AdjustFontSize(KillTallyTextBox);
AddKillHistoryKillsToUI();
// Update Start/Stop button states based on the isRunning flag // Initialize and start the status check timer
public void UpdateButtonState(bool isRunning) _statusCheckTimer = new System.Timers.Timer(1000); // Check every second
_statusCheckTimer.Elapsed += CheckStarCitizenStatus;
_statusCheckTimer.Start();
// Check if Star Citizen is already running and initialize accordingly
if (IsStarCitizenRunning())
{ {
var accentColor = (Color)Application.Current.Resources["AccentColor"]; Dispatcher.Invoke(() =>
{
UpdateStatusIndicator(true);
ReadInitialStates(); // Read states first
InitializeLogHandler(); // Then initialize the log handler
});
}
}
private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e)
{
bool isRunning = IsStarCitizenRunning();
Dispatcher.Invoke(() =>
{
UpdateStatusIndicator(isRunning);
if (isRunning) if (isRunning)
{ {
// Set Start button to "Running..." and apply glow effect if (!_isLogHandlerRunning)
StartButton.Content = "Running...";
StartButton.IsEnabled = false; // Disable Start button
StartButton.Style = (Style)FindResource("DisabledButtonStyle");
// Add glow effect to the Start button
StartButton.Effect = new DropShadowEffect
{ {
Color = accentColor, // Game is running, start log monitoring and read initial states
BlurRadius = 30, // Adjust blur radius for desired glow intensity InitializeLogHandler();
ShadowDepth = 0, // Set shadow depth to 0 for a pure glow effect ReadInitialStates();
Opacity = 1, // Set opacity for glow visibility }
Direction = 0 // Direction doesn't matter for glow
};
StopButton.Style = (Style)FindResource("ButtonStyle");
StopButton.IsEnabled = true; // Enable Stop button
} }
else else
{ {
// Reset Start button back to its original state // Game is not running, set everything to Unknown
StartButton.Content = "Start"; GameModeTextBox.Text = "Unknown";
StartButton.IsEnabled = true; // Enable Start button PlayerShipTextBox.Text = "Unknown";
PilotNameTextBox.Text = "Unknown";
LocalPlayerData.CurrentGameMode = GameMode.Unknown;
LocalPlayerData.PlayerShip = string.Empty;
LocalPlayerData.Username = string.Empty;
// Remove the glow effect from Start button // Stop log monitoring if it's running
StartButton.Effect = null; if (_isLogHandlerRunning)
StopButton.Style = (Style)FindResource("DisabledButtonStyle");
StartButton.Style = (Style)FindResource("ButtonStyle");
StopButton.IsEnabled = false; // Disable Stop button
}
}
public void StartButton_Click(object sender, RoutedEventArgs e)
{
UpdateButtonState(true);
string scriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "KillTrackR_MainScript.ps1");
TailFileAsync(scriptPath);
}
private async void TailFileAsync(string scriptPath)
{
await Task.Run(() =>
{
try
{ {
ProcessStartInfo psi = new ProcessStartInfo _logHandler?.StopMonitoring();
{ _isLogHandlerRunning = false;
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy Bypass -File \"{scriptPath}\"",
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
runningProcess = new Process { StartInfo = psi }; // Store the process in the field
runningProcess.OutputDataReceived += (s, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Dispatcher.Invoke(() =>
{
// Parse and display key-value pairs in the OutputTextBox
if (e.Data.Contains("PlayerName="))
{
string pilotName = e.Data.Split('=')[1].Trim();
PilotNameTextBox.Text = pilotName; // Update the Button's Content
AdjustFontSize(PilotNameTextBox);
}
else if (e.Data.Contains("PlayerShip="))
{
string playerShip = e.Data.Split('=')[1].Trim();
PlayerShipTextBox.Text = playerShip;
AdjustFontSize(PlayerShipTextBox);
}
else if (e.Data.Contains("GameMode="))
{
string gameMode = e.Data.Split('=')[1].Trim();
GameModeTextBox.Text = gameMode;
AdjustFontSize(GameModeTextBox);
}
else if (e.Data.Contains("KillTally="))
{
string killTally = e.Data.Split('=')[1].Trim();
KillTallyTextBox.Text = killTally;
AdjustFontSize(KillTallyTextBox);
}
else if (e.Data.Contains("NewKill="))
{
// Parse the kill data
var killData = e.Data.Split('=')[1].Trim(); // Assume the kill data follows after "NewKill="
var killParts = killData.Split(',');
// 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
var killTextBlock = new TextBlock
{
Margin = new Thickness(0, 10, 0, 10),
Style = (Style)Application.Current.Resources["RoundedTextBlock"], // Apply style for text
FontSize = 14,
FontWeight = FontWeights.Bold,
FontFamily = gemunuFontFamily,
};
// Add styled content using Run elements
killTextBlock.Inlines.Add(new Run("Victim Name: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killParts[1]}\n"));
// Repeat for other lines
killTextBlock.Inlines.Add(new Run("Victim Ship: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killParts[2]}\n"));
killTextBlock.Inlines.Add(new Run("Victim Org: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killParts[3]}\n"));
killTextBlock.Inlines.Add(new Run("Join Date: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killParts[4]}\n"));
killTextBlock.Inlines.Add(new Run("UEE Record: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killParts[5]}\n"));
killTextBlock.Inlines.Add(new Run("Kill Time: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killParts[6]}"));
// Create a Border and apply the RoundedTextBlockWithBorder style
var killBorder = new Border
{
Style = (Style)Application.Current.Resources["RoundedTextBlockWithBorder"], // Apply border style
};
// Create a Grid to hold the TextBlock and the Image
var killGrid = new Grid
{
Width = 400, // Adjust the width of the Grid
Height = 130, // Adjust the height as needed
};
// Define two columns in the Grid: one for the text and one for the image
killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) }); // Text column
killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }); // Image column
// Add the TextBlock to the first column of the Grid
Grid.SetColumn(killTextBlock, 0);
killGrid.Children.Add(killTextBlock);
// Create the Image for the profile
var profileImage = new Image
{
Source = new BitmapImage(new Uri(killParts[7])), // Assuming the 8th part contains the profile image URL
Width = 90,
Height = 90,
Stretch = Stretch.Fill, // Adjust how the image fits
};
// Create a Border around the Image
var imageBorder = new Border
{
BorderBrush = accentColorBrush, // Set the border color
BorderThickness = new Thickness(2), // Set the border thickness
Padding = new Thickness(0), // Optional padding inside the border
CornerRadius = new CornerRadius(5),
Margin = new Thickness(10,18,15,18),
Child = profileImage // Set the Image as the content of the Border
};
// Add the Border (with the image inside) to the Grid
Grid.SetColumn(imageBorder, 1);
killGrid.Children.Add(imageBorder);
// Set the Grid as the child of the Border
killBorder.Child = killGrid;
// Add the new Border to the StackPanel inside the Border
KillFeedStackPanel.Children.Insert(0, killBorder);
}
else
{
DebugPanel.AppendText(e.Data + Environment.NewLine);
}
});
}
};
runningProcess.ErrorDataReceived += (s, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Dispatcher.Invoke(() =>
{
DebugPanel.AppendText(e.Data + Environment.NewLine);
});
}
};
runningProcess.Start();
runningProcess.BeginOutputReadLine();
runningProcess.BeginErrorReadLine();
runningProcess.WaitForExit();
} }
catch (Exception ex) }
});
}
private void UpdateStatusIndicator(bool isRunning)
{
if (isRunning)
{
StatusLight.Fill = new SolidColorBrush(Colors.Green);
StatusText.Text = "TrackR\nActive";
}
else
{
StatusLight.Fill = new SolidColorBrush(Colors.Red);
StatusText.Text = "TrackR\nStandby";
}
}
private void AddKillHistoryKillsToUI()
{
var kills = _killHistoryManager.GetKills();
foreach (var kill in kills)
{
Dispatcher.Invoke(() => { AddKillToScreen(kill); });
}
}
private void RegisterUIEventHandlers()
{
if (_UIEventsRegistered)
return;
// Username
TrackREventDispatcher.PlayerLoginEvent += (username) =>
{
Dispatcher.Invoke(() =>
{
PilotNameTextBox.Text = username;
AdjustFontSize(PilotNameTextBox);
LocalPlayerData.Username = username;
});
};
// Ship
TrackREventDispatcher.JumpDriveStateChangedEvent += (shipName) =>
{
Dispatcher.Invoke(() =>
{
PlayerShipTextBox.Text = LocalPlayerData.CurrentGameMode == GameMode.PersistentUniverse ? "Player" : shipName;
AdjustFontSize(PlayerShipTextBox);
LocalPlayerData.PlayerShip = shipName;
});
};
// Game Mode
TrackREventDispatcher.PlayerChangedGameModeEvent += (mode) =>
{
Dispatcher.Invoke(() =>
{
GameModeTextBox.Text = mode == GameMode.PersistentUniverse ? "Player" : mode.ToString();
AdjustFontSize(GameModeTextBox);
LocalPlayerData.CurrentGameMode = mode;
});
};
// Game Version
TrackREventDispatcher.GameVersionEvent += (version) =>
{
LocalPlayerData.GameVersion = version;
};
// Actor Death
TrackREventDispatcher.ActorDeathEvent += async (actorDeathData) =>
{
if (actorDeathData.VictimPilot != LocalPlayerData.Username)
{
var playerData = await WebHandler.GetPlayerData(actorDeathData.VictimPilot);
if (playerData != null)
{ {
var killData = new KillData
{
EnemyPilot = actorDeathData.VictimPilot,
EnemyShip = actorDeathData.VictimShip,
OrgAffiliation = playerData?.OrgName,
Weapon = actorDeathData.Weapon,
Ship = LocalPlayerData.PlayerShip ?? "Unknown",
Method = actorDeathData.DamageType,
RecordNumber = playerData?.UEERecord,
GameVersion = LocalPlayerData.GameVersion ?? "Unknown",
TrackRver = LocalPlayerData.GameVersion?.Replace("v", "") ?? "Unknown",
Enlisted = playerData?.JoinDate,
KillTime = DateTime.UtcNow.ToString("dd MMM yyyy HH:mm"),
PFP = playerData?.PFPURL ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg"
};
switch (LocalPlayerData.CurrentGameMode)
{
case GameMode.PersistentUniverse:
killData.Mode = "pu";
break;
case GameMode.ArenaCommander:
killData.Mode = "ac";
break;
}
// Add kill to UI
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
MessageBox.Show($"Error running script: {ex.Message}"); AddKillToScreen(killData);
}); });
// Only submit kill data if not in offline mode
if (ConfigManager.OfflineMode == 0)
{
await WebHandler.SubmitKill(killData);
}
_killHistoryManager.AddKill(killData);
VisorWipe();
VideoRecord();
} }
});
}
public void StopButton_Click(object sender, RoutedEventArgs e)
{
if (runningProcess != null && !runningProcess.HasExited)
{
// Kill the running process
runningProcess.Kill();
runningProcess = null; // Clear the reference to the process
} }
};
// Clear the text boxes // Vehicle Destruction
System.Threading.Thread.Sleep(200); TrackREventDispatcher.VehicleDestructionEvent += (data) =>
PilotNameTextBox.Text = string.Empty; {
PlayerShipTextBox.Text = string.Empty; LocalPlayerData.LastSeenVehicleLocation = data.VehicleZone;
GameModeTextBox.Text = string.Empty; };
KillTallyTextBox.Text = string.Empty;
KillFeedStackPanel.Children.Clear(); _UIEventsRegistered = true;
}
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
var killTextBlock = new TextBlock
{
Margin = new Thickness(0, 10, 0, 10),
Style = (Style)Application.Current.Resources["RoundedTextBlock"], // Apply style for text
FontSize = 14,
FontWeight = FontWeights.Bold,
FontFamily = gemunuFontFamily,
};
// Add styled content using Run elements
killTextBlock.Inlines.Add(new Run("Victim Name: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killData.EnemyPilot}\n"));
// Repeat for other lines
killTextBlock.Inlines.Add(new Run("Victim Ship: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killData.EnemyShip}\n"));
killTextBlock.Inlines.Add(new Run("Victim Org: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killData.OrgAffiliation}\n"));
killTextBlock.Inlines.Add(new Run("Join Date: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killData.Enlisted}\n"));
killTextBlock.Inlines.Add(new Run("UEE Record: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killData.RecordNumber}\n"));
killTextBlock.Inlines.Add(new Run("Kill Time: ")
{
Foreground = altTextColorBrush,
FontFamily = orbitronFontFamily,
});
killTextBlock.Inlines.Add(new Run($"{killData.KillTime}"));
// Create a Border and apply the RoundedTextBlockWithBorder style
var killBorder = new Border
{
Style = (Style)Application.Current.Resources["RoundedTextBlockWithBorder"], // Apply border style
};
// Create a Grid to hold the TextBlock and the Image
var killGrid = new Grid
{
Width = 400, // Adjust the width of the Grid
Height = 130, // Adjust the height as needed
};
// Define two columns in the Grid: one for the text and one for the image
killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) }); // Text column
killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }); // Image column
// Add the TextBlock to the first column of the Grid
Grid.SetColumn(killTextBlock, 0);
killGrid.Children.Add(killTextBlock);
if (killData.PFP == "")
{
killData.PFP = "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg";
} }
private void AdjustFontSize(TextBlock textBlock) // Create the Image for the profile
var profileImage = new Image
{ {
// Set a starting font size Source = new BitmapImage(new Uri(killData.PFP ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg")),
double fontSize = 14; Width = 90,
double maxWidth = textBlock.Width; Height = 90,
Stretch = Stretch.Fill, // Adjust how the image fits
};
if (string.IsNullOrEmpty(textBlock.Text) || double.IsNaN(maxWidth)) // Create a Border around the Image
return; var imageBorder = new Border();
imageBorder.SetResourceReference(Border.BorderBrushProperty, "AccentBrush");
imageBorder.BorderThickness = new Thickness(2);
imageBorder.Padding = new Thickness(0);
imageBorder.CornerRadius = new CornerRadius(5);
imageBorder.Margin = new Thickness(10, 18, 15, 18);
imageBorder.Child = profileImage;
// Measure the rendered width of the text // Add the Border (with the image inside) to the Grid
FormattedText formattedText = new FormattedText( Grid.SetColumn(imageBorder, 1);
killGrid.Children.Add(imageBorder);
// Set the Grid as the child of the Border
killBorder.Child = killGrid;
// Add the new Border to the StackPanel inside the Border
Dispatcher.Invoke(() =>
{
KillFeedStackPanel.Children.Insert(0, killBorder);
});
}
public void StopButton_Click(object sender, RoutedEventArgs e)
{
_logHandler?.StopMonitoring();
// Clear the text boxes
// System.Threading.Thread.Sleep(200);
// PilotNameTextBox.Text = string.Empty;
// PlayerShipTextBox.Text = string.Empty;
// GameModeTextBox.Text = string.Empty;
// KillTallyTextBox.Text = string.Empty;
// KillFeedStackPanel.Children.Clear();
}
private void AdjustFontSize(TextBlock textBlock)
{
// Set a starting font size
double fontSize = 14;
double maxWidth = textBlock.Width;
if (string.IsNullOrEmpty(textBlock.Text) || double.IsNaN(maxWidth))
return;
// Measure the rendered width of the text
FormattedText formattedText = new FormattedText(
textBlock.Text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch),
fontSize,
textBlock.Foreground,
VisualTreeHelper.GetDpi(this).PixelsPerDip
);
// Reduce font size until text fits within the width
while (formattedText.Width > maxWidth && fontSize > 6)
{
fontSize -= 0.5;
formattedText = new FormattedText(
textBlock.Text, textBlock.Text,
CultureInfo.CurrentCulture, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight, FlowDirection.LeftToRight,
@ -314,24 +391,203 @@ namespace AutoTrackR2
textBlock.Foreground, textBlock.Foreground,
VisualTreeHelper.GetDpi(this).PixelsPerDip VisualTreeHelper.GetDpi(this).PixelsPerDip
); );
}
// Apply the adjusted font size
textBlock.FontSize = fontSize;
}
// Reduce font size until text fits within the width public static void RunAHKScript(string? path)
while (formattedText.Width > maxWidth && fontSize > 6) {
{ if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(ConfigManager.AHKScriptFolder))
fontSize -= 0.5; {
formattedText = new FormattedText( return;
textBlock.Text, }
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch),
fontSize,
textBlock.Foreground,
VisualTreeHelper.GetDpi(this).PixelsPerDip
);
}
// Apply the adjusted font size string scriptPath = Path.Combine(ConfigManager.AHKScriptFolder, path);
textBlock.FontSize = fontSize;
if (!File.Exists(scriptPath))
{
return;
}
// Run the script using powershell
using var ahkProcess = new Process();
// Runs the script via Explorer, ensuring it uses whatever the
// default binary for AHK is. Skips having to find a specific path to AHK
ahkProcess.StartInfo.FileName = "explorer";
ahkProcess.StartInfo.Arguments = "\"" + scriptPath + "\"";
ahkProcess.Start();
}
private void VisorWipe()
{
if (ConfigManager.VisorWipe == 1)
{
RunAHKScript(ConfigManager.VisorWipeScript);
} }
} }
private void VideoRecord()
{
if (ConfigManager.VideoRecord == 1)
{
RunAHKScript(ConfigManager.VideoRecordScript);
}
}
public void InitializeLogHandler()
{
if (_logHandler == null)
{
_logHandler = new LogHandler(ConfigManager.LogFile);
_logHandler.Initialize();
RegisterUIEventHandlers();
_isLogHandlerRunning = true;
// Read initial states after initializing log handler
ReadInitialStates();
}
else if (!_isLogHandlerRunning)
{
_logHandler.Initialize();
_isLogHandlerRunning = true;
ReadInitialStates();
}
}
private void ReadInitialStates()
{
if (string.IsNullOrEmpty(ConfigManager.LogFile) || !File.Exists(ConfigManager.LogFile))
{
Debug.WriteLine("Log file not found or path is empty");
return;
}
try
{
Debug.WriteLine("Reading initial states from log file...");
// Read the entire log file
var lines = File.ReadAllLines(ConfigManager.LogFile);
string username = "";
string shipName = "";
GameMode gameMode = GameMode.Unknown;
// Read from the end of the file to get the most recent states
for (int i = lines.Length - 1; i >= 0; i--)
{
var line = lines[i];
// Check for username (login)
if (line.Contains("'s Character"))
{
int startIndex = line.IndexOf("'s Character");
if (startIndex > 0)
{
username = line.Substring(0, startIndex).Trim();
Debug.WriteLine($"Found username: {username}");
}
}
// Check for ship name
else if (line.Contains("Entering quantum travel from"))
{
int startIndex = line.IndexOf("in ship") + 8;
int endIndex = line.IndexOf(" to ", startIndex);
if (startIndex > 8 && endIndex > startIndex)
{
shipName = line.Substring(startIndex, endIndex - startIndex).Trim();
Debug.WriteLine($"Found ship: {shipName}");
}
}
// Check for game mode
else if (line.Contains("Loading level"))
{
if (line.Contains("Persistent_Universe"))
{
gameMode = GameMode.PersistentUniverse;
Debug.WriteLine("Found game mode: PU");
}
else if (line.Contains("Arena_Commander"))
{
gameMode = GameMode.ArenaCommander;
Debug.WriteLine("Found game mode: AC");
}
}
// If we've found all the information we need, we can stop reading
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(shipName) && gameMode != GameMode.Unknown)
{
break;
}
}
// Update UI with found states
Dispatcher.Invoke(() =>
{
if (!string.IsNullOrEmpty(username))
{
PilotNameTextBox.Text = username;
LocalPlayerData.Username = username;
AdjustFontSize(PilotNameTextBox);
Debug.WriteLine($"Set username in UI: {username}");
}
else
{
PilotNameTextBox.Text = "Unknown";
LocalPlayerData.Username = string.Empty;
AdjustFontSize(PilotNameTextBox);
Debug.WriteLine("Username not found, set to Unknown");
}
if (!string.IsNullOrEmpty(shipName))
{
PlayerShipTextBox.Text = gameMode == GameMode.PersistentUniverse ? "Player" : shipName;
LocalPlayerData.PlayerShip = shipName;
AdjustFontSize(PlayerShipTextBox);
Debug.WriteLine($"Set ship in UI: {PlayerShipTextBox.Text}");
}
else
{
PlayerShipTextBox.Text = "Unknown";
LocalPlayerData.PlayerShip = string.Empty;
AdjustFontSize(PlayerShipTextBox);
Debug.WriteLine("Ship not found, set to Unknown");
}
if (gameMode != GameMode.Unknown)
{
GameModeTextBox.Text = gameMode == GameMode.PersistentUniverse ? "Player" : gameMode.ToString();
LocalPlayerData.CurrentGameMode = gameMode;
AdjustFontSize(GameModeTextBox);
Debug.WriteLine($"Set game mode in UI: {GameModeTextBox.Text}");
}
else
{
GameModeTextBox.Text = "Unknown";
LocalPlayerData.CurrentGameMode = GameMode.Unknown;
AdjustFontSize(GameModeTextBox);
Debug.WriteLine("Game mode not found, set to Unknown");
}
});
}
catch (Exception ex)
{
Debug.WriteLine($"Error reading initial states: {ex.Message}");
}
}
public void Cleanup()
{
// Stop and dispose the status check timer
_statusCheckTimer?.Stop();
_statusCheckTimer?.Dispose();
// Stop the log handler if it's running
_logHandler?.StopMonitoring();
}
private bool IsStarCitizenRunning()
{
return Process.GetProcessesByName("StarCitizen").Length > 0;
}
} }

View file

@ -0,0 +1,98 @@
using System.Globalization;
using System.IO;
using System.Text;
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";
public KillHistoryManager(string logPath)
{
_killHistoryPath = logPath;
if (!File.Exists(_killHistoryPath))
{
File.WriteAllText(_killHistoryPath, _headers);
}
}
public void AddKill(KillData killData)
{
// Ensure the CSV file exists
// This should only happen if the file was deleted or corrupted
if (!File.Exists(_killHistoryPath))
{
File.WriteAllText(_killHistoryPath, _headers);
}
// Remove comma from Enlisted
killData.Enlisted = killData.Enlisted?.Replace(",", string.Empty);
// 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}\"");
// Check file can be written to
try
{
using var fileStream = new FileStream(_killHistoryPath, FileMode.Append, FileAccess.Write, FileShare.None);
using var writer = new StreamWriter(fileStream);
writer.Write(csv.ToString());
}
catch (IOException ex)
{
// Handle the exception (e.g., log it)
Console.WriteLine($"Error writing to file: {ex.Message}");
}
}
public List<KillData> GetKills()
{
var kills = new List<KillData>();
using var reader = new StreamReader(_killHistoryPath);
reader.ReadLine(); // Skip headers
while (reader.Peek() >= 0)
{
var line = reader.ReadLine();
// Remove extra quotes from CSV data
// Todo: These quotes are for handling commas in the data, but not sure if they're necessary
line = line?.Replace("\"", string.Empty);
var data = line?.Split(',');
kills.Add(new KillData
{
KillTime = data?[0],
EnemyPilot = data?[1],
EnemyShip = data?[2],
Enlisted = data?[3],
RecordNumber = data?[4],
OrgAffiliation = data?[5],
Player = data?[6],
Weapon = data?[7],
Ship = data?[8],
Method = data?[9],
Mode = data?[10],
GameVersion = data?[11],
TrackRver = data?[12],
Logged = data?[13],
PFP = data?[14]
});
}
return kills;
}
public List<KillData> GetKillsInCurrentMonth()
{
string currentMonth = DateTime.Now.ToString("MMM", CultureInfo.InvariantCulture);
var kills = GetKills();
return kills.Where(kill => kill.KillTime?.Contains(currentMonth) == true).ToList();
}
}

View file

@ -1,510 +0,0 @@
$TrackRver = "2.07"
# Path to the config file
$appName = "AutoTrackR2"
$scriptFolder = Join-Path -Path $env:LOCALAPPDATA -ChildPath $appName
$configFile = Join-Path -Path $scriptFolder -ChildPath "config.ini"
# Read the config file into a hashtable
if (Test-Path $configFile) {
Write-Output "PlayerName=Config.ini found."
$configContent = Get-Content $configFile | Where-Object { $_ -notmatch '^#|^\s*$' }
# Escape backslashes by doubling them
$configContent = $configContent -replace '\\', '\\\\'
# Convert to key-value pairs
$config = $configContent -replace '^([^=]+)=(.+)$', '$1=$2' | ConvertFrom-StringData
} else {
Write-Output "Config.ini not found."
exit
}
$parentApp = (Get-Process -Name AutoTrackR2).ID
# Access config values
$logFilePath = $config.Logfile
$apiUrl = $config.ApiUrl
$apiKey = $config.ApiKey
$videoPath = $config.VideoPath
$visorWipe = $config.VisorWipe
$videoRecord = $config.VideoRecord
$offlineMode = $config.OfflineMode
if ($offlineMode -eq 1){
$offlineMode = $true
} else {
$offlineMode = $false
}
Write-Output "PlayerName=OfflineMode: $offlineMode"
if ($videoRecord -eq 1){
$videoRecord = $true
} else {
$videoRecord = $false
}
Write-Output "PlayerName=VideoRecord: $videoRecord"
if ($visorWipe -eq 1){
$visorWipe = $true
} else {
$visorWipe = $false
}
Write-Output "PlayerName=VisorWipe: $visorWipe"
If (Test-Path $logFilePath) {
Write-Output "PlayerName=Logfile found"
} else {
Write-Output "Logfile not found."
}
If ($null -ne $apiUrl){
if ($apiUrl -notlike "*/register-kill") {
$apiUrl = $apiUrl.TrimEnd("/") + "/register-kill"
}
Write-output "PlayerName=$apiURL"
}
# Ship Manufacturers
$prefixes = @(
"ORIG",
"CRUS",
"RSI",
"AEGS",
"VNCL",
"DRAK",
"ANVL",
"BANU",
"MISC",
"CNOU",
"XIAN",
"GAMA",
"TMBL",
"ESPR",
"KRIG",
"GRIN",
"XNAA",
"MRAI"
)
# Define the regex pattern to extract information
$killPattern = "<Actor Death> CActor::Kill: '(?<EnemyPilot>[^']+)' \[\d+\] in zone '(?<EnemyShip>[^']+)' killed by '(?<Player>[^']+)' \[[^']+\] using '(?<Weapon>[^']+)' \[Class (?<Class>[^\]]+)\] with damage type '(?<DamageType>[^']+)'"
$puPattern = '<\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z> \[Notice\] <ContextEstablisherTaskFinished> establisher="CReplicationModel" message="CET completed" taskname="StopLoadingScreen" state=[^\s()]+\(\d+\) status="Finished" runningTime=\d+\.\d+ numRuns=\d+ map="megamap" gamerules="SC_Default" sessionId="[a-f0-9\-]+" \[Team_Network\]\[Network\]\[Replication\]\[Loading\]\[Persistence\]'
$loadoutPattern = '<Jump Drive State Changed>.*.adam: (?<ShipName>.*.) in'
$acPattern = "Requesting Mode Change" # "ArenaCommanderFeature"
$shipManPattern = "^(" + ($prefixes -join "|") + ")"
# $loginPattern = "\[Notice\] <AccountLoginCharacterStatus_Character> Character: createdAt [A-Za-z0-9]+ - updatedAt [A-Za-z0-9]+ - geid [A-Za-z0-9]+ - accountId [A-Za-z0-9]+ - name (?<Player>[A-Za-z0-9_-]+) - state STATE_CURRENT" # KEEP THIS INCASE LEGACY LOGIN IS REMOVED
$loginPattern = "\[Notice\] <Legacy login response> \[CIG-net\] User Login Success - Handle\[(?<Player>[A-Za-z0-9_-]+)\]"
$cleanupPattern = '^(.+?)_\d+$'
$versionPattern = "--system-trace-env-id='pub-sc-alpha-(?<gameversion>\d{3,4}-\d{7})'"
$vehiclePattern = "<(?<timestamp>[^>]+)> \[Notice\] <Vehicle Destruction> CVehicle::OnAdvanceDestroyLevel: " +
"Vehicle '(?<vehicle>[^']+)' \[\d+\] in zone '(?<vehicle_zone>[^']+)' " +
"\[pos x: (?<pos_x>[-\d\.]+), y: (?<pos_y>[-\d\.]+), z: (?<pos_z>[-\d\.]+) " +
"vel x: [^,]+, y: [^,]+, z: [^\]]+\] driven by '(?<driver>[^']+)' \[\d+\] " +
"advanced from destroy level (?<destroy_level_from>\d+) to (?<destroy_level_to>\d+) " +
"caused by '(?<caused_by>[^']+)' \[\d+\] with '(?<damage_type>[^']+)'"
# Lookup Patterns
$joinDatePattern = '<span class="label">Enlisted</span>\s*<strong class="value">([^<]+)</strong>'
$ueePattern = '<p class="entry citizen-record">\s*<span class="label">UEE Citizen Record<\/span>\s*<strong class="value">#?(n\/a|\d+)<\/strong>\s*<\/p>'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$process = Get-Process | Where-Object {$_.Name -like "AutoTrackR2"}
$global:killTally = 0
# Load historic kills from csv
if (Test-Path "$scriptFolder\Kill-Log.csv") {
$historicKills = Import-CSV "$scriptFolder\Kill-log.csv"
$currentDate = Get-Date
$dateFormat = "dd MMM yyyy HH:mm UTC"
foreach ($kill in $historicKills) {
$killDate = [datetime]::ParseExact($kill.KillTime.Trim(), $dateFormat, [System.Globalization.CultureInfo]::InvariantCulture)
If ($killdate.year -eq $currentDate.Year -and $killdate.month -eq $currentDate.Month) {
$global:killTally++
}
Try {
Write-Output "NewKill=throwaway,$($kill.EnemyPilot),$($kill.EnemyShip),$($kill.OrgAffiliation),$($kill.Enlisted),$($kill.RecordNumber),$($kill.KillTime), $($kill.PFP)"
} Catch {
Write-Output "Error Loading Kill: $($kill.EnemyPilot)"
}
}
}
Write-Output "KillTally=$global:killTally"
# Match and extract username from gamelog
Do {
# Load gamelog into memory
$authLog = Get-Content -Path $logFilePath
# Initialize variable to store username
$global:userName = $null
$global:loadout = "Player"
# Loop through each line in the log to find the matching line
foreach ($line in $authLog) {
if ($line -match $loginPattern) {
$global:userName = $matches['Player']
Write-Output "PlayerName=$global:userName"
}
# Get Loadout
if ($line -match $loadoutPattern) {
If ($matches['ShipName'] -match $cleanupPattern){
if ($null -ne $matches[1]){
$global:loadOut = $matches[1]
}
}
Write-Output "PlayerShip=$global:loadOut"
}
If ($line -match $versionPattern){
$global:GameVersion = $matches['gameversion']
}
if ($line -match $acPattern){
$global:GameMode = "AC"
}
if ($line -match $puPattern){
$global:GameMode = "PU"
}
Write-Output "GameMode=$global:GameMode"
}
# If no match found, print "Logged In: False"
if (-not $global:userName) {
Write-Output "PlayerName=No Player Found..."
Start-Sleep -Seconds 30
}
# Clear the log from memory
$authLog = $null
} until ($null -ne $global:userName)
# Function to process new log entries and write to the host
function Read-LogEntry {
param (
[string]$line
)
# Look for vehicle events
if ($line -match $vehiclePattern) {
# Access the named capture groups from the regex match
$global:vehicle_id = $matches['vehicle']
$global:location = $matches['vehicle_zone']
}
# Apply the regex pattern to the line
if ($line -match $killPattern) {
# Access the named capture groups from the regex match
$enemyPilot = $matches['EnemyPilot']
$enemyShip = $matches['EnemyShip']
$player = $matches['Player']
$weapon = $matches['Weapon']
$damageType = $matches['DamageType']
$ship = $global:loadOut
If ($enemyShip -ne "vehicle_id"){
$global:got_location = $location
}
else
{
$global:got_location = "NONE"
}
Try {
$page1 = Invoke-WebRequest -uri "https://robertsspaceindustries.com/citizens/$enemyPilot"
} Catch {
$page1 = $null
}
If ($null -ne $page1){
# Check if the Autotrackr2 process is running
if ($null -eq (Get-Process -ID $parentApp -ErrorAction SilentlyContinue)) {
Stop-Process -Id $PID -Force
}
If ($enemyShip -ne "Player"){
If ($enemyShip -eq $global:lastKill){
$enemyShip = "Passenger"
} Else {
$global:lastKill = $enemyShip
}
}
If ($player -eq $global:userName -and $enemyPilot -ne $global:userName){
If ($enemyShip -match $cleanupPattern){
$enemyShip = $matches[1]
}
If ($weapon -match $cleanupPattern){
$weapon = $matches[1]
}
If ($weapon -eq "KLWE_MassDriver_S10"){
$global:loadOut = "AEGS_Idris"
$ship = "AEGS_Idris"
}
if ($damageType -eq "Bullet" -or $weapon -like "apar_special_ballistic*") {
$ship = "Player"
$enemyShip = "Player"
$global:got_location = "NONE"
}
If ($ship -match $cleanupPattern){
$ship = $matches[1]
}
if ($ship -notmatch $shipManPattern){
$ship = "Player"
$global:got_location = "NONE"
}
If ($enemyShip -notmatch $shipManPattern -and $enemyShip -notlike "Passenger" ) {
$enemyShip = "Player"
$global:got_location = "NONE"
}
# Repeatedly remove all suffixes
while ($enemyShip -match '_(PU|AI|CIV|MIL|PIR)$') {
$enemyShip = $enemyShip -replace '_(PU|AI|CIV|MIL|PIR)$', ''
}
# Repeatedly remove all suffixes
while ($ship -match '_(PU|AI|CIV|MIL|PIR)$') {
$ship = $ship -replace '_(PU|AI|CIV|MIL|PIR)$', ''
}
while ($enemyShip -match '-00(1|2|3|4|5|6|7|8|9|0)$') {
$enemyShip = $enemyShip -replace '-00(1|2|3|4|5|6|7|8|9|0)$', ''
}while ($ship -match '-00(1|2|3|4|5|6|7|8|9|0)$') {
$ship = $ship -replace '-00(1|2|3|4|5|6|7|8|9|0)$', ''
}
$KillTime = (Get-Date).ToUniversalTime().ToString("dd MMM yyyy HH:mm 'UTC'", [System.Globalization.CultureInfo]::InvariantCulture)
# Get Enlisted Date
if ($($page1.content) -match $joinDatePattern) {
$joinDate = $matches[1]
$joinDate2 = $joinDate -replace ',', ''
} else {
$joinDate2 = "-"
}
# Check if there are any matches
If ($null -eq $page1.links[0].innerHTML) {
$enemyOrgs = $page1.links[4].innerHTML
} Else {
$enemyOrgs = $page1.links[3].innerHTML
}
if ($null -eq $enemyOrgs) {
$enemyOrgs = "-"
}
# Get UEE Number
if ($($page1.content) -match $ueePattern) {
# The matched UEE Citizen Record number is in $matches[1]
$citizenRecord = $matches[1]
} else {
$citizenRecord = "n/a"
}
If ($citizenRecord -eq "n/a") {
$citizenRecordAPI = "-1"
$citizenRecord = "-"
} Else {
$citizenRecordAPI = $citizenRecord
}
# Get PFP
if ($page1.images[0].src -like "/media/*") {
$victimPFP = "https://robertsspaceindustries.com$($page1.images[0].src)"
} Else {
$victimPFP = "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg"
}
$global:killTally++
Write-Output "KillTally=$global:killTally"
Write-Output "NewKill=throwaway,$enemyPilot,$enemyShip,$enemyOrgs,$joinDate2,$citizenRecord,$killTime,$victimPFP"
$global:GameMode = $global:GameMode.ToLower()
# Send to API
# Define the data to send
If ($null -ne $apiUrl -and $offlineMode -eq $false){
$data = @{
victim_ship = $enemyShip
victim = $enemyPilot
enlisted = $joinDate
rsi = $citizenRecordAPI
weapon = $weapon
method = $damageType
loadout_ship = $ship
game_version = $global:GameVersion
gamemode = $global:GameMode
trackr_version = $TrackRver
location = $got_location
}
# Headers which may or may not be necessary
$headers = @{
"Authorization" = "Bearer $apiKey"
"Content-Type" = "application/json"
"User-Agent" = "AutoTrackR2"
}
try {
# Send the POST request with JSON data
$null = Invoke-RestMethod -Uri $apiURL -Method Post -Body ($data | ConvertTo-Json -Depth 5) -Headers $headers
$logMode = "API"
$global:got_location = "NONE"
} catch {
# Catch and display errors
$apiError = $_
# Add to output file
$logMode = "Err-Local"
}
} Else {
$logMode = "Local"
}
# Define the output CSV path
$csvPath = "$scriptFolder\Kill-log.csv"
# Create an object to hold the data
$killData = [PSCustomObject]@{
KillTime = $killTime
EnemyPilot = $enemyPilot
EnemyShip = $enemyShip
Enlisted = $joinDate2
RecordNumber = $citizenRecord
OrgAffiliation = $enemyOrgs
Player = $player
Weapon = $weapon
Ship = $ship
Method = $damageType
Mode = $global:GameMode
GameVersion = $global:GameVersion
TrackRver = $TrackRver
Logged = $logMode
PFP = $victimPFP
}
# Remove commas from all properties
foreach ($property in $killData.PSObject.Properties) {
if ($property.Value -is [string]) {
$property.Value = $property.Value -replace ',', ''
}
}
# Export to CSV
if (-Not (Test-Path $csvPath)) {
# If file doesn't exist, create it with headers
$killData | Export-Csv -Path $csvPath -NoTypeInformation
} else {
# Append data to the existing file without adding headers
$killData | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1 | Out-File -Append -Encoding utf8 -FilePath $csvPath
}
$sleeptimer = 10
# VisorWipe
If ($visorwipe -eq $true -and $enemyShip -ne "Passenger" -and $damageType -notlike "*Bullet*"){
# send keybind for visorwipe
start-sleep 1
$sleeptimer = $sleeptimer -1
&"$scriptFolder\visorwipe.ahk"
}
# Record video
if ($videoRecord -eq $true -and $enemyShip -ne "Passenger"){
# send keybind for windows game bar recording
Start-Sleep 2
$sleeptimer = $sleeptimer -9
&"$scriptFolder\videorecord.ahk"
Start-Sleep 7
$latestFile = Get-ChildItem -Path $videoPath | Where-Object { -not $_.PSIsContainer } | Sort-Object CreationTime -Descending | Select-Object -First 1
# Check if the latest file is no more than 30 seconds old
if ($latestFile) {
$fileAgeInSeconds = (New-TimeSpan -Start $latestFile.CreationTime -End (Get-Date)).TotalSeconds
if ($fileAgeInSeconds -le 30) {
# Generate a timestamp in ddMMMyyyy-HH:mm format
$timestamp = (Get-Date).ToString("ddMMMyyyy-HHmm")
# Extract the file extension to preserve it
$fileExtension = $latestFile.Extension
# Rename the file, preserving the original file extension
Rename-Item -Path $latestFile.FullName -NewName "$enemyPilot.$enemyShip.$timestamp$fileExtension"
} else {}
} else {}
}
Start-Sleep $sleeptimer
}
}
}
# Get Logged-in User
If ($line -match $loginPattern) {
# Load gamelog into memory
$authLog = Get-Content -Path $logFilePath
$authLog = $authlog -match $loginPattern
$authLog = $authLog | Out-String
# Extract User Name
$nameExtract = "name\s+(?<PlayerName>[^\s-]+)"
If ($authLog -match $nameExtract -and $global:userName -ne $nameExtract){
$global:userName = $matches['PlayerName']
Write-Output "PlayerName=$global:userName"
}
}
# Detect PU or AC
if ($line -match $puPattern) {
$global:GameMode = "PU"
Write-Output "GameMode=$global:GameMode"
}
if ($line -match $acPattern) {
$global:GameMode = "AC"
Write-Output "GameMode=$global:GameMode"
}
#Set loadout
if ($line -match $loadoutPattern) {
If ($matches['ShipName'] -match $cleanupPattern){
if ($null -ne $matches[1]){
$global:loadOut = $matches[1]
}
}
Write-Output "PlayerShip=$global:loadOut"
}
}
# Monitor the log file and process new lines as they are added
Get-Content -Path $logFilePath -Wait -Tail 0 | ForEach-Object {
Read-LogEntry $_
}
<#
# Open the log file with shared access for reading and writing
$fileStream = [System.IO.FileStream]::new($logFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
$reader = [System.IO.StreamReader]::new($fileStream, [System.Text.Encoding]::UTF8) # Ensure we're reading as UTF-8
try {
# Move to the end of the file to start monitoring new entries
$reader.BaseStream.Seek(0, [System.IO.SeekOrigin]::End)
while ($true) {
# Read the next line from the file
$line = $reader.ReadLine()
# Ensure we have new content to process
if ($line) {
# Process the line (this is where your log entry handler would go)
Read-LogEntry $line
}
# Sleep for a brief moment to avoid high CPU usage
Start-Sleep -Milliseconds 100
}
}
finally {
# Ensure we close the reader and file stream properly when done
$reader.Close()
$fileStream.Close()
}
#>

View file

@ -0,0 +1,18 @@
namespace AutoTrackR2;
public enum GameMode
{
Unknown,
PersistentUniverse,
ArenaCommander
}
public static class LocalPlayerData
{
public static string? Username;
public static string? PlayerShip;
public static string? GameVersion;
public static GameMode CurrentGameMode;
public static string? LastSeenVehicleLocation;
}

View file

@ -0,0 +1,59 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public struct ActorDeathData
{
public string VictimPilot;
public string VictimShip;
public string Player;
public string Weapon;
public string Class;
public string DamageType;
public string Timestamp;
}
public class ActorDeathEvent : ILogEventHandler
{
public Regex Pattern { get; }
public ActorDeathEvent()
{
Pattern = new Regex(@"<Actor Death> CActor::Kill: '(?<EnemyPilot>[^']+)' \[\d+\] in zone '(?<EnemyShip>[^']+)' killed by '(?<Player>[^']+)' \[[^']+\] using '(?<Weapon>[^']+)' \[Class (?<Class>[^\]]+)\] with damage type '(?<DamageType>[^']+)");
}
Regex cleanUpPattern = new Regex(@"^(.+?)_\d+$");
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
var data = new ActorDeathData {
VictimPilot = match.Groups["EnemyPilot"].Value,
VictimShip = match.Groups["EnemyShip"].Value,
Player = match.Groups["Player"].Value,
Weapon = match.Groups["Weapon"].Value,
Class = match.Groups["Class"].Value,
DamageType = match.Groups["DamageType"].Value,
Timestamp = entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")
};
if (cleanUpPattern.IsMatch(data.VictimShip))
{
data.VictimShip = cleanUpPattern.Match(data.VictimShip).Groups[1].Value;
}
if (cleanUpPattern.IsMatch(data.Weapon))
{
data.Weapon = cleanUpPattern.Match(data.Weapon).Groups[1].Value;
}
TrackREventDispatcher.OnActorDeathEvent(data);
}
}

View file

@ -0,0 +1,21 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public class GameVersionEvent : ILogEventHandler
{
public Regex Pattern { get; }
public GameVersionEvent()
{
Pattern = new Regex(@"--system-trace-env-id='pub-sc-alpha-(?<GameVersion>\d{3,4}-\d{7})'");
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
TrackREventDispatcher.OnGameVersionEvent(match.Groups["GameVersion"].Value);
}
}

View file

@ -0,0 +1,10 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public interface ILogEventHandler
{
Regex Pattern { get; }
void Handle(LogEntry entry);
}

View file

@ -0,0 +1,22 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public class InArenaCommanderEvent : ILogEventHandler
{
public Regex Pattern { get; }
public InArenaCommanderEvent()
{
Pattern = new Regex("Requesting Mode Change");
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
TrackREventDispatcher.OnPlayerChangedGameModeEvent(GameMode.ArenaCommander);
}
}

View file

@ -0,0 +1,22 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public class InPersistentUniverseEvent : ILogEventHandler
{
public Regex Pattern { get; }
public InPersistentUniverseEvent()
{
Pattern = new Regex(@"<\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z> \[Notice\] <ContextEstablisherTaskFinished> establisher=""CReplicationModel"" message=""CET completed"" taskname=""StopLoadingScreen"" state=[^\s()]+\(\d+\) status=""Finished"" runningTime=\d+\.\d+ numRuns=\d+ map=""megamap"" gamerules=""SC_Default"" sessionId=""[a-f0-9\-]+"" \[Team_Network\]\[Network\]\[Replication\]\[Loading\]\[Persistence\]");
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
TrackREventDispatcher.OnPlayerChangedGameModeEvent(GameMode.PersistentUniverse);
}
}

View file

@ -0,0 +1,77 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public struct InstancedInteriorData
{
public string Entity;
public string OwnerGEID;
public string ManagerGEID;
public string InstancedInterior;
public string? Ship;
}
// A ship loadout has been changed
public class InstancedInteriorEvent : ILogEventHandler
{
public Regex Pattern { get; }
private Regex _shipManufacturerPattern;
private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
private List<string> _shipManufacturers = new List<string>
{
"ORIG",
"CRUS",
"RSI",
"AEGS",
"VNCL",
"DRAK",
"ANVL",
"BANU",
"MISC",
"CNOU",
"XIAN",
"GAMA",
"TMBL",
"ESPR",
"KRIG",
"GRIN",
"XNAA",
"MRAI"
};
public InstancedInteriorEvent()
{
Pattern = new Regex(@"\[InstancedInterior\] OnEntityLeaveZone - InstancedInterior \[(?<InstancedInterior>[^\]]+)\] \[\d+\] -> Entity \[(?<Entity>[^\]]+)\] \[\d+\] -- m_openDoors\[\d+\], m_managerGEID\[(?<ManagerGEID>\d+)\], m_ownerGEID\[(?<OwnerGEID>[^\[]+)\]");
_shipManufacturerPattern = new Regex($"^({string.Join("|", _shipManufacturers)})");
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
var data = new InstancedInteriorData {
Entity = match.Groups["Entity"].Value,
OwnerGEID = match.Groups["OwnerGEID"].Value,
ManagerGEID = match.Groups["ManagerGEID"].Value,
InstancedInterior = match.Groups["InstancedInterior"].Value,
};
match = _shipManufacturerPattern.Match(data.Entity);
if (match.Success)
{
match = _cleanUpPattern.Match(data.Entity);
if (match.Success)
{
data.Ship = match.Groups[1].Value;
}
}
TrackREventDispatcher.OnInstancedInteriorEvent(data);
}
}

View file

@ -0,0 +1,27 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public class JumpDriveStateChangedEvent : ILogEventHandler
{
public Regex Pattern { get; }
private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
public JumpDriveStateChangedEvent()
{
Pattern = new Regex(@"<Jump Drive State Changed>.*.adam: (?<ShipName>.*.) in");
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
match = _cleanUpPattern.Match(match.Groups["ShipName"].Value);
if (match.Success)
{
TrackREventDispatcher.OnJumpDriveStateChangedEvent(match.Groups[1].Value);;
}
}
}

View file

@ -0,0 +1,24 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
// Local player has logged in
public class LoginEvent : ILogEventHandler
{
public Regex Pattern { get; }
public LoginEvent()
{
Pattern = new Regex(@"\[Notice\] <Legacy login response> \[CIG-net\] User Login Success - Handle\[(?<Player>[A-Za-z0-9_-]+)\]");
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
TrackREventDispatcher.OnPlayerLoginEvent(match.Groups["Player"].Value);
}
}

View file

@ -0,0 +1,27 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public class RequestJumpFailedEvent : ILogEventHandler
{
public Regex Pattern { get; }
private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
public RequestJumpFailedEvent()
{
Pattern = new Regex(@"<Request Jump Failed>.*.adam: (?<ShipName>.*.) in");
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (!match.Success) return;
match = _cleanUpPattern.Match(match.Groups["ShipName"].Value);
if (match.Success)
{
TrackREventDispatcher.OnJumpDriveStateChangedEvent(match.Groups[1].Value);;
}
}
}

View file

@ -0,0 +1,62 @@
using System.Text.RegularExpressions;
namespace AutoTrackR2.LogEventHandlers;
public struct VehicleDestructionData
{
public string Vehicle { get; set; }
public string VehicleZone { get; set; }
public float PosX { get; set; }
public float PosY { get; set; }
public float PosZ { get; set; }
public string Driver { get; set; }
public int DestroyLevelFrom { get; set; }
public int DestroyLevelTo { get; set; }
public string CausedBy { get; set; }
public string DamageType { get; set; }
}
public class VehicleDestructionEvent : ILogEventHandler
{
public Regex Pattern { get; }
public VehicleDestructionEvent()
{
Pattern = new Regex("""
"<(?<timestamp>[^>]+)> \[Notice\] <Vehicle Destruction> CVehicle::OnAdvanceDestroyLevel: " +
"Vehicle '(?<vehicle>[^']+)' \[\d+\] in zone '(?<vehicle_zone>[^']+)' " +
"\[pos x: (?<pos_x>[-\d\.]+), y: (?<pos_y>[-\d\.]+), z: (?<pos_z>[-\d\.]+) " +
"vel x: [^,]+, y: [^,]+, z: [^\]]+\] driven by '(?<driver>[^']+)' \[\d+\] " +
"advanced from destroy level (?<destroy_level_from>\d+) to (?<destroy_level_to>\d+) " +
"caused by '(?<caused_by>[^']+)' \[\d+\] with '(?<damage_type>[^']+)'"
""");
}
public void Handle(LogEntry entry)
{
if (entry.Message == null)
{
return;
}
var match = Pattern.Match(entry.Message);
if (!match.Success)
{
return;
}
var data = new VehicleDestructionData
{
Vehicle = match.Groups["vehicle"].Value,
VehicleZone = match.Groups["vehicle_zone"].Value,
PosX = float.Parse(match.Groups["pos_x"].Value),
PosY = float.Parse(match.Groups["pos_y"].Value),
PosZ = float.Parse(match.Groups["pos_z"].Value),
Driver = match.Groups["driver"].Value,
DestroyLevelFrom = int.Parse(match.Groups["destroy_level_from"].Value),
DestroyLevelTo = int.Parse(match.Groups["destroy_level_to"].Value),
CausedBy = match.Groups["caused_by"].Value,
DamageType = match.Groups["damage_type"].Value,
};
TrackREventDispatcher.OnVehicleDestructionEvent(data);
}
}

169
AutoTrackR2/LogHandler.cs Normal file
View file

@ -0,0 +1,169 @@
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using AutoTrackR2.LogEventHandlers;
namespace AutoTrackR2;
// Represents a single log entry
// This is the object that will be passed to each handler, mostly for convenience
public class LogEntry
{
public DateTime Timestamp { get; set; }
public required string? Message { get; set; }
}
enum GameProcessState
{
NotRunning,
Running,
Unknown
}
public class LogHandler
{
private string _logPath;
private FileStream? _fileStream;
private StreamReader? _reader;
private Thread? _monitorThread;
private CancellationTokenSource? _cancellationTokenSource;
private GameProcessState _gameProcessState = GameProcessState.NotRunning;
private bool _isMonitoring = false;
public bool IsMonitoring => _isMonitoring;
// Handlers that should be run on every log entry
// Overlap with _startupEventHandlers is fine
private readonly List<ILogEventHandler> _eventHandlers = [
new LoginEvent(),
new InstancedInteriorEvent(),
new InArenaCommanderEvent(),
new InPersistentUniverseEvent(),
new GameVersionEvent(),
new JumpDriveStateChangedEvent(),
new RequestJumpFailedEvent()
];
public LogHandler(string? logPath)
{
if (string.IsNullOrEmpty(logPath))
{
throw new ArgumentNullException(nameof(logPath), "Log path cannot be null or empty");
}
_logPath = logPath;
}
public void Initialize()
{
if (!File.Exists(_logPath))
{
throw new FileNotFoundException("Log file not found", _logPath);
}
_fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_reader = new StreamReader(_fileStream);
while (_reader.ReadLine() is { } line)
{
HandleLogEntry(line);
}
// Ensures that any deaths already in log aren't sent to the APIs until the monitor thread is running
_eventHandlers.Add(new ActorDeathEvent());
StartMonitoring();
}
public void StartMonitoring()
{
if (_isMonitoring) return;
_cancellationTokenSource = new CancellationTokenSource();
_monitorThread = new Thread(() => MonitorLog(_cancellationTokenSource.Token));
_monitorThread.Start();
_isMonitoring = true;
}
public void StopMonitoring()
{
if (!_isMonitoring) return;
_cancellationTokenSource?.Cancel();
_monitorThread?.Join();
_reader?.Close();
_fileStream?.Close();
_isMonitoring = false;
}
// Parse a single line of the log file and run matching handlers
private void HandleLogEntry(string line)
{
// Console.WriteLine(line);
foreach (var handler in _eventHandlers)
{
var match = handler.Pattern.Match(line);
if (!match.Success) continue;
var entry = new LogEntry
{
Timestamp = DateTime.Now,
Message = line
};
handler.Handle(entry);
break;
}
}
private void MonitorLog(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
if (_reader == null || _fileStream == null)
{
break;
}
CheckGameProcessState();
List<string> lines = new List<string>();
while (_reader.ReadLine() is { } line)
{
lines.Add(line);
}
foreach (var line in lines)
{
// start new thread to handle log entry
var thread = new Thread(() => HandleLogEntry(line));
thread.Start();
// Console.WriteLine(line);
}
{
// Wait for new lines to be written to the log file
Thread.Sleep(1000);
}
}
Console.WriteLine("Monitor thread stopped");
}
private void CheckGameProcessState()
{
// Check if the game process is running by window name
var process = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle == "Star Citizen");
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");
_reader?.Close();
_fileStream?.Close();
_fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_reader = new StreamReader(_fileStream);
}
_gameProcessState = newGameProcessState;
}
}

View file

@ -25,8 +25,6 @@
<StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="5,0,0,0"> <StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="5,0,0,0">
<Image x:Name="Logo" Height="138" Source="/Assets/AutoTrackR.png" Stretch="Fill" Width="141" RenderOptions.BitmapScalingMode="Fant"/> <Image x:Name="Logo" Height="138" Source="/Assets/AutoTrackR.png" Stretch="Fill" Width="141" RenderOptions.BitmapScalingMode="Fant"/>
<Button Content="Home" Name="HomeTab" Margin="10,40,10,10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/> <Button Content="Home" Name="HomeTab" Margin="10,40,10,10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/>
<Button Content="Stats" Name="StatsTab" Margin="10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/>
<Button Content="Update" Name="UpdateTab" Margin="10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/>
<Button Content="Config" Name="ConfigTab" Margin="10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/> <Button Content="Config" Name="ConfigTab" Margin="10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/>
</StackPanel> </StackPanel>

View file

@ -1,4 +1,6 @@
//using System.Collections.Generic; //using System.Collections.Generic;
using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
@ -11,7 +13,6 @@ namespace AutoTrackR2
{ {
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private Dictionary<string, bool> tabStates = new Dictionary<string, bool> private Dictionary<string, bool> tabStates = new Dictionary<string, bool>
{ {
{ "HomeTab", true }, // HomeTab is selected by default { "HomeTab", true }, // HomeTab is selected by default
@ -21,9 +22,7 @@ namespace AutoTrackR2
}; };
private HomePage homePage; // Persistent HomePage instance private HomePage homePage; // Persistent HomePage instance
private bool isRunning = false; // Single source of truth for the running state
// Ensure this method is not static
public void ChangeLogoImage(string imagePath) public void ChangeLogoImage(string imagePath)
{ {
Logo.Source = new BitmapImage(new Uri(imagePath, UriKind.RelativeOrAbsolute)); Logo.Source = new BitmapImage(new Uri(imagePath, UriKind.RelativeOrAbsolute));
@ -33,16 +32,9 @@ namespace AutoTrackR2
{ {
InitializeComponent(); InitializeComponent();
// Load configuration settings before setting them in any page
ConfigManager.LoadConfig();
homePage = new HomePage(); // Create a single instance of HomePage homePage = new HomePage(); // Create a single instance of HomePage
ContentControl.Content = homePage; // Default to HomePage ContentControl.Content = homePage; // Default to HomePage
// Attach event handlers for the HomePage buttons
homePage.StartButton.Click += StartButton_Click;
homePage.StopButton.Click += StopButton_Click;
// Create ConfigPage and pass the MainWindow reference to it // Create ConfigPage and pass the MainWindow reference to it
var configPage = new ConfigPage(this); var configPage = new ConfigPage(this);
@ -52,6 +44,7 @@ namespace AutoTrackR2
UpdateTabVisuals(); UpdateTabVisuals();
Loaded += MainWindow_Loaded; // Handle Loaded event Loaded += MainWindow_Loaded; // Handle Loaded event
Closing += MainWindow_Closing; // Handle Closing event
} }
private void MainWindow_Loaded(object sender, RoutedEventArgs e) private void MainWindow_Loaded(object sender, RoutedEventArgs e)
@ -60,16 +53,23 @@ namespace AutoTrackR2
var args = Environment.GetCommandLineArgs(); var args = Environment.GetCommandLineArgs();
if (args.Contains("-start", StringComparer.OrdinalIgnoreCase)) if (args.Contains("-start", StringComparer.OrdinalIgnoreCase))
{ {
homePage.StartButton_Click(null, null); // Initialize log handler if needed
homePage.InitializeLogHandler();
} }
} }
private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
{
// Clean up resources
homePage?.Cleanup();
// Make sure the application exits completely
Application.Current.Shutdown();
}
private void CloseWindow(object sender, RoutedEventArgs e) private void CloseWindow(object sender, RoutedEventArgs e)
{ {
// If runningProcess is not null and still active, terminate it // This will trigger the Closing event
homePage.StopButton_Click(sender, e);
// Close the main window
this.Close(); this.Close();
} }
@ -92,17 +92,6 @@ namespace AutoTrackR2
{ {
// Reuse the existing HomePage instance // Reuse the existing HomePage instance
ContentControl.Content = homePage; ContentControl.Content = homePage;
// Update the button state on the HomePage
homePage.UpdateButtonState(isRunning);
}
else if (clickedTabName == "StatsTab")
{
ContentControl.Content = new StatsPage();
}
else if (clickedTabName == "UpdateTab")
{
ContentControl.Content = new UpdatePage();
} }
else if (clickedTabName == "ConfigTab") else if (clickedTabName == "ConfigTab")
{ {
@ -159,20 +148,6 @@ namespace AutoTrackR2
} }
} }
private void StartButton_Click(object sender, RoutedEventArgs e)
{
isRunning = true; // Update the running state
homePage.UpdateButtonState(isRunning); // Update HomePage button visuals
// Start your logic here
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
isRunning = false; // Update the running state
homePage.UpdateButtonState(isRunning); // Update HomePage button visuals
// Stop your logic here
}
private void InitializeConfigPage() private void InitializeConfigPage()
{ {
// Set the values from the loaded config // Set the values from the loaded config
@ -180,10 +155,10 @@ namespace AutoTrackR2
// Set the fields in ConfigPage.xaml.cs based on the loaded config // Set the fields in ConfigPage.xaml.cs based on the loaded config
configPage.SetConfigValues( configPage.SetConfigValues(
ConfigManager.LogFile, ConfigManager.LogFile ?? string.Empty,
ConfigManager.ApiUrl, ConfigManager.ApiUrl ?? string.Empty,
ConfigManager.ApiKey, ConfigManager.ApiKey ?? string.Empty,
ConfigManager.VideoPath, ConfigManager.VideoPath ?? string.Empty,
ConfigManager.VisorWipe, ConfigManager.VisorWipe,
ConfigManager.VideoRecord, ConfigManager.VideoRecord,
ConfigManager.OfflineMode, ConfigManager.OfflineMode,
@ -194,14 +169,39 @@ namespace AutoTrackR2
public static class ConfigManager public static class ConfigManager
{ {
public static string LogFile { get; set; } public static string? LogFile { get; set; } = string.Empty;
public static string ApiUrl { get; set; } public static string? KillHistoryFile { get; set; } = string.Empty;
public static string ApiKey { get; set; } public static string? AHKScriptFolder { get; set; } = string.Empty;
public static string VideoPath { get; set; } public static string? VisorWipeScript { get; set; } = string.Empty;
public static string? VideoRecordScript { get; set; } = string.Empty;
public static string? ApiUrl { get; set; } = string.Empty;
public static string? ApiKey { get; set; } = string.Empty;
public static string? VideoPath { get; set; } = string.Empty;
public static int VisorWipe { get; set; } public static int VisorWipe { get; set; }
public static int VideoRecord { get; set; } public static int VideoRecord { get; set; }
public static int OfflineMode { get; set; } public static int OfflineMode { get; set; }
public static int Theme { get; set; } public static int Theme { get; set; }
static ConfigManager()
{
LoadConfig();
// Set default values
// AppData\Local\AutoTrackR2\Kill-log.csv
KillHistoryFile = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"AutoTrackR2",
"Kill-log.csv"
);
AHKScriptFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"AutoTrackR2"
);
VisorWipeScript = "visorwipe.ahk";
VideoRecordScript = "videorecord.ahk";
}
public static void LoadConfig() public static void LoadConfig()
{ {
@ -241,7 +241,7 @@ namespace AutoTrackR2
// Define the config file path in a writable location // Define the config file path in a writable location
string configDirectory = Path.Combine( string configDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"YourAppName" "AutoTrackR2"
); );
// Ensure the directory exists // Ensure the directory exists

View file

@ -1,8 +0,0 @@
<UserControl x:Class="AutoTrackR2.StatsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="396" Width="626">
<Grid Background="{DynamicResource BackgroundLightBrush}">
<TextBlock Text="Stats and graphs coming soon!" FontSize="24" Foreground="{DynamicResource TextBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</UserControl>

View file

@ -1,12 +0,0 @@
using System.Windows.Controls;
namespace AutoTrackR2
{
public partial class StatsPage : UserControl
{
public StatsPage()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,57 @@
using AutoTrackR2.LogEventHandlers;
namespace AutoTrackR2;
public static class TrackREventDispatcher
{
// Local Player Login
public static event Action<string>? PlayerLoginEvent;
public static void OnPlayerLoginEvent(string playerName)
{
PlayerLoginEvent?.Invoke(playerName);
}
// An instanced interior has changed
// Example: Player enters/leaves a ship
public static event Action<InstancedInteriorData>? InstancedInteriorEvent;
public static void OnInstancedInteriorEvent(InstancedInteriorData data)
{
InstancedInteriorEvent?.Invoke(data);
}
// Player changed GameMode (AC or PU)
public static event Action<GameMode>? PlayerChangedGameModeEvent;
public static void OnPlayerChangedGameModeEvent(GameMode mode)
{
PlayerChangedGameModeEvent?.Invoke(mode);
}
// Game version has been detected
public static event Action<string>? GameVersionEvent;
public static void OnGameVersionEvent(string value)
{
GameVersionEvent?.Invoke(value);
}
// Actor has died
public static event Action<ActorDeathData>? ActorDeathEvent;
public static void OnActorDeathEvent(ActorDeathData data)
{
ActorDeathEvent?.Invoke(data);
}
// Vehicle has been destroyed
public static event Action<VehicleDestructionData>? VehicleDestructionEvent;
public static void OnVehicleDestructionEvent(VehicleDestructionData data)
{
VehicleDestructionEvent?.Invoke(data);
}
// Jump Drive state has changed
// Todo: Add proper data for this event. Right now only ship name is used.
public static event Action<string>? JumpDriveStateChangedEvent;
public static void OnJumpDriveStateChangedEvent(string shipName)
{
JumpDriveStateChangedEvent?.Invoke(shipName);
}
}

View file

@ -1,39 +0,0 @@
<UserControl x:Class="AutoTrackR2.UpdatePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="396" Width="626">
<Grid Background="{DynamicResource BackgroundLightBrush}">
<Grid Margin="0,0,5,7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Current Version Display -->
<StackPanel Orientation="Horizontal" Margin="10" Grid.Row="0">
<TextBlock Text="Current Version: " FontSize="16" FontWeight="Bold" VerticalAlignment="Center" Foreground="{DynamicResource AltTextBrush}" />
<TextBlock x:Name="CurrentVersionText" Text="2.0-beta.0" FontSize="16" VerticalAlignment="Center" Foreground="{DynamicResource TextBrush}"/>
</StackPanel>
<!-- Available Version Display -->
<StackPanel Orientation="Horizontal" Margin="10" Grid.Row="1">
<TextBlock Text="Available Version: " FontSize="16" FontWeight="Bold" VerticalAlignment="Center" Foreground="{DynamicResource AltTextBrush}"/>
<TextBlock x:Name="AvailableVersionText" Text="Checking..." FontSize="16" VerticalAlignment="Center" Foreground="{DynamicResource TextBrush}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="20" Grid.Row="2">
<TextBlock Text="Still a work in progress. Please uninstall current version before installing update." FontSize="16" Foreground="{DynamicResource TextBrush}" />
</StackPanel>
<!-- Install Button -->
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Row="2" Grid.Column="2">
<Button x:Name="InstallButton" Content="Download Update" Width="150" Height="40" IsEnabled="False"
Click="InstallButton_Click" Style="{StaticResource DisabledButtonStyle}" FontFamily="{StaticResource Orbitron}"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

View file

@ -1,145 +0,0 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace AutoTrackR2
{
public partial class UpdatePage : UserControl
{
private string currentVersion = "v2.08";
private string latestVersion;
public UpdatePage()
{
InitializeComponent();
CurrentVersionText.Text = currentVersion;
CheckForUpdates();
}
private async void CheckForUpdates()
{
try
{
// Fetch the latest release info from GitHub
latestVersion = await GetLatestVersionFromGitHub();
// Update the Available Version field
AvailableVersionText.Text = latestVersion;
// Enable the Install button if a new version is available
if (IsNewVersionAvailable(currentVersion, latestVersion))
{
InstallButton.IsEnabled = true;
InstallButton.Style = (Style)FindResource("ButtonStyle");
}
}
catch (Exception ex)
{
AvailableVersionText.Text = "Error checking updates.";
MessageBox.Show($"Failed to check for updates: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async Task<string> GetLatestVersionFromGitHub()
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("User-Agent", "AutoTrackR2");
string repoOwner = "BubbaGumpShrump";
string repoName = "AutoTrackR2";
try
{
// Attempt to fetch the latest release
var url = $"https://api.github.com/repos/{repoOwner}/{repoName}/releases/latest";
var response = await client.GetStringAsync(url);
// Parse the JSON using System.Text.Json
using var document = System.Text.Json.JsonDocument.Parse(response);
var root = document.RootElement;
var tagName = root.GetProperty("tag_name").GetString();
return tagName;
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
// Fallback to releases list if 'latest' not found
var url = $"https://api.github.com/repos/{repoOwner}/{repoName}/releases";
var response = await client.GetStringAsync(url);
using var document = System.Text.Json.JsonDocument.Parse(response);
var root = document.RootElement;
// Get the tag name of the first release
if (root.GetArrayLength() > 0)
{
var firstRelease = root[0];
return firstRelease.GetProperty("tag_name").GetString();
}
throw new Exception("No releases found.");
}
}
private bool IsNewVersionAvailable(string currentVersion, string latestVersion)
{
// Return true if the versions are different
return !currentVersion.Equals(latestVersion, StringComparison.Ordinal);
}
private async void InstallButton_Click(object sender, RoutedEventArgs e)
{
try
{
InstallButton.IsEnabled = false;
InstallButton.Content = "Preparing to Update...";
// Get the path to the update.ps1 script
string scriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update.ps1");
// Run the PowerShell script
RunPowerShellScript(scriptPath);
// Gracefully close the app after running the script
Application.Current.Shutdown();
MessageBox.Show("Update process has started. Please follow the instructions in the PowerShell script.", "Update Started", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Failed to run the update script: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
InstallButton.IsEnabled = true;
InstallButton.Content = "Install Update";
}
}
private void RunPowerShellScript(string scriptPath)
{
try
{
// Prepare the command to run the PowerShell script with elevation (admin rights)
var processStartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-ExecutionPolicy Bypass -File \"{scriptPath}\"", // Allow script to run
Verb = "runas", // Request elevation (admin rights)
UseShellExecute = true, // Use the shell to execute the process
CreateNoWindow = false // Show the PowerShell window
};
// Start the PowerShell process to run the script with admin rights
System.Diagnostics.Process.Start(processStartInfo);
}
catch (Exception ex)
{
MessageBox.Show($"Failed to run the PowerShell script with admin rights: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}

31
AutoTrackR2/Util.cs Normal file
View file

@ -0,0 +1,31 @@
namespace AutoTrackR2;
// Data returned from the CIG API
public struct PlayerData
{
public string? PFPURL;
public string? UEERecord;
public string? OrgURL;
public string? OrgName;
public string? JoinDate;
}
// Amalgamation of all data from a single kill
public struct KillData
{
public string? KillTime;
public string? EnemyPilot;
public string? EnemyShip;
public string? Enlisted;
public string? RecordNumber;
public string? OrgAffiliation;
public string? Player;
public string? Weapon;
public string? Ship;
public string? Method;
public string? Mode;
public string? GameVersion;
public string? TrackRver;
public string? Logged;
public string? PFP;
}

122
AutoTrackR2/WebHandler.cs Normal file
View file

@ -0,0 +1,122 @@
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using AutoTrackR2.LogEventHandlers;
namespace AutoTrackR2;
public static class WebHandler
{
class APIKillData
{
public string? victim_ship { get; set; }
public string? victim{ get; set; }
public string? enlisted{ get; set; }
public string? rsi{ get; set; }
public string? weapon{ get; set; }
public string? method{ get; set; }
public string? loadout_ship{ get; set; }
public string? game_version{ get; set; }
public string? gamemode{ get; set; }
public string? trackr_version{ get; set; }
public string? location{ get; set; }
}
public static async Task<PlayerData?> GetPlayerData(string enemyPilot)
{
var joinDataPattern = new Regex("<span class=\"label\">Enlisted</span>\\s*<strong class=\"value\">([^<]+)</strong>");
var ueePattern = new Regex("<p class=\"entry citizen-record\">\\n.*.<span class=\"label\">UEE Citizen Record<\\/span>\\n.*.<strong class=\"value\">#(?<UEERecord>\\d+)<\\/strong>");
var orgPattern = new Regex("\\/orgs\\/(?<OrgURL>[A-z0-9]+)\" .*\\>(?<OrgName>.*)<");
var pfpPattern = new Regex("/media/(.*)\"");
// Make web request to check player data
var playerData = new PlayerData();
var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"https://robertsspaceindustries.com/en/citizens/{enemyPilot}");
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
var content = await response.Content.ReadAsStringAsync();
var joinDataMatch = joinDataPattern.Match(content);
if (joinDataMatch.Success)
{
playerData.JoinDate = joinDataMatch.Groups[1].Value;
}
var ueeMatch = ueePattern.Match(content);
if (ueeMatch.Success)
{
playerData.UEERecord = ueeMatch.Groups["UEERecord"].Value == "n/a" ? "-1" : ueeMatch.Groups[1].Value;
}
var orgMatch = orgPattern.Match(content);
if (orgMatch.Success)
{
playerData.OrgName = orgMatch.Groups["OrgName"].Value;
playerData.OrgURL = "https://robertsspaceindustries.com/en/orgs/" + orgMatch.Groups["OrgURL"].Value;
}
var pfpMatch = pfpPattern.Match(content);
if (pfpMatch.Success)
{
var match = pfpMatch.Groups[1].Value;
if (match.Contains("heap_thumb"))
{
playerData.PFPURL = "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg";
}
else
{
playerData.PFPURL = "https://robertsspaceindustries.com/media/" + pfpMatch.Groups[1].Value;
}
}
return playerData;
}
public static async Task SubmitKill(KillData killData)
{
// int secondSpaceIndex = killData.Enlisted.IndexOf(" ", killData.Enlisted.IndexOf(" ") + 1);
// killData.Enlisted = killData.Enlisted.Insert(secondSpaceIndex, ",");
var apiKillData = new APIKillData
{
victim_ship = killData.EnemyShip,
victim = killData.EnemyPilot,
enlisted = killData.Enlisted,
rsi = killData.RecordNumber,
weapon = killData.Weapon,
method = killData.Method,
gamemode = killData.Mode,
// loadout_ship = LocalPlayerData.PlayerShip ?? "Unknown",
loadout_ship = killData.Ship,
game_version = killData.GameVersion,
trackr_version = killData.TrackRver,
location = "Unknown"
};
if (string.IsNullOrEmpty(apiKillData.rsi))
{
apiKillData.rsi = "-1";
}
var httpClient = new HttpClient();
string jsonData = JsonSerializer.Serialize(apiKillData);
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + ConfigManager.ApiKey);
httpClient.DefaultRequestHeaders.Add("User-Agent", "AutoTrackR2");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
var response = await httpClient.PostAsync(ConfigManager.ApiUrl + "register-kill", new StringContent(jsonData, Encoding.UTF8, "application/json"));
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine("Failed to submit kill data: ");
Console.WriteLine(jsonData);
}
else if (response.StatusCode == HttpStatusCode.OK)
{
Console.WriteLine("Successfully submitted kill data");
}
}
}

View file

@ -1,68 +0,0 @@
$repoUrl = "https://api.github.com/repos/BubbaGumpShrump/AutoTrackR2/releases/latest"
$outputMsi = Join-Path -Path $env:TEMP -ChildPath "AutoTrackR2_Setup.msi"
$tempFolder = Join-Path -Path $env:TEMP -ChildPath "AutoTrackR2"
$headers = @{ "User-Agent" = "Mozilla/5.0" }
# Fetch latest release data
$response = Invoke-RestMethod -Uri $repoUrl -Headers $headers
# Find the MSI asset
$asset = $response.assets | Where-Object { $_.name -eq "AutoTrackR2_Setup.msi" }
if ($asset -ne $null) {
$downloadUrl = $asset.browser_download_url
Write-Host "Downloading $($asset.name) from $downloadUrl"
Invoke-WebRequest -Uri $downloadUrl -OutFile $outputMsi -Headers $headers
Write-Host "Download completed: $outputMsi"
# Extract MSI contents
if (Test-Path $tempFolder) {
Remove-Item -Recurse -Force $tempFolder
}
Write-Host "Extracting MSI files..."
# Unpack the MSI installer to the temporary folder using msiexec with /a (administrative install) and /qb (quiet mode)
Start-Process msiexec.exe -ArgumentList "/a `"$outputMsi`" /qb TARGETDIR=`"$tempFolder`"" -Wait
# Generate checksums of extracted files and current directory files
$tempFiles = Get-ChildItem -Path $tempFolder -Recurse
$currentFiles = Get-ChildItem -Path (Get-Location) -Recurse
$tempChecksums = @{}
$currentChecksums = @{}
# Generate checksums for the temp folder files
foreach ($file in $tempFiles) {
if (-not $file.PSIsContainer) {
$tempChecksums[$file.FullName] = Get-FileHash $file.FullName -Algorithm SHA256
}
}
# Generate checksums for the current directory files
foreach ($file in $currentFiles) {
if (-not $file.PSIsContainer) {
$currentChecksums[$file.FullName] = Get-FileHash $file.FullName -Algorithm SHA256
}
}
# Compare and overwrite files if changed or missing, excluding update.ps1
foreach ($file in $tempChecksums.Keys) {
$relativePath = $file.Substring($tempFolder.Length)
# Skip the update.ps1 file
if ($relativePath -eq "\update.ps1") {
continue
}
$currentFilePath = Join-Path -Path (Get-Location) -ChildPath $relativePath
if (-not (Test-Path $currentFilePath) -or ($currentChecksums[$currentFilePath].Hash -ne $tempChecksums[$file].Hash)) {
Write-Host "Copying $relativePath to current directory"
Copy-Item -Path $file -Destination $currentFilePath -Force
}
}
Write-Host "Files are successfully updated."
} else {
Write-Host "AutoTrackR2_Setup.msi not found in the latest release."
}

View file

@ -1,779 +0,0 @@
"DeployProject"
{
"VSVersion" = "3:800"
"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}"
"IsWebType" = "8:FALSE"
"ProjectName" = "8:AutoTrackR2_Setup"
"LanguageId" = "3:1033"
"CodePage" = "3:1252"
"UILanguageId" = "3:1033"
"SccProjectName" = "8:"
"SccLocalPath" = "8:"
"SccAuxPath" = "8:"
"SccProvider" = "8:"
"Hierarchy"
{
"Entry"
{
"MsmKey" = "8:_647AB2AE75964E44A2518F96EAF8D77D"
"OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_C368A4F254AA4735B9679DC281414D2B"
"OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED"
}
}
"Configurations"
{
"Debug"
{
"DisplayName" = "8:Debug"
"IsDebugOnly" = "11:TRUE"
"IsReleaseOnly" = "11:FALSE"
"OutputFilename" = "8:Debug\\AutoTrackR2_Setup.msi"
"PackageFilesAs" = "3:2"
"PackageFileSize" = "3:-2147483648"
"CabType" = "3:1"
"Compression" = "3:2"
"SignOutput" = "11:FALSE"
"CertificateFile" = "8:"
"PrivateKeyFile" = "8:"
"TimeStampServer" = "8:"
"InstallerBootstrapper" = "3:2"
"BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}"
{
"Enabled" = "11:TRUE"
"PromptEnabled" = "11:TRUE"
"PrerequisitesLocation" = "2:1"
"Url" = "8:"
"ComponentsUrl" = "8:"
"Items"
{
"{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2"
{
"Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)"
"ProductCode" = "8:.NETFramework,Version=v4.7.2"
}
}
}
}
"Release"
{
"DisplayName" = "8:Release"
"IsDebugOnly" = "11:FALSE"
"IsReleaseOnly" = "11:TRUE"
"OutputFilename" = "8:Release\\AutoTrackR2_Setup.msi"
"PackageFilesAs" = "3:2"
"PackageFileSize" = "3:-2147483648"
"CabType" = "3:1"
"Compression" = "3:2"
"SignOutput" = "11:FALSE"
"CertificateFile" = "8:"
"PrivateKeyFile" = "8:"
"TimeStampServer" = "8:"
"InstallerBootstrapper" = "3:2"
"BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}"
{
"Enabled" = "11:TRUE"
"PromptEnabled" = "11:TRUE"
"PrerequisitesLocation" = "2:1"
"Url" = "8:"
"ComponentsUrl" = "8:"
"Items"
{
"{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2"
{
"Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)"
"ProductCode" = "8:.NETFramework,Version=v4.7.2"
}
}
}
}
}
"Deployable"
{
"CustomAction"
{
}
"DefaultFeature"
{
"Name" = "8:DefaultFeature"
"Title" = "8:"
"Description" = "8:"
}
"ExternalPersistence"
{
"LaunchCondition"
{
"{A06ECF26-33A3-4562-8140-9B0E340D4F24}:_276C583D35A94D9E9E0B547565F576F3"
{
"Name" = "8:.NET Core"
"Message" = "8:[VSDNETCOREMSG]"
"AllowLaterVersions" = "11:FALSE"
"InstallUrl" = "8:https://dotnet.microsoft.com/download/dotnet-core/[NetCoreVerMajorDotMinor]"
"IsNETCore" = "11:TRUE"
"Architecture" = "2:0"
"Runtime" = "2:0"
}
}
}
"File"
{
"{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_647AB2AE75964E44A2518F96EAF8D77D"
{
"SourcePath" = "8:..\\..\\..\\..\\Downloads\\AutoTrackR2.ico"
"TargetName" = "8:AutoTrackR2.ico"
"Tag" = "8:"
"Folder" = "8:_DEFDA4997C74429F99A6EA3C6AFECAD3"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:FALSE"
"IsolateTo" = "8:"
}
}
"FileType"
{
}
"Folder"
{
"{1525181F-901A-416C-8A58-119130FE478E}:_7E841D8A7938485C942B9E382D55836B"
{
"Name" = "8:#1916"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Property" = "8:DesktopFolder"
"Folders"
{
}
}
"{1525181F-901A-416C-8A58-119130FE478E}:_B9ADF94E042543B5B1221BEFB26FF6C3"
{
"Name" = "8:#1919"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Property" = "8:ProgramMenuFolder"
"Folders"
{
}
}
"{3C67513D-01DD-4637-8A68-80971EB9504F}:_DEFDA4997C74429F99A6EA3C6AFECAD3"
{
"DefaultLocation" = "8:[ProgramFiles64Folder][Manufacturer]\\[ProductName]"
"Name" = "8:#1925"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Property" = "8:TARGETDIR"
"Folders"
{
}
}
}
"LaunchCondition"
{
}
"Locator"
{
}
"MsiBootstrapper"
{
"LangId" = "3:1033"
"RequiresElevation" = "11:FALSE"
}
"Product"
{
"Name" = "8:Microsoft Visual Studio"
"ProductName" = "8:AutoTrackR2_Setup"
"ProductCode" = "8:{EA6F4A24-93AD-4470-8F1F-B7C30E1B0F3B}"
"PackageCode" = "8:{9D14EC78-0C53-4066-8C2F-325ED9C6ACE4}"
"UpgradeCode" = "8:{0B78A147-D0DE-4F72-8906-A62611787CA7}"
"AspNetVersion" = "8:"
"RestartWWWService" = "11:FALSE"
"RemovePreviousVersions" = "11:TRUE"
"DetectNewerInstalledVersion" = "11:FALSE"
"InstallAllUsers" = "11:TRUE"
"ProductVersion" = "8:2.0.6"
"Manufacturer" = "8:GrieferNET"
"ARPHELPTELEPHONE" = "8:"
"ARPHELPLINK" = "8:discord.gg/griefernet"
"Title" = "8:AutoTrackR2_Setup"
"Subject" = "8:"
"ARPCONTACT" = "8:Fisk"
"Keywords" = "8:"
"ARPCOMMENTS" = "8:Star Citizen Kill Tracking App"
"ARPURLINFOABOUT" = "8:https://GrieferNET.org"
"ARPPRODUCTICON" = "8:"
"ARPIconIndex" = "3:0"
"SearchPath" = "8:"
"UseSystemSearchPath" = "11:TRUE"
"TargetPlatform" = "3:1"
"PreBuildEvent" = "8:"
"PostBuildEvent" = "8:"
"RunPostBuildEvent" = "3:0"
}
"Registry"
{
"HKLM"
{
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_2F3BD2498FC542DEB1965C4BC4085DD1"
{
"Name" = "8:Software"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_ED395372F679421DA161DF2744926FD3"
{
"Name" = "8:[Manufacturer]"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
}
"Values"
{
}
}
}
"Values"
{
}
}
}
}
"HKCU"
{
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_0DE359661B62468F9B0C73472B457E7B"
{
"Name" = "8:Software"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_A8ECE3DCE8AE4DCE8DBB79D6D95B92F2"
{
"Name" = "8:[Manufacturer]"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
}
"Values"
{
}
}
}
"Values"
{
}
}
}
}
"HKCR"
{
"Keys"
{
}
}
"HKU"
{
"Keys"
{
}
}
"HKPU"
{
"Keys"
{
}
}
}
"Sequences"
{
}
"Shortcut"
{
"{970C0BB2-C7D0-45D7-ABFA-7EC378858BC0}:_024675C1BAB04F3792A2073CDE2E501F"
{
"Name" = "8:AutoTrackR2"
"Arguments" = "8:"
"Description" = "8:"
"ShowCmd" = "3:1"
"IconIndex" = "3:0"
"Transitive" = "11:FALSE"
"Target" = "8:_C368A4F254AA4735B9679DC281414D2B"
"Folder" = "8:_B9ADF94E042543B5B1221BEFB26FF6C3"
"WorkingFolder" = "8:_DEFDA4997C74429F99A6EA3C6AFECAD3"
"Icon" = "8:_647AB2AE75964E44A2518F96EAF8D77D"
"Feature" = "8:"
}
"{970C0BB2-C7D0-45D7-ABFA-7EC378858BC0}:_C32D358287C9451C8D5F3F24777C476A"
{
"Name" = "8:AutoTrackR2"
"Arguments" = "8:"
"Description" = "8:"
"ShowCmd" = "3:1"
"IconIndex" = "3:0"
"Transitive" = "11:FALSE"
"Target" = "8:_C368A4F254AA4735B9679DC281414D2B"
"Folder" = "8:_7E841D8A7938485C942B9E382D55836B"
"WorkingFolder" = "8:_DEFDA4997C74429F99A6EA3C6AFECAD3"
"Icon" = "8:_647AB2AE75964E44A2518F96EAF8D77D"
"Feature" = "8:"
}
}
"UserInterface"
{
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_09D37C5DAF50432FAFE946DC2C89EECF"
{
"Name" = "8:#1902"
"Sequence" = "3:2"
"Attributes" = "3:3"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_7197CF07741C42CC924DC7B4BBDCD776"
{
"Sequence" = "3:100"
"DisplayName" = "8:Finished"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminFinishedDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_1C953A3AD06B4E2D8726A57902525E93"
{
"UseDynamicProperties" = "11:FALSE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdBasicDialogs.wim"
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_25A3388A62654BA2A95CBCF1EA37DA16"
{
"Name" = "8:#1901"
"Sequence" = "3:1"
"Attributes" = "3:2"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_C23E70A1FA414511BFF4551092323DD2"
{
"Sequence" = "3:100"
"DisplayName" = "8:Progress"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdProgressDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"ShowProgress"
{
"Name" = "8:ShowProgress"
"DisplayName" = "8:#1009"
"Description" = "8:#1109"
"Type" = "3:5"
"ContextData" = "8:1;True=1;False=0"
"Attributes" = "3:0"
"Setting" = "3:0"
"Value" = "3:1"
"DefaultValue" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_271F117A8A994C278BBE51122646302C"
{
"Name" = "8:#1900"
"Sequence" = "3:2"
"Attributes" = "3:1"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_45664FEBC1F14857A58C61C0243CFF19"
{
"Sequence" = "3:300"
"DisplayName" = "8:Confirm Installation"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminConfirmDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_75DBBE7328854B96BD74863ADFB756E9"
{
"Sequence" = "3:100"
"DisplayName" = "8:Welcome"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminWelcomeDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"CopyrightWarning"
{
"Name" = "8:CopyrightWarning"
"DisplayName" = "8:#1002"
"Description" = "8:#1102"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1202"
"DefaultValue" = "8:#1202"
"UsePlugInResources" = "11:TRUE"
}
"Welcome"
{
"Name" = "8:Welcome"
"DisplayName" = "8:#1003"
"Description" = "8:#1103"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1203"
"DefaultValue" = "8:#1203"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_8B9F468AAD25451AB530842E9E18CA91"
{
"Sequence" = "3:200"
"DisplayName" = "8:Installation Folder"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminFolderDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_A2E96B970BB24DA7B0690F48DB6FCAFA"
{
"UseDynamicProperties" = "11:FALSE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdUserInterface.wim"
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_BC406471F57045DD858B49D1AA5347A6"
{
"Name" = "8:#1900"
"Sequence" = "3:1"
"Attributes" = "3:1"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_074E57B64CA644A58139156EA9DCF5F8"
{
"Sequence" = "3:100"
"DisplayName" = "8:Welcome"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdWelcomeDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:0"
"UsePlugInResources" = "11:TRUE"
}
"CopyrightWarning"
{
"Name" = "8:CopyrightWarning"
"DisplayName" = "8:#1002"
"Description" = "8:#1102"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:2"
"Value" = "8:GNU GENERAL PUBLIC LICENSE v3"
"DefaultValue" = "8:#1202"
"UsePlugInResources" = "11:TRUE"
}
"Welcome"
{
"Name" = "8:Welcome"
"DisplayName" = "8:#1003"
"Description" = "8:#1103"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1203"
"DefaultValue" = "8:#1203"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_299DEEF75E974038985108A67F002846"
{
"Sequence" = "3:200"
"DisplayName" = "8:Installation Folder"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdFolderDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"InstallAllUsersVisible"
{
"Name" = "8:InstallAllUsersVisible"
"DisplayName" = "8:#1059"
"Description" = "8:#1159"
"Type" = "3:5"
"ContextData" = "8:1;True=1;False=0"
"Attributes" = "3:0"
"Setting" = "3:0"
"Value" = "3:1"
"DefaultValue" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_B3648A393F14437C94AA44A5C900315A"
{
"Sequence" = "3:300"
"DisplayName" = "8:Confirm Installation"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdConfirmDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_D847AC6800D0442595843337523D21BD"
{
"Name" = "8:#1901"
"Sequence" = "3:2"
"Attributes" = "3:2"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_FCB2747515C94BA68B985D2715B41786"
{
"Sequence" = "3:100"
"DisplayName" = "8:Progress"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminProgressDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"ShowProgress"
{
"Name" = "8:ShowProgress"
"DisplayName" = "8:#1009"
"Description" = "8:#1109"
"Type" = "3:5"
"ContextData" = "8:1;True=1;False=0"
"Attributes" = "3:0"
"Setting" = "3:0"
"Value" = "3:1"
"DefaultValue" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_F3C2D7B1337A40D09E957F167799C69B"
{
"Name" = "8:#1902"
"Sequence" = "3:1"
"Attributes" = "3:3"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_F49A9187003D4D8DBB8E8AAF2491D61B"
{
"Sequence" = "3:100"
"DisplayName" = "8:Finished"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdFinishedDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"UpdateText"
{
"Name" = "8:UpdateText"
"DisplayName" = "8:#1058"
"Description" = "8:#1158"
"Type" = "3:15"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1258"
"DefaultValue" = "8:#1258"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
}
"MergeModule"
{
}
"ProjectOutput"
{
"{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_C368A4F254AA4735B9679DC281414D2B"
{
"SourcePath" = "8:..\\AutoTrackR2\\obj\\x64\\Release\\net9.0-windows\\apphost.exe"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_DEFDA4997C74429F99A6EA3C6AFECAD3"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:FALSE"
"IsolateTo" = "8:"
"ProjectOutputGroupRegister" = "3:1"
"OutputConfiguration" = "8:"
"OutputGroupCanonicalName" = "8:PublishItems"
"OutputProjectGuid" = "8:{31093634-8FBB-4BC6-BEA4-DAD6C11404F3}"
"ShowKeyOutput" = "11:TRUE"
"ExcludeFilters"
{
}
}
}
}
}