diff --git a/.idea/.idea.SPTInstaller/.idea/avalonia.xml b/.idea/.idea.SPTInstaller/.idea/avalonia.xml
index c49db03..64fdacd 100644
--- a/.idea/.idea.SPTInstaller/.idea/avalonia.xml
+++ b/.idea/.idea.SPTInstaller/.idea/avalonia.xml
@@ -19,6 +19,9 @@
+
+
+
diff --git a/SPTInstaller/App.axaml b/SPTInstaller/App.axaml
index 3331a7d..795db43 100644
--- a/SPTInstaller/App.axaml
+++ b/SPTInstaller/App.axaml
@@ -48,5 +48,8 @@
+
+
\ No newline at end of file
diff --git a/SPTInstaller/CustomControls/Dialogs/ChangeLogDialog.axaml b/SPTInstaller/CustomControls/Dialogs/ChangeLogDialog.axaml
deleted file mode 100644
index 99cbbbd..0000000
--- a/SPTInstaller/CustomControls/Dialogs/ChangeLogDialog.axaml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/SPTInstaller/CustomControls/Dialogs/ChangeLogDialog.axaml.cs b/SPTInstaller/CustomControls/Dialogs/ChangeLogDialog.axaml.cs
deleted file mode 100644
index b3552ed..0000000
--- a/SPTInstaller/CustomControls/Dialogs/ChangeLogDialog.axaml.cs
+++ /dev/null
@@ -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 MessageProperty =
- // AvaloniaProperty.Register("Message");
- //
- // public string Message
- // {
- // get => GetValue(MessageProperty);
- // set => SetValue(MessageProperty, value);
- // }
-}
\ No newline at end of file
diff --git a/SPTInstaller/CustomControls/UpdateButton.axaml b/SPTInstaller/CustomControls/UpdateButton.axaml
index 6c63fe7..d5134e6 100644
--- a/SPTInstaller/CustomControls/UpdateButton.axaml
+++ b/SPTInstaller/CustomControls/UpdateButton.axaml
@@ -26,8 +26,6 @@
-
diff --git a/SPTInstaller/CustomControls/UpdateButton.axaml.cs b/SPTInstaller/CustomControls/UpdateButton.axaml.cs
index 155311d..4ea2783 100644
--- a/SPTInstaller/CustomControls/UpdateButton.axaml.cs
+++ b/SPTInstaller/CustomControls/UpdateButton.axaml.cs
@@ -50,15 +50,6 @@ public partial class UpdateButton : UserControl
set => SetValue(UpdateCommandProperty, value);
}
- public static readonly StyledProperty WhatsNewCommandProperty =
- AvaloniaProperty.Register("WhatsNewCommand");
-
- public ICommand WhatsNewCommand
- {
- get => GetValue(WhatsNewCommandProperty);
- set => SetValue(WhatsNewCommandProperty, value);
- }
-
public static readonly StyledProperty UpdatingProperty = AvaloniaProperty.Register(
"Updating");
diff --git a/SPTInstaller/Helpers/FileHelper.cs b/SPTInstaller/Helpers/FileHelper.cs
index 61681c9..da05f99 100644
--- a/SPTInstaller/Helpers/FileHelper.cs
+++ b/SPTInstaller/Helpers/FileHelper.cs
@@ -134,12 +134,21 @@ public static class FileHelper
}
}
+ ///
+ /// Check if a path is problematic
+ ///
+ /// The path the check
+ /// The check that failed
+ /// Returns true if the path is bad, otherwise false
public static bool CheckPathForProblemLocations(string path, out PathCheck failedCheck)
{
+ path = Path.TrimEndingDirectorySeparator(path);
+
failedCheck = new();
var problemPaths = new List()
{
+ 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),
diff --git a/SPTInstaller/Models/InstallerUpdateInfo.cs b/SPTInstaller/Models/InstallerUpdateInfo.cs
index cd58bc8..813f7e7 100644
--- a/SPTInstaller/Models/InstallerUpdateInfo.cs
+++ b/SPTInstaller/Models/InstallerUpdateInfo.cs
@@ -11,7 +11,7 @@ public class InstallerUpdateInfo : ReactiveObject
{
public Version? NewVersion { get; private set; }
- public string ChangeLog = "";
+ public string ChangeLog { get; private set; }= "";
private string _updateInfoText = "";
@@ -21,14 +21,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 +115,6 @@ public class InstallerUpdateInfo : ReactiveObject
}
UpdateInfoText = infoText;
- Show = updateAvailable;
CheckingForUpdates = false;
UpdateAvailable = updateAvailable;
}
@@ -134,7 +125,6 @@ public class InstallerUpdateInfo : ReactiveObject
return;
UpdateInfoText = "Checking for installer updates";
- Show = true;
CheckingForUpdates = true;
try
diff --git a/SPTInstaller/SPTInstaller.csproj b/SPTInstaller/SPTInstaller.csproj
index 960f8e6..ba8701d 100644
--- a/SPTInstaller/SPTInstaller.csproj
+++ b/SPTInstaller/SPTInstaller.csproj
@@ -10,8 +10,8 @@
icon.ico
Assets\spt_installer.ico
Debug;Release;TEST
- 2.71
- 2.71
+ 2.80
+ 2.80
SPT
diff --git a/SPTInstaller/ViewModels/InstallPathSelectionViewModel.cs b/SPTInstaller/ViewModels/InstallPathSelectionViewModel.cs
new file mode 100644
index 0000000..b2c9547
--- /dev/null
+++ b/SPTInstaller/ViewModels/InstallPathSelectionViewModel.cs
@@ -0,0 +1,145 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform.Storage;
+using ReactiveUI;
+using SPTInstaller.Helpers;
+using SPTInstaller.Models;
+
+namespace SPTInstaller.ViewModels;
+
+public class InstallPathSelectionViewModel : ViewModelBase
+{
+ private bool _debugging = false;
+ 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, bool debugging) : base(host)
+ {
+ _debugging = debugging;
+ _data = ServiceHelper.Get() ?? throw new Exception("Failed to get internal data");
+ SelectedPath = Environment.CurrentDirectory;
+ ValidPath = false;
+
+ 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.AbsolutePath.Replace("/", "\\");
+ }
+ }
+
+ 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 async Task NextCommand()
+ {
+ if (FileHelper.CheckPathForProblemLocations(SelectedPath, out var failedCheck) && failedCheck.CheckAction == PathCheckAction.Deny)
+ {
+ return;
+ }
+
+ _data.TargetInstallPath = SelectedPath;
+
+ NavigateTo(new PreChecksViewModel(HostScreen, _debugging));
+ }
+}
\ No newline at end of file
diff --git a/SPTInstaller/ViewModels/InstallerUpdateViewModel.cs b/SPTInstaller/ViewModels/InstallerUpdateViewModel.cs
new file mode 100644
index 0000000..24fd3ee
--- /dev/null
+++ b/SPTInstaller/ViewModels/InstallerUpdateViewModel.cs
@@ -0,0 +1,40 @@
+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 bool _debugging;
+ public InstallerUpdateViewModel(IScreen Host, bool debugging) : base(Host)
+ {
+ _debugging = debugging;
+ _data = ServiceHelper.Get() ?? throw new Exception("Failed to get internal data");
+
+ Task.Run(async () =>
+ {
+ await UpdateInfo.CheckForUpdates(Assembly.GetExecutingAssembly().GetName().Version);
+ if (!UpdateInfo.UpdateAvailable)
+ {
+ NavigateTo(new InstallPathSelectionViewModel(HostScreen, _debugging));
+ }
+ });
+ }
+
+ public void NotNowCommand()
+ {
+ NavigateTo(new InstallPathSelectionViewModel(HostScreen, _debugging));
+ }
+
+ public async Task UpdateInstallCommand()
+ {
+ await UpdateInfo.UpdateInstaller();
+ }
+}
\ No newline at end of file
diff --git a/SPTInstaller/ViewModels/MainWindowViewModel.cs b/SPTInstaller/ViewModels/MainWindowViewModel.cs
index 908320d..40fc793 100644
--- a/SPTInstaller/ViewModels/MainWindowViewModel.cs
+++ b/SPTInstaller/ViewModels/MainWindowViewModel.cs
@@ -31,7 +31,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, debugging));
}
public void CloseCommand()
diff --git a/SPTInstaller/ViewModels/PreChecksViewModel.cs b/SPTInstaller/ViewModels/PreChecksViewModel.cs
index 41ba8f2..e535e15 100644
--- a/SPTInstaller/ViewModels/PreChecksViewModel.cs
+++ b/SPTInstaller/ViewModels/PreChecksViewModel.cs
@@ -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
@@ -138,7 +129,6 @@ public class PreChecksViewModel : ViewModelBase
data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
- data.TargetInstallPath = Environment.CurrentDirectory;
InstallPath = data.TargetInstallPath;
Log.Information($"Install Path: {FileHelper.GetRedactedPath(InstallPath)}");
@@ -196,8 +186,8 @@ public class PreChecksViewModel : ViewModelBase
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();
}
});
@@ -224,7 +214,8 @@ public class PreChecksViewModel : ViewModelBase
{
try
{
- var installerPath = Path.Join(_installPath, "SPTInstaller.exe");
+ var installerPath = Path.Join(Environment.CurrentDirectory, "SPTInstaller.exe");
+
Process.Start(new ProcessStartInfo()
{
FileName = installerPath,
@@ -258,31 +249,14 @@ 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;
@@ -302,6 +276,7 @@ public class PreChecksViewModel : ViewModelBase
var SPTReleaseInfo =
JsonConvert.DeserializeObject(File.ReadAllText(SPTReleaseInfoFile.FullName));
+
if (SPTReleaseInfo == null)
{
InstallButtonText = "Could not parse latest SPT release";
diff --git a/SPTInstaller/ViewModels/ViewModelBase.cs b/SPTInstaller/ViewModels/ViewModelBase.cs
index e8873c9..f417e86 100644
--- a/SPTInstaller/ViewModels/ViewModelBase.cs
+++ b/SPTInstaller/ViewModels/ViewModelBase.cs
@@ -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;
diff --git a/SPTInstaller/Views/InstallPathSelectionView.axaml b/SPTInstaller/Views/InstallPathSelectionView.axaml
new file mode 100644
index 0000000..ea4a7c8
--- /dev/null
+++ b/SPTInstaller/Views/InstallPathSelectionView.axaml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SPTInstaller/Views/InstallPathSelectionView.axaml.cs b/SPTInstaller/Views/InstallPathSelectionView.axaml.cs
new file mode 100644
index 0000000..3364a07
--- /dev/null
+++ b/SPTInstaller/Views/InstallPathSelectionView.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.ReactiveUI;
+using SPTInstaller.ViewModels;
+
+namespace SPTInstaller.Views;
+
+public partial class InstallPathSelectionView : ReactiveUserControl
+{
+ public InstallPathSelectionView()
+ {
+ InitializeComponent();
+ }
+
+ private void TextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
+ {
+ ViewModel?.ValidatePath();
+ }
+}
\ No newline at end of file
diff --git a/SPTInstaller/Views/InstallerUpdateView.axaml b/SPTInstaller/Views/InstallerUpdateView.axaml
new file mode 100644
index 0000000..13493bb
--- /dev/null
+++ b/SPTInstaller/Views/InstallerUpdateView.axaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SPTInstaller/Views/InstallerUpdateView.axaml.cs b/SPTInstaller/Views/InstallerUpdateView.axaml.cs
new file mode 100644
index 0000000..3de3504
--- /dev/null
+++ b/SPTInstaller/Views/InstallerUpdateView.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.ReactiveUI;
+using SPTInstaller.ViewModels;
+
+namespace SPTInstaller.Views;
+
+public partial class InstallerUpdateView : ReactiveUserControl
+{
+ public InstallerUpdateView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/SPTInstaller/Views/PreChecksView.axaml b/SPTInstaller/Views/PreChecksView.axaml
index d7dd642..d48242a 100644
--- a/SPTInstaller/Views/PreChecksView.axaml
+++ b/SPTInstaller/Views/PreChecksView.axaml
@@ -93,19 +93,5 @@
IsVisible="{Binding !AllowInstall}" />
-
-
-
\ No newline at end of file