mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-18 00:21:33 -07:00
Refactor automatic updater + fix install details + fix launch flag parser + fix temp directory
Automatic updater now relies on the -upgrade flag specifically being set and uses a mutex for coordinating the process Temp directory is now obtained appropriately (should fix exceptions relating to it?) Installation details are now reconfigured on every upgrade Specifying a nonexistant flag would insta-crash the app Also, the message box was making the wrong sound for the warning icon
This commit is contained in:
parent
f747f40ca5
commit
2791cb0b2e
Bloxstrap
App.xaml.csBootstrapper.csInstaller.csLaunchSettings.csPaths.cs
Resources
UI
Elements/Dialogs
ViewModels
@ -15,7 +15,11 @@ namespace Bloxstrap
|
||||
public partial class App : Application
|
||||
{
|
||||
public const string ProjectName = "Bloxstrap";
|
||||
public const string ProjectOwner = "pizzaboxer";
|
||||
public const string ProjectRepository = "pizzaboxer/bloxstrap";
|
||||
public const string ProjectDownloadLink = "https://bloxstrap.pizzaboxer.xyz";
|
||||
public const string ProjectHelpLink = "https://github.com/pizzaboxer/bloxstrap/wiki";
|
||||
public const string ProjectSupportLink = "https://github.com/pizzaboxer/bloxstrap/issues/new";
|
||||
|
||||
public const string RobloxPlayerAppName = "RobloxPlayerBeta";
|
||||
public const string RobloxStudioAppName = "RobloxStudioBeta";
|
||||
@ -103,6 +107,27 @@ namespace Bloxstrap
|
||||
Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
||||
}
|
||||
|
||||
public static async Task<GithubRelease?> GetLatestRelease()
|
||||
{
|
||||
const string LOG_IDENT = "App::GetLatestRelease";
|
||||
|
||||
GithubRelease? releaseInfo = null;
|
||||
|
||||
try
|
||||
{
|
||||
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
|
||||
|
||||
if (releaseInfo is null || releaseInfo.Assets is null)
|
||||
Logger.WriteLine(LOG_IDENT, "Encountered invalid data");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.WriteException(LOG_IDENT, ex);
|
||||
}
|
||||
|
||||
return releaseInfo;
|
||||
}
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
const string LOG_IDENT = "App::OnStartup";
|
||||
@ -221,7 +246,7 @@ namespace Bloxstrap
|
||||
Locale.Set(Settings.Prop.Locale);
|
||||
|
||||
#if !DEBUG
|
||||
if (!LaunchSettings.UninstallFlag.Active)
|
||||
if (!LaunchSettings.BypassUpdateCheck)
|
||||
Installer.HandleUpgrade();
|
||||
#endif
|
||||
|
||||
|
@ -1,4 +1,17 @@
|
||||
using System.Windows;
|
||||
// To debug the automatic updater:
|
||||
// - Uncomment the definition below
|
||||
// - Publish the executable
|
||||
// - Launch the executable (click no when it asks you to upgrade)
|
||||
// - Launch Roblox (for testing web launches, run it from the command prompt)
|
||||
// - To re-test the same executable, delete it from the installation folder
|
||||
|
||||
// #define DEBUG_UPDATER
|
||||
|
||||
#if DEBUG_UPDATER
|
||||
#warning "Automatic updater debugging is enabled"
|
||||
#endif
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Microsoft.Win32;
|
||||
@ -152,9 +165,14 @@ namespace Bloxstrap
|
||||
|
||||
await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
|
||||
|
||||
#if !DEBUG
|
||||
if (App.Settings.Prop.CheckForUpdates)
|
||||
await CheckForUpdates();
|
||||
#if !DEBUG || DEBUG_UPDATER
|
||||
if (App.Settings.Prop.CheckForUpdates && !App.LaunchSettings.UpgradeFlag.Active)
|
||||
{
|
||||
bool updatePresent = await CheckForUpdates();
|
||||
|
||||
if (updatePresent)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ensure only one instance of the bootstrapper is running at the time
|
||||
@ -405,7 +423,7 @@ namespace Bloxstrap
|
||||
|
||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region App Install
|
||||
public void RegisterProgramSize()
|
||||
@ -449,53 +467,56 @@ namespace Bloxstrap
|
||||
#endif
|
||||
}
|
||||
|
||||
private async Task CheckForUpdates()
|
||||
private async Task<bool> CheckForUpdates()
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::CheckForUpdates";
|
||||
|
||||
// don't update if there's another instance running (likely running in the background)
|
||||
if (Process.GetProcessesByName(App.ProjectName).Count() > 1)
|
||||
// i don't like this, but there isn't much better way of doing it /shrug
|
||||
if (Process.GetProcessesByName(App.ProjectName).Length > 1)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"More than one Bloxstrap instance running, aborting update check");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Checking for updates...");
|
||||
App.Logger.WriteLine(LOG_IDENT, "Checking for updates...");
|
||||
|
||||
GithubRelease? releaseInfo;
|
||||
#if !DEBUG_UPDATER
|
||||
var releaseInfo = await App.GetLatestRelease();
|
||||
|
||||
try
|
||||
{
|
||||
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Failed to fetch releases: {ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (releaseInfo is null || releaseInfo.Assets is null)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"No updates found");
|
||||
return;
|
||||
}
|
||||
if (releaseInfo is null)
|
||||
return false;
|
||||
|
||||
var versionComparison = Utilities.CompareVersions(App.Version, releaseInfo.TagName);
|
||||
|
||||
// check if we aren't using a deployed build, so we can update to one if a new version comes out
|
||||
if (versionComparison == VersionComparison.Equal && App.IsProductionBuild || versionComparison == VersionComparison.GreaterThan)
|
||||
if (App.IsProductionBuild && versionComparison == VersionComparison.Equal || versionComparison == VersionComparison.GreaterThan)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"No updates found");
|
||||
return;
|
||||
App.Logger.WriteLine(LOG_IDENT, "No updates found");
|
||||
return false;
|
||||
}
|
||||
|
||||
string version = releaseInfo.TagName;
|
||||
#else
|
||||
string version = App.Version;
|
||||
#endif
|
||||
|
||||
SetStatus(Strings.Bootstrapper_Status_UpgradingBloxstrap);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// 64-bit is always the first option
|
||||
GithubReleaseAsset asset = releaseInfo.Assets[0];
|
||||
string downloadLocation = Path.Combine(Paths.LocalAppData, "Temp", asset.Name);
|
||||
#if DEBUG_UPDATER
|
||||
string downloadLocation = Path.Combine(Paths.TempUpdates, "Bloxstrap.exe");
|
||||
|
||||
Directory.CreateDirectory(Paths.TempUpdates);
|
||||
|
||||
File.Copy(Paths.Process, downloadLocation, true);
|
||||
#else
|
||||
var asset = releaseInfo.Assets![0];
|
||||
|
||||
string downloadLocation = Path.Combine(Paths.TempUpdates, asset.Name);
|
||||
|
||||
Directory.CreateDirectory(Paths.TempUpdates);
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Downloading {releaseInfo.TagName}...");
|
||||
|
||||
@ -503,25 +524,35 @@ namespace Bloxstrap
|
||||
{
|
||||
var response = await App.HttpClient.GetAsync(asset.BrowserDownloadUrl);
|
||||
|
||||
await using var fileStream = new FileStream(downloadLocation, FileMode.CreateNew);
|
||||
await using var fileStream = new FileStream(downloadLocation, FileMode.OpenOrCreate, FileAccess.Write);
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
#endif
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Starting {releaseInfo.TagName}...");
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Starting {version}...");
|
||||
|
||||
ProcessStartInfo startInfo = new()
|
||||
{
|
||||
FileName = downloadLocation,
|
||||
};
|
||||
|
||||
startInfo.ArgumentList.Add("-upgrade");
|
||||
|
||||
foreach (string arg in App.LaunchSettings.Args)
|
||||
startInfo.ArgumentList.Add(arg);
|
||||
|
||||
|
||||
if (_launchMode == LaunchMode.Player && !startInfo.ArgumentList.Contains("-player"))
|
||||
startInfo.ArgumentList.Add("-player");
|
||||
else if (_launchMode == LaunchMode.Studio && !startInfo.ArgumentList.Contains("-studio"))
|
||||
startInfo.ArgumentList.Add("-studio");
|
||||
|
||||
App.Settings.Save();
|
||||
|
||||
new InterProcessLock("AutoUpdater");
|
||||
|
||||
Process.Start(startInfo);
|
||||
|
||||
App.Terminate();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -529,10 +560,14 @@ namespace Bloxstrap
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
|
||||
Frontend.ShowMessageBox(
|
||||
string.Format(Strings.Bootstrapper_AutoUpdateFailed, releaseInfo.TagName),
|
||||
string.Format(Strings.Bootstrapper_AutoUpdateFailed, version),
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
|
||||
Utilities.ShellExecute(App.ProjectDownloadLink);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -851,6 +886,7 @@ namespace Bloxstrap
|
||||
foreach (FontFace fontFace in fontFamilyData.Faces)
|
||||
fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf";
|
||||
|
||||
// TODO: writing on every launch is not necessary
|
||||
File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
|
||||
}
|
||||
|
||||
@ -902,6 +938,8 @@ namespace Bloxstrap
|
||||
// the manifest is primarily here to keep track of what files have been
|
||||
// deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages
|
||||
// now check for files that have been deleted from the mod folder according to the manifest
|
||||
|
||||
// TODO: this needs to extract the files from packages in bulk, this is way too slow
|
||||
foreach (string fileLocation in App.State.Prop.ModManifest)
|
||||
{
|
||||
if (modFolderFiles.Contains(fileLocation))
|
||||
|
@ -1,9 +1,4 @@
|
||||
using System.DirectoryServices;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
using Bloxstrap.Resources;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Bloxstrap
|
||||
@ -50,12 +45,13 @@ namespace Bloxstrap
|
||||
|
||||
uninstallKey.SetValue("InstallLocation", Paths.Base);
|
||||
uninstallKey.SetValue("NoRepair", 1);
|
||||
uninstallKey.SetValue("Publisher", "pizzaboxer");
|
||||
uninstallKey.SetValue("Publisher", App.ProjectOwner);
|
||||
uninstallKey.SetValue("ModifyPath", $"\"{Paths.Application}\" -settings");
|
||||
uninstallKey.SetValue("QuietUninstallString", $"\"{Paths.Application}\" -uninstall -quiet");
|
||||
uninstallKey.SetValue("UninstallString", $"\"{Paths.Application}\" -uninstall");
|
||||
uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{App.ProjectRepository}");
|
||||
uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{App.ProjectRepository}/releases/latest");
|
||||
uninstallKey.SetValue("HelpLink", App.ProjectHelpLink);
|
||||
uninstallKey.SetValue("URLInfoAbout", App.ProjectSupportLink);
|
||||
uninstallKey.SetValue("URLUpdateInfo", App.ProjectDownloadLink);
|
||||
}
|
||||
|
||||
// only register player, for the scenario where the user installs bloxstrap, closes it,
|
||||
@ -331,8 +327,9 @@ namespace Bloxstrap
|
||||
return;
|
||||
|
||||
// 2.0.0 downloads updates to <BaseFolder>/Updates so lol
|
||||
// TODO: 2.8.0 will download them to <Temp>/Bloxstrap/Updates
|
||||
bool isAutoUpgrade = Paths.Process.StartsWith(Path.Combine(Paths.Base, "Updates")) || Paths.Process.StartsWith(Path.Combine(Paths.LocalAppData, "Temp"));
|
||||
bool isAutoUpgrade = App.LaunchSettings.UpgradeFlag.Active
|
||||
|| Paths.Process.StartsWith(Path.Combine(Paths.Base, "Updates"))
|
||||
|| Paths.Process.StartsWith(Paths.Temp);
|
||||
|
||||
var existingVer = FileVersionInfo.GetVersionInfo(Paths.Application).ProductVersion;
|
||||
var currentVer = FileVersionInfo.GetVersionInfo(Paths.Process).ProductVersion;
|
||||
@ -353,7 +350,7 @@ namespace Bloxstrap
|
||||
}
|
||||
|
||||
// silently upgrade version if the command line flag is set or if we're launching from an auto update
|
||||
if (!App.LaunchSettings.UpgradeFlag.Active && !isAutoUpgrade)
|
||||
if (!isAutoUpgrade)
|
||||
{
|
||||
var result = Frontend.ShowMessageBox(
|
||||
Strings.InstallChecker_VersionDifferentThanInstalled,
|
||||
@ -365,41 +362,38 @@ namespace Bloxstrap
|
||||
return;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Doing upgrade");
|
||||
|
||||
Filesystem.AssertReadOnly(Paths.Application);
|
||||
|
||||
// TODO: make this use a mutex somehow
|
||||
// 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)
|
||||
using (var ipl = new InterProcessLock("AutoUpdater", TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
attempts++;
|
||||
|
||||
try
|
||||
if (!ipl.IsAcquired)
|
||||
{
|
||||
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);
|
||||
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not obtain singleton mutex)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (attempts == 10)
|
||||
try
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not get write permissions after 5 seconds)");
|
||||
File.Copy(Paths.Process, Paths.Application, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not replace executable)");
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
File.Copy(Paths.Process, Paths.Application);
|
||||
|
||||
using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
|
||||
{
|
||||
uninstallKey.SetValue("DisplayVersion", App.Version);
|
||||
|
||||
uninstallKey.SetValue("Publisher", App.ProjectOwner);
|
||||
uninstallKey.SetValue("HelpLink", App.ProjectHelpLink);
|
||||
uninstallKey.SetValue("URLInfoAbout", App.ProjectSupportLink);
|
||||
uninstallKey.SetValue("URLUpdateInfo", App.ProjectDownloadLink);
|
||||
}
|
||||
|
||||
// update migrations
|
||||
|
@ -28,6 +28,8 @@ namespace Bloxstrap
|
||||
|
||||
public LaunchFlag StudioFlag { get; } = new("studio");
|
||||
|
||||
public bool BypassUpdateCheck => UninstallFlag.Active || WatcherFlag.Active;
|
||||
|
||||
public LaunchMode RobloxLaunchMode { get; set; } = LaunchMode.None;
|
||||
|
||||
public string RobloxLaunchArgs { get; private set; } = "";
|
||||
@ -37,7 +39,7 @@ namespace Bloxstrap
|
||||
/// </summary>
|
||||
public string[] Args { get; private set; }
|
||||
|
||||
private Dictionary<string, LaunchFlag> _flagMap = new();
|
||||
private readonly Dictionary<string, LaunchFlag> _flagMap = new();
|
||||
|
||||
public LaunchSettings(string[] args)
|
||||
{
|
||||
@ -68,7 +70,7 @@ namespace Bloxstrap
|
||||
|
||||
string identifier = arg[1..];
|
||||
|
||||
if (_flagMap[identifier] is not LaunchFlag flag)
|
||||
if (!_flagMap.TryGetValue(identifier, out LaunchFlag? flag) || flag is null)
|
||||
continue;
|
||||
|
||||
flag.Active = true;
|
||||
|
@ -4,6 +4,7 @@
|
||||
{
|
||||
// note that these are directories that aren't tethered to the basedirectory
|
||||
// so these can safely be called before initialization
|
||||
public static string Temp => Path.Combine(Path.GetTempPath(), App.ProjectName);
|
||||
public static string UserProfile => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
public static string LocalAppData => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
public static string Desktop => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||||
@ -12,6 +13,9 @@
|
||||
|
||||
public static string Process => Environment.ProcessPath!;
|
||||
|
||||
public static string TempUpdates => Path.Combine(Temp, "Updates");
|
||||
public static string TempLogs => Path.Combine(Temp, "Logs");
|
||||
|
||||
public static string Base { get; private set; } = "";
|
||||
public static string Downloads { get; private set; } = "";
|
||||
public static string Logs { get; private set; } = "";
|
||||
|
2
Bloxstrap/Resources/Strings.Designer.cs
generated
2
Bloxstrap/Resources/Strings.Designer.cs
generated
@ -106,7 +106,7 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bloxstrap was unable to auto-update to {0}. Please update it manually by downloading and running the latest release from the GitHub page..
|
||||
/// Looks up a localized string similar to Bloxstrap was unable to automatically update to version {0}. Please update it manually by downloading and running it from the website..
|
||||
/// </summary>
|
||||
public static string Bootstrapper_AutoUpdateFailed {
|
||||
get {
|
||||
|
@ -124,7 +124,7 @@
|
||||
<value>lookup failed</value>
|
||||
</data>
|
||||
<data name="Bootstrapper.AutoUpdateFailed" xml:space="preserve">
|
||||
<value>Bloxstrap was unable to auto-update to {0}. Please update it manually by downloading and running the latest release from the GitHub page.</value>
|
||||
<value>Bloxstrap was unable to automatically update to version {0}. Please update it manually by downloading and running it from the website.</value>
|
||||
</data>
|
||||
<data name="Bootstrapper.ConfirmLaunch" xml:space="preserve">
|
||||
<value>Roblox is currently running, and launching another instance will close it. Are you sure you want to continue launching?</value>
|
||||
|
@ -41,7 +41,7 @@ namespace Bloxstrap.UI.Elements.Dialogs
|
||||
|
||||
case MessageBoxImage.Warning:
|
||||
iconFilename = "Warning";
|
||||
sound = SystemSounds.Asterisk;
|
||||
sound = SystemSounds.Exclamation;
|
||||
break;
|
||||
|
||||
case MessageBoxImage.Information:
|
||||
|
@ -5,7 +5,6 @@ using Bloxstrap.UI.Elements.About;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Installer
|
||||
{
|
||||
// TODO: have it so it shows "Launch Roblox"/"Install and Launch Roblox" depending on state of /App/ folder
|
||||
public class LaunchMenuViewModel
|
||||
{
|
||||
public string Version => string.Format(Strings.Menu_About_Version, App.Version);
|
||||
|
@ -19,32 +19,15 @@
|
||||
// called by codebehind on page load
|
||||
public async void DoChecks()
|
||||
{
|
||||
const string LOG_IDENT = "WelcomeViewModel::DoChecks";
|
||||
var releaseInfo = await App.GetLatestRelease();
|
||||
|
||||
// TODO: move into unified function that bootstrapper can use too
|
||||
GithubRelease? releaseInfo = null;
|
||||
|
||||
try
|
||||
if (releaseInfo is not null)
|
||||
{
|
||||
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
|
||||
|
||||
if (releaseInfo is null || releaseInfo.Assets is null)
|
||||
if (Utilities.CompareVersions(App.Version, releaseInfo.TagName) == VersionComparison.LessThan)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Encountered invalid data when fetching GitHub releases");
|
||||
VersionNotice = String.Format(Strings.Installer_Welcome_UpdateNotice, App.Version, releaseInfo.TagName.Replace("v", ""));
|
||||
OnPropertyChanged(nameof(VersionNotice));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Utilities.CompareVersions(App.Version, releaseInfo.TagName) == VersionComparison.LessThan)
|
||||
{
|
||||
VersionNotice = String.Format(Resources.Strings.Installer_Welcome_UpdateNotice, App.Version, releaseInfo.TagName.Replace("v", ""));
|
||||
OnPropertyChanged(nameof(VersionNotice));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Error occurred when fetching GitHub releases");
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
}
|
||||
|
||||
CanContinue = true;
|
||||
|
Loading…
Reference in New Issue
Block a user