From 568e198d653631e7a26e7a0ce23fba386b3795ec Mon Sep 17 00:00:00 2001 From: Pear-231 <61670316+Pear-231@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:28:42 +0000 Subject: [PATCH 1/2] Added AssetEditorUpdater --- AssetEditor.sln | 6 + AssetEditorUpdater/AssetEditorUpdater.cs | 236 +++++++++++++++++++ AssetEditorUpdater/AssetEditorUpdater.csproj | 16 ++ 3 files changed, 258 insertions(+) create mode 100644 AssetEditorUpdater/AssetEditorUpdater.cs create mode 100644 AssetEditorUpdater/AssetEditorUpdater.csproj diff --git a/AssetEditor.sln b/AssetEditor.sln index b59bb2955..d3895a800 100644 --- a/AssetEditor.sln +++ b/AssetEditor.sln @@ -98,6 +98,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Editors.AnimationMeta", "Ed EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.AnimationMeta", "Editors\AnimationMeta\Test.AnimationMeta\Test.AnimationMeta.csproj", "{E759BE6D-E0A4-46B8-A02A-E8573F579E2F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetEditorUpdater", "AssetEditorUpdater\AssetEditorUpdater.csproj", "{24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -228,6 +230,10 @@ Global {E759BE6D-E0A4-46B8-A02A-E8573F579E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {E759BE6D-E0A4-46B8-A02A-E8573F579E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {E759BE6D-E0A4-46B8-A02A-E8573F579E2F}.Release|Any CPU.Build.0 = Release|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24483A6F-DBD6-4415-B4D8-1A2FB86A2A12}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AssetEditorUpdater/AssetEditorUpdater.cs b/AssetEditorUpdater/AssetEditorUpdater.cs new file mode 100644 index 000000000..8ca9d3a66 --- /dev/null +++ b/AssetEditorUpdater/AssetEditorUpdater.cs @@ -0,0 +1,236 @@ +using System.Diagnostics; +using Octokit; +using SharpCompress.Archives; +using FileMode = System.IO.FileMode; + +namespace AssetEditorUpdater +{ + public class AssetEditorUpdater + { + private const string GitHubOwner = "donkeyProgramming"; + private const string GitHubRepository = "TheAssetEditor"; + private const string AssetEditorExe = "AssetEditor.exe"; + private const string AssetEditorUpdaterExe = "AssetEditorUpdater.exe"; + // We assume the release RAR contains a single folder named AssetEditor with all the files for the update in it + private const string UpdateFilesDirectoryName = "AssetEditor"; + private const string InstallationUpdateBackupDirectoryName = "UpdateBackup"; + + public static async Task Main(string[] args) + { + // The first time the updater is run it should be from the installation directory (with no args) + // so we use the app's base directory to get the installation directory. When we rerun the updater + // from the update directory we pass the installation directory as an arg and access it that way. + + var currentDirectory = AppContext.BaseDirectory; + var userDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var updateDirectory = Path.Combine(userDirectory, "AssetEditor", "Temp", "Update"); + + var isInitialLaunch = args.Length == 0; + var installationDirectory = isInitialLaunch ? currentDirectory : args[0]; + + Console.WriteLine($"Running updater from {currentDirectory}."); + + if (isInitialLaunch) + RelaunchFromUpdateDirectory(updateDirectory, installationDirectory); + else + await UpdateAsync(installationDirectory, updateDirectory); + } + + private static void RelaunchFromUpdateDirectory(string updateDirectory, string installationDirectory) + { + Console.WriteLine($"Copying updater to {updateDirectory} and relaunching..."); + + if (!Directory.Exists(updateDirectory)) + Directory.CreateDirectory(updateDirectory); + + var currentUpdaterPath = Path.Combine(installationDirectory, AssetEditorUpdaterExe); + var newUpdaterPath = Path.Combine(updateDirectory, AssetEditorUpdaterExe); + File.Copy(currentUpdaterPath, newUpdaterPath, true); + + LaunchUpdater(newUpdaterPath, updateDirectory, installationDirectory); + } + + private static void LaunchUpdater(string updaterPath, string workingDirectory, string installationDirectory) + { + var processStartInfo = new ProcessStartInfo + { + FileName = updaterPath, + WorkingDirectory = workingDirectory, + UseShellExecute = false, + ArgumentList = { installationDirectory } + }; + Process.Start(processStartInfo); + } + + private static async Task UpdateAsync(string installationDirectory, string updateDirectory) + { + var latestRelease = await GetLatestReleaseAsync(); + if (latestRelease == null) + return; + + var installedVersion = GetAssetEditorVersion(installationDirectory); + var latestVersion = ParseReleaseVersion(latestRelease.TagName); + if (installedVersion >= latestVersion) + { + Console.WriteLine("No update required."); + return; + } + + Console.WriteLine($"Updating AssetEditor from version {installedVersion} to {latestVersion}."); + + var asset = GetAsset(latestRelease); + var assetPath = Path.Combine(updateDirectory, asset.Name); + var downloadResult = await DownloadAssetAsync(asset.BrowserDownloadUrl, assetPath); + if (downloadResult == false) + return; + + BackupOldFiles(installationDirectory); + + ExtractRar(assetPath, installationDirectory); + + var assetEditorPath = Path.Combine(installationDirectory, AssetEditorExe); + if (File.Exists(assetEditorPath)) + LaunchAssetEditor(installationDirectory, assetEditorPath); + else + Console.WriteLine("Uh oh something with the update went wrong."); + + Console.WriteLine("Press any key to close."); + Console.ReadKey(); + } + + private static async Task GetLatestReleaseAsync() + { + try + { + var gitHubClient = new GitHubClient(new ProductHeaderValue("AssetEditor")); + var releases = await gitHubClient.Repository.Release.GetAll(GitHubOwner, GitHubRepository); + return releases.Count > 0 ? releases[0] : null; + } + catch (ApiException exception) + { + Console.WriteLine($"Unable to retrieve latest release from GitHub: {exception.Message}"); + return null; + } + } + + private static Version GetAssetEditorVersion(string installationDirectory) + { + var assetEditorPath = Path.Combine(installationDirectory, AssetEditorExe); + var versionInfo = FileVersionInfo.GetVersionInfo(assetEditorPath); + if (string.IsNullOrWhiteSpace(versionInfo.FileVersion)) + return new Version(); + + var parsedVersion = new Version(versionInfo.FileVersion); + if (parsedVersion.Build == 0 && parsedVersion.Revision == 0) + return new Version(parsedVersion.Major, parsedVersion.Minor); + + if (parsedVersion.Revision == 0) + return new Version(parsedVersion.Major, parsedVersion.Minor, parsedVersion.Build); + + return parsedVersion; + } + + private static Version ParseReleaseVersion(string tagName) + { + var cleanedVersion = tagName.Trim().TrimStart('v', 'V'); + return new Version(cleanedVersion); + } + + private static ReleaseAsset GetAsset(Release latestRelease) + { + if (latestRelease.Assets.Count != 1) + throw new InvalidOperationException($"Expected 1 asset, found {latestRelease.Assets.Count}."); + + var asset = latestRelease.Assets[0]; + var extension = Path.GetExtension(asset.Name); + if (extension != ".rar") + throw new InvalidOperationException($"Asset has extension {extension}, expected .rar."); + + return asset; + } + + private static async Task DownloadAssetAsync(string downloadUrl, string downloadPath) + { + Console.WriteLine("Downloading the latest release..."); + + try + { + using var client = new HttpClient(); + client.DefaultRequestHeaders.UserAgent.ParseAdd("AssetEditor_instance"); + + using var response = await client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + await using var fileStream = new FileStream(downloadPath, FileMode.Create, FileAccess.Write, FileShare.None); + await responseStream.CopyToAsync(fileStream); + + return true; + } + catch + { + Console.WriteLine("Unable to download latest release from Github."); + return false; + } + } + + private static void BackupOldFiles(string installationDirectory) + { + var updateBackupDirectory = Path.Combine(installationDirectory, InstallationUpdateBackupDirectoryName); + if (Directory.Exists(updateBackupDirectory)) + Directory.Delete(updateBackupDirectory, true); + Directory.CreateDirectory(updateBackupDirectory); + + Console.WriteLine($"Backing up files from {installationDirectory} to {updateBackupDirectory}..."); + + foreach (var entryPath in Directory.EnumerateFileSystemEntries(installationDirectory)) + { + var entryName = Path.GetFileName(entryPath); + if (entryName == InstallationUpdateBackupDirectoryName) + continue; + + var destinationPath = Path.Combine(updateBackupDirectory, entryName); + if (Directory.Exists(entryPath)) + Directory.Move(entryPath, destinationPath); + else + File.Move(entryPath, destinationPath); + } + } + + private static void ExtractRar(string rarPath, string installationDirectory) + { + Console.WriteLine($"Extracting update files from {rarPath} to {installationDirectory}..."); + + var prefix = UpdateFilesDirectoryName + Path.DirectorySeparatorChar; + + using var archive = ArchiveFactory.OpenArchive(rarPath); + foreach (var entry in archive.Entries) + { + if (entry.Key == null || entry.Key == UpdateFilesDirectoryName || entry.IsDirectory) + continue; + + var entryKeyWithoutPrefix = entry.Key[prefix.Length..]; + var destinationPath = Path.Combine(installationDirectory, entryKeyWithoutPrefix); + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!); + entry.WriteToFile(destinationPath); + } + } + + private static void LaunchAssetEditor(string installationDirectory, string assetEditorPath) + { + Console.WriteLine("Update complete."); + Console.WriteLine("Relaunching AssetEditor..."); + + // We launch using explorer.exe as that ensures admin priveliges aren't used as they + // would otherwise automatically be used if the AssetEditorUpdater.exe required them. + var processStartInfo = new ProcessStartInfo + { + FileName = "explorer.exe", + WorkingDirectory = installationDirectory, + UseShellExecute = true, + Arguments = $"\"{assetEditorPath}\"".Trim() + }; + Process.Start(processStartInfo); + } + } +} diff --git a/AssetEditorUpdater/AssetEditorUpdater.csproj b/AssetEditorUpdater/AssetEditorUpdater.csproj new file mode 100644 index 000000000..2ec76ef0e --- /dev/null +++ b/AssetEditorUpdater/AssetEditorUpdater.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0-windows + enable + enable + + + + + + + + + From 128bc65f5c045589e023b4449c25c701429142bb Mon Sep 17 00:00:00 2001 From: Pear-231 <61670316+Pear-231@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:33:54 +0000 Subject: [PATCH 2/2] Added Updater UI --- AssetEditor/App.xaml.cs | 66 +++++---- AssetEditor/AssetEditor.csproj | 14 ++ AssetEditor/DependencyInjectionContainer.cs | 4 + .../UiCommands/OpenUpdaterWindowCommand.cs | 26 ++++ AssetEditor/ViewModels/MainViewModel.cs | 2 +- AssetEditor/ViewModels/UpdaterViewModel.cs | 125 ++++++++++++++++++ AssetEditor/Views/Updater/UpdaterWindow.xaml | 121 +++++++++++++++++ .../Views/Updater/UpdaterWindow.xaml.cs | 60 +++++++++ .../BasicExceptionInformationProvider.cs | 2 +- Shared/SharedCore/Misc/DirectoryHelper.cs | 1 + Shared/SharedCore/Services/VersionChecker.cs | 125 +++++++----------- 11 files changed, 445 insertions(+), 101 deletions(-) create mode 100644 AssetEditor/UiCommands/OpenUpdaterWindowCommand.cs create mode 100644 AssetEditor/ViewModels/UpdaterViewModel.cs create mode 100644 AssetEditor/Views/Updater/UpdaterWindow.xaml create mode 100644 AssetEditor/Views/Updater/UpdaterWindow.xaml.cs diff --git a/AssetEditor/App.xaml.cs b/AssetEditor/App.xaml.cs index fb81397e0..1c05f5b9d 100644 --- a/AssetEditor/App.xaml.cs +++ b/AssetEditor/App.xaml.cs @@ -1,15 +1,17 @@ using System; using System.Diagnostics; +using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using AssetEditor.Services; +using AssetEditor.UiCommands; using AssetEditor.ViewModels; using AssetEditor.Views; -using AssetEditor.Views.Settings; using Microsoft.Extensions.DependencyInjection; using Shared.Core.DependencyInjection; using Shared.Core.DevConfig; using Shared.Core.ErrorHandling; +using Shared.Core.Events; using Shared.Core.PackFiles; using Shared.Core.PackFiles.Utility; using Shared.Core.Services; @@ -29,7 +31,6 @@ protected override void OnStartup(StartupEventArgs e) PackFileLog.IsLoggingEnabled = false; ShutdownMode = ShutdownMode.OnExplicitShutdown; - VersionChecker.CheckVersion(); Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(DispatcherUnhandledExceptionHandler); var forceValidateServiceScopes = Debugger.IsAttached; @@ -38,6 +39,8 @@ protected override void OnStartup(StartupEventArgs e) _ = _serviceProvider.GetRequiredService(); // Force instance of the RecentFilesTracker _ = _serviceProvider.GetRequiredService(); // Force instance of the IScopeRepository + var uiCommandFactory = _serviceProvider.GetRequiredService(); + var settingsService = _serviceProvider.GetRequiredService(); settingsService.AllowSettingsUpdate = true; settingsService.Load(); @@ -48,14 +51,7 @@ protected override void OnStartup(StartupEventArgs e) // Show the settings window if its the first time the tool is ran if (settingsService.CurrentSettings.IsFirstTimeStartingApplication) - { - var settingsWindow = _serviceProvider.GetRequiredService(); - settingsWindow.DataContext = _serviceProvider.GetRequiredService(); - settingsWindow.ShowDialog(); - - settingsService.CurrentSettings.IsFirstTimeStartingApplication = false; - settingsService.Save(); - } + HandleFirstTimeSettings(uiCommandFactory, settingsService); var devConfigManager = _serviceProvider.GetRequiredService(); devConfigManager.Initialize(e); @@ -63,25 +59,38 @@ protected override void OnStartup(StartupEventArgs e) // Load all packfiles if (settingsService.CurrentSettings.LoadCaPacksByDefault) - { - var gamePath = settingsService.GetGamePathForCurrentGame(); - if (gamePath != null) - { - var packfileService = _serviceProvider.GetRequiredService(); - var containerLoader = _serviceProvider.GetRequiredService(); - var loadRes = containerLoader.LoadAllCaFiles(settingsService.CurrentSettings.CurrentGame); - - if (loadRes == null) - MessageBox.Show($"Unable to load all CA packfiles in {gamePath}"); - else - packfileService.AddContainer(loadRes); - } - } + LoadCAPackFiles(settingsService); devConfigManager.CreateTestPackFiles(); devConfigManager.OpenFileOnLoad(); ShowMainWindow(); + + _ = CheckVersion(uiCommandFactory); + } + + private static void HandleFirstTimeSettings(IUiCommandFactory uiCommandFactory, ApplicationSettingsService settingsService) + { + uiCommandFactory.Create().Execute(); + + settingsService.CurrentSettings.IsFirstTimeStartingApplication = false; + settingsService.Save(); + } + + private void LoadCAPackFiles(ApplicationSettingsService settingsService) + { + var gamePath = settingsService.GetGamePathForCurrentGame(); + if (gamePath != null) + { + var packfileService = _serviceProvider.GetRequiredService(); + var containerLoader = _serviceProvider.GetRequiredService(); + var loadRes = containerLoader.LoadAllCaFiles(settingsService.CurrentSettings.CurrentGame); + + if (loadRes == null) + MessageBox.Show($"Unable to load all CA packfiles in {gamePath}"); + else + packfileService.AddContainer(loadRes); + } } void ShowMainWindow() @@ -94,7 +103,7 @@ void ShowMainWindow() mainWindow.Closed += OnMainWindowClosed; mainWindow.Show(); - // Ensure the window doesn't cover up the windows bar. + // Ensure the window doesn't cover up the windows bar mainWindow.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight; mainWindow.MaxWidth = SystemParameters.MaximizedPrimaryScreenWidth; @@ -109,6 +118,13 @@ private void OnMainWindowClosed(object sender, EventArgs e) Shutdown(); } + private static async Task CheckVersion(IUiCommandFactory uiCommandFactory) + { + var newerReleases = await VersionChecker.GetNewerReleases(); + if (newerReleases != null) + uiCommandFactory.Create().Execute(newerReleases); + } + void DispatcherUnhandledExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs args) { Logging.Create().Here().Fatal(args.Exception.ToString()); diff --git a/AssetEditor/AssetEditor.csproj b/AssetEditor/AssetEditor.csproj index 551719983..7c9a7c601 100644 --- a/AssetEditor/AssetEditor.csproj +++ b/AssetEditor/AssetEditor.csproj @@ -46,6 +46,7 @@ https://github.com/donkeyProgramming/TheAssetEditor https://github.com/donkeyProgramming/TheAssetEditor AssetEditor + 0.68.0 6.0 @@ -53,6 +54,10 @@ + + + + @@ -77,4 +82,13 @@ False $(NoWarn),1573,1591,1712 + + + + $(MSBuildProjectDirectory)\..\AssetEditorUpdater\AssetEditorUpdater.csproj + $([System.IO.Path]::GetFullPath('$(PublishDir)')) + + + + \ No newline at end of file diff --git a/AssetEditor/DependencyInjectionContainer.cs b/AssetEditor/DependencyInjectionContainer.cs index 21fa86b00..9633f055f 100644 --- a/AssetEditor/DependencyInjectionContainer.cs +++ b/AssetEditor/DependencyInjectionContainer.cs @@ -3,6 +3,7 @@ using AssetEditor.ViewModels; using AssetEditor.Views; using AssetEditor.Views.Settings; +using AssetEditor.Views.Updater; using Microsoft.Extensions.DependencyInjection; using Shared.Core.DependencyInjection; using Shared.Core.DevConfig; @@ -24,6 +25,7 @@ public override void Register(IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); @@ -31,6 +33,8 @@ public override void Register(IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); diff --git a/AssetEditor/UiCommands/OpenUpdaterWindowCommand.cs b/AssetEditor/UiCommands/OpenUpdaterWindowCommand.cs new file mode 100644 index 000000000..c2047ae4e --- /dev/null +++ b/AssetEditor/UiCommands/OpenUpdaterWindowCommand.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using AssetEditor.ViewModels; +using AssetEditor.Views.Updater; +using Microsoft.Extensions.DependencyInjection; +using Octokit; +using Shared.Core.Events; + +namespace AssetEditor.UiCommands +{ + public class OpenUpdaterWindowCommand(IServiceProvider serviceProvider) : IUiCommand + { + private readonly IServiceProvider _serviceProvider = serviceProvider; + + public void Execute(List newerReleases) + { + var window = _serviceProvider.GetRequiredService(); + var viewModel = _serviceProvider.GetRequiredService(); + viewModel.SetReleaseInfo(newerReleases); + viewModel.SetCloseAction(window.Close); + window.DataContext = viewModel; + window.ShowDialog(); + } + } +} + diff --git a/AssetEditor/ViewModels/MainViewModel.cs b/AssetEditor/ViewModels/MainViewModel.cs index e225e1429..f9ee1739c 100644 --- a/AssetEditor/ViewModels/MainViewModel.cs +++ b/AssetEditor/ViewModels/MainViewModel.cs @@ -55,7 +55,7 @@ public MainViewModel( ToolsFactory = toolFactory; - ApplicationTitle = $"AssetEditor v{VersionChecker.CurrentVersion}"; + ApplicationTitle = $"AssetEditor v{VersionChecker.GetCurrentVersion()}"; CurrentGame = $"Current Game: {GameInformationDatabase.GetGameById(applicationSettingsService.CurrentSettings.CurrentGame).DisplayName}"; } diff --git a/AssetEditor/ViewModels/UpdaterViewModel.cs b/AssetEditor/ViewModels/UpdaterViewModel.cs new file mode 100644 index 000000000..6d5a0aad9 --- /dev/null +++ b/AssetEditor/ViewModels/UpdaterViewModel.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Octokit; +using Serilog; +using Shared.Core.ErrorHandling; +using Shared.Core.Misc; +using Shared.Core.Services; +using Application = System.Windows.Application; + +namespace AssetEditor.ViewModels +{ + public class ReleaseNoteItem(Release release) + { + public string ReleaseName { get; } = release.Name; + public string PublishedAt { get; } = $"Published {release.PublishedAt.Value:dd MMM yyyy}"; + public string ReleaseNotes { get; } = release.Body; + } + + partial class UpdaterViewModel : ObservableObject + { + private readonly ILogger _logger = Logging.Create(); + private Action _closeAction; + + private const string AssetEditorUpdaterExe = "AssetEditorUpdater.exe"; + + private List _newerReleases = []; + + [ObservableProperty] private ObservableCollection _releaseNotesItems = []; + + [ObservableProperty] private string _latestVersionInfo; + + public void SetReleaseInfo(List newerReleases) + { + _newerReleases = newerReleases; + + var currentVersion = VersionChecker.GetCurrentVersion(); + var latestRelease = _newerReleases[0]; + var latestVersion = VersionChecker.ParseReleaseVersion(latestRelease.TagName); + + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine($"A new version of AssetEditor is available! The AssetEditor donkeys have been busy..."); + stringBuilder.AppendLine(); + stringBuilder.AppendLine($"Your current version is {currentVersion}. The latest verison is {latestVersion}."); + stringBuilder.AppendLine(); + stringBuilder.AppendLine("Update to get the changes detailed in the release notes below."); + LatestVersionInfo = stringBuilder.ToString(); + + ReleaseNotesItems.Clear(); + foreach (var release in _newerReleases) + ReleaseNotesItems.Add(new ReleaseNoteItem(release)); + } + + [RelayCommand] public void Update() + { + if (Debugger.IsAttached) + return; + + var currentVersion = VersionChecker.GetCurrentVersion(); + var latestRelease = _newerReleases[0]; + var latestVersion = VersionChecker.ParseReleaseVersion(latestRelease.TagName); + _logger.Information($"Updating AssetEditor from version {currentVersion} to version {latestVersion}"); + + DeleteUpdateDirectory(); + + LaunchUpdater(); + + Application.Current.Shutdown(); + } + + public static void DeleteUpdateDirectory() + { + var updateDirectory = DirectoryHelper.UpdateDirectory; + if (Directory.Exists(updateDirectory)) + Directory.Delete(updateDirectory, true); + } + + public static void LaunchUpdater() + { + var currentDirectory = AppContext.BaseDirectory; + var updaterPath = Path.Combine(currentDirectory, AssetEditorUpdaterExe); + + var processStartInfo = new ProcessStartInfo + { + FileName = updaterPath, + WorkingDirectory = currentDirectory, + UseShellExecute = true, + }; + + if (UpdaterRequiresAdministratorPrivileges()) + processStartInfo.Verb = "runas"; + + Process.Start(processStartInfo); + } + + public static bool UpdaterRequiresAdministratorPrivileges() + { + try + { + var currentDirectory = AppContext.BaseDirectory; + var testFilePath = Path.Combine(currentDirectory, $"updater_admin_privileges_test{Guid.NewGuid():N}.tmp"); + + using (File.Create(testFilePath, 1, FileOptions.DeleteOnClose)) + { + } + + return false; + } + catch (UnauthorizedAccessException) + { + return true; + } + } + + [RelayCommand] public void CloseWindowAction() => _closeAction?.Invoke(); + + + public void SetCloseAction(Action closeAction) => _closeAction = closeAction; + } +} diff --git a/AssetEditor/Views/Updater/UpdaterWindow.xaml b/AssetEditor/Views/Updater/UpdaterWindow.xaml new file mode 100644 index 000000000..e1781fb56 --- /dev/null +++ b/AssetEditor/Views/Updater/UpdaterWindow.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +