Compare commits
65 Commits
ec3bf67995
...
e93f0d62a6
Author | SHA1 | Date | |
---|---|---|---|
|
e93f0d62a6 | ||
|
e4b8fe10ab | ||
|
34828b3240 | ||
|
d2d1e584c1 | ||
7ea9b42452 | |||
c258d4ea92 | |||
|
65a63e09ed | ||
|
16576d3a81 | ||
0a0615813c | |||
a0a6e6dd81 | |||
944e34fa6b | |||
c2c1670ab2 | |||
b2b64dcae0 | |||
2ffa8f6b6d | |||
45e988f7f8 | |||
ca2dec269f | |||
498eeaa0ef | |||
e751f937a2 | |||
6502dde280 | |||
a61e53d56a | |||
a70590d92a | |||
43fa10e3a5 | |||
f7fbdee568 | |||
74ef0c096a | |||
b9a5ad9442 | |||
ae08be8367 | |||
ff717aab73 | |||
0f1d4653a2 | |||
f8e4a668ff | |||
469f74ee6c | |||
36ee8182f9 | |||
bb8b05a2d4 | |||
1dc4202353 | |||
0c6ce9e681 | |||
ccc91b09f6 | |||
9b3d9fd755 | |||
2110b185d2 | |||
|
918106a469 | ||
074ac6e826 | |||
a909013a86 | |||
2929c859b0 | |||
42902f0bd6 | |||
|
24094054ba | ||
886b0985e1 | |||
e57fa4e64a | |||
|
43fee371de | ||
67492b834f | |||
ed050ea820 | |||
faa581e2b4 | |||
9748461024 | |||
1e7a6fd8c5 | |||
|
9fe08cd794 | ||
51a2190559 | |||
8711bca159 | |||
080cc0ab35 | |||
5715f97956 | |||
2bd28f0796 | |||
5c76864595 | |||
3372db8b2b | |||
73262db871 | |||
a4131fe276 | |||
90be28a1f0 | |||
b51c373e96 | |||
1bfb6e9946 | |||
64d0b4b35e |
5
.idea/.idea.SPTInstaller/.idea/avalonia.xml
generated
5
.idea/.idea.SPTInstaller/.idea/avalonia.xml
generated
@ -19,7 +19,12 @@
|
||||
<entry key="SPTInstaller/CustomControls/UpdateButton.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/UpdateInfoCard.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/DetailedPreChecksView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/InstallPathSelectionView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/InstallView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/InstallerUpdateView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/MainWindow.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/MessageView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/OverviewView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/PreChecksView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
</map>
|
||||
</option>
|
||||
|
@ -1,4 +1,4 @@
|
||||
# SPT-AKI Installer made for EFT.
|
||||
# SPT Installer made for EFT.
|
||||
|
||||
<img src="https://i.imgur.com/jtlwLsr.png" alt="spt installer 2.59" width="700"/>
|
||||
|
||||
@ -9,12 +9,12 @@
|
||||
- Checks if there is enough space before install
|
||||
- Checks installer is not in a problematic path
|
||||
- Checks install folder does not have game files already in it
|
||||
- Checks if gameversion matches aki version, if so skip patcher process
|
||||
- Checks if gameversion matches SPT version, if so skip patcher process
|
||||
- Checks both zips are there, other than when the above match, patcher isnt checked for
|
||||
- downloads both Zips from the Repo's if needed
|
||||
|
||||
### Installer Processes:
|
||||
- Copies files from registry logged GamePath to new location
|
||||
- Extracts, runs and deletes patcher with no user input
|
||||
- Extracts Aki
|
||||
- Deletes both Patcher and AKI zips at the end
|
||||
- Extracts SPT
|
||||
- Deletes both Patcher and SPT zips at the end
|
@ -16,21 +16,21 @@
|
||||
<Application.Resources>
|
||||
|
||||
<!-- Colors -->
|
||||
<Color x:Key="AKI_DarkGray">#121212</Color>
|
||||
<Color x:Key="AKI_Yellow">#FFC107</Color>
|
||||
<Color x:Key="AKI_White">#FFFFFF</Color>
|
||||
<Color x:Key="AKI_Gray">#282828</Color>
|
||||
<Color x:Key="AKI_DarkGrayBlue">#323947</Color>
|
||||
<Color x:Key="AKI_LightGrayBlue">#444259</Color>
|
||||
<Color x:Key="SPT_DarkGray">#121212</Color>
|
||||
<Color x:Key="SPT_Yellow">#FFC107</Color>
|
||||
<Color x:Key="SPT_White">#FFFFFF</Color>
|
||||
<Color x:Key="SPT_Gray">#282828</Color>
|
||||
<Color x:Key="SPT_DarkGrayBlue">#323947</Color>
|
||||
<Color x:Key="SPT_LightGrayBlue">#444259</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="AKI_Foreground_Light" Color="{StaticResource AKI_White}" />
|
||||
<SolidColorBrush x:Key="AKI_Background_Light" Color="{StaticResource AKI_Gray}" />
|
||||
<SolidColorBrush x:Key="AKI_Background_Dark" Color="{StaticResource AKI_DarkGray}" />
|
||||
<SolidColorBrush x:Key="AKI_Brush_Yellow" Color="{StaticResource AKI_Yellow}" />
|
||||
<SolidColorBrush x:Key="AKI_Brush_DarkGrayBlue" Color="{StaticResource AKI_DarkGrayBlue}" />
|
||||
<SolidColorBrush x:Key="AKI_Brush_LightGrayBlue" Color="{StaticResource AKI_LightGrayBlue}" />
|
||||
<SolidColorBrush x:Key="AKI_Brush_Lighter" Color="Gainsboro" />
|
||||
<SolidColorBrush x:Key="SPT_Foreground_Light" Color="{StaticResource SPT_White}" />
|
||||
<SolidColorBrush x:Key="SPT_Background_Light" Color="{StaticResource SPT_Gray}" />
|
||||
<SolidColorBrush x:Key="SPT_Background_Dark" Color="{StaticResource SPT_DarkGray}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_Yellow" Color="{StaticResource SPT_Yellow}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_DarkGrayBlue" Color="{StaticResource SPT_DarkGrayBlue}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_LightGrayBlue" Color="{StaticResource SPT_LightGrayBlue}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_Lighter" Color="Gainsboro" />
|
||||
|
||||
<!-- Path Geometry -->
|
||||
<PathGeometry x:Key="CircledCheck"
|
||||
@ -48,5 +48,8 @@
|
||||
<PathGeometry x:Key="Bug"
|
||||
Figures="m 12.25 0 a 0.75 0.75 0 0 1 0.743 0.648 L 13 0.75 v 0.752 c 0 0.633 -0.196 1.22 -0.53 1.704 a 3.75 3.75 0 0 1 2.521 3.29 h 0.256 a 2.25 2.25 0 0 0 2.24 -2.259 L 17.481 2.752 a 0.750006 0.750006 0 0 1 1.5 -0.006 l 0.007 1.485 a 3.75 3.75 0 0 1 -3.536 3.76 L 15.238 7.997 L 15 7.996 v 1.502 h 4.253 a 0.75 0.75 0 0 1 0.743 0.649 l 0.007 0.102 a 0.75 0.75 0 0 1 -0.648 0.743 l -0.102 0.007 H 15 v 1.999 h 0.238 l 0.214 0.007 a 3.75 3.75 0 0 1 3.531 3.56 l 0.005 0.2 l -0.007 1.485 a 0.75 0.75 0 0 1 -1.493 0.095 l -0.007 -0.102 l 0.007 -1.485 a 2.25 2.25 0 0 0 -2.087 -2.253 l -0.154 -0.006 h -0.476 a 5.002 5.002 0 0 1 -9.542 0 H 4.74 A 2.25 2.25 0 0 0 2.5 16.758 l 0.005 1.485 a 0.750008 0.750008 0 1 1 -1.5 0.007 L 1 16.764 a 3.75 3.75 0 0 1 3.535 -3.76 L 4.75 12.999 L 5 12.998 v -2 H 0.75 A 0.75 0.75 0 0 1 0.007 10.35 L 0 10.249 A 0.75 0.75 0 0 1 0.648 9.506 L 0.75 9.499 L 5 9.498 V 7.996 H 4.75 L 4.535 7.991 A 3.75 3.75 0 0 1 1.005 4.431 L 1 4.23 L 1.006 2.745 A 0.75 0.75 0 0 1 2.5 2.649 L 2.506 2.751 L 2.5 4.237 A 2.25 2.25 0 0 0 4.587 6.491 L 4.741 6.497 H 5.009 A 3.753 3.753 0 0 1 7.53 3.205 A 2.968 2.968 0 0 1 7.006 1.711 L 7 1.502 V 0.75 A 0.75 0.75 0 0 1 8.493 0.648 L 8.5 0.75 v 0.752 a 1.5 1.5 0 0 0 2.993 0.145 L 11.5 1.502 V 0.75 A 0.75 0.75 0 0 1 12.25 0 Z"
|
||||
FillRule="NonZero" />
|
||||
<PathGeometry x:Key="OpenFolder" Figures="M 2.2731724 14.474999 C 2.5381753 14.186249 3.2824783 12.195001 3.9271792 10.05 5.6676413 4.2592679 4.7621113 4.8000009 12.719033 4.8000009 c 5.6684 0 6.78597 0.072438 7.12511 0.4618343 0.332844 0.3821726 0.17704 1.1971998 -0.903259 4.7250006 -0.763041 2.4917722 -1.52781 4.4189802 -1.840552 4.6381652 C 16.708149 14.899859 14.592619 15 9.1783054 15 2.1694393 15 1.8160107 14.973129 2.2731724 14.474999 Z M 0.36305228 14.025959 C 0.11166709 13.786409 0 11.721164 0 7.3114288 0 1.9218189 0.0760474 0.8703905 0.49472143 0.47142828 0.8806724 0.10364926 1.7051307 0 4.2446088 0 7.4749739 0 7.5058294 0.00685701 8.2944922 0.89999983 L 9.0892098 1.8 h 3.6407872 c 3.221023 0 3.71338 0.069177 4.270431 0.5999996 0.346306 0.3300009 0.629646 0.802501 0.629646 1.0500009 0 0.3838238 -0.858607 0.4500002 -5.83853 0.4500002 -5.6986082 0 -5.856156 0.016794 -6.5739181 0.7007613 C 4.8131633 4.9861817 4.2426547 6.0999322 3.9498292 7.0757619 2.3566037 12.385128 1.8127023 13.81777 1.2887903 14.084957 c -0.37832867 0.192941 -0.68163535 0.173611 -0.92573802 -0.059 z"
|
||||
FillRule="NonZero" />
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
@ -7,12 +8,35 @@ using Serilog;
|
||||
using SPTInstaller.ViewModels;
|
||||
using SPTInstaller.Views;
|
||||
using System.Reactive;
|
||||
using System.Text;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly string _logPath = Path.Join(Environment.CurrentDirectory, "spt-aki-installer_.log");
|
||||
public static string LogPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer", "spt-installer.log");
|
||||
public static string LogDebugPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"spt-installer", "spt-isntaller-debug.log");
|
||||
|
||||
public static void ReLaunch(bool debug, string installPath = "")
|
||||
{
|
||||
var installerPath = Path.Join(Environment.CurrentDirectory, "SPTInstaller.exe");
|
||||
|
||||
var args = new StringBuilder()
|
||||
.Append(debug ? "debug " : "")
|
||||
.Append(!string.IsNullOrEmpty(installPath) ? $"installPath=\"{installPath}\"" : "")
|
||||
.ToString();
|
||||
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = installerPath,
|
||||
Arguments = args
|
||||
});
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@ -21,9 +45,8 @@ public partial class App : Application
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.WriteTo
|
||||
.File(path: _logPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information,
|
||||
rollingInterval: RollingInterval.Day)
|
||||
.File(path: LogPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information)
|
||||
.CreateLogger();
|
||||
|
||||
RxApp.DefaultExceptionHandler = Observer.Create<Exception>((exception) =>
|
||||
@ -36,25 +59,38 @@ public partial class App : Application
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var debug = desktop.Args != null && desktop.Args.Any(x => x.ToLower() == "debug");
|
||||
if (debug)
|
||||
var data = ServiceHelper.Get<InternalData>() ?? throw new Exception("failed to get internal data");
|
||||
|
||||
data.DebugMode = false;
|
||||
var providedPath = "";
|
||||
|
||||
if (desktop.Args != null)
|
||||
{
|
||||
data.DebugMode = desktop.Args.Any(x => x.ToLower() == "debug");
|
||||
var installPath = desktop.Args.FirstOrDefault(x => x.StartsWith("installPath=", StringComparison.CurrentCultureIgnoreCase));
|
||||
|
||||
providedPath = installPath != null && installPath.Contains('=') ? installPath?.Split('=')[1] ?? "" : "";
|
||||
}
|
||||
|
||||
if (data.DebugMode)
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo
|
||||
.File(path: _logPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug,
|
||||
rollingInterval: RollingInterval.Day)
|
||||
.File(path: LogDebugPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug)
|
||||
.CreateLogger();
|
||||
|
||||
System.Diagnostics.Trace.Listeners.Add(new SerilogTraceListener.SerilogTraceListener());
|
||||
Trace.Listeners.Add(new SerilogTraceListener.SerilogTraceListener());
|
||||
|
||||
Log.Debug("TraceListener is registered");
|
||||
}
|
||||
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(debug),
|
||||
DataContext = new MainWindowViewModel(providedPath),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cc="using:SPTInstaller.CustomControls">
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Spacing="5" Background="{StaticResource AKI_Background_Dark}">
|
||||
<StackPanel Spacing="5" Background="{StaticResource SPT_Background_Dark}">
|
||||
<Button Classes="icon" x:Name="testBtn">
|
||||
<Path Data="{StaticResource Bug}"
|
||||
Fill="{Binding ElementName=testBtn, Path=Foreground}" />
|
||||
</Button>
|
||||
<TextBox Text="Some cool text here" Margin="5" />
|
||||
<TextBox Watermark="This is a watermark" Margin="5" />
|
||||
<CheckBox Content="sldkflskdf" />
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
|
||||
@ -16,30 +17,30 @@
|
||||
|
||||
<!-- TitleBar Styles -->
|
||||
<Style Selector="cc|TitleBar">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}" />
|
||||
<Setter Property="ButtonForeground" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Foreground_Light}" />
|
||||
<Setter Property="ButtonForeground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="cc|TitleBar.versiontag">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 2" />
|
||||
</Style>
|
||||
|
||||
<!-- TextBox Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml -->
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Background_Light}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:focus">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:pointerover">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:pointerover /template/ Border#PART_BorderElement">
|
||||
@ -64,23 +65,23 @@
|
||||
|
||||
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<!-- TextBlock Styles -->
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Foreground_Light}" />
|
||||
</Style>
|
||||
|
||||
<!-- Label Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Label.xaml -->
|
||||
<Style Selector="Label">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Foreground_Light}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Label.yellow">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Label.dark">
|
||||
@ -94,8 +95,8 @@
|
||||
<!-- ProgressBar Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml -->
|
||||
<Style Selector="ProgressBar">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ProgressBar.error">
|
||||
@ -103,7 +104,7 @@
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.5" FillMode="Forward">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Value" Value="0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
@ -117,36 +118,36 @@
|
||||
<!-- Seperator Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Separator.xaml -->
|
||||
<Style Selector="Separator">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Background_Dark}" />
|
||||
</Style>
|
||||
|
||||
<!-- Button Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Button.xaml -->
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_White}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_White}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_LightGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_LightGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_White}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_LightGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_LightGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_White}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
|
||||
<!-- Button yellow -->
|
||||
<Style Selector="Button.yellow">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
|
||||
@ -156,37 +157,37 @@
|
||||
|
||||
<Style Selector="Button.yellow:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Gold" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_Lighter}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
|
||||
<!-- Button outlined Style -->
|
||||
<Style Selector="Button.outlined">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlined:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlined:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
@ -194,7 +195,7 @@
|
||||
|
||||
<!-- Button Link Style -->
|
||||
<Style Selector="Button.link">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1" />
|
||||
@ -208,22 +209,22 @@
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pointerover TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pressed TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1" />
|
||||
@ -231,21 +232,21 @@
|
||||
|
||||
<!-- Button outlinedTLCorner Style -->
|
||||
<Style Selector="Button.outlinedTLCorner">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="2 2 0 0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlinedTLCorner:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="2 2 0 0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlinedTLCorner:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
@ -258,13 +259,39 @@
|
||||
</Style>
|
||||
<Style Selector="Button.icon:pointerover">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
</Style>
|
||||
<Style Selector="Button.icon:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="Button.icon:pressed">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_DarkGrayBlue}"></Setter>
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_DarkGrayBlue}"></Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Checkbox Styles -->
|
||||
<Style Selector="CheckBox">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Style.Resources>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeUnchecked" Color="DimGray"/>
|
||||
</Style.Resources>
|
||||
</Style>
|
||||
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#ContentPresenter">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:checked /template/ ContentPresenter#ContentPresenter">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:pointerover /template/ Border#NormalRectangle">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Yellow}"/>
|
||||
</Style>
|
||||
<Style Selector="CheckBox:checked /template/ Border#NormalRectangle">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Yellow}"/>
|
||||
<Setter Property="Background" Value="{StaticResource SPT_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
<Style Selector="CheckBox:checked /template/ Path#CheckGlyph">
|
||||
<Setter Property="Fill" Value="{StaticResource SPT_Yellow}"/>
|
||||
</Style>
|
||||
</Styles>
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
BIN
SPTInstaller/Assets/spt_installer.ico
Normal file
BIN
SPTInstaller/Assets/spt_installer.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
@ -1,23 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.Dialogs.ChangeLogDialog"
|
||||
MinWidth="400" MaxWidth="600">
|
||||
<StackPanel>
|
||||
<Label Content="{Binding Version, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat='{}Installer Change Log for {0}'}" FontSize="18" FontWeight="SemiBold"
|
||||
/>
|
||||
<Separator Margin="0 10" Padding="0" Background="{StaticResource AKI_Yellow}"/>
|
||||
<ScrollViewer MaxHeight="250">
|
||||
<TextBlock Text="{Binding Message, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
TextWrapping="Wrap" MinHeight="100"
|
||||
/>
|
||||
</ScrollViewer>
|
||||
<Button Content="Close" Classes="yellow"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dialogHost:DialogHost}, Path=CloseDialogCommand}"
|
||||
/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
@ -1,27 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Tmds.DBus.Protocol;
|
||||
|
||||
namespace SPTInstaller.CustomControls.Dialogs;
|
||||
|
||||
public partial class ChangeLogDialog : UserControl
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Version { get; set; }
|
||||
public ChangeLogDialog(string newVersion, string message)
|
||||
{
|
||||
InitializeComponent();
|
||||
Message = message;
|
||||
Version = newVersion;
|
||||
}
|
||||
|
||||
// public static readonly StyledProperty<string> MessageProperty =
|
||||
// AvaloniaProperty.Register<ChangeLogDialog, string>("Message");
|
||||
//
|
||||
// public string Message
|
||||
// {
|
||||
// get => GetValue(MessageProperty);
|
||||
// set => SetValue(MessageProperty, value);
|
||||
// }
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
MinWidth="300" MinHeight="100"
|
||||
MaxWidth="600" MaxHeight="300">
|
||||
<Grid RowDefinitions="10,AUTO,*,AUTO,10" ColumnDefinitions="10,*,AUTO,10,AUTO,10"
|
||||
Background="{StaticResource AKI_Background_Light}">
|
||||
Background="{StaticResource SPT_Background_Light}">
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="4"
|
||||
Text="{Binding Message, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
TextWrapping="Wrap" />
|
||||
|
@ -6,17 +6,16 @@
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.Dialogs.WhyCacheThoughDialog">
|
||||
<Grid RowDefinitions="AUTO,AUTO,AUTO,*,AUTO" ColumnDefinitions="*,AUTO, AUTO"
|
||||
Background="{StaticResource AKI_Background_Light}">
|
||||
<Grid RowDefinitions="AUTO,AUTO,AUTO,*,AUTO" ColumnDefinitions="*,AUTO"
|
||||
Background="{StaticResource SPT_Background_Light}">
|
||||
<Label Content="What is the installer cache for?" FontSize="20"
|
||||
Foreground="{StaticResource AKI_Brush_Yellow}" />
|
||||
Foreground="{StaticResource SPT_Brush_Yellow}" />
|
||||
<TextBlock Grid.Row="1" Grid.ColumnSpan="2" TextWrapping="Wrap" xml:space="preserve">
|
||||
The installer cache is used to ensure you don't re-download large files that you've already downloaded before.
|
||||
<Span Foreground="red">You should only delete the cache folder if</Span>
|
||||
- You are low on space
|
||||
or
|
||||
- You are not planning on installing SPT again any time soon
|
||||
|
||||
If possible, you should leave the cache in place to avoid uneccessary, lengthy downloads.
|
||||
It also helps us prevent extra traffic to our limited download mirrors. Every bit helps <Span Foreground="red"
|
||||
FontSize="25">♥️</Span>
|
||||
@ -25,7 +24,6 @@ It also helps us prevent extra traffic to our limited download mirrors. Every bi
|
||||
<Button Grid.Row="3" Grid.ColumnSpan="2"
|
||||
Content="{Binding Source={x:Static helpers:DownloadCacheHelper.CachePath}}"
|
||||
Classes="link"
|
||||
Margin="0 10"
|
||||
IsVisible="{Binding CacheExists, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding OpenCacheFolder, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
<Label Grid.Row="3" Content="No cache folder exists"
|
||||
@ -35,10 +33,16 @@ It also helps us prevent extra traffic to our limited download mirrors. Every bi
|
||||
Content="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=AdditionalInfo}"
|
||||
Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=AdditionalInfoColor}" />
|
||||
|
||||
<Button Grid.Row="4" Grid.Column="1" Content="Move Downloaded Patcher" Margin="0 0 10 0"
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="4" Grid.Column="1" Spacing="10">
|
||||
<Button Content="Move Downloaded Patcher"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=MoveDownloadsPatcherToCache}" />
|
||||
|
||||
<Button Grid.Row="4" Grid.Column="2" Content="Close" Classes="yellow"
|
||||
<Button Content="Clear Metadata Cache"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClearCachedMetaData}"
|
||||
/>
|
||||
|
||||
<Button Content="Close" Classes="yellow"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dialogHost:DialogHost}, Path=CloseDialogCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -3,7 +3,10 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Serilog;
|
||||
using SPTInstaller.Models;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace SPTInstaller.CustomControls.Dialogs;
|
||||
|
||||
@ -54,13 +57,56 @@ public partial class WhyCacheThoughDialog : UserControl
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearCachedMetaData()
|
||||
{
|
||||
var cachedMetadata =
|
||||
new DirectoryInfo(DownloadCacheHelper.CachePath).GetFiles("*.json", SearchOption.TopDirectoryOnly);
|
||||
|
||||
var message = "no cached metadata to remove";
|
||||
|
||||
if (cachedMetadata.Length == 0)
|
||||
{
|
||||
AdditionalInfo = message;
|
||||
AdditionalInfoColor = "dodgerblue";
|
||||
Log.Information(message);
|
||||
return;
|
||||
}
|
||||
|
||||
var allDeleted = true;
|
||||
|
||||
foreach (var file in cachedMetadata)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
file.Refresh();
|
||||
if (file.Exists)
|
||||
{
|
||||
allDeleted = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to delete cached metadata file: {file.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
message = allDeleted ? "cached metadata removed" : "some files could not be removed. Check logs";
|
||||
AdditionalInfo = message;
|
||||
AdditionalInfoColor = allDeleted ? "green" : "red";
|
||||
Log.Information(message);
|
||||
|
||||
var data = ServiceHelper.Get<InternalData>();
|
||||
|
||||
App.ReLaunch(false, data.TargetInstallPath!);
|
||||
}
|
||||
|
||||
public void MoveDownloadsPatcherToCache()
|
||||
{
|
||||
switch (_movePatcherState)
|
||||
{
|
||||
case 0:
|
||||
var downloadsPath =
|
||||
Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
|
||||
var downloadsPath = KnownFolders.GetPath(KnownFolder.Downloads);
|
||||
|
||||
var downloadsFolder = new DirectoryInfo(downloadsPath);
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
<Style Selector="Button.selectable">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
|
||||
@ -26,18 +26,18 @@
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selectable:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selectable:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selected">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_LightGrayBlue}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_LightGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
@ -62,7 +62,7 @@
|
||||
|
||||
<Ellipse Height="30" Width="30"
|
||||
StrokeThickness="4"
|
||||
Fill="{StaticResource AKI_Background_Dark}"
|
||||
Fill="{StaticResource SPT_Background_Dark}"
|
||||
HorizontalAlignment="Left"
|
||||
Classes.running="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.completed="{Binding IsCompleted, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
|
@ -34,11 +34,11 @@
|
||||
Width="35">
|
||||
<Button.Styles>
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Background_Light}" />
|
||||
</Style>
|
||||
</Button.Styles>
|
||||
</Button>
|
||||
|
@ -26,8 +26,6 @@
|
||||
<Button Content="Not now" CornerRadius="0 20 20 0"
|
||||
Command="{Binding DismissCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
</StackPanel>
|
||||
<Button HorizontalAlignment="Center" Content="What's new?" Classes="link"
|
||||
Command="{Binding WhatsNewCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Panel Margin="0 10">
|
||||
|
@ -50,15 +50,6 @@ public partial class UpdateButton : UserControl
|
||||
set => SetValue(UpdateCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ICommand> WhatsNewCommandProperty =
|
||||
AvaloniaProperty.Register<UpdateButton, ICommand>("WhatsNewCommand");
|
||||
|
||||
public ICommand WhatsNewCommand
|
||||
{
|
||||
get => GetValue(WhatsNewCommandProperty);
|
||||
set => SetValue(WhatsNewCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> UpdatingProperty = AvaloniaProperty.Register<UpdateButton, bool>(
|
||||
"Updating");
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.Helpers;
|
||||
|
||||
@ -9,6 +8,7 @@ public static class DownloadCacheHelper
|
||||
{
|
||||
private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromMinutes(15) };
|
||||
|
||||
public static TimeSpan SuggestedTtl = TimeSpan.FromHours(1);
|
||||
public static string CachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"spt-installer/cache");
|
||||
|
||||
@ -50,10 +50,10 @@ public static class DownloadCacheHelper
|
||||
/// <param name="expectedHash">The expected hash of the file in the cache</param>
|
||||
/// <param name="cachedFile">The file found in the cache; null if no file is found</param>
|
||||
/// <returns>True if the file is in the cache and its hash matches the expected hash, otherwise false</returns>
|
||||
public static bool CheckCache(string fileName, string expectedHash, out FileInfo cachedFile)
|
||||
=> CheckCache(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
|
||||
public static bool CheckCacheHash(string fileName, string expectedHash, out FileInfo cachedFile)
|
||||
=> CheckCacheHash(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
|
||||
|
||||
private static bool CheckCache(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
|
||||
private static bool CheckCacheHash(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
|
||||
{
|
||||
fileInCache = cacheFile;
|
||||
|
||||
@ -86,6 +86,44 @@ public static class DownloadCacheHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a file in the cache based on a time-to-live from its last modified time
|
||||
/// </summary>
|
||||
/// <param name="fileName">The name of the file to look for in the cache</param>
|
||||
/// <param name="ttl">The time-to-live to check against</param>
|
||||
/// <param name="cachedFile">The file found in the cache if it exists</param>
|
||||
/// <returns>Returns true if the file was found in the cache, otherwise false</returns>
|
||||
public static bool CheckCacheTTL(string fileName, TimeSpan ttl, out FileInfo cachedFile) =>
|
||||
CheckCacheTTL(new FileInfo(Path.Join(CachePath, fileName)), ttl, out cachedFile);
|
||||
|
||||
private static bool CheckCacheTTL(FileInfo cacheFile, TimeSpan ttl, out FileInfo fileInCache)
|
||||
{
|
||||
fileInCache = cacheFile;
|
||||
|
||||
try
|
||||
{
|
||||
cacheFile.Refresh();
|
||||
Directory.CreateDirectory(CachePath);
|
||||
|
||||
if (!cacheFile.Exists)
|
||||
{
|
||||
Log.Information($"{cacheFile.Name} {(cacheFile.Exists ? "is in cache" : "NOT in cache")}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var validTimeToLive = cacheFile.LastWriteTime.Add(ttl) > DateTime.Now;
|
||||
|
||||
Log.Information($"{cacheFile.Name} TTL is {(validTimeToLive ? "OK" : "INVALID")}");
|
||||
|
||||
return validTimeToLive;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Something went wrong during hashing");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a file to the cache folder
|
||||
/// </summary>
|
||||
@ -107,7 +145,20 @@ public static class DownloadCacheHelper
|
||||
|
||||
// Use the provided extension method
|
||||
using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
await _httpClient.DownloadDataAsync(targetLink, file, progress);
|
||||
{
|
||||
if (!await _httpClient.DownloadDataAsync(targetLink, file, progress))
|
||||
{
|
||||
Log.Error($"Download failed: {targetLink}");
|
||||
|
||||
outputFile.Refresh();
|
||||
|
||||
if (outputFile.Exists)
|
||||
{
|
||||
outputFile.Delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputFile.Refresh();
|
||||
|
||||
@ -167,6 +218,34 @@ public static class DownloadCacheHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or download a file using a time to live
|
||||
/// </summary>
|
||||
/// <param name="fileName">The file to get from cache</param>
|
||||
/// <param name="targetLink">The link to use for the download</param>
|
||||
/// <param name="progress">A progress object for reporting download progress</param>
|
||||
/// <param name="timeToLive">The time-to-live to check against in the cache</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink,
|
||||
IProgress<double> progress, TimeSpan timeToLive)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CheckCacheTTL(fileName, timeToLive, out FileInfo cachedFile))
|
||||
{
|
||||
return cachedFile;
|
||||
}
|
||||
|
||||
Log.Information($"Downloading File: {targetLink}");
|
||||
return await DownloadFileAsync(fileName, targetLink, progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error while getting file: {fileName}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the file from cache or download it
|
||||
/// </summary>
|
||||
@ -181,9 +260,10 @@ public static class DownloadCacheHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CheckCache(fileName, expectedHash, out var cacheFile))
|
||||
if (CheckCacheHash(fileName, expectedHash, out var cacheFile))
|
||||
return cacheFile;
|
||||
|
||||
Log.Information($"Downloading File: {targetLink}");
|
||||
return await DownloadFileAsync(fileName, targetLink, progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -206,7 +286,7 @@ public static class DownloadCacheHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CheckCache(fileName, expectedHash, out var cacheFile))
|
||||
if (CheckCacheHash(fileName, expectedHash, out var cacheFile))
|
||||
return cacheFile;
|
||||
|
||||
return await DownloadFileAsync(fileName, fileDownloadStream);
|
||||
|
@ -9,98 +9,6 @@ namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class FileHelper
|
||||
{
|
||||
private static Result IterateDirectories(DirectoryInfo sourceDir, DirectoryInfo targetDir, string[] exclusions)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var dir in sourceDir.GetDirectories("*", SearchOption.AllDirectories))
|
||||
{
|
||||
var exclude = false;
|
||||
|
||||
foreach (var exclusion in exclusions)
|
||||
{
|
||||
var currentDirRelativePath = dir.FullName.Replace(sourceDir.FullName, "");
|
||||
|
||||
if (currentDirRelativePath.StartsWith(exclusion) || currentDirRelativePath == exclusion)
|
||||
{
|
||||
exclude = true;
|
||||
Log.Debug(
|
||||
$"EXCLUSION FOUND :: DIR\nExclusion: '{exclusion}'\nPath: '{currentDirRelativePath}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (exclude)
|
||||
continue;
|
||||
|
||||
Directory.CreateDirectory(dir.FullName.Replace(sourceDir.FullName, targetDir.FullName));
|
||||
}
|
||||
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while creating directories");
|
||||
return Result.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, string[] exclusions,
|
||||
Action<string, int> updateCallback = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length;
|
||||
int processedFiles = 0;
|
||||
|
||||
foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
var exclude = false;
|
||||
|
||||
updateCallback?.Invoke(file.Name, (int)Math.Floor(((double)processedFiles / totalFiles) * 100));
|
||||
|
||||
foreach (var exclusion in exclusions)
|
||||
{
|
||||
var currentFileRelativePath = file.FullName.Replace(sourceDir.FullName, "");
|
||||
|
||||
if (currentFileRelativePath.StartsWith(exclusion) || currentFileRelativePath == exclusion)
|
||||
{
|
||||
exclude = true;
|
||||
Log.Debug(
|
||||
$"EXCLUSION FOUND :: FILE\nExclusion: '{exclusion}'\nPath: '{currentFileRelativePath}'");
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentFileRelativePath.EndsWith(".bak"))
|
||||
{
|
||||
exclude = true;
|
||||
Log.Debug($"EXCLUDING BAK FILE :: {currentFileRelativePath}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (exclude)
|
||||
continue;
|
||||
|
||||
|
||||
var targetFile = file.FullName.Replace(sourceDir.FullName, targetDir.FullName);
|
||||
|
||||
Log.Debug(
|
||||
$"COPY\nSourceDir: '{sourceDir.FullName}'\nTargetDir: '{targetDir.FullName}'\nNewPath: '{targetFile}'");
|
||||
|
||||
File.Copy(file.FullName, targetFile, true);
|
||||
processedFiles++;
|
||||
}
|
||||
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while copying files");
|
||||
return Result.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetRedactedPath(string path)
|
||||
{
|
||||
var nameMatched = Regex.Match(path, @".:\\[uU]sers\\(?<NAME>[^\\]+)");
|
||||
@ -123,13 +31,57 @@ public static class FileHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
var iterateDirectoriesResult = IterateDirectories(sourceDir, targetDir, exclusions ??= new string[0]);
|
||||
var allFiles = sourceDir.GetFiles("*", SearchOption.AllDirectories);
|
||||
var fileCopies = new List<CopyInfo>();
|
||||
int count = 0;
|
||||
|
||||
if (!iterateDirectoriesResult.Succeeded) return iterateDirectoriesResult;
|
||||
// filter files before starting copy
|
||||
foreach (var file in allFiles)
|
||||
{
|
||||
count++;
|
||||
updateCallback?.Invoke("getting list of files to copy", (int)Math.Floor((double)count / allFiles.Length * 100));
|
||||
|
||||
var iterateFilesResult = IterateFiles(sourceDir, targetDir, exclusions ??= new string[0], updateCallback);
|
||||
var currentFileRelativePath = file.FullName.Replace(sourceDir.FullName, "");
|
||||
|
||||
if (!iterateFilesResult.Succeeded) return iterateDirectoriesResult;
|
||||
if (exclusions != null)
|
||||
{
|
||||
// check exclusions
|
||||
foreach (var exclusion in exclusions)
|
||||
{
|
||||
if (currentFileRelativePath.StartsWith(exclusion) || currentFileRelativePath == exclusion)
|
||||
{
|
||||
Log.Debug(
|
||||
$"EXCLUSION FOUND :: FILE\nExclusion: '{exclusion}'\nPath: '{currentFileRelativePath}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't copy .bak files
|
||||
if (currentFileRelativePath.EndsWith(".bak"))
|
||||
{
|
||||
Log.Debug($"EXCLUDING BAK FILE :: {currentFileRelativePath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
fileCopies.Add(new CopyInfo(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName)));
|
||||
}
|
||||
|
||||
count = 0;
|
||||
|
||||
// process copy info for files that need to be copied
|
||||
foreach (var copyInfo in fileCopies)
|
||||
{
|
||||
count++;
|
||||
updateCallback?.Invoke(copyInfo.FileName, (int)Math.Floor((double)count / fileCopies.Count * 100));
|
||||
|
||||
var result = copyInfo.Copy();
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
@ -144,6 +96,7 @@ public static class FileHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug($"Starting StreamAssemblyResourceOut, resourcename: {resourceName}, outputFilePath: {outputFilePath}");
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
FileInfo outputFile = new FileInfo(outputFilePath);
|
||||
@ -167,6 +120,7 @@ public static class FileHelper
|
||||
}
|
||||
|
||||
outputFile.Refresh();
|
||||
|
||||
return outputFile.Exists;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -176,12 +130,21 @@ public static class FileHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a path is problematic
|
||||
/// </summary>
|
||||
/// <param name="path">The path the check</param>
|
||||
/// <param name="failedCheck">The check that failed</param>
|
||||
/// <returns>Returns true if the path is bad, otherwise false</returns>
|
||||
public static bool CheckPathForProblemLocations(string path, out PathCheck failedCheck)
|
||||
{
|
||||
path = Path.TrimEndingDirectorySeparator(path);
|
||||
|
||||
failedCheck = new();
|
||||
|
||||
var problemPaths = new List<PathCheck>()
|
||||
{
|
||||
new("SteamApps", PathCheckType.EndsWith, PathCheckAction.Warn),
|
||||
new("Documents", PathCheckType.EndsWith, PathCheckAction.Warn),
|
||||
new("Desktop", PathCheckType.EndsWith, PathCheckAction.Deny),
|
||||
new("Battlestate Games", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
|
@ -6,7 +6,7 @@ namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class HttpClientProgressExtensions
|
||||
{
|
||||
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination,
|
||||
public static async Task<bool> DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination,
|
||||
IProgress<double> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
using (var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
@ -18,20 +18,21 @@ public static class HttpClientProgressExtensions
|
||||
if (progress is null || !contentLength.HasValue)
|
||||
{
|
||||
await download.CopyToAsync(destination);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Such progress and contentLength much reporting Wow!
|
||||
var progressWrapper = new Progress<long>(totalBytes =>
|
||||
progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
||||
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
||||
var readBytes = await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
||||
return readBytes == contentLength.Value;
|
||||
}
|
||||
}
|
||||
|
||||
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
|
||||
}
|
||||
|
||||
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize,
|
||||
static async Task<long> CopyToAsync(this Stream source, Stream destination, int bufferSize,
|
||||
IProgress<long> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (bufferSize < 0)
|
||||
@ -55,5 +56,7 @@ public static class HttpClientProgressExtensions
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report(totalBytesRead);
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
}
|
||||
}
|
@ -20,7 +20,10 @@ public static class PreCheckHelper
|
||||
?.GetValue("InstallLocation");
|
||||
var info = (uninstallStringValue is string key) ? new DirectoryInfo(key) : null;
|
||||
|
||||
return info?.FullName;
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
return Path.TrimEndingDirectorySeparator(info.FullName);
|
||||
}
|
||||
|
||||
public static Result DetectOriginalGameVersion(string gamePath)
|
||||
|
@ -38,7 +38,7 @@ public static class ProcessHelper
|
||||
switch ((PatcherExitCode)process.ExitCode)
|
||||
{
|
||||
case PatcherExitCode.Success:
|
||||
return Result.FromSuccess("Patcher Finished Successfully, extracting Aki");
|
||||
return Result.FromSuccess("Patcher Finished Successfully, extracting SPT");
|
||||
|
||||
case PatcherExitCode.ProgramClosed:
|
||||
return Result.FromError("Patcher was closed before completing!");
|
||||
@ -47,16 +47,16 @@ public static class ProcessHelper
|
||||
return Result.FromError("EscapeFromTarkov.exe is missing from the install Path");
|
||||
|
||||
case PatcherExitCode.NoPatchFolder:
|
||||
return Result.FromError("Patchers Folder called 'Aki_Patches' is missing");
|
||||
return Result.FromError("Patchers Folder called 'SPT_Patches' is missing");
|
||||
|
||||
case PatcherExitCode.MissingFile:
|
||||
return Result.FromError("EFT files was missing a Vital file to continue");
|
||||
return Result.FromError("Vital EFT files were not found. The installer is unable to continue. Please reinstall EFT and try again.");
|
||||
|
||||
case PatcherExitCode.PatchFailed:
|
||||
return Result.FromError("A patch failed to apply");
|
||||
|
||||
default:
|
||||
return Result.FromError("an unknown error occurred in the patcher");
|
||||
return Result.FromError("An unknown error occurred in the patcher");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ public class DownloadTask : InstallerTaskBase
|
||||
{
|
||||
SetStatus("Downloading Patcher", "Verifying cached patcher ...", progressStyle: ProgressStyle.Indeterminate);
|
||||
|
||||
if (DownloadCacheHelper.CheckCache("patcher", _expectedPatcherHash, out var cacheFile))
|
||||
if (DownloadCacheHelper.CheckCacheHash("patcher", _expectedPatcherHash, out var cacheFile))
|
||||
{
|
||||
_data.PatcherZipInfo = cacheFile;
|
||||
Log.Information("Using cached file {fileName} - Hash: {hash}", _data.PatcherZipInfo.Name,
|
||||
@ -68,23 +68,23 @@ public class DownloadTask : InstallerTaskBase
|
||||
return Result.FromError("Failed to download Patcher");
|
||||
}
|
||||
|
||||
private async Task<IResult> DownloadSptAkiFromMirrors(IProgress<double> progress)
|
||||
private async Task<IResult> DownloadSPTFromMirrors(IProgress<double> progress)
|
||||
{
|
||||
// Note that GetOrDownloadFileAsync handles the cached file hash check, so we don't need to check it first
|
||||
foreach (var mirror in _data.ReleaseInfo.Mirrors)
|
||||
{
|
||||
SetStatus("Downloading SPT-AKI", mirror.DownloadUrl, progressStyle: ProgressStyle.Indeterminate);
|
||||
SetStatus("Downloading SPT", mirror.DownloadUrl, progressStyle: ProgressStyle.Indeterminate);
|
||||
|
||||
_data.AkiZipInfo =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("sptaki", mirror.DownloadUrl, progress, mirror.Hash);
|
||||
_data.SPTZipInfo =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("SPT", mirror.DownloadUrl, progress, mirror.Hash);
|
||||
|
||||
if (_data.AkiZipInfo != null)
|
||||
if (_data.SPTZipInfo != null)
|
||||
{
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.FromError("Failed to download spt-aki");
|
||||
return Result.FromError("Failed to download SPT");
|
||||
}
|
||||
|
||||
public override async Task<IResult> TaskOperation()
|
||||
@ -110,6 +110,6 @@ public class DownloadTask : InstallerTaskBase
|
||||
}
|
||||
}
|
||||
|
||||
return await DownloadSptAkiFromMirrors(progress);
|
||||
return await DownloadSPTFromMirrors(progress);
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ public class InitializationTask : InstallerTaskBase
|
||||
if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe")))
|
||||
{
|
||||
return Result.FromError(
|
||||
"Installer is located in a folder that has existing game files. Please make sure the installer is in an empty folder as per the guide");
|
||||
"Install location is a folder that has existing game files. Please make sure the folder doesn't contain an existing SPT install");
|
||||
}
|
||||
|
||||
return Result.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}");
|
||||
|
@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.Installer_Tasks.PreChecks;
|
||||
|
||||
public class EftInstalledPreCheck : PreCheckBase
|
||||
{
|
||||
private InternalData _internalData;
|
||||
|
||||
public EftInstalledPreCheck(InternalData data) : base("EFT Installed", true)
|
||||
{
|
||||
_internalData = data;
|
||||
}
|
||||
|
||||
public override async Task<PreCheckResult> CheckOperation()
|
||||
{
|
||||
if (_internalData.OriginalGamePath is null || !Directory.Exists(_internalData.OriginalGamePath) || !File.Exists(Path.Join(_internalData.OriginalGamePath, "Escapefromtarkov.exe")))
|
||||
{
|
||||
return PreCheckResult.FromError("Your EFT installation could not be found, try running the Battlestate Games Launcher and ensure EFT is installed on your computer", "Retry", RequestReevaluation);
|
||||
}
|
||||
|
||||
return PreCheckResult.FromSuccess("EFT install folder found");
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ public class EftLauncherPreCheck : PreCheckBase
|
||||
|
||||
return eftLauncherProcs.Length == 0
|
||||
? PreCheckResult.FromSuccess("Eft launcher is closed")
|
||||
: PreCheckResult.FromError("Eft launcher is open. Please close it to install SPT",
|
||||
: PreCheckResult.FromError("Your Battlestate Games Launcher is open. Please close it to continue installing SPT",
|
||||
"Kill EFT Launcher Processes",
|
||||
() =>
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ public class FreeSpacePreCheck : PreCheckBase
|
||||
|
||||
if (eftSourceDirSize == -1)
|
||||
{
|
||||
return PreCheckResult.FromError("An error occurred while getting the EFT source directory size");
|
||||
return PreCheckResult.FromError("An error occurred while getting the EFT source directory size. This is most likely because EFT is not installed");
|
||||
}
|
||||
|
||||
var availableSize = DriveInfo.GetDrives()
|
||||
|
@ -24,66 +24,77 @@ public class ReleaseCheckTask : InstallerTaskBase
|
||||
SetStatus("Checking SPT Releases", "", null, ProgressStyle.Indeterminate);
|
||||
|
||||
var progress = new Progress<double>((d) => { SetStatus(null, null, (int)Math.Floor(d)); });
|
||||
var akiReleaseInfoFile =
|
||||
await DownloadCacheHelper.DownloadFileAsync("release.json", DownloadCacheHelper.ReleaseMirrorUrl,
|
||||
progress);
|
||||
if (akiReleaseInfoFile == null)
|
||||
var SPTReleaseInfoFile =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("release.json", DownloadCacheHelper.ReleaseMirrorUrl,
|
||||
progress, DownloadCacheHelper.SuggestedTtl);
|
||||
|
||||
if (SPTReleaseInfoFile == null)
|
||||
{
|
||||
return Result.FromError("Failed to download release metadata");
|
||||
return Result.FromError("Failed to download release metadata, try clicking the 'Whats this' button below followed by the 'Clear Metadata cache' button");
|
||||
}
|
||||
|
||||
var akiReleaseInfo =
|
||||
JsonConvert.DeserializeObject<ReleaseInfo>(File.ReadAllText(akiReleaseInfoFile.FullName));
|
||||
var SPTReleaseInfo =
|
||||
JsonConvert.DeserializeObject<ReleaseInfo>(File.ReadAllText(SPTReleaseInfoFile.FullName));
|
||||
|
||||
SetStatus("Checking for Patches", "", null, ProgressStyle.Indeterminate);
|
||||
|
||||
var akiPatchMirrorsFile =
|
||||
await DownloadCacheHelper.DownloadFileAsync("mirrors.json", DownloadCacheHelper.PatchMirrorUrl,
|
||||
progress);
|
||||
var SPTPatchMirrorsFile =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", DownloadCacheHelper.PatchMirrorUrl,
|
||||
progress, DownloadCacheHelper.SuggestedTtl);
|
||||
|
||||
if (akiPatchMirrorsFile == null)
|
||||
if (SPTPatchMirrorsFile == null)
|
||||
{
|
||||
return Result.FromError("Failed to download patch mirror data");
|
||||
return Result.FromError("Failed to download patch mirror data, try clicking the 'Whats this' button below followed by the 'Clear Metadata cache' button");
|
||||
}
|
||||
|
||||
var patchMirrorInfo =
|
||||
JsonConvert.DeserializeObject<PatchInfo>(File.ReadAllText(akiPatchMirrorsFile.FullName));
|
||||
JsonConvert.DeserializeObject<PatchInfo>(File.ReadAllText(SPTPatchMirrorsFile.FullName));
|
||||
|
||||
if (akiReleaseInfo == null || patchMirrorInfo == null)
|
||||
if (SPTReleaseInfo == null || patchMirrorInfo == null)
|
||||
{
|
||||
return Result.FromError("An error occurred while deserializing aki or patch data");
|
||||
return Result.FromError("An error occurred while deserializing SPT or patch data, try clicking the 'Whats this' button below followed by the 'Clear Metadata cache' button");
|
||||
}
|
||||
|
||||
_data.ReleaseInfo = akiReleaseInfo;
|
||||
_data.ReleaseInfo = SPTReleaseInfo;
|
||||
_data.PatchInfo = patchMirrorInfo;
|
||||
int intAkiVersion = int.Parse(akiReleaseInfo.ClientVersion);
|
||||
int intSPTVersion = int.Parse(SPTReleaseInfo.ClientVersion);
|
||||
int intGameVersion = int.Parse(_data.OriginalGameVersion);
|
||||
|
||||
// note: it's possible the game version could be lower than the aki version and still need a patch if the major version numbers change
|
||||
// note: it's possible the game version could be lower than the SPT version and still need a patch if the major version numbers change
|
||||
// : it's probably a low chance though
|
||||
bool patchNeedCheck = intGameVersion > intAkiVersion;
|
||||
bool patchNeedCheck = intGameVersion > intSPTVersion;
|
||||
|
||||
if (intGameVersion < intAkiVersion)
|
||||
if (intGameVersion < intSPTVersion)
|
||||
{
|
||||
return Result.FromError("Your client is outdated. Please update EFT");
|
||||
return Result.FromError("Your live EFT is out of date. Please update it using the Battlestate Games Launcher and try runing the SPT Installer again");
|
||||
}
|
||||
|
||||
if (intGameVersion == intAkiVersion)
|
||||
if (intGameVersion == intSPTVersion)
|
||||
{
|
||||
patchNeedCheck = false;
|
||||
}
|
||||
|
||||
if ((intGameVersion != patchMirrorInfo.SourceClientVersion ||
|
||||
intAkiVersion != patchMirrorInfo.TargetClientVersion) && patchNeedCheck)
|
||||
bool sptClientIsOutdated = intSPTVersion != patchMirrorInfo.TargetClientVersion && patchNeedCheck;
|
||||
bool liveClientIsOutdated = intGameVersion != patchMirrorInfo.SourceClientVersion && patchNeedCheck;
|
||||
|
||||
if (sptClientIsOutdated)
|
||||
{
|
||||
return Result.FromError(
|
||||
"No patcher available for your version.\nA patcher is usually created within 24 hours of an EFT update.");
|
||||
"Could not find a downgrade patcher for the version of EFT you have installed." +
|
||||
"\nThis can happen due to one of the following reasons:" +
|
||||
"\n* Live EFT just updated. The SPT team will create a new patcher within 24 hours, hold tight!" +
|
||||
"\n* Live EFT just updated. You have not installed it on your computer using your Battlestate Games launcher");
|
||||
}
|
||||
|
||||
if (liveClientIsOutdated)
|
||||
{
|
||||
return Result.FromError("Your live EFT is out of date. Please update it using your Battlestate Games Launcher then run the SPT Installer again");
|
||||
}
|
||||
|
||||
_data.PatchNeeded = patchNeedCheck;
|
||||
|
||||
string status =
|
||||
$"Current Release: {akiReleaseInfo.ClientVersion} - {(_data.PatchNeeded ? "Patch Available" : "No Patch Needed")}";
|
||||
$"Current Release: {SPTReleaseInfo.ClientVersion} - {(_data.PatchNeeded ? "Patch Available" : "No Patch Needed")}";
|
||||
|
||||
SetStatus(null, status);
|
||||
|
||||
|
@ -35,7 +35,7 @@ public class SetupClientTask : InstallerTaskBase
|
||||
if (_data.PatchNeeded)
|
||||
{
|
||||
// extract patcher files
|
||||
SetStatus("Extrating Patcher", "", 0);
|
||||
SetStatus("Extracting Patcher", "", 0);
|
||||
|
||||
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, progress);
|
||||
|
||||
@ -71,7 +71,7 @@ public class SetupClientTask : InstallerTaskBase
|
||||
// extract release files
|
||||
SetStatus("Extracting Release", "", 0);
|
||||
|
||||
var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, progress);
|
||||
var extractReleaseResult = ZipHelper.Decompress(_data.SPTZipInfo, targetInstallDirInfo, progress);
|
||||
|
||||
if (!extractReleaseResult.Succeeded)
|
||||
{
|
||||
|
24
SPTInstaller/Models/CopyInfo.cs
Normal file
24
SPTInstaller/Models/CopyInfo.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Serilog;
|
||||
using SPTInstaller.Helpers;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
class CopyInfo(string sourcePath, string targetPath)
|
||||
{
|
||||
public string FileName => $"{Path.GetFileName(sourcePath)}";
|
||||
public Result Copy()
|
||||
{
|
||||
try
|
||||
{
|
||||
var directory = Path.GetDirectoryName(targetPath);
|
||||
Directory.CreateDirectory(directory);
|
||||
Log.Debug($"COPY\nSource: {FileHelper.GetRedactedPath(sourcePath)}\nTarget: {FileHelper.GetRedactedPath(targetPath)}");
|
||||
File.Copy(sourcePath, targetPath);
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,9 +9,21 @@ namespace SPTInstaller.Models;
|
||||
|
||||
public class InstallerUpdateInfo : ReactiveObject
|
||||
{
|
||||
public Version? NewVersion { get; private set; }
|
||||
private Version? _newVersion;
|
||||
|
||||
public string ChangeLog = "";
|
||||
public Version? NewVersion
|
||||
{
|
||||
get => _newVersion;
|
||||
set => this.RaiseAndSetIfChanged(ref _newVersion, value);
|
||||
}
|
||||
|
||||
private string _changeLog;
|
||||
|
||||
public string ChangeLog
|
||||
{
|
||||
get => _changeLog;
|
||||
set => this.RaiseAndSetIfChanged(ref _changeLog, value);
|
||||
}
|
||||
|
||||
private string _updateInfoText = "";
|
||||
|
||||
@ -21,14 +33,6 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
set => this.RaiseAndSetIfChanged(ref _updateInfoText, value);
|
||||
}
|
||||
|
||||
private bool _show = false;
|
||||
|
||||
public bool Show
|
||||
{
|
||||
get => _show;
|
||||
set => this.RaiseAndSetIfChanged(ref _show, value);
|
||||
}
|
||||
|
||||
private bool _updating = false;
|
||||
|
||||
public bool Updating
|
||||
@ -123,7 +127,6 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
}
|
||||
|
||||
UpdateInfoText = infoText;
|
||||
Show = updateAvailable;
|
||||
CheckingForUpdates = false;
|
||||
UpdateAvailable = updateAvailable;
|
||||
}
|
||||
@ -134,14 +137,13 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
return;
|
||||
|
||||
UpdateInfoText = "Checking for installer updates";
|
||||
Show = true;
|
||||
CheckingForUpdates = true;
|
||||
|
||||
try
|
||||
{
|
||||
var installerInfoFile =
|
||||
await DownloadCacheHelper.DownloadFileAsync("installer.json", DownloadCacheHelper.InstallerInfoUrl,
|
||||
null);
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("installer.json", DownloadCacheHelper.InstallerInfoUrl, null
|
||||
, DownloadCacheHelper.SuggestedTtl);
|
||||
|
||||
if (installerInfoFile == null)
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ namespace SPTInstaller.Models;
|
||||
|
||||
public class InternalData
|
||||
{
|
||||
public bool DebugMode { get; set; } = false;
|
||||
/// <summary>
|
||||
/// The folder to install SPT into
|
||||
/// </summary>
|
||||
@ -26,9 +27,9 @@ public class InternalData
|
||||
public FileInfo PatcherZipInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SPT-AKI zip file info
|
||||
/// SPT zip file info
|
||||
/// </summary>
|
||||
public FileInfo AkiZipInfo { get; set; }
|
||||
public FileInfo SPTZipInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The release information from release.json
|
||||
|
38
SPTInstaller/Models/KnownFolders.cs
Normal file
38
SPTInstaller/Models/KnownFolders.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
public enum KnownFolder
|
||||
{
|
||||
Contacts,
|
||||
Downloads,
|
||||
Favorites,
|
||||
Links,
|
||||
SavedGames,
|
||||
SavedSearches
|
||||
}
|
||||
|
||||
public static class KnownFolders
|
||||
{
|
||||
private static readonly Dictionary<KnownFolder, Guid> _guids = new()
|
||||
{
|
||||
[KnownFolder.Contacts] = new("56784854-C6CB-462B-8169-88E350ACB882"),
|
||||
[KnownFolder.Downloads] = new("374DE290-123F-4565-9164-39C4925E467B"),
|
||||
[KnownFolder.Favorites] = new("1777F761-68AD-4D8A-87BD-30B759FA33DD"),
|
||||
[KnownFolder.Links] = new("BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968"),
|
||||
[KnownFolder.SavedGames] = new("4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4"),
|
||||
[KnownFolder.SavedSearches] = new("7D1D3A04-DEBB-4115-95CF-2F29DA2920DA")
|
||||
};
|
||||
|
||||
public static string GetPath(KnownFolder knownFolder)
|
||||
{
|
||||
return SHGetKnownFolderPath(_guids[knownFolder], 0);
|
||||
}
|
||||
|
||||
[DllImport("shell32",
|
||||
CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
|
||||
private static extern string SHGetKnownFolderPath(
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags,
|
||||
nint hToken = 0);
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SPTInstaller.Models.ReleaseInfo;
|
||||
|
||||
public class ReleaseInfo
|
||||
{
|
||||
public string AkiVersion { get; set; }
|
||||
[JsonProperty("AkiVersion")] // TODO: Change this and what gets uploaded to SPTVersion
|
||||
public string SPTVersion { get; set; }
|
||||
public string ClientVersion { get; set; }
|
||||
public List<ReleaseInfoMirror> Mirrors { get; set; }
|
||||
}
|
@ -44,6 +44,7 @@ internal class Program
|
||||
ServiceHelper.Register<InternalData>();
|
||||
|
||||
#if !TEST
|
||||
ServiceHelper.Register<PreCheckBase, EftInstalledPreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, NetFramework472PreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, Net8PreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, FreeSpacePreCheck>();
|
||||
|
23
SPTInstaller/Resources/add_shortcuts.ps1
Normal file
23
SPTInstaller/Resources/add_shortcuts.ps1
Normal file
@ -0,0 +1,23 @@
|
||||
param (
|
||||
[string]$installPath
|
||||
)
|
||||
|
||||
$desktop = Join-Path $env:USERPROFILE "Desktop"
|
||||
|
||||
$launcherExe = gci $installPath | where {$_.Name -like "*.Launcher.exe"} | select -ExpandProperty FullName
|
||||
$serverExe = gci $installPath | where {$_.Name -like "*.Server.exe"} | select -ExpandProperty FullName
|
||||
|
||||
$launcherShortcut = Join-Path $desktop "SPT.Launcher.lnk"
|
||||
$serverShortcut = Join-Path $desktop "SPT.Server.lnk"
|
||||
|
||||
$WshShell = New-Object -comObject WScript.Shell
|
||||
|
||||
$launcher = $WshShell.CreateShortcut($launcherShortcut)
|
||||
$launcher.TargetPath = $launcherExe
|
||||
$launcher.WorkingDirectory = $installPath
|
||||
$launcher.Save()
|
||||
|
||||
$server = $WshShell.CreateShortcut($serverShortcut)
|
||||
$server.TargetPath = $serverExe
|
||||
$server.WorkingDirectory = $installPath
|
||||
$server.Save()
|
@ -5,23 +5,69 @@
|
||||
|
||||
Clear-Host
|
||||
|
||||
Write-Host "Stopping installer ..."
|
||||
Write-Host "Stopping installer ... " -ForegroundColor cyan -NoNewLine
|
||||
|
||||
$installer = Stop-Process -Name "SPTInstaller" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($installer -ne $null)
|
||||
{
|
||||
Write-Host "Something went wrong, couldn't stop installer process'"
|
||||
Write-Warning "Something went wrong, couldn't stop installer process'"
|
||||
return;
|
||||
}
|
||||
|
||||
Write-Host "Copying new installer ..."
|
||||
Write-Host "OK" -ForegroundColor green
|
||||
|
||||
Import-Module BitsTransfer
|
||||
if (-not(Test-Path $source) -and -not(Test-Path $destination)) {
|
||||
Write-Warning "Can't find a required file"
|
||||
Write-host ""
|
||||
Write-Host "Press [enter] to close ..."
|
||||
Read-Host
|
||||
exit
|
||||
}
|
||||
|
||||
Start-BitsTransfer -Source $source -Destination $destination
|
||||
Write-Host "Copying new installer ... " -ForegroundColor cyan
|
||||
|
||||
Remove-Module BitsTransfer
|
||||
$maxAttempts = 10
|
||||
$copied = $false
|
||||
|
||||
while (-not $copied) {
|
||||
|
||||
$maxAttempts--
|
||||
|
||||
Write-Host " > Please wait ... " -NoNewLine
|
||||
|
||||
if ($maxAttempts -le 0) {
|
||||
Write-Host "Couldn't copy new installer :( Please re-download the installer"
|
||||
Write-Host ""
|
||||
Write-Host "Press [enter] to close ..."
|
||||
Read-Host
|
||||
exit
|
||||
}
|
||||
|
||||
try {
|
||||
Remove-Item $destination -ErrorAction SilentlyContinue
|
||||
Copy-Item $source $destination -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch {
|
||||
Write-Host "file locked, retrying ..." -ForegroundColor yellow
|
||||
sleep(2)
|
||||
continue
|
||||
}
|
||||
|
||||
if (Test-Path $destination) {
|
||||
$sLength = (Get-Item $source).Length
|
||||
$dLength = (Get-Item $destination).Length
|
||||
|
||||
if ($sLength -eq $dLength) {
|
||||
$copied = $true
|
||||
Write-Host "OK" -ForegroundColor green
|
||||
break
|
||||
}
|
||||
|
||||
Write-Host "sizes differ, retrying ..." -ForegroundColor yellow
|
||||
sleep(2)
|
||||
}
|
||||
}
|
||||
|
||||
# remove the new installer from the cache folder after it is copied
|
||||
Remove-Item -Path $source
|
||||
|
@ -6,18 +6,19 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<!-- TODO: To change -->
|
||||
<PackageIcon>icon.ico</PackageIcon>
|
||||
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||
<ApplicationIcon>Assets\spt_installer.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;TEST</Configurations>
|
||||
<AssemblyVersion>2.62</AssemblyVersion>
|
||||
<FileVersion>2.62</FileVersion>
|
||||
<Company>SPT-AKI</Company>
|
||||
<AssemblyVersion>2.90</AssemblyVersion>
|
||||
<FileVersion>2.90</FileVersion>
|
||||
<Company>SPT</Company>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**"/>
|
||||
<None Remove=".gitignore"/>
|
||||
<None Remove="Assets\icon.ico"/>
|
||||
<None Remove="Assets\spt_installer.ico"/>
|
||||
<None Remove="Resources\update.ps1"/>
|
||||
</ItemGroup>
|
||||
|
||||
@ -25,6 +26,8 @@
|
||||
<EmbeddedResource Include="Resources\update.ps1"/>
|
||||
<None Remove="Resources\7z.dll"/>
|
||||
<EmbeddedResource Include="Resources\7z.dll"/>
|
||||
<None Remove="Resources\add_shortcuts.ps1" />
|
||||
<EmbeddedResource Include="Resources\add_shortcuts.ps1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
157
SPTInstaller/ViewModels/InstallPathSelectionViewModel.cs
Normal file
157
SPTInstaller/ViewModels/InstallPathSelectionViewModel.cs
Normal file
@ -0,0 +1,157 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Platform.Storage;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
public class InstallPathSelectionViewModel : ViewModelBase
|
||||
{
|
||||
private InternalData _data;
|
||||
|
||||
private string _selectedPath;
|
||||
|
||||
public string SelectedPath
|
||||
{
|
||||
get => _selectedPath;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedPath, value);
|
||||
}
|
||||
|
||||
private bool _validPath;
|
||||
public bool ValidPath
|
||||
{
|
||||
get => _validPath;
|
||||
set => this.RaiseAndSetIfChanged(ref _validPath, value);
|
||||
}
|
||||
|
||||
private string _errorMessage;
|
||||
|
||||
public string ErrorMessage
|
||||
{
|
||||
get => _errorMessage;
|
||||
set => this.RaiseAndSetIfChanged(ref _errorMessage, value);
|
||||
}
|
||||
|
||||
public InstallPathSelectionViewModel(IScreen host, string installPath) : base(host)
|
||||
{
|
||||
_data = ServiceHelper.Get<InternalData?>() ?? throw new Exception("Failed to get internal data");
|
||||
SelectedPath = Environment.CurrentDirectory;
|
||||
ValidPath = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(installPath))
|
||||
{
|
||||
SelectedPath = installPath;
|
||||
ValidatePath();
|
||||
|
||||
if (ValidPath)
|
||||
{
|
||||
Log.Information("Install Path was provided by parameter and seems valid");
|
||||
Task.Run(NextCommand);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AdjustInstallPath();
|
||||
}
|
||||
|
||||
public async Task SelectFolderCommand()
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (desktop.MainWindow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var startingFolderPath = Directory.Exists(SelectedPath) ? SelectedPath : Environment.CurrentDirectory;
|
||||
|
||||
var suggestedFolder = await desktop.MainWindow.StorageProvider.TryGetFolderFromPathAsync(startingFolderPath);
|
||||
|
||||
var selections = await desktop.MainWindow.StorageProvider.OpenFolderPickerAsync(
|
||||
new FolderPickerOpenOptions()
|
||||
{
|
||||
AllowMultiple = false,
|
||||
SuggestedStartLocation = suggestedFolder,
|
||||
Title = "Select a folder to install SPT into"
|
||||
});
|
||||
|
||||
SelectedPath = selections.First().Path.LocalPath;
|
||||
}
|
||||
}
|
||||
|
||||
public void ValidatePath()
|
||||
{
|
||||
if (String.IsNullOrEmpty(SelectedPath))
|
||||
{
|
||||
ErrorMessage = "Please provide an install path";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var match = Regex.Match(SelectedPath[2..], @"[\/:*?""<>|]");
|
||||
if (match.Success)
|
||||
{
|
||||
ErrorMessage = "Path cannot contain these characters: / : * ? \" < > |";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FileHelper.CheckPathForProblemLocations(SelectedPath, out var failedCheck))
|
||||
{
|
||||
if (failedCheck.CheckType == PathCheckType.EndsWith)
|
||||
{
|
||||
ErrorMessage = $"You can install in {failedCheck.Target}, but only in a subdirectory. Example: ..\\{failedCheck.Target}\\SPT";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (failedCheck.CheckAction == PathCheckAction.Deny)
|
||||
{
|
||||
ErrorMessage = $"Sorry, you cannot install in {failedCheck.Target}";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ValidPath = true;
|
||||
}
|
||||
|
||||
private void AdjustInstallPath()
|
||||
{
|
||||
if (FileHelper.CheckPathForProblemLocations(SelectedPath, out var failedCheck))
|
||||
{
|
||||
switch (failedCheck.CheckType)
|
||||
{
|
||||
case PathCheckType.EndsWith:
|
||||
SelectedPath = Path.Join(Environment.CurrentDirectory, "SPT");
|
||||
break;
|
||||
|
||||
case PathCheckType.Contains:
|
||||
case PathCheckType.DriveRoot:
|
||||
SelectedPath = Path.Join(Directory.GetDirectoryRoot(Environment.CurrentDirectory), "SPT");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NextCommand()
|
||||
{
|
||||
if (FileHelper.CheckPathForProblemLocations(SelectedPath, out var failedCheck) && failedCheck.CheckAction == PathCheckAction.Deny)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_data.TargetInstallPath = SelectedPath;
|
||||
|
||||
NavigateTo(new PreChecksViewModel(HostScreen));
|
||||
}
|
||||
}
|
42
SPTInstaller/ViewModels/InstallerUpdateViewModel.cs
Normal file
42
SPTInstaller/ViewModels/InstallerUpdateViewModel.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
public class InstallerUpdateViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
public InstallerUpdateInfo UpdateInfo { get; set; } = new();
|
||||
private InternalData _data;
|
||||
|
||||
private string _installPath;
|
||||
public InstallerUpdateViewModel(IScreen Host, string installPath) : base(Host)
|
||||
{
|
||||
_installPath = installPath;
|
||||
|
||||
_data = ServiceHelper.Get<InternalData>() ?? throw new Exception("Failed to get internal data");
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await UpdateInfo.CheckForUpdates(Assembly.GetExecutingAssembly().GetName().Version);
|
||||
|
||||
if (!UpdateInfo.UpdateAvailable)
|
||||
{
|
||||
NavigateTo(new OverviewViewModel(HostScreen, _installPath));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void NotNowCommand()
|
||||
{
|
||||
NavigateTo(new OverviewViewModel(HostScreen, _installPath));
|
||||
}
|
||||
|
||||
public async Task UpdateInstallCommand()
|
||||
{
|
||||
await UpdateInfo.UpdateInstaller();
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ using ReactiveUI;
|
||||
using Serilog;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
@ -19,10 +21,12 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
|
||||
set => this.RaiseAndSetIfChanged(ref _title, value);
|
||||
}
|
||||
|
||||
public MainWindowViewModel(bool debugging)
|
||||
public MainWindowViewModel(string installPath)
|
||||
{
|
||||
var data = ServiceHelper.Get<InternalData>() ?? throw new Exception("failed to get interanl data");
|
||||
|
||||
Title =
|
||||
$"{(debugging ? "-debug-" : "")} SPT Installer {"v" + Assembly.GetExecutingAssembly().GetName()?.Version?.ToString() ?? "--unknown version--"}";
|
||||
$"{(data.DebugMode ? "-debug-" : "")} SPT Installer {"v" + Assembly.GetExecutingAssembly().GetName()?.Version?.ToString() ?? "--unknown version--"}";
|
||||
|
||||
Log.Information($"========= {Title} Started =========");
|
||||
Log.Information(Environment.OSVersion.VersionString);
|
||||
@ -31,7 +35,7 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
|
||||
|
||||
Log.Information("System Language: {iso} - {name}", uiCulture.TwoLetterISOLanguageName, uiCulture.DisplayName);
|
||||
|
||||
Router.Navigate.Execute(new PreChecksViewModel(this, debugging));
|
||||
Router.Navigate.Execute(new InstallerUpdateViewModel(this, installPath));
|
||||
}
|
||||
|
||||
public void CloseCommand()
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Avalonia;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using SPTInstaller.CustomControls;
|
||||
@ -6,6 +8,10 @@ using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform.Storage;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
@ -35,6 +41,14 @@ public class MessageViewModel : ViewModelBase
|
||||
set => this.RaiseAndSetIfChanged(ref _showCloseButton, value);
|
||||
}
|
||||
|
||||
private bool _showOptions;
|
||||
|
||||
public bool ShowOptions
|
||||
{
|
||||
get => _showOptions;
|
||||
set => this.RaiseAndSetIfChanged(ref _showOptions, value);
|
||||
}
|
||||
|
||||
private string _cacheInfoText;
|
||||
|
||||
public string CacheInfoText
|
||||
@ -43,6 +57,74 @@ public class MessageViewModel : ViewModelBase
|
||||
set => this.RaiseAndSetIfChanged(ref _cacheInfoText, value);
|
||||
}
|
||||
|
||||
private string _clipCommandText;
|
||||
|
||||
public string ClipCommandText
|
||||
{
|
||||
get => _clipCommandText;
|
||||
set => this.RaiseAndSetIfChanged(ref _clipCommandText, value);
|
||||
}
|
||||
|
||||
private bool _addShortcuts;
|
||||
public bool AddShortcuts
|
||||
{
|
||||
get => _addShortcuts;
|
||||
set => this.RaiseAndSetIfChanged(ref _addShortcuts, value);
|
||||
}
|
||||
|
||||
private bool _openInstallFolder = true;
|
||||
public bool OpenInstallFolder
|
||||
{
|
||||
get => _openInstallFolder;
|
||||
set => this.RaiseAndSetIfChanged(ref _openInstallFolder, value);
|
||||
}
|
||||
|
||||
public ICommand CopyLogFileToClipboard => ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var data = ServiceHelper.Get<InternalData>();
|
||||
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (desktop.MainWindow?.Clipboard == null)
|
||||
{
|
||||
ClipCommandText = "Could not get clipboard :(";
|
||||
return;
|
||||
}
|
||||
|
||||
var dataObject = new DataObject();
|
||||
|
||||
var filesToCopy = new List<IStorageFile>();
|
||||
|
||||
var logFile = await desktop.MainWindow.StorageProvider.TryGetFileFromPathAsync(data.DebugMode ? App.LogDebugPath : App.LogPath);
|
||||
var patcherLogFile = await desktop.MainWindow.StorageProvider.TryGetFileFromPathAsync(Path.Join(data.TargetInstallPath, "patcher.log"));
|
||||
|
||||
if (logFile == null)
|
||||
{
|
||||
ClipCommandText = "Could not get log file :(";
|
||||
return;
|
||||
}
|
||||
|
||||
filesToCopy.Add(logFile);
|
||||
|
||||
if (patcherLogFile != null)
|
||||
{
|
||||
filesToCopy.Add(patcherLogFile);
|
||||
}
|
||||
|
||||
dataObject.Set(DataFormats.Files, filesToCopy.ToArray());
|
||||
|
||||
await desktop.MainWindow.Clipboard.SetDataObjectAsync(dataObject);
|
||||
ClipCommandText = "Copied!";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ClipCommandText = ex.Message;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private StatusSpinner.SpinnerState _cacheCheckState;
|
||||
|
||||
public StatusSpinner.SpinnerState CacheCheckState
|
||||
@ -53,8 +135,7 @@ public class MessageViewModel : ViewModelBase
|
||||
|
||||
public ICommand CloseCommand { get; set; } = ReactiveCommand.Create(() =>
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is
|
||||
Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
desktopApp.MainWindow.Close();
|
||||
}
|
||||
@ -64,6 +145,75 @@ public class MessageViewModel : ViewModelBase
|
||||
{
|
||||
ShowCloseButton = showCloseButton;
|
||||
Message = result.Message;
|
||||
ClipCommandText = "Copy installer log to clipboard";
|
||||
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
var data = ServiceHelper.Get<InternalData?>();
|
||||
|
||||
desktopApp.MainWindow.Closing += (_, _) =>
|
||||
{
|
||||
if (ShowOptions)
|
||||
{
|
||||
if (OpenInstallFolder)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = data.TargetInstallPath
|
||||
});
|
||||
}
|
||||
|
||||
if (AddShortcuts)
|
||||
{
|
||||
var shortcuts = new FileInfo(Path.Join(DownloadCacheHelper.CachePath, "add_shortcuts.ps1"));
|
||||
|
||||
if (!FileHelper.StreamAssemblyResourceOut("add_shortcuts.ps1", shortcuts.FullName))
|
||||
{
|
||||
Log.Fatal("Failed to prepare shortcuts file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(shortcuts.FullName))
|
||||
{
|
||||
Log.Fatal("Shortcuts file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("Running add shortcuts script ...");
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
CreateNoWindow = true,
|
||||
ArgumentList =
|
||||
{
|
||||
"-ExecutionPolicy", "Bypass", "-File", $"{shortcuts.FullName}", $"{data.TargetInstallPath}"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (data.TargetInstallPath == Environment.CurrentDirectory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
File.Copy(App.LogPath, Path.Join(data.TargetInstallPath, "spt-installer.log"), true);
|
||||
|
||||
if (data.DebugMode)
|
||||
{
|
||||
File.Copy(App.LogDebugPath, Path.Join(data.TargetInstallPath, "spt-installer-debug.log"), true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to copy installer log to install path");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
@ -77,6 +227,7 @@ public class MessageViewModel : ViewModelBase
|
||||
if (result.Succeeded)
|
||||
{
|
||||
Log.Information(Message);
|
||||
ShowOptions = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
23
SPTInstaller/ViewModels/OverviewViewModel.cs
Normal file
23
SPTInstaller/ViewModels/OverviewViewModel.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
public class OverviewViewModel : ViewModelBase
|
||||
{
|
||||
private string _providedPath;
|
||||
public OverviewViewModel(IScreen Host, string providedPath) : base(Host)
|
||||
{
|
||||
_providedPath = providedPath;
|
||||
|
||||
if (!string.IsNullOrEmpty(_providedPath))
|
||||
{
|
||||
Task.Run(NextCommand);
|
||||
}
|
||||
}
|
||||
|
||||
public void NextCommand()
|
||||
{
|
||||
NavigateTo(new InstallPathSelectionViewModel(HostScreen, _providedPath));
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Threading;
|
||||
@ -32,16 +31,8 @@ public class PreChecksViewModel : ViewModelBase
|
||||
public ICommand SelectPreCheckCommand { get; set; }
|
||||
public ICommand StartInstallCommand { get; set; }
|
||||
|
||||
public ICommand UpdateInstallerCommand { get; set; }
|
||||
|
||||
public ICommand DismissUpdateCommand { get; set; }
|
||||
|
||||
public ICommand WhatsNewCommand { get; set; }
|
||||
|
||||
public ICommand LaunchWithDebug { get; set; }
|
||||
|
||||
public InstallerUpdateInfo UpdateInfo { get; set; } = new();
|
||||
|
||||
private bool _debugging;
|
||||
|
||||
public bool Debugging
|
||||
@ -118,12 +109,13 @@ public class PreChecksViewModel : ViewModelBase
|
||||
});
|
||||
}
|
||||
|
||||
public PreChecksViewModel(IScreen host, bool debugging) : base(host)
|
||||
public PreChecksViewModel(IScreen host) : base(host)
|
||||
{
|
||||
Debugging = debugging;
|
||||
var data = ServiceHelper.Get<InternalData?>();
|
||||
var installer = ServiceHelper.Get<InstallController?>();
|
||||
|
||||
Debugging = data.DebugMode;
|
||||
|
||||
installer.RecheckRequested += ReCheckRequested;
|
||||
|
||||
InstallButtonText = "Please wait ...";
|
||||
@ -138,7 +130,6 @@ public class PreChecksViewModel : ViewModelBase
|
||||
|
||||
data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
||||
|
||||
data.TargetInstallPath = Environment.CurrentDirectory;
|
||||
InstallPath = data.TargetInstallPath;
|
||||
|
||||
Log.Information($"Install Path: {FileHelper.GetRedactedPath(InstallPath)}");
|
||||
@ -147,7 +138,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
if (data.OriginalGamePath == null)
|
||||
{
|
||||
NavigateTo(new MessageViewModel(HostScreen,
|
||||
Result.FromError("Could not find EFT install.\n\nDo you own and have the game installed?")));
|
||||
Result.FromError("Could not find where you installed EFT.\n\nDo you own and have the game installed?")));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@ -156,7 +147,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
|
||||
var logFiles = Directory.GetFiles(InstallPath, "spt-aki-installer_*.log");
|
||||
var logFiles = Directory.GetFiles(InstallPath, "spt-installer_*.log");
|
||||
|
||||
// remove log file from original game path if they exist
|
||||
foreach (var file in logFiles)
|
||||
@ -172,7 +163,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
|
||||
NavigateTo(new MessageViewModel(HostScreen,
|
||||
Result.FromError(
|
||||
"Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide"),
|
||||
"You have chosen to install in the same folder as EFT. Please choose a another folder. Refer to the install guide on where best to place the installer before running it."),
|
||||
noLog: true));
|
||||
return;
|
||||
}
|
||||
@ -189,13 +180,15 @@ public class PreChecksViewModel : ViewModelBase
|
||||
{
|
||||
Log.Warning("Problem path detected, confirming install path ...");
|
||||
var confirmation = await DialogHost.Show(new ConfirmationDialog(
|
||||
$"We suspect you may be installing into a problematic folder: {failedCheck.Target}.\nYou might want to consider installing somewhere else to avoid issues.\n\nAre you sure you want to install to this path?\n{InstallPath}"));
|
||||
$"It appears you are installing into a folder known to cause problems: {failedCheck.Target}." +
|
||||
$"\nPlease consider installing SPT somewhere else to avoid issues later on." +
|
||||
$"\n\nAre you sure you want to install to this path?\n{InstallPath}"));
|
||||
|
||||
if (confirmation == null || !bool.TryParse(confirmation.ToString(), out var confirm) ||
|
||||
!confirm)
|
||||
{
|
||||
Log.Information("User declined install path, exiting");
|
||||
Environment.Exit(0);
|
||||
Log.Information("User declined install path");
|
||||
NavigateBack();
|
||||
}
|
||||
});
|
||||
|
||||
@ -207,7 +200,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
Log.Error("Problem path detected, install denied");
|
||||
NavigateTo(new MessageViewModel(HostScreen,
|
||||
Result.FromError(
|
||||
$"We suspect you may be installing into a problematic folder: {failedCheck.Target}.\nWe won't be letting you install here. Please move the installer to another folder.\nSuggestion: a folder under your drive root, such as 'C:\\spt\\'\nDenied Path: {InstallPath}")));
|
||||
$"We suspect you may be installing into a problematic folder: {failedCheck.Target}.\nWe won't be letting you install here. How did you do this?")));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -222,14 +215,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var installerPath = Path.Join(_installPath, "SPTInstaller.exe");
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = installerPath,
|
||||
Arguments = "debug"
|
||||
});
|
||||
|
||||
Environment.Exit(0);
|
||||
App.ReLaunch(true, InstallPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -256,56 +242,42 @@ public class PreChecksViewModel : ViewModelBase
|
||||
|
||||
StartInstallCommand = ReactiveCommand.Create(async () =>
|
||||
{
|
||||
UpdateInfo.Show = false;
|
||||
NavigateTo(new InstallViewModel(HostScreen));
|
||||
});
|
||||
|
||||
UpdateInstallerCommand = ReactiveCommand.Create(async () =>
|
||||
{
|
||||
AllowDetailsButton = false;
|
||||
AllowInstall = false;
|
||||
await UpdateInfo.UpdateInstaller();
|
||||
});
|
||||
|
||||
DismissUpdateCommand = ReactiveCommand.Create(() => { UpdateInfo.Show = false; });
|
||||
|
||||
WhatsNewCommand =
|
||||
ReactiveCommand.Create(async () => await DialogHost.Show(new ChangeLogDialog(UpdateInfo.NewVersion.ToString(), UpdateInfo.ChangeLog)));
|
||||
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// run prechecks
|
||||
var result = await installer.RunPreChecks();
|
||||
|
||||
// check for updates
|
||||
await UpdateInfo.CheckForUpdates(Assembly.GetExecutingAssembly().GetName()?.Version);
|
||||
|
||||
// get latest spt version
|
||||
InstallButtonText = "Getting latest release ...";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.Running;
|
||||
|
||||
var progress = new Progress<double>((d) => { });
|
||||
var akiReleaseInfoFile =
|
||||
await DownloadCacheHelper.DownloadFileAsync("release.json", DownloadCacheHelper.ReleaseMirrorUrl,
|
||||
progress);
|
||||
if (akiReleaseInfoFile == null)
|
||||
|
||||
var SPTReleaseInfoFile =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("release.json", DownloadCacheHelper.ReleaseMirrorUrl,
|
||||
progress, DownloadCacheHelper.SuggestedTtl);
|
||||
|
||||
if (SPTReleaseInfoFile == null)
|
||||
{
|
||||
InstallButtonText = "Could not get SPT release metadata";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
var akiReleaseInfo =
|
||||
JsonConvert.DeserializeObject<ReleaseInfo>(File.ReadAllText(akiReleaseInfoFile.FullName));
|
||||
if (akiReleaseInfo == null)
|
||||
var SPTReleaseInfo =
|
||||
JsonConvert.DeserializeObject<ReleaseInfo>(File.ReadAllText(SPTReleaseInfoFile.FullName));
|
||||
|
||||
if (SPTReleaseInfo == null)
|
||||
{
|
||||
InstallButtonText = "Could not parse latest SPT release";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
InstallButtonText = $"Start Install: SPT v{akiReleaseInfo.AkiVersion}";
|
||||
InstallButtonText = $"Start Install: SPT v{SPTReleaseInfo.SPTVersion}";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.OK;
|
||||
|
||||
AllowDetailsButton = true;
|
||||
|
@ -48,6 +48,11 @@ public class ViewModelBase : ReactiveObject, IActivatableViewModel, IRoutableVie
|
||||
Dispatcher.UIThread.InvokeAsync(() => { HostScreen.Router.Navigate.Execute(ViewModel); });
|
||||
}
|
||||
|
||||
public void NavigateBack()
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() => { HostScreen.Router.NavigateBack.Execute(); });
|
||||
}
|
||||
|
||||
public ViewModelBase(IScreen Host)
|
||||
{
|
||||
HostScreen = Host;
|
||||
|
65
SPTInstaller/Views/InstallPathSelectionView.axaml
Normal file
65
SPTInstaller/Views/InstallPathSelectionView.axaml
Normal file
@ -0,0 +1,65 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.InstallPathSelectionView">
|
||||
|
||||
<Grid RowDefinitions="10,*,Auto,*,10" ColumnDefinitions="10,*,Auto,10">
|
||||
|
||||
<!-- Path Controls Grid -->
|
||||
<Grid Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="Install Folder Path" FontSize="20"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="0"
|
||||
TextChanged="TextBox_OnTextChanged"
|
||||
Watermark="Where we dropping?"
|
||||
FontSize="16"
|
||||
Text="{Binding SelectedPath}"
|
||||
Classes.hasErrors="{Binding !ValidPath}"
|
||||
>
|
||||
<TextBox.Styles>
|
||||
<Style Selector="TextBox.hasErrors">
|
||||
<Setter Property="Foreground" Value="Red"/>
|
||||
</Style>
|
||||
</TextBox.Styles>
|
||||
</TextBox>
|
||||
<Button Grid.Row="1" Grid.Column="1"
|
||||
CornerRadius="20"
|
||||
Margin="10 0 0 0"
|
||||
Command="{Binding SelectFolderCommand}"
|
||||
>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Data="{StaticResource OpenFolder}" Fill="{Binding $parent[Button].Foreground}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Label Content="Select Folder"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Validation error text -->
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding ErrorMessage}"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="16" Foreground="red" FontWeight="SemiBold"
|
||||
IsVisible="{Binding !ValidPath}"
|
||||
/>
|
||||
|
||||
<!-- Next button -->
|
||||
<Button Grid.Row="3" Grid.Column="2"
|
||||
MinWidth="100"
|
||||
MinHeight="30"
|
||||
FontSize="16"
|
||||
CornerRadius="20"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Classes="yellow"
|
||||
Content="Next"
|
||||
Command="{Binding NextCommand}"
|
||||
IsEnabled="{Binding ValidPath}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
18
SPTInstaller/Views/InstallPathSelectionView.axaml.cs
Normal file
18
SPTInstaller/Views/InstallPathSelectionView.axaml.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.ReactiveUI;
|
||||
using SPTInstaller.ViewModels;
|
||||
|
||||
namespace SPTInstaller.Views;
|
||||
|
||||
public partial class InstallPathSelectionView : ReactiveUserControl<InstallPathSelectionViewModel>
|
||||
{
|
||||
public InstallPathSelectionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void TextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
ViewModel?.ValidatePath();
|
||||
}
|
||||
}
|
@ -9,10 +9,10 @@
|
||||
<Grid ColumnDefinitions="*, 2*">
|
||||
<cc:ProgressableTaskList Tasks="{Binding MyTasks}"
|
||||
Padding="20"
|
||||
Background="{StaticResource AKI_Background_Dark}"
|
||||
Background="{StaticResource SPT_Background_Dark}"
|
||||
PendingColor="Gray"
|
||||
RunningColor="DodgerBlue"
|
||||
CompletedColor="{StaticResource AKI_Brush_Yellow}" />
|
||||
CompletedColor="{StaticResource SPT_Brush_Yellow}" />
|
||||
<cc:TaskDetails Grid.Column="1"
|
||||
Message="{Binding CurrentTask.StatusMessage}"
|
||||
Details="{Binding CurrentTask.StatusDetails}"
|
||||
|
31
SPTInstaller/Views/InstallerUpdateView.axaml
Normal file
31
SPTInstaller/Views/InstallerUpdateView.axaml
Normal file
@ -0,0 +1,31 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:cc="using:SPTInstaller.CustomControls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.InstallerUpdateView">
|
||||
<Grid RowDefinitions="10,Auto,*,10" ColumnDefinitions="10,*,10">
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" IsVisible="{Binding UpdateInfo.UpdateAvailable}">
|
||||
<Label Content="{Binding UpdateInfo.NewVersion, StringFormat='{}Installer Change Log for {0}'}" FontSize="18" FontWeight="SemiBold"
|
||||
/>
|
||||
<Separator Margin="0 10" Padding="0" Background="{StaticResource SPT_Yellow}"/>
|
||||
<ScrollViewer MaxHeight="250" Background="#323232">
|
||||
<TextBlock Text="{Binding UpdateInfo.ChangeLog}"
|
||||
TextWrapping="Wrap" MinHeight="100"
|
||||
Margin="10"
|
||||
/>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
<cc:UpdateButton Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
IsIndeterminate="{Binding UpdateInfo.CheckingForUpdates}"
|
||||
InfoText="{Binding UpdateInfo.UpdateInfoText}"
|
||||
Updating="{Binding UpdateInfo.Updating}"
|
||||
DismissCommand="{Binding NotNowCommand}"
|
||||
UpdateCommand="{Binding UpdateInstallCommand}"
|
||||
DownloadProgress="{Binding UpdateInfo.DownloadProgress}"
|
||||
UpdateAvailable="{Binding UpdateInfo.UpdateAvailable}"
|
||||
CheckingForUpdate="{Binding UpdateInfo.CheckingForUpdates}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
12
SPTInstaller/Views/InstallerUpdateView.axaml.cs
Normal file
12
SPTInstaller/Views/InstallerUpdateView.axaml.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using SPTInstaller.ViewModels;
|
||||
|
||||
namespace SPTInstaller.Views;
|
||||
|
||||
public partial class InstallerUpdateView : ReactiveUserControl<InstallerUpdateViewModel>
|
||||
{
|
||||
public InstallerUpdateView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
@ -8,14 +8,14 @@
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.MainWindow"
|
||||
Icon="/Assets/icon.ico"
|
||||
Icon="/Assets/spt_installer.ico"
|
||||
Title="SPT Installer"
|
||||
Height="450" Width="750"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaChromeHints="NoChrome"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
Background="{StaticResource AKI_Background_Light}"
|
||||
Background="{StaticResource SPT_Background_Light}"
|
||||
MinWidth="800" MinHeight="400">
|
||||
|
||||
<Window.Styles>
|
||||
@ -32,7 +32,7 @@
|
||||
XButtonCommand="{Binding CloseCommand}"
|
||||
MinButtonCommand="{Binding MinimizeCommand}" />
|
||||
|
||||
<dialogHost:DialogHost Grid.Row="1" Background="{StaticResource AKI_Background_Light}">
|
||||
<dialogHost:DialogHost Grid.Row="1" Background="{StaticResource SPT_Background_Light}">
|
||||
<rxui:RoutedViewHost Router="{Binding Router}" />
|
||||
</dialogHost:DialogHost>
|
||||
</Grid>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="*,AUTO,*" RowDefinitions="*,AUTO,20,AUTO,*"
|
||||
<Grid ColumnDefinitions="*,AUTO,*" RowDefinitions="*,AUTO,20,AUTO,20,Auto,Auto,*"
|
||||
Classes.error="{Binding HasErrors}">
|
||||
|
||||
<Label Grid.Column="1" Grid.Row="1"
|
||||
@ -35,8 +35,18 @@
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
Padding="20 10" />
|
||||
|
||||
<cc:CacheInfo Grid.Row="4" Grid.ColumnSpan="3" Padding="10" Margin="10 0 0 0"
|
||||
<StackPanel Grid.Row="5" Grid.Column="1" Orientation="Horizontal" Spacing="10">
|
||||
<CheckBox IsChecked="{Binding OpenInstallFolder}" Content="Open Install Folder" IsVisible="{Binding ShowOptions}"/>
|
||||
<CheckBox IsChecked="{Binding AddShortcuts}" Content="Add Desktop Shortcuts" IsVisible="{Binding ShowOptions}"/>
|
||||
</StackPanel>
|
||||
|
||||
<cc:CacheInfo Grid.Row="7" Grid.ColumnSpan="3" Padding="10" Margin="10 0 0 0"
|
||||
VerticalAlignment="Bottom"
|
||||
InfoText="{Binding CacheInfoText}" State="{Binding CacheCheckState}" />
|
||||
InfoText="{Binding CacheInfoText}" State="{Binding CacheCheckState}"
|
||||
/>
|
||||
|
||||
<Button Grid.Row="7" Grid.Column="2" Classes="link" Content="{Binding ClipCommandText}"
|
||||
Command="{Binding CopyLogFileToClipboard}" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
55
SPTInstaller/Views/OverviewView.axaml
Normal file
55
SPTInstaller/Views/OverviewView.axaml
Normal file
@ -0,0 +1,55 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.OverviewView">
|
||||
<Grid RowDefinitions="10,Auto,Auto,Auto,Auto,*,10" ColumnDefinitions="10,*,Auto,10">
|
||||
|
||||
<!-- Overview text -->
|
||||
<Label Grid.Row="1" Grid.Column="1" Content="This installer will:" FontSize="20" Margin="0 5"
|
||||
Foreground="{StaticResource SPT_Yellow}"
|
||||
/>
|
||||
|
||||
<!-- Overview info -->
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2">
|
||||
<Label>◉ Check dependencies are installed, such as .NET</Label>
|
||||
<Label>◉ Automatically locate and copy your EFT client files to the path you supply on the next page</Label>
|
||||
<Label>◉ Downgrade your client files to the version SPT uses, if needed</Label>
|
||||
<Label>◉ Download and extract the SPT release files</Label>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Notes text -->
|
||||
<Label Grid.Row="3" Grid.Column="1" Content="Additional Notes:" FontSize="20" Margin="0 5"
|
||||
Foreground="{StaticResource SPT_Yellow}"
|
||||
/>
|
||||
|
||||
<!-- Notes info -->
|
||||
<StackPanel Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2">
|
||||
<Label>◉ You do not need to install SPT in the same drive as EFT</Label>
|
||||
<Label Margin="0" Padding="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center">◉ This tool does</Label>
|
||||
<TextBlock TextDecorations="Underline" FontWeight="SemiBold" Foreground="Crimson" VerticalAlignment="Center">NOT</TextBlock>
|
||||
<Label VerticalAlignment="Center">update an existing SPT install</Label>
|
||||
</StackPanel>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Next button -->
|
||||
<Button Grid.Row="5" Grid.Column="2"
|
||||
MinWidth="100"
|
||||
MinHeight="30"
|
||||
FontSize="16"
|
||||
CornerRadius="20"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Classes="yellow"
|
||||
Content="Next"
|
||||
Command="{Binding NextCommand}"
|
||||
IsEnabled="{Binding ValidPath}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
12
SPTInstaller/Views/OverviewView.axaml.cs
Normal file
12
SPTInstaller/Views/OverviewView.axaml.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using SPTInstaller.ViewModels;
|
||||
|
||||
namespace SPTInstaller.Views;
|
||||
|
||||
public partial class OverviewView : ReactiveUserControl<OverviewViewModel>
|
||||
{
|
||||
public OverviewView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
@ -93,19 +93,5 @@
|
||||
IsVisible="{Binding !AllowInstall}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Update installer button -->
|
||||
<cc:UpdateButton Grid.Column="2" Grid.Row="3"
|
||||
IsVisible="{Binding UpdateInfo.Show}"
|
||||
IsEnabled="{Binding UpdateInfo.Show}"
|
||||
IsIndeterminate="{Binding UpdateInfo.CheckingForUpdates}"
|
||||
InfoText="{Binding UpdateInfo.UpdateInfoText}"
|
||||
Updating="{Binding UpdateInfo.Updating}"
|
||||
DismissCommand="{Binding DismissUpdateCommand}"
|
||||
UpdateCommand="{Binding UpdateInstallerCommand}"
|
||||
WhatsNewCommand="{Binding WhatsNewCommand}"
|
||||
DownloadProgress="{Binding UpdateInfo.DownloadProgress}"
|
||||
UpdateAvailable="{Binding UpdateInfo.UpdateAvailable}"
|
||||
CheckingForUpdate="{Binding UpdateInfo.CheckingForUpdates}" />
|
||||
</Grid>
|
||||
</UserControl>
|
Loading…
x
Reference in New Issue
Block a user