using System.Windows; using Microsoft.Win32; namespace Bloxstrap { internal class InstallChecker : IDisposable { private RegistryKey? _registryKey; private string? _installLocation; internal InstallChecker() { _registryKey = Registry.CurrentUser.OpenSubKey($"Software\\{App.ProjectName}", true); if (_registryKey is not null) _installLocation = (string?)_registryKey.GetValue("InstallLocation"); } internal void Check() { const string LOG_IDENT = "InstallChecker::Check"; if (_registryKey is null || _installLocation is null) { if (!File.Exists("Settings.json") || !File.Exists("State.json")) { FirstTimeRun(); return; } App.Logger.WriteLine(LOG_IDENT, "Installation registry key is likely malformed"); _installLocation = Path.GetDirectoryName(Paths.Process)!; var result = Controls.ShowMessageBox( $"It appears as if {App.ProjectName} hasn't been properly installed. Is it supposed to be installed at {_installLocation}?", MessageBoxImage.Warning, MessageBoxButton.YesNo ); if (result != MessageBoxResult.Yes) { FirstTimeRun(); return; } App.Logger.WriteLine(LOG_IDENT, $"Setting install location as '{_installLocation}'"); if (_registryKey is null) _registryKey = Registry.CurrentUser.CreateSubKey($"Software\\{App.ProjectName}"); _registryKey.SetValue("InstallLocation", _installLocation); } // check if drive that bloxstrap was installed to was removed from system, or had its drive letter changed if (!Directory.Exists(_installLocation)) { App.Logger.WriteLine(LOG_IDENT, "Could not find install location. Checking if drive has changed..."); bool driveExists = false; string driveName = _installLocation[..3]; string? newDriveName = null; foreach (var drive in DriveInfo.GetDrives()) { if (drive.Name == driveName) driveExists = true; else if (Directory.Exists(_installLocation.Replace(driveName, drive.Name))) newDriveName = drive.Name; } if (newDriveName is not null) { App.Logger.WriteLine(LOG_IDENT, $"Drive has changed from {driveName} to {newDriveName}"); Controls.ShowMessageBox( $"{App.ProjectName} has detected a drive letter change and has reconfigured its install location from the {driveName} drive to the {newDriveName} drive.\n" + "\n" + $"While {App.ProjectName} will continue to work, it's recommended that you change the drive leter back to its original value as other installed applications can experience similar issues.", MessageBoxImage.Warning, MessageBoxButton.OK ); _installLocation = _installLocation.Replace(driveName, newDriveName); _registryKey.SetValue("InstallLocation", _installLocation); } else if (!driveExists) { App.Logger.WriteLine(LOG_IDENT, $"Drive {driveName} does not exist anymore, and has likely been removed"); var result = Controls.ShowMessageBox( $"{App.ProjectName} was originally installed to the {driveName} drive, but it appears to no longer be present. Would you like to continue and carry out a fresh install?", MessageBoxImage.Warning, MessageBoxButton.OKCancel ); if (result != MessageBoxResult.OK) App.Terminate(); FirstTimeRun(); return; } else { App.Logger.WriteLine(LOG_IDENT, "Drive has not changed, folder was likely moved or deleted"); } } App.BaseDirectory = _installLocation; App.IsFirstRun = false; } public void Dispose() { _registryKey?.Dispose(); GC.SuppressFinalize(this); } private static void FirstTimeRun() { const string LOG_IDENT = "InstallChecker::FirstTimeRun"; App.Logger.WriteLine(LOG_IDENT, "Running first-time install"); App.BaseDirectory = Path.Combine(Paths.LocalAppData, App.ProjectName); App.Logger.Initialize(true); if (App.IsQuiet) return; App.IsSetupComplete = false; App.FastFlags.Load(); Controls.ShowMenu(); // exit if we don't click the install button on installation if (App.IsSetupComplete) return; App.Logger.WriteLine(LOG_IDENT, "Installation cancelled!"); App.Terminate(ErrorCode.ERROR_CANCELLED); } internal static void CheckUpgrade() { const string LOG_IDENT = "InstallChecker::CheckUpgrade"; if (!File.Exists(Paths.Application) || Paths.Process == Paths.Application) return; // 2.0.0 downloads updates to /Updates so lol bool isAutoUpgrade = Paths.Process.StartsWith(Path.Combine(Paths.Base, "Updates")) || Paths.Process.StartsWith(Path.Combine(Paths.LocalAppData, "Temp")); FileVersionInfo existingVersionInfo = FileVersionInfo.GetVersionInfo(Paths.Application); FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Paths.Process); if (MD5Hash.FromFile(Paths.Process) == MD5Hash.FromFile(Paths.Application)) return; MessageBoxResult result; // silently upgrade version if the command line flag is set or if we're launching from an auto update if (App.IsUpgrade || isAutoUpgrade) { result = MessageBoxResult.Yes; } else { result = Controls.ShowMessageBox( $"The version of {App.ProjectName} you've launched is different to the version you currently have installed.\nWould you like to upgrade your currently installed version?", MessageBoxImage.Question, MessageBoxButton.YesNo ); } if (result != MessageBoxResult.Yes) return; Filesystem.AssertReadOnly(Paths.Application); // yes, this is EXTREMELY hacky, but the updater process that launched the // new version may still be open and so we have to wait for it to close int attempts = 0; while (attempts < 10) { attempts++; try { File.Delete(Paths.Application); break; } catch (Exception) { if (attempts == 1) App.Logger.WriteLine(LOG_IDENT, "Waiting for write permissions to update version"); Thread.Sleep(500); } } if (attempts == 10) { App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not get write permissions after 5 seconds)"); return; } File.Copy(Paths.Process, Paths.Application); Bootstrapper.Register(); // update migrations if (App.BuildMetadata.CommitRef.StartsWith("tag")) { if (existingVersionInfo.ProductVersion == "2.4.0") { App.FastFlags.SetValue("DFFlagDisableDPIScale", null); App.FastFlags.SetValue("DFFlagVariableDPIScale2", null); App.FastFlags.Save(); } else if (existingVersionInfo.ProductVersion == "2.5.0") { App.FastFlags.SetValue("FIntDebugForceMSAASamples", null); if (App.FastFlags.GetPreset("UI.Menu.Style.DisableV2") is not null) App.FastFlags.SetPreset("UI.Menu.Style.ABTest", false); App.FastFlags.Save(); } } if (isAutoUpgrade) { App.NotifyIcon?.ShowAlert( $"{App.ProjectName} has been upgraded to v{currentVersionInfo.ProductVersion}", "See what's new in this version", 30, (_, _) => Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/releases/tag/v{currentVersionInfo.ProductVersion}") ); } else if (!App.IsQuiet) { Controls.ShowMessageBox( $"{App.ProjectName} has been upgraded to v{currentVersionInfo.ProductVersion}", MessageBoxImage.Information, MessageBoxButton.OK ); Controls.ShowMenu(); App.Terminate(); } } } }