mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-16 02:01:29 -07:00
Draft: new installer system
the beginning of a long arduous cleanup of two years of debt
This commit is contained in:
parent
d1343d35dc
commit
776dbc4097
@ -1,10 +1,10 @@
|
||||
using System.Reflection;
|
||||
using System.Web;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Microsoft.Win32;
|
||||
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
@ -15,29 +15,27 @@ namespace Bloxstrap
|
||||
{
|
||||
public const string ProjectName = "Bloxstrap";
|
||||
public const string ProjectRepository = "pizzaboxer/bloxstrap";
|
||||
|
||||
public const string RobloxPlayerAppName = "RobloxPlayerBeta";
|
||||
public const string RobloxStudioAppName = "RobloxStudioBeta";
|
||||
|
||||
// used only for communicating between app and menu - use Directories.Base for anything else
|
||||
public static string BaseDirectory = null!;
|
||||
public static string? CustomFontLocation;
|
||||
|
||||
public static bool ShouldSaveConfigs { get; set; } = false;
|
||||
|
||||
public static bool IsSetupComplete { get; set; } = true;
|
||||
public static bool IsFirstRun { get; set; } = true;
|
||||
// simple shorthand for extremely frequently used and long string - this goes under HKCU
|
||||
public const string UninstallKey = $@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{ProjectName}";
|
||||
|
||||
public static LaunchSettings LaunchSettings { get; private set; } = null!;
|
||||
|
||||
public static BuildMetadataAttribute BuildMetadata = Assembly.GetExecutingAssembly().GetCustomAttribute<BuildMetadataAttribute>()!;
|
||||
|
||||
public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2];
|
||||
|
||||
public static NotifyIconWrapper? NotifyIcon { get; private set; }
|
||||
public static NotifyIconWrapper? NotifyIcon { get; set; }
|
||||
|
||||
public static readonly Logger Logger = new();
|
||||
|
||||
public static readonly JsonManager<Settings> Settings = new();
|
||||
|
||||
public static readonly JsonManager<State> State = new();
|
||||
|
||||
public static readonly FastFlagManager FastFlags = new();
|
||||
|
||||
public static readonly HttpClient HttpClient = new(
|
||||
@ -52,18 +50,10 @@ namespace Bloxstrap
|
||||
|
||||
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
|
||||
{
|
||||
if (IsFirstRun)
|
||||
{
|
||||
if (exitCode == ErrorCode.ERROR_CANCELLED)
|
||||
exitCode = ErrorCode.ERROR_INSTALL_USEREXIT;
|
||||
}
|
||||
|
||||
int exitCodeNum = (int)exitCode;
|
||||
|
||||
Logger.WriteLine("App::Terminate", $"Terminating with exit code {exitCodeNum} ({exitCode})");
|
||||
|
||||
Settings.Save();
|
||||
State.Save();
|
||||
NotifyIcon?.Dispose();
|
||||
|
||||
Environment.Exit(exitCodeNum);
|
||||
@ -98,16 +88,7 @@ namespace Bloxstrap
|
||||
#endif
|
||||
}
|
||||
|
||||
private void StartupFinished()
|
||||
{
|
||||
const string LOG_IDENT = "App::StartupFinished";
|
||||
|
||||
Logger.WriteLine(LOG_IDENT, "Successfully reached end of main thread. Terminating...");
|
||||
|
||||
Terminate();
|
||||
}
|
||||
|
||||
protected override async void OnStartup(StartupEventArgs e)
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
const string LOG_IDENT = "App::OnStartup";
|
||||
|
||||
@ -128,19 +109,62 @@ namespace Bloxstrap
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
|
||||
HttpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||
HttpClient.DefaultRequestHeaders.Add("User-Agent", ProjectRepository);
|
||||
|
||||
LaunchSettings = new LaunchSettings(e.Args);
|
||||
|
||||
using (var checker = new InstallChecker())
|
||||
// installation check begins here
|
||||
using var uninstallKey = Registry.CurrentUser.OpenSubKey(UninstallKey);
|
||||
string? installLocation = null;
|
||||
|
||||
if (uninstallKey?.GetValue("InstallLocation") is string value && Directory.Exists(value))
|
||||
installLocation = value;
|
||||
|
||||
// silently change install location if we detect a portable run
|
||||
// this should also handle renaming of the user profile folder
|
||||
if (installLocation is null && Directory.GetParent(Paths.Process)?.FullName is string processDir)
|
||||
{
|
||||
checker.Check();
|
||||
var files = Directory.GetFiles(processDir).Select(x => Path.GetFileName(x)).ToArray();
|
||||
var installer = new Installer
|
||||
{
|
||||
InstallLocation = processDir,
|
||||
IsImplicitInstall = true
|
||||
};
|
||||
|
||||
// check if settings.json and state.json are the only files in the folder, and if we can write to it
|
||||
if (files.Length <= 3
|
||||
&& files.Contains("Settings.json")
|
||||
&& files.Contains("State.json")
|
||||
&& installer.CheckInstallLocation())
|
||||
{
|
||||
Logger.WriteLine(LOG_IDENT, $"Changing install location to '{processDir}'");
|
||||
installer.DoInstall();
|
||||
installLocation = processDir;
|
||||
}
|
||||
}
|
||||
|
||||
Paths.Initialize(BaseDirectory);
|
||||
|
||||
// we shouldn't save settings on the first run until the first installation is finished,
|
||||
// just in case the user decides to cancel the install
|
||||
if (!IsFirstRun)
|
||||
if (installLocation is null)
|
||||
{
|
||||
Logger.Initialize(true);
|
||||
LaunchHandler.LaunchInstaller();
|
||||
}
|
||||
else
|
||||
{
|
||||
Paths.Initialize(installLocation);
|
||||
|
||||
// ensure executable is in the install directory
|
||||
if (Paths.Process != Paths.Application && !File.Exists(Paths.Application))
|
||||
File.Copy(Paths.Process, Paths.Application);
|
||||
|
||||
Logger.Initialize(LaunchSettings.IsUninstall);
|
||||
|
||||
if (!Logger.Initialized && !Logger.NoWriteMode)
|
||||
{
|
||||
Logger.WriteLine(LOG_IDENT, "Possible duplicate launch detected, terminating.");
|
||||
Terminate();
|
||||
}
|
||||
|
||||
Settings.Load();
|
||||
State.Load();
|
||||
FastFlags.Load();
|
||||
@ -152,145 +176,14 @@ namespace Bloxstrap
|
||||
}
|
||||
|
||||
Locale.Set(Settings.Prop.Locale);
|
||||
|
||||
if (!LaunchSettings.IsUninstall)
|
||||
Installer.HandleUpgrade();
|
||||
|
||||
LaunchHandler.ProcessLaunchArgs();
|
||||
}
|
||||
|
||||
LaunchSettings.ParseRoblox();
|
||||
|
||||
HttpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||
HttpClient.DefaultRequestHeaders.Add("User-Agent", ProjectRepository);
|
||||
|
||||
// TEMPORARY FILL-IN FOR NEW FUNCTIONALITY
|
||||
// REMOVE WHEN LARGER REFACTORING IS DONE
|
||||
var connectionResult = await RobloxDeployment.InitializeConnectivity();
|
||||
|
||||
if (connectionResult is not null)
|
||||
{
|
||||
Logger.WriteException(LOG_IDENT, connectionResult);
|
||||
|
||||
Frontend.ShowConnectivityDialog(
|
||||
Bloxstrap.Resources.Strings.Dialog_Connectivity_UnableToConnect,
|
||||
Bloxstrap.Resources.Strings.Bootstrapper_Connectivity_Preventing,
|
||||
connectionResult
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (LaunchSettings.IsUninstall && IsFirstRun)
|
||||
{
|
||||
Frontend.ShowMessageBox(Bloxstrap.Resources.Strings.Bootstrapper_FirstRunUninstall, MessageBoxImage.Error);
|
||||
Terminate(ErrorCode.ERROR_INVALID_FUNCTION);
|
||||
return;
|
||||
}
|
||||
|
||||
// we shouldn't save settings on the first run until the first installation is finished,
|
||||
// just in case the user decides to cancel the install
|
||||
if (!IsFirstRun)
|
||||
{
|
||||
Logger.Initialize(LaunchSettings.IsUninstall);
|
||||
|
||||
if (!Logger.Initialized && !Logger.NoWriteMode)
|
||||
{
|
||||
Logger.WriteLine(LOG_IDENT, "Possible duplicate launch detected, terminating.");
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
if (!LaunchSettings.IsUninstall && !LaunchSettings.IsMenuLaunch)
|
||||
NotifyIcon = new();
|
||||
|
||||
#if !DEBUG
|
||||
if (!LaunchSettings.IsUninstall && !IsFirstRun)
|
||||
InstallChecker.CheckUpgrade();
|
||||
#endif
|
||||
|
||||
if (LaunchSettings.IsMenuLaunch)
|
||||
{
|
||||
Process? menuProcess = Utilities.GetProcessesSafe().Where(x => x.MainWindowTitle == $"{ProjectName} Menu").FirstOrDefault();
|
||||
|
||||
if (menuProcess is not null)
|
||||
{
|
||||
var handle = menuProcess.MainWindowHandle;
|
||||
Logger.WriteLine(LOG_IDENT, $"Found an already existing menu window with handle {handle}");
|
||||
PInvoke.SetForegroundWindow((HWND)handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool showAlreadyRunningWarning = Process.GetProcessesByName(ProjectName).Length > 1 && !LaunchSettings.IsQuiet;
|
||||
Frontend.ShowMenu(showAlreadyRunningWarning);
|
||||
}
|
||||
|
||||
StartupFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsFirstRun)
|
||||
ShouldSaveConfigs = true;
|
||||
|
||||
if (Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _))
|
||||
{
|
||||
// this currently doesn't work very well since it relies on checking the existence of the singleton mutex
|
||||
// which often hangs around for a few seconds after the window closes
|
||||
// it would be better to have this rely on the activity tracker when we implement IPC in the planned refactoring
|
||||
|
||||
var result = Frontend.ShowMessageBox(Bloxstrap.Resources.Strings.Bootstrapper_ConfirmLaunch, MessageBoxImage.Warning, MessageBoxButton.YesNo);
|
||||
|
||||
if (result != MessageBoxResult.Yes)
|
||||
{
|
||||
StartupFinished();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
||||
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
||||
Bootstrapper bootstrapper = new(LaunchSettings.RobloxLaunchArgs, LaunchSettings.RobloxLaunchMode);
|
||||
IBootstrapperDialog? dialog = null;
|
||||
|
||||
if (!LaunchSettings.IsQuiet)
|
||||
{
|
||||
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog");
|
||||
dialog = Settings.Prop.BootstrapperStyle.GetNew();
|
||||
bootstrapper.Dialog = dialog;
|
||||
dialog.Bootstrapper = bootstrapper;
|
||||
}
|
||||
|
||||
Task bootstrapperTask = Task.Run(async () => await bootstrapper.Run()).ContinueWith(t =>
|
||||
{
|
||||
Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished");
|
||||
|
||||
// notifyicon is blocking main thread, must be disposed here
|
||||
NotifyIcon?.Dispose();
|
||||
|
||||
if (t.IsFaulted)
|
||||
Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
||||
|
||||
if (t.Exception is null)
|
||||
return;
|
||||
|
||||
Logger.WriteException(LOG_IDENT, t.Exception);
|
||||
|
||||
Exception exception = t.Exception;
|
||||
|
||||
#if !DEBUG
|
||||
if (t.Exception.GetType().ToString() == "System.AggregateException")
|
||||
exception = t.Exception.InnerException!;
|
||||
#endif
|
||||
|
||||
FinalizeExceptionHandling(exception, false);
|
||||
});
|
||||
|
||||
// this ordering is very important as all wpf windows are shown as modal dialogs, mess it up and you'll end up blocking input to one of them
|
||||
dialog?.ShowBootstrapper();
|
||||
|
||||
if (!LaunchSettings.IsNoLaunch && Settings.Prop.EnableActivityTracking)
|
||||
NotifyIcon?.InitializeContextMenu();
|
||||
|
||||
Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish");
|
||||
|
||||
bootstrapperTask.Wait();
|
||||
|
||||
StartupFinished();
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,8 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
|
||||
<Version>2.7.0</Version>
|
||||
<FileVersion>2.7.0</FileVersion>
|
||||
<Version>2.8.0</Version>
|
||||
<FileVersion>2.8.0</FileVersion>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
@ -20,7 +20,6 @@
|
||||
<Resource Include="Resources\Fonts\Rubik-VariableFont_wght.ttf" />
|
||||
<Resource Include="Resources\BootstrapperStyles\ByfronDialog\ByfronLogoDark.jpg" />
|
||||
<Resource Include="Resources\BootstrapperStyles\ByfronDialog\ByfronLogoLight.jpg" />
|
||||
<Resource Include="Resources\Menu\StartMenu.png" />
|
||||
<Resource Include="Resources\MessageBox\Error.png" />
|
||||
<Resource Include="Resources\MessageBox\Information.png" />
|
||||
<Resource Include="Resources\MessageBox\Question.png" />
|
||||
|
@ -25,11 +25,11 @@ namespace Bloxstrap
|
||||
private bool FreshInstall => String.IsNullOrEmpty(_versionGuid);
|
||||
|
||||
private string _playerFileName => _launchMode == LaunchMode.Player ? "RobloxPlayerBeta.exe" : "RobloxStudioBeta.exe";
|
||||
// TODO: change name
|
||||
private string _playerLocation => Path.Combine(_versionFolder, _playerFileName);
|
||||
|
||||
private string _launchCommandLine;
|
||||
private LaunchMode _launchMode;
|
||||
private bool _installWebView2;
|
||||
|
||||
private string _versionGuid
|
||||
{
|
||||
@ -81,10 +81,11 @@ namespace Bloxstrap
|
||||
#endregion
|
||||
|
||||
#region Core
|
||||
public Bootstrapper(string launchCommandLine, LaunchMode launchMode)
|
||||
public Bootstrapper(string launchCommandLine, LaunchMode launchMode, bool installWebView2)
|
||||
{
|
||||
_launchCommandLine = launchCommandLine;
|
||||
_launchMode = launchMode;
|
||||
_installWebView2 = installWebView2;
|
||||
|
||||
_packageDirectories = _launchMode == LaunchMode.Player ? PackageMap.Player : PackageMap.Studio;
|
||||
}
|
||||
@ -96,7 +97,7 @@ namespace Bloxstrap
|
||||
string productName = "Roblox";
|
||||
|
||||
if (_launchMode != LaunchMode.Player)
|
||||
productName += " Studio";
|
||||
productName = "Roblox Studio";
|
||||
|
||||
message = message.Replace("{product}", productName);
|
||||
|
||||
@ -124,47 +125,41 @@ namespace Bloxstrap
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Running bootstrapper");
|
||||
|
||||
if (App.LaunchSettings.IsUninstall)
|
||||
{
|
||||
Uninstall();
|
||||
return;
|
||||
}
|
||||
|
||||
// connectivity check
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Performing connectivity check...");
|
||||
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_Connecting);
|
||||
SetStatus(Strings.Bootstrapper_Status_Connecting);
|
||||
|
||||
try
|
||||
{
|
||||
await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
var connectionResult = await RobloxDeployment.InitializeConnectivity();
|
||||
|
||||
if (connectionResult is not null)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Connectivity check failed!");
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
App.Logger.WriteException(LOG_IDENT, connectionResult);
|
||||
|
||||
string message = Resources.Strings.Bootstrapper_Connectivity_Preventing;
|
||||
string message = Strings.Bootstrapper_Connectivity_Preventing;
|
||||
|
||||
if (ex.GetType() == typeof(HttpResponseException))
|
||||
message = Resources.Strings.Bootstrapper_Connectivity_RobloxDown;
|
||||
else if (ex.GetType() == typeof(TaskCanceledException))
|
||||
message = Resources.Strings.Bootstrapper_Connectivity_TimedOut;
|
||||
else if (ex.GetType() == typeof(AggregateException))
|
||||
ex = ex.InnerException!;
|
||||
if (connectionResult.GetType() == typeof(HttpResponseException))
|
||||
message = Strings.Bootstrapper_Connectivity_RobloxDown;
|
||||
else if (connectionResult.GetType() == typeof(TaskCanceledException))
|
||||
message = Strings.Bootstrapper_Connectivity_TimedOut;
|
||||
else if (connectionResult.GetType() == typeof(AggregateException))
|
||||
connectionResult = connectionResult.InnerException!;
|
||||
|
||||
Frontend.ShowConnectivityDialog(Strings.Dialog_Connectivity_UnableToConnect, message, ex);
|
||||
Frontend.ShowConnectivityDialog(Strings.Dialog_Connectivity_UnableToConnect, message, connectionResult);
|
||||
|
||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
}
|
||||
finally
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
|
||||
|
||||
await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
|
||||
|
||||
#if !DEBUG
|
||||
if (!App.IsFirstRun && App.Settings.Prop.CheckForUpdates)
|
||||
if (App.Settings.Prop.CheckForUpdates)
|
||||
await CheckForUpdates();
|
||||
#endif
|
||||
|
||||
@ -177,7 +172,7 @@ namespace Bloxstrap
|
||||
{
|
||||
Mutex.OpenExisting("Bloxstrap_SingletonMutex").Close();
|
||||
App.Logger.WriteLine(LOG_IDENT, "Bloxstrap_SingletonMutex mutex exists, waiting...");
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_WaitingOtherInstances);
|
||||
SetStatus(Strings.Bootstrapper_Status_WaitingOtherInstances);
|
||||
mutexExists = true;
|
||||
}
|
||||
catch (Exception)
|
||||
@ -199,37 +194,30 @@ namespace Bloxstrap
|
||||
await CheckLatestVersion();
|
||||
|
||||
// install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist
|
||||
if (App.IsFirstRun || _latestVersionGuid != _versionGuid || !File.Exists(_playerLocation))
|
||||
if (_latestVersionGuid != _versionGuid || !File.Exists(_playerLocation))
|
||||
await InstallLatestVersion();
|
||||
|
||||
if (App.IsFirstRun)
|
||||
App.ShouldSaveConfigs = true;
|
||||
|
||||
MigrateIntegrations();
|
||||
|
||||
await InstallWebView2();
|
||||
if (_installWebView2)
|
||||
await InstallWebView2();
|
||||
|
||||
App.FastFlags.Save();
|
||||
await ApplyModifications();
|
||||
|
||||
if (App.IsFirstRun || FreshInstall)
|
||||
{
|
||||
Register();
|
||||
// TODO: move this to install/upgrade flow
|
||||
if (FreshInstall)
|
||||
RegisterProgramSize();
|
||||
}
|
||||
|
||||
CheckInstall();
|
||||
|
||||
// at this point we've finished updating our configs
|
||||
App.Settings.Save();
|
||||
App.State.Save();
|
||||
App.ShouldSaveConfigs = false;
|
||||
|
||||
await mutex.ReleaseAsync();
|
||||
|
||||
if (App.IsFirstRun && App.LaunchSettings.IsNoLaunch)
|
||||
Dialog?.ShowSuccess(Resources.Strings.Bootstrapper_SuccessfullyInstalled);
|
||||
else if (!App.LaunchSettings.IsNoLaunch && !_cancelFired)
|
||||
if (!App.LaunchSettings.IsNoLaunch && !_cancelFired)
|
||||
await StartRoblox();
|
||||
}
|
||||
|
||||
@ -272,21 +260,7 @@ namespace Bloxstrap
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::StartRoblox";
|
||||
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_Starting);
|
||||
|
||||
if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll")))
|
||||
{
|
||||
Frontend.ShowMessageBox(
|
||||
Resources.Strings.Bootstrapper_WMFNotFound,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
|
||||
if (!App.LaunchSettings.IsQuiet)
|
||||
Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a");
|
||||
|
||||
Dialog?.CloseBootstrapper();
|
||||
return;
|
||||
}
|
||||
SetStatus(Strings.Bootstrapper_Status_Starting);
|
||||
|
||||
if (_launchMode != LaunchMode.StudioAuth)
|
||||
{
|
||||
@ -450,9 +424,7 @@ namespace Bloxstrap
|
||||
try
|
||||
{
|
||||
// clean up install
|
||||
if (App.IsFirstRun)
|
||||
Directory.Delete(Paths.Base, true);
|
||||
else if (Directory.Exists(_versionFolder))
|
||||
if (Directory.Exists(_versionFolder))
|
||||
Directory.Delete(_versionFolder, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -468,38 +440,6 @@ namespace Bloxstrap
|
||||
#endregion
|
||||
|
||||
#region App Install
|
||||
public static void Register()
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::Register";
|
||||
|
||||
using (RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{App.ProjectName}"))
|
||||
{
|
||||
applicationKey.SetValue("InstallLocation", Paths.Base);
|
||||
}
|
||||
|
||||
// set uninstall key
|
||||
using (RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{App.ProjectName}"))
|
||||
{
|
||||
uninstallKey.SetValue("DisplayIcon", $"{Paths.Application},0");
|
||||
uninstallKey.SetValue("DisplayName", App.ProjectName);
|
||||
uninstallKey.SetValue("DisplayVersion", App.Version);
|
||||
|
||||
if (uninstallKey.GetValue("InstallDate") is null)
|
||||
uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
|
||||
|
||||
uninstallKey.SetValue("InstallLocation", Paths.Base);
|
||||
uninstallKey.SetValue("NoRepair", 1);
|
||||
uninstallKey.SetValue("Publisher", "pizzaboxer");
|
||||
uninstallKey.SetValue("ModifyPath", $"\"{Paths.Application}\" -menu");
|
||||
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");
|
||||
}
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Registered application");
|
||||
}
|
||||
|
||||
public void RegisterProgramSize()
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::RegisterProgramSize";
|
||||
@ -539,57 +479,6 @@ namespace Bloxstrap
|
||||
ProtocolHandler.RegisterExtension(".rbxl");
|
||||
ProtocolHandler.RegisterExtension(".rbxlx");
|
||||
#endif
|
||||
|
||||
if (Environment.ProcessPath is not null && Environment.ProcessPath != Paths.Application)
|
||||
{
|
||||
// in case the user is reinstalling
|
||||
if (File.Exists(Paths.Application) && App.IsFirstRun)
|
||||
{
|
||||
Filesystem.AssertReadOnly(Paths.Application);
|
||||
File.Delete(Paths.Application);
|
||||
}
|
||||
|
||||
// check to make sure bootstrapper is in the install folder
|
||||
if (!File.Exists(Paths.Application))
|
||||
File.Copy(Environment.ProcessPath, Paths.Application);
|
||||
}
|
||||
|
||||
// this SHOULD go under Register(),
|
||||
// but then people who have Bloxstrap v1.0.0 installed won't have this without a reinstall
|
||||
// maybe in a later version?
|
||||
if (!Directory.Exists(Paths.StartMenu))
|
||||
{
|
||||
Directory.CreateDirectory(Paths.StartMenu);
|
||||
}
|
||||
else
|
||||
{
|
||||
// v2.0.0 - rebadge configuration menu as just "Bloxstrap Menu"
|
||||
string oldMenuShortcut = Path.Combine(Paths.StartMenu, $"Configure {App.ProjectName}.lnk");
|
||||
|
||||
if (File.Exists(oldMenuShortcut))
|
||||
File.Delete(oldMenuShortcut);
|
||||
}
|
||||
|
||||
Utility.Shortcut.Create(Paths.Application, "", Path.Combine(Paths.StartMenu, "Play Roblox.lnk"));
|
||||
Utility.Shortcut.Create(Paths.Application, "-menu", Path.Combine(Paths.StartMenu, $"{App.ProjectName} Menu.lnk"));
|
||||
#if STUDIO_FEATURES
|
||||
Utility.Shortcut.Create(Paths.Application, "-ide", Path.Combine(Paths.StartMenu, $"Roblox Studio ({App.ProjectName}).lnk"));
|
||||
#endif
|
||||
|
||||
if (App.Settings.Prop.CreateDesktopIcon)
|
||||
{
|
||||
try
|
||||
{
|
||||
Utility.Shortcut.Create(Paths.Application, "", Path.Combine(Paths.Desktop, "Play Roblox.lnk"));
|
||||
|
||||
// one-time toggle, set it back to false
|
||||
App.Settings.Prop.CreateDesktopIcon = false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// suppress, we likely just don't have write perms for the desktop folder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckForUpdates()
|
||||
@ -606,6 +495,7 @@ namespace Bloxstrap
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Checking for updates...");
|
||||
|
||||
GithubRelease? releaseInfo;
|
||||
|
||||
try
|
||||
{
|
||||
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
|
||||
@ -622,16 +512,16 @@ namespace Bloxstrap
|
||||
return;
|
||||
}
|
||||
|
||||
int versionComparison = Utilities.CompareVersions(App.Version, releaseInfo.TagName);
|
||||
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 == 0 && App.BuildMetadata.CommitRef.StartsWith("tag") || versionComparison == 1)
|
||||
if (versionComparison == VersionComparison.Equal && App.BuildMetadata.CommitRef.StartsWith("tag") || versionComparison == VersionComparison.GreaterThan)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"No updates found");
|
||||
return;
|
||||
}
|
||||
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_UpgradingBloxstrap);
|
||||
SetStatus(Strings.Bootstrapper_Status_UpgradingBloxstrap);
|
||||
|
||||
try
|
||||
{
|
||||
@ -660,7 +550,6 @@ namespace Bloxstrap
|
||||
startInfo.ArgumentList.Add(arg);
|
||||
|
||||
App.Settings.Save();
|
||||
App.ShouldSaveConfigs = false;
|
||||
|
||||
Process.Start(startInfo);
|
||||
|
||||
@ -672,177 +561,11 @@ namespace Bloxstrap
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
|
||||
Frontend.ShowMessageBox(
|
||||
string.Format(Resources.Strings.Bootstrapper_AutoUpdateFailed, releaseInfo.TagName),
|
||||
string.Format(Strings.Bootstrapper_AutoUpdateFailed, releaseInfo.TagName),
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void Uninstall()
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::Uninstall";
|
||||
|
||||
// prompt to shutdown roblox if its currently running
|
||||
if (Process.GetProcessesByName(App.RobloxPlayerAppName).Any() || Process.GetProcessesByName(App.RobloxStudioAppName).Any())
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Prompting to shut down all open Roblox instances");
|
||||
|
||||
MessageBoxResult result = Frontend.ShowMessageBox(
|
||||
Resources.Strings.Bootstrapper_Uninstall_RobloxRunning,
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OKCancel
|
||||
);
|
||||
|
||||
if (result != MessageBoxResult.OK)
|
||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (Process process in Process.GetProcessesByName(App.RobloxPlayerAppName))
|
||||
{
|
||||
process.Kill();
|
||||
process.Close();
|
||||
}
|
||||
|
||||
#if STUDIO_FEATURES
|
||||
foreach (Process process in Process.GetProcessesByName(App.RobloxStudioAppName))
|
||||
{
|
||||
process.Kill();
|
||||
process.Close();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Failed to close process! {ex}");
|
||||
}
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, $"All Roblox processes closed");
|
||||
}
|
||||
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_Uninstalling);
|
||||
|
||||
App.ShouldSaveConfigs = false;
|
||||
bool robloxPlayerStillInstalled = true;
|
||||
bool robloxStudioStillInstalled = true;
|
||||
|
||||
// check if stock bootstrapper is still installed
|
||||
using RegistryKey? bootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
|
||||
if (bootstrapperKey is null)
|
||||
{
|
||||
robloxPlayerStillInstalled = false;
|
||||
|
||||
ProtocolHandler.Unregister("roblox");
|
||||
ProtocolHandler.Unregister("roblox-player");
|
||||
}
|
||||
else
|
||||
{
|
||||
// revert launch uri handler to stock bootstrapper
|
||||
|
||||
string bootstrapperLocation = (string?)bootstrapperKey.GetValue("InstallLocation") + "RobloxPlayerLauncher.exe";
|
||||
|
||||
ProtocolHandler.Register("roblox", "Roblox", bootstrapperLocation);
|
||||
ProtocolHandler.Register("roblox-player", "Roblox", bootstrapperLocation);
|
||||
}
|
||||
|
||||
#if STUDIO_FEATURES
|
||||
using RegistryKey? studioBootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-studio");
|
||||
if (studioBootstrapperKey is null)
|
||||
{
|
||||
robloxStudioStillInstalled = false;
|
||||
|
||||
ProtocolHandler.Unregister("roblox-studio");
|
||||
ProtocolHandler.Unregister("roblox-studio-auth");
|
||||
|
||||
ProtocolHandler.Unregister("Roblox.Place");
|
||||
ProtocolHandler.Unregister(".rbxl");
|
||||
ProtocolHandler.Unregister(".rbxlx");
|
||||
}
|
||||
else
|
||||
{
|
||||
string studioLocation = (string?)studioBootstrapperKey.GetValue("InstallLocation") + "RobloxStudioBeta.exe"; // points to studio exe instead of bootstrapper
|
||||
ProtocolHandler.Register("roblox-studio", "Roblox", studioLocation);
|
||||
ProtocolHandler.Register("roblox-studio-auth", "Roblox", studioLocation);
|
||||
|
||||
ProtocolHandler.RegisterRobloxPlace(studioLocation);
|
||||
}
|
||||
#endif
|
||||
|
||||
// if the folder we're installed to does not end with "Bloxstrap", we're installed to a user-selected folder
|
||||
// in which case, chances are they chose to install to somewhere they didn't really mean to (prior to the added warning in 2.4.0)
|
||||
// if so, we're walking on eggshells and have to ensure we only clean up what we need to clean up
|
||||
bool cautiousUninstall = !Paths.Base.ToLower().EndsWith(App.ProjectName.ToLower());
|
||||
|
||||
var cleanupSequence = new List<Action>
|
||||
{
|
||||
() => Registry.CurrentUser.DeleteSubKey($@"Software\{App.ProjectName}"),
|
||||
() => Directory.Delete(Paths.StartMenu, true),
|
||||
() => File.Delete(Path.Combine(Paths.Desktop, "Play Roblox.lnk")),
|
||||
() => Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{App.ProjectName}")
|
||||
};
|
||||
|
||||
if (cautiousUninstall)
|
||||
{
|
||||
cleanupSequence.Add(() => Directory.Delete(Paths.Downloads, true));
|
||||
cleanupSequence.Add(() => Directory.Delete(Paths.Modifications, true));
|
||||
cleanupSequence.Add(() => Directory.Delete(Paths.Versions, true));
|
||||
cleanupSequence.Add(() => Directory.Delete(Paths.Logs, true));
|
||||
|
||||
cleanupSequence.Add(() => File.Delete(App.Settings.FileLocation));
|
||||
cleanupSequence.Add(() => File.Delete(App.State.FileLocation));
|
||||
}
|
||||
else
|
||||
{
|
||||
cleanupSequence.Add(() => Directory.Delete(Paths.Base, true));
|
||||
}
|
||||
|
||||
string robloxFolder = Path.Combine(Paths.LocalAppData, "Roblox");
|
||||
|
||||
if (!robloxPlayerStillInstalled && !robloxStudioStillInstalled && Directory.Exists(robloxFolder))
|
||||
cleanupSequence.Add(() => Directory.Delete(robloxFolder, true));
|
||||
|
||||
foreach (var process in cleanupSequence)
|
||||
{
|
||||
try
|
||||
{
|
||||
process();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Encountered exception when running cleanup sequence (#{cleanupSequence.IndexOf(process)})");
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
}
|
||||
}
|
||||
|
||||
Action? callback = null;
|
||||
|
||||
if (Directory.Exists(Paths.Base))
|
||||
{
|
||||
callback = delegate
|
||||
{
|
||||
// this is definitely one of the workaround hacks of all time
|
||||
// could antiviruses falsely detect this as malicious behaviour though?
|
||||
// "hmm whats this program doing running a cmd command chain quietly in the background that auto deletes an entire folder"
|
||||
|
||||
string deleteCommand;
|
||||
|
||||
if (cautiousUninstall)
|
||||
deleteCommand = $"del /Q \"{Paths.Application}\"";
|
||||
else
|
||||
deleteCommand = $"del /Q \"{Paths.Base}\\*\" && rmdir \"{Paths.Base}\"";
|
||||
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c timeout 5 && {deleteCommand}",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Dialog?.ShowSuccess(Resources.Strings.Bootstrapper_SuccessfullyUninstalled, callback);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Roblox Install
|
||||
@ -852,7 +575,7 @@ namespace Bloxstrap
|
||||
|
||||
_isInstalling = true;
|
||||
|
||||
SetStatus(FreshInstall ? Resources.Strings.Bootstrapper_Status_Installing : Resources.Strings.Bootstrapper_Status_Upgrading);
|
||||
SetStatus(FreshInstall ? Strings.Bootstrapper_Status_Installing : Strings.Bootstrapper_Status_Upgrading);
|
||||
|
||||
Directory.CreateDirectory(Paths.Base);
|
||||
Directory.CreateDirectory(Paths.Downloads);
|
||||
@ -866,7 +589,7 @@ namespace Bloxstrap
|
||||
if (Filesystem.GetFreeDiskSpace(Paths.Base) < totalSizeRequired)
|
||||
{
|
||||
Frontend.ShowMessageBox(
|
||||
Resources.Strings.Bootstrapper_NotEnoughSpace,
|
||||
Strings.Bootstrapper_NotEnoughSpace,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
|
||||
@ -911,7 +634,7 @@ namespace Bloxstrap
|
||||
if (Dialog is not null)
|
||||
{
|
||||
Dialog.ProgressStyle = ProgressBarStyle.Marquee;
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_Configuring);
|
||||
SetStatus(Strings.Bootstrapper_Status_Configuring);
|
||||
}
|
||||
|
||||
// wait for all packages to finish extracting, with an exception for the webview2 runtime installer
|
||||
@ -996,7 +719,7 @@ namespace Bloxstrap
|
||||
}
|
||||
|
||||
// don't register program size until the program is registered, which will be done after this
|
||||
if (!App.IsFirstRun && !FreshInstall)
|
||||
if (!FreshInstall)
|
||||
RegisterProgramSize();
|
||||
|
||||
if (Dialog is not null)
|
||||
@ -1004,20 +727,10 @@ namespace Bloxstrap
|
||||
|
||||
_isInstalling = false;
|
||||
}
|
||||
|
||||
|
||||
private async Task InstallWebView2()
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::InstallWebView2";
|
||||
|
||||
// check if the webview2 runtime needs to be installed
|
||||
// webview2 can either be installed be per-user or globally, so we need to check in both hklm and hkcu
|
||||
// https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#detect-if-a-suitable-webview2-runtime-is-already-installed
|
||||
|
||||
using RegistryKey? hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||
using RegistryKey? hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||
|
||||
if (hklmKey is not null || hkcuKey is not null)
|
||||
return;
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Installing runtime...");
|
||||
|
||||
@ -1036,7 +749,7 @@ namespace Bloxstrap
|
||||
await ExtractPackage(package);
|
||||
}
|
||||
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_InstallingWebView2);
|
||||
SetStatus(Strings.Bootstrapper_Status_InstallingWebView2);
|
||||
|
||||
ProcessStartInfo startInfo = new()
|
||||
{
|
||||
@ -1065,7 +778,7 @@ namespace Bloxstrap
|
||||
if (File.Exists(injectorLocation))
|
||||
{
|
||||
Frontend.ShowMessageBox(
|
||||
Resources.Strings.Bootstrapper_HyperionUpdateInfo,
|
||||
Strings.Bootstrapper_HyperionUpdateInfo,
|
||||
MessageBoxImage.Warning
|
||||
);
|
||||
|
||||
@ -1086,7 +799,7 @@ namespace Bloxstrap
|
||||
return;
|
||||
}
|
||||
|
||||
SetStatus(Resources.Strings.Bootstrapper_Status_ApplyingModifications);
|
||||
SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);
|
||||
|
||||
// set executable flags for fullscreen optimizations
|
||||
App.Logger.WriteLine(LOG_IDENT, "Checking executable flags...");
|
||||
@ -1212,12 +925,6 @@ namespace Bloxstrap
|
||||
|
||||
string modFontFamiliesFolder = Path.Combine(Paths.Modifications, "content\\fonts\\families");
|
||||
|
||||
if (App.IsFirstRun && App.CustomFontLocation is not null)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Paths.CustomFont)!);
|
||||
File.Copy(App.CustomFontLocation, Paths.CustomFont, true);
|
||||
}
|
||||
|
||||
if (File.Exists(Paths.CustomFont))
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Begin font check");
|
||||
|
@ -9,9 +9,11 @@
|
||||
{
|
||||
ERROR_SUCCESS = 0,
|
||||
ERROR_INVALID_FUNCTION = 1,
|
||||
ERROR_FILE_NOT_FOUND = 2,
|
||||
|
||||
ERROR_CANCELLED = 1223,
|
||||
ERROR_INSTALL_USEREXIT = 1602,
|
||||
ERROR_INSTALL_FAILURE = 1603,
|
||||
ERROR_CANCELLED = 1223,
|
||||
|
||||
CO_E_APPNOTFOUND = -2147221003
|
||||
}
|
||||
|
9
Bloxstrap/Enums/NextAction.cs
Normal file
9
Bloxstrap/Enums/NextAction.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Bloxstrap.Enums
|
||||
{
|
||||
public enum NextAction
|
||||
{
|
||||
Terminate,
|
||||
LaunchSettings,
|
||||
LaunchRoblox
|
||||
}
|
||||
}
|
9
Bloxstrap/Enums/VersionComparison.cs
Normal file
9
Bloxstrap/Enums/VersionComparison.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Bloxstrap.Enums
|
||||
{
|
||||
enum VersionComparison
|
||||
{
|
||||
LessThan = -1,
|
||||
Equal = 0,
|
||||
GreaterThan = 1
|
||||
}
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
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 = Frontend.ShowMessageBox(
|
||||
string.Format(Resources.Strings.InstallChecker_NotInstalledProperly, _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}");
|
||||
|
||||
Frontend.ShowMessageBox(
|
||||
string.Format(Resources.Strings.InstallChecker_DriveLetterChangeDetected, driveName, newDriveName),
|
||||
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 = Frontend.ShowMessageBox(
|
||||
string.Format(Resources.Strings.InstallChecker_InstallDriveMissing, driveName),
|
||||
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.LaunchSettings.IsQuiet)
|
||||
return;
|
||||
|
||||
App.IsSetupComplete = false;
|
||||
|
||||
App.FastFlags.Load();
|
||||
Frontend.ShowLanguageSelection();
|
||||
Frontend.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 <BaseFolder>/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.LaunchSettings.IsUpgrade || isAutoUpgrade)
|
||||
{
|
||||
result = MessageBoxResult.Yes;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Frontend.ShowMessageBox(
|
||||
Resources.Strings.InstallChecker_VersionDifferentThanInstalled,
|
||||
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") && currentVersionInfo.ProductVersion is not null)
|
||||
{
|
||||
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();
|
||||
}
|
||||
else if (existingVersionInfo.ProductVersion == "2.5.4")
|
||||
{
|
||||
if (App.Settings.Prop.UseDisableAppPatch)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Path.Combine(Paths.Modifications, "ExtraContent\\places\\Mobile.rbxl"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
}
|
||||
|
||||
App.Settings.Prop.EnableActivityTracking = true;
|
||||
}
|
||||
|
||||
if (App.Settings.Prop.BootstrapperStyle == BootstrapperStyle.ClassicFluentDialog)
|
||||
App.Settings.Prop.BootstrapperStyle = BootstrapperStyle.FluentDialog;
|
||||
|
||||
_ = int.TryParse(App.FastFlags.GetPreset("Rendering.Framerate"), out int x);
|
||||
if (x == 0)
|
||||
{
|
||||
App.FastFlags.SetPreset("Rendering.Framerate", null);
|
||||
App.FastFlags.Save();
|
||||
}
|
||||
|
||||
App.Settings.Save();
|
||||
}
|
||||
}
|
||||
|
||||
if (isAutoUpgrade)
|
||||
{
|
||||
Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Release-notes-for-Bloxstrap-v{currentVersionInfo.ProductVersion}");
|
||||
}
|
||||
else if (!App.LaunchSettings.IsQuiet)
|
||||
{
|
||||
Frontend.ShowMessageBox(
|
||||
string.Format(Resources.Strings.InstallChecker_Updated, currentVersionInfo.ProductVersion),
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OK
|
||||
);
|
||||
|
||||
Frontend.ShowMenu();
|
||||
|
||||
App.Terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
470
Bloxstrap/Installer.cs
Normal file
470
Bloxstrap/Installer.cs
Normal file
@ -0,0 +1,470 @@
|
||||
using System.DirectoryServices;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
using Bloxstrap.Resources;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
internal class Installer
|
||||
{
|
||||
private static string DesktopShortcut => Path.Combine(Paths.Desktop, "Bloxstrap.lnk");
|
||||
|
||||
private static string StartMenuShortcut => Path.Combine(Paths.WindowsStartMenu, "Bloxstrap.lnk");
|
||||
|
||||
public string InstallLocation = Path.Combine(Paths.LocalAppData, "Bloxstrap");
|
||||
|
||||
public bool CreateDesktopShortcuts = true;
|
||||
|
||||
public bool CreateStartMenuShortcuts = true;
|
||||
|
||||
public bool IsImplicitInstall = false;
|
||||
|
||||
public string InstallLocationError { get; set; } = "";
|
||||
|
||||
public void DoInstall()
|
||||
{
|
||||
// should've been created earlier from the write test anyway
|
||||
Directory.CreateDirectory(InstallLocation);
|
||||
|
||||
Paths.Initialize(InstallLocation);
|
||||
|
||||
if (!IsImplicitInstall)
|
||||
{
|
||||
Filesystem.AssertReadOnly(Paths.Application);
|
||||
File.Copy(Paths.Process, Paths.Application, true);
|
||||
}
|
||||
|
||||
// TODO: registry access checks, i'll need to look back on issues to see what the error looks like
|
||||
using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
|
||||
{
|
||||
uninstallKey.SetValue("DisplayIcon", $"{Paths.Application},0");
|
||||
uninstallKey.SetValue("DisplayName", App.ProjectName);
|
||||
|
||||
uninstallKey.SetValue("DisplayVersion", App.Version);
|
||||
|
||||
if (uninstallKey.GetValue("InstallDate") is null)
|
||||
uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
|
||||
|
||||
uninstallKey.SetValue("InstallLocation", Paths.Base);
|
||||
uninstallKey.SetValue("NoRepair", 1);
|
||||
uninstallKey.SetValue("Publisher", "pizzaboxer");
|
||||
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");
|
||||
}
|
||||
|
||||
// only register player, for the scenario where the user installs bloxstrap, closes it,
|
||||
// and then launches from the website expecting it to work
|
||||
// studio can be implicitly registered when it's first launched manually
|
||||
ProtocolHandler.Register("roblox", "Roblox", Paths.Application);
|
||||
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application);
|
||||
|
||||
// TODO: implicit installation needs to reregister studio
|
||||
|
||||
if (CreateDesktopShortcuts)
|
||||
Shortcut.Create(Paths.Application, "", DesktopShortcut);
|
||||
|
||||
if (CreateStartMenuShortcuts)
|
||||
Shortcut.Create(Paths.Application, "", StartMenuShortcut);
|
||||
|
||||
// existing configuration persisting from an earlier install
|
||||
App.Settings.Load();
|
||||
App.State.Load();
|
||||
App.FastFlags.Load();
|
||||
}
|
||||
|
||||
private bool ValidateLocation()
|
||||
{
|
||||
// prevent from installing to the root of a drive
|
||||
if (InstallLocation.Length <= 3)
|
||||
return false;
|
||||
|
||||
// unc path, just to be safe
|
||||
if (InstallLocation.StartsWith("\\\\"))
|
||||
return false;
|
||||
|
||||
// prevent from installing to a onedrive folder
|
||||
if (InstallLocation.Contains("OneDrive", StringComparison.InvariantCultureIgnoreCase))
|
||||
return false;
|
||||
|
||||
// prevent from installing to an essential user profile folder (e.g. Documents, Downloads, Contacts idk)
|
||||
if (String.Compare(Directory.GetParent(InstallLocation)?.FullName, Paths.UserProfile, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CheckInstallLocation()
|
||||
{
|
||||
if (string.IsNullOrEmpty(InstallLocation))
|
||||
{
|
||||
InstallLocationError = Strings.Menu_InstallLocation_NotSet;
|
||||
}
|
||||
else if (!ValidateLocation())
|
||||
{
|
||||
InstallLocationError = Strings.Menu_InstallLocation_CantInstall;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsImplicitInstall
|
||||
&& !InstallLocation.EndsWith(App.ProjectName, StringComparison.InvariantCultureIgnoreCase)
|
||||
&& Directory.Exists(InstallLocation)
|
||||
&& Directory.EnumerateFileSystemEntries(InstallLocation).Any())
|
||||
{
|
||||
string suggestedChange = Path.Combine(InstallLocation, App.ProjectName);
|
||||
|
||||
MessageBoxResult result = Frontend.ShowMessageBox(
|
||||
String.Format(Strings.Menu_InstallLocation_NotEmpty, suggestedChange),
|
||||
MessageBoxImage.Warning,
|
||||
MessageBoxButton.YesNoCancel,
|
||||
MessageBoxResult.Yes
|
||||
);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
InstallLocation = suggestedChange;
|
||||
else if (result == MessageBoxResult.Cancel || result == MessageBoxResult.None)
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// check if we can write to the directory (a bit hacky but eh)
|
||||
string testFile = Path.Combine(InstallLocation, $"{App.ProjectName}WriteTest.txt");
|
||||
|
||||
Directory.CreateDirectory(InstallLocation);
|
||||
File.WriteAllText(testFile, "");
|
||||
File.Delete(testFile);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
InstallLocationError = Strings.Menu_InstallLocation_NoWritePerms;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InstallLocationError = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
return String.IsNullOrEmpty(InstallLocationError);
|
||||
}
|
||||
|
||||
public static void DoUninstall(bool keepData)
|
||||
{
|
||||
const string LOG_IDENT = "Installer::DoUninstall";
|
||||
|
||||
var processes = new List<Process>();
|
||||
processes.AddRange(Process.GetProcessesByName(App.RobloxPlayerAppName));
|
||||
|
||||
#if STUDIO_FEATURES
|
||||
processes.AddRange(Process.GetProcessesByName(App.RobloxStudioAppName));
|
||||
#endif
|
||||
|
||||
// prompt to shutdown roblox if its currently running
|
||||
if (processes.Any())
|
||||
{
|
||||
if (!App.LaunchSettings.IsQuiet)
|
||||
{
|
||||
MessageBoxResult result = Frontend.ShowMessageBox(
|
||||
Strings.Bootstrapper_Uninstall_RobloxRunning,
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OKCancel
|
||||
);
|
||||
|
||||
if (result != MessageBoxResult.OK)
|
||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var process in processes)
|
||||
{
|
||||
process.Kill();
|
||||
process.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Failed to close process! {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
string robloxFolder = Path.Combine(Paths.LocalAppData, "Roblox");
|
||||
bool playerStillInstalled = true;
|
||||
bool studioStillInstalled = true;
|
||||
|
||||
// check if stock bootstrapper is still installed
|
||||
using var playerKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
|
||||
var playerFolder = playerKey?.GetValue("InstallLocation");
|
||||
|
||||
if (playerKey is null || playerFolder is not string)
|
||||
{
|
||||
playerStillInstalled = false;
|
||||
|
||||
ProtocolHandler.Unregister("roblox");
|
||||
ProtocolHandler.Unregister("roblox-player");
|
||||
}
|
||||
else
|
||||
{
|
||||
// revert launch uri handler to stock bootstrapper
|
||||
string playerPath = Path.Combine((string)playerFolder, "RobloxPlayerBeta.exe");
|
||||
|
||||
ProtocolHandler.Register("roblox", "Roblox", playerPath);
|
||||
ProtocolHandler.Register("roblox-player", "Roblox", playerPath);
|
||||
}
|
||||
|
||||
using RegistryKey? studioBootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-studio");
|
||||
if (studioBootstrapperKey is null)
|
||||
{
|
||||
studioStillInstalled = false;
|
||||
|
||||
#if STUDIO_FEATURES
|
||||
ProtocolHandler.Unregister("roblox-studio");
|
||||
ProtocolHandler.Unregister("roblox-studio-auth");
|
||||
|
||||
ProtocolHandler.Unregister("Roblox.Place");
|
||||
ProtocolHandler.Unregister(".rbxl");
|
||||
ProtocolHandler.Unregister(".rbxlx");
|
||||
#endif
|
||||
}
|
||||
#if STUDIO_FEATURES
|
||||
else
|
||||
{
|
||||
string studioLocation = (string?)studioBootstrapperKey.GetValue("InstallLocation") + "RobloxStudioBeta.exe"; // points to studio exe instead of bootstrapper
|
||||
ProtocolHandler.Register("roblox-studio", "Roblox", studioLocation);
|
||||
ProtocolHandler.Register("roblox-studio-auth", "Roblox", studioLocation);
|
||||
|
||||
ProtocolHandler.RegisterRobloxPlace(studioLocation);
|
||||
}
|
||||
#endif
|
||||
|
||||
var cleanupSequence = new List<Action>
|
||||
{
|
||||
() => File.Delete(DesktopShortcut),
|
||||
() => File.Delete(StartMenuShortcut),
|
||||
|
||||
() => Directory.Delete(Paths.Versions, true),
|
||||
() => Directory.Delete(Paths.Downloads, true),
|
||||
};
|
||||
|
||||
if (!keepData)
|
||||
{
|
||||
cleanupSequence.AddRange(new List<Action>
|
||||
{
|
||||
() => Directory.Delete(Paths.Modifications, true),
|
||||
() => Directory.Delete(Paths.Logs, true),
|
||||
|
||||
() => File.Delete(App.Settings.FileLocation),
|
||||
() => File.Delete(App.State.FileLocation), // TODO: maybe this should always be deleted? not sure yet
|
||||
});
|
||||
}
|
||||
|
||||
bool deleteFolder = false;
|
||||
|
||||
if (Directory.Exists(Paths.Base))
|
||||
{
|
||||
var folderFiles = Directory.GetFiles(Paths.Base);
|
||||
deleteFolder = folderFiles.Length == 1 && folderFiles.First().EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
if (deleteFolder)
|
||||
cleanupSequence.Add(() => Directory.Delete(Paths.Base, true));
|
||||
|
||||
if (!playerStillInstalled && !studioStillInstalled && Directory.Exists(robloxFolder))
|
||||
cleanupSequence.Add(() => Directory.Delete(robloxFolder, true));
|
||||
|
||||
cleanupSequence.Add(() => Registry.CurrentUser.DeleteSubKey(App.UninstallKey));
|
||||
|
||||
foreach (var process in cleanupSequence)
|
||||
{
|
||||
try
|
||||
{
|
||||
process();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Encountered exception when running cleanup sequence (#{cleanupSequence.IndexOf(process)})");
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (Directory.Exists(Paths.Base))
|
||||
{
|
||||
// this is definitely one of the workaround hacks of all time
|
||||
|
||||
string deleteCommand;
|
||||
|
||||
if (deleteFolder)
|
||||
deleteCommand = $"del /Q \"{Paths.Base}\\*\" && rmdir \"{Paths.Base}\"";
|
||||
else
|
||||
deleteCommand = $"del /Q \"{Paths.Application}\"";
|
||||
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c timeout 5 && {deleteCommand}",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void HandleUpgrade()
|
||||
{
|
||||
const string LOG_IDENT = "Installer::HandleUpgrade";
|
||||
|
||||
if (!File.Exists(Paths.Application) || Paths.Process == Paths.Application)
|
||||
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"));
|
||||
|
||||
var existingVer = FileVersionInfo.GetVersionInfo(Paths.Application).ProductVersion;
|
||||
var currentVer = FileVersionInfo.GetVersionInfo(Paths.Process).ProductVersion;
|
||||
|
||||
if (MD5Hash.FromFile(Paths.Process) == MD5Hash.FromFile(Paths.Application))
|
||||
return;
|
||||
|
||||
// silently upgrade version if the command line flag is set or if we're launching from an auto update
|
||||
if (!App.LaunchSettings.IsUpgrade && !isAutoUpgrade)
|
||||
{
|
||||
var result = Frontend.ShowMessageBox(
|
||||
Strings.InstallChecker_VersionDifferentThanInstalled,
|
||||
MessageBoxImage.Question,
|
||||
MessageBoxButton.YesNo
|
||||
);
|
||||
|
||||
if (result != MessageBoxResult.Yes)
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
|
||||
{
|
||||
uninstallKey.SetValue("DisplayVersion", App.Version);
|
||||
}
|
||||
|
||||
// update migrations
|
||||
|
||||
if (existingVer is not null)
|
||||
{
|
||||
if (Utilities.CompareVersions(existingVer, "2.5.0") == VersionComparison.LessThan)
|
||||
{
|
||||
App.FastFlags.SetValue("DFFlagDisableDPIScale", null);
|
||||
App.FastFlags.SetValue("DFFlagVariableDPIScale2", null);
|
||||
}
|
||||
|
||||
if (Utilities.CompareVersions(existingVer, "2.5.1") == VersionComparison.LessThan)
|
||||
{
|
||||
App.FastFlags.SetValue("FIntDebugForceMSAASamples", null);
|
||||
|
||||
if (App.FastFlags.GetPreset("UI.Menu.Style.DisableV2") is not null)
|
||||
App.FastFlags.SetPreset("UI.Menu.Style.ABTest", false);
|
||||
}
|
||||
|
||||
if (Utilities.CompareVersions(existingVer, "2.6.0") == VersionComparison.LessThan)
|
||||
{
|
||||
if (App.Settings.Prop.UseDisableAppPatch)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Path.Combine(Paths.Modifications, "ExtraContent\\places\\Mobile.rbxl"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
}
|
||||
|
||||
App.Settings.Prop.EnableActivityTracking = true;
|
||||
}
|
||||
|
||||
if (App.Settings.Prop.BootstrapperStyle == BootstrapperStyle.ClassicFluentDialog)
|
||||
App.Settings.Prop.BootstrapperStyle = BootstrapperStyle.FluentDialog;
|
||||
|
||||
_ = int.TryParse(App.FastFlags.GetPreset("Rendering.Framerate"), out int x);
|
||||
if (x == 0)
|
||||
{
|
||||
App.FastFlags.SetPreset("Rendering.Framerate", null);
|
||||
}
|
||||
}
|
||||
|
||||
if (Utilities.CompareVersions(existingVer, "2.8.0") == VersionComparison.LessThan)
|
||||
{
|
||||
string oldDesktopPath = Path.Combine(Paths.Desktop, "Play Roblox.lnk");
|
||||
string oldStartPath = Path.Combine(Paths.WindowsStartMenu, "Bloxstrap");
|
||||
|
||||
if (File.Exists(oldDesktopPath))
|
||||
File.Move(oldDesktopPath, DesktopShortcut);
|
||||
|
||||
if (Directory.Exists(oldStartPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(oldStartPath, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
}
|
||||
|
||||
Shortcut.Create(Paths.Application, "", StartMenuShortcut);
|
||||
}
|
||||
|
||||
Registry.CurrentUser.DeleteSubKeyTree("Software\\Bloxstrap", false);
|
||||
}
|
||||
|
||||
App.Settings.Save();
|
||||
App.FastFlags.Save();
|
||||
}
|
||||
|
||||
if (isAutoUpgrade)
|
||||
{
|
||||
Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Release-notes-for-Bloxstrap-v{currentVer}");
|
||||
}
|
||||
else if (!App.LaunchSettings.IsQuiet)
|
||||
{
|
||||
Frontend.ShowMessageBox(
|
||||
string.Format(Strings.InstallChecker_Updated, currentVer),
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OK
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -37,12 +37,6 @@ namespace Bloxstrap
|
||||
{
|
||||
string LOG_IDENT = $"{LOG_IDENT_CLASS}::Save";
|
||||
|
||||
if (!App.ShouldSaveConfigs)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Save request ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Saving to {FileLocation}...");
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!);
|
||||
|
230
Bloxstrap/LaunchHandler.cs
Normal file
230
Bloxstrap/LaunchHandler.cs
Normal file
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
using Bloxstrap.UI.Elements.Dialogs;
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
public static class LaunchHandler
|
||||
{
|
||||
public static void ProcessNextAction(NextAction action, bool isUnfinishedInstall = false)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case NextAction.LaunchSettings:
|
||||
LaunchSettings();
|
||||
break;
|
||||
|
||||
case NextAction.LaunchRoblox:
|
||||
LaunchRoblox();
|
||||
break;
|
||||
|
||||
default:
|
||||
App.Terminate(isUnfinishedInstall ? ErrorCode.ERROR_INSTALL_USEREXIT : ErrorCode.ERROR_SUCCESS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ProcessLaunchArgs()
|
||||
{
|
||||
// this order is specific
|
||||
|
||||
if (App.LaunchSettings.IsUninstall)
|
||||
LaunchUninstaller();
|
||||
else if (App.LaunchSettings.IsMenuLaunch)
|
||||
LaunchSettings();
|
||||
else if (App.LaunchSettings.IsRobloxLaunch)
|
||||
LaunchRoblox();
|
||||
else if (!App.LaunchSettings.IsQuiet)
|
||||
LaunchMenu();
|
||||
}
|
||||
|
||||
public static void LaunchInstaller()
|
||||
{
|
||||
// TODO: detect duplicate launch, mutex maybe?
|
||||
|
||||
if (App.LaunchSettings.IsUninstall)
|
||||
{
|
||||
Frontend.ShowMessageBox(Strings.Bootstrapper_FirstRunUninstall, MessageBoxImage.Error);
|
||||
App.Terminate(ErrorCode.ERROR_INVALID_FUNCTION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (App.LaunchSettings.IsQuiet)
|
||||
{
|
||||
var installer = new Installer();
|
||||
|
||||
if (!installer.CheckInstallLocation())
|
||||
App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
||||
|
||||
installer.DoInstall();
|
||||
|
||||
ProcessLaunchArgs();
|
||||
}
|
||||
else
|
||||
{
|
||||
new LanguageSelectorDialog().ShowDialog();
|
||||
|
||||
var installer = new UI.Elements.Installer.MainWindow();
|
||||
installer.ShowDialog();
|
||||
|
||||
ProcessNextAction(installer.CloseAction, !installer.Finished);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void LaunchUninstaller()
|
||||
{
|
||||
bool confirmed = false;
|
||||
bool keepData = true;
|
||||
|
||||
if (App.LaunchSettings.IsQuiet)
|
||||
{
|
||||
confirmed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dialog = new UninstallerDialog();
|
||||
dialog.ShowDialog();
|
||||
|
||||
confirmed = dialog.Confirmed;
|
||||
keepData = dialog.KeepData;
|
||||
}
|
||||
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
Installer.DoUninstall(keepData);
|
||||
|
||||
Frontend.ShowMessageBox(Strings.Bootstrapper_SuccessfullyUninstalled, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
public static void LaunchSettings()
|
||||
{
|
||||
const string LOG_IDENT = "LaunchHandler::LaunchSettings";
|
||||
|
||||
// TODO: move to mutex (especially because multi language whatever)
|
||||
|
||||
Process? menuProcess = Utilities.GetProcessesSafe().Where(x => x.MainWindowTitle == Strings.Menu_Title).FirstOrDefault();
|
||||
|
||||
if (menuProcess is not null)
|
||||
{
|
||||
var handle = menuProcess.MainWindowHandle;
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Found an already existing menu window with handle {handle}");
|
||||
PInvoke.SetForegroundWindow((HWND)handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1 && !App.LaunchSettings.IsQuiet;
|
||||
new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
public static void LaunchMenu()
|
||||
{
|
||||
var dialog = new LaunchMenuDialog();
|
||||
dialog.ShowDialog();
|
||||
|
||||
ProcessNextAction(dialog.CloseAction);
|
||||
}
|
||||
|
||||
public static void LaunchRoblox()
|
||||
{
|
||||
const string LOG_IDENT = "LaunchHandler::LaunchRoblox";
|
||||
|
||||
bool installWebView2 = false;
|
||||
|
||||
if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll")))
|
||||
{
|
||||
Frontend.ShowMessageBox(Strings.Bootstrapper_WMFNotFound, MessageBoxImage.Error);
|
||||
|
||||
if (!App.LaunchSettings.IsQuiet)
|
||||
Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a");
|
||||
|
||||
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
{
|
||||
using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||
using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||
|
||||
if (hklmKey is null && hkcuKey is null)
|
||||
installWebView2 = Frontend.ShowMessageBox(Strings.Bootstrapper_WebView2NotFound, MessageBoxImage.Warning, MessageBoxButton.YesNo, MessageBoxResult.Yes) == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _))
|
||||
{
|
||||
// this currently doesn't work very well since it relies on checking the existence of the singleton mutex
|
||||
// which often hangs around for a few seconds after the window closes
|
||||
// it would be better to have this rely on the activity tracker when we implement IPC in the planned refactoring
|
||||
|
||||
var result = Frontend.ShowMessageBox(Strings.Bootstrapper_ConfirmLaunch, MessageBoxImage.Warning, MessageBoxButton.YesNo);
|
||||
|
||||
if (result != MessageBoxResult.Yes)
|
||||
{
|
||||
App.Terminate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
App.NotifyIcon = new();
|
||||
|
||||
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
||||
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
||||
var bootstrapper = new Bootstrapper(App.LaunchSettings.RobloxLaunchArgs, App.LaunchSettings.RobloxLaunchMode, installWebView2);
|
||||
IBootstrapperDialog? dialog = null;
|
||||
|
||||
if (!App.LaunchSettings.IsQuiet)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog");
|
||||
dialog = App.Settings.Prop.BootstrapperStyle.GetNew();
|
||||
bootstrapper.Dialog = dialog;
|
||||
dialog.Bootstrapper = bootstrapper;
|
||||
}
|
||||
|
||||
Task bootstrapperTask = Task.Run(async () => await bootstrapper.Run()).ContinueWith(t =>
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished");
|
||||
|
||||
// notifyicon is blocking main thread, must be disposed here
|
||||
App.NotifyIcon?.Dispose();
|
||||
|
||||
if (t.IsFaulted)
|
||||
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
||||
|
||||
if (t.Exception is null)
|
||||
return;
|
||||
|
||||
App.Logger.WriteException(LOG_IDENT, t.Exception);
|
||||
|
||||
Exception exception = t.Exception;
|
||||
|
||||
#if !DEBUG
|
||||
if (t.Exception.GetType().ToString() == "System.AggregateException")
|
||||
exception = t.Exception.InnerException!;
|
||||
#endif
|
||||
|
||||
App.FinalizeExceptionHandling(exception, false);
|
||||
});
|
||||
|
||||
// this ordering is very important as all wpf windows are shown as modal dialogs, mess it up and you'll end up blocking input to one of them
|
||||
dialog?.ShowBootstrapper();
|
||||
|
||||
if (!App.LaunchSettings.IsNoLaunch && App.Settings.Prop.EnableActivityTracking)
|
||||
App.NotifyIcon?.InitializeContextMenu();
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish");
|
||||
|
||||
bootstrapperTask.Wait();
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,11 @@ namespace Bloxstrap
|
||||
{
|
||||
public class LaunchSettings
|
||||
{
|
||||
[LaunchFlag(new[] { "-preferences", "-menu" })]
|
||||
public bool IsMenuLaunch { get; private set; } = false;
|
||||
[LaunchFlag(new[] { "-preferences", "-menu", "-settings" })]
|
||||
public bool IsMenuLaunch { get; set; } = false;
|
||||
|
||||
[LaunchFlag("-player")]
|
||||
public bool IsRobloxLaunch { get; set; } = false;
|
||||
|
||||
[LaunchFlag("-quiet")]
|
||||
public bool IsQuiet { get; private set; } = false;
|
||||
|
@ -16,6 +16,7 @@
|
||||
{
|
||||
const string LOG_IDENT = "Logger::Initialize";
|
||||
|
||||
// TODO: <Temp>/Bloxstrap/Logs/
|
||||
string directory = useTempDir ? Path.Combine(Paths.LocalAppData, "Temp") : Path.Combine(Paths.Base, "Logs");
|
||||
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");
|
||||
string filename = $"{App.ProjectName}_{timestamp}.log";
|
||||
|
@ -7,7 +7,7 @@
|
||||
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);
|
||||
public static string StartMenu => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs", App.ProjectName);
|
||||
public static string WindowsStartMenu => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs");
|
||||
public static string System => Environment.GetFolderPath(Environment.SpecialFolder.System);
|
||||
|
||||
public static string Process => Environment.ProcessPath!;
|
||||
@ -35,8 +35,6 @@
|
||||
Modifications = Path.Combine(Base, "Modifications");
|
||||
|
||||
Application = Path.Combine(Base, $"{App.ProjectName}.exe");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,9 +73,9 @@ namespace Bloxstrap
|
||||
{
|
||||
string handlerArgs = $"\"{handler}\" %1";
|
||||
|
||||
using RegistryKey uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}");
|
||||
using RegistryKey uriIconKey = uriKey.CreateSubKey("DefaultIcon");
|
||||
using RegistryKey uriCommandKey = uriKey.CreateSubKey(@"shell\open\command");
|
||||
using var uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}");
|
||||
using var uriIconKey = uriKey.CreateSubKey("DefaultIcon");
|
||||
using var uriCommandKey = uriKey.CreateSubKey(@"shell\open\command");
|
||||
|
||||
if (uriKey.GetValue("") is null)
|
||||
{
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 250 KiB |
432
Bloxstrap/Resources/Strings.Designer.cs
generated
432
Bloxstrap/Resources/Strings.Designer.cs
generated
@ -296,6 +296,15 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You currently do not have the WebView2 runtime installed. Some Roblox features will not work properly without it, such as the desktop app. Would you like to download it now?.
|
||||
/// </summary>
|
||||
public static string Bootstrapper_WebView2NotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("Bootstrapper.WebView2NotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Roblox requires the use of Windows Media Foundation components. You appear to be missing them, likely because you are using an N edition of Windows. Please install them first, and then launch Roblox..
|
||||
/// </summary>
|
||||
@ -440,6 +449,24 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Back.
|
||||
/// </summary>
|
||||
public static string Common_Navigation_Back {
|
||||
get {
|
||||
return ResourceManager.GetString("Common.Navigation.Back", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Next.
|
||||
/// </summary>
|
||||
public static string Common_Navigation_Next {
|
||||
get {
|
||||
return ResourceManager.GetString("Common.Navigation.Next", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New.
|
||||
/// </summary>
|
||||
@ -657,6 +684,15 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bloxstrap was unable to create shortcuts for the Desktop and Start menu. Try creating them later through Bloxstrap Settings..
|
||||
/// </summary>
|
||||
public static string Dialog_CannotCreateShortcuts {
|
||||
get {
|
||||
return ResourceManager.GetString("Dialog.CannotCreateShortcuts", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to More information:.
|
||||
/// </summary>
|
||||
@ -1139,6 +1175,223 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Will drop you into the desktop app once everything's done.
|
||||
/// </summary>
|
||||
public static string Installer_Completion_Launch_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Completion.Launch.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install and launch Roblox.
|
||||
/// </summary>
|
||||
public static string Installer_Completion_Launch_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Completion.Launch.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Tweak with all the features it has to offer.
|
||||
/// </summary>
|
||||
public static string Installer_Completion_Settings_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Completion.Settings.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Configure Bloxstrap's settings.
|
||||
/// </summary>
|
||||
public static string Installer_Completion_Settings_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Completion.Settings.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bloxstrap has successfully been installed.
|
||||
///
|
||||
///Roblox has not yet been installed, that will happen when you launch it with Bloxstrap for the first time. However, before you do that, you may want to configure Bloxstrap's settings first.
|
||||
///
|
||||
///Also, to keep Bloxstrap registered as the website launch handler, avoid using the "Roblox Player" shortcut to launch Roblox. If you don't see Bloxstrap show when launching from the website, simply launch Roblox with Bloxstrap once from the desktop to fix it.
|
||||
///
|
||||
///What would y [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
public static string Installer_Completion_Text {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Completion.Text", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Completion.
|
||||
/// </summary>
|
||||
public static string Installer_Completion_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Completion.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Existing data found. Your mods and settings will be restored..
|
||||
/// </summary>
|
||||
public static string Installer_Install_Location_DataFound {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Install.Location.DataFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Roblox will also be installed to this path. Change this if you prefer to install all your games to a separate drive. Otherwise, it's recommended that you keep this as it is..
|
||||
/// </summary>
|
||||
public static string Installer_Install_Location_Text {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Install.Location.Text", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose where to install to.
|
||||
/// </summary>
|
||||
public static string Installer_Install_Location_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Install.Location.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Create Desktop shortcuts.
|
||||
/// </summary>
|
||||
public static string Installer_Install_Shortcuts_Desktop {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Install.Shortcuts.Desktop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Create Start Menu shortcuts.
|
||||
/// </summary>
|
||||
public static string Installer_Install_Shortcuts_StartMenu {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Install.Shortcuts.StartMenu", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Shortcuts.
|
||||
/// </summary>
|
||||
public static string Installer_Install_Shortcuts_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Install.Shortcuts.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install.
|
||||
/// </summary>
|
||||
public static string Installer_Install_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Install.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bloxstrap Installer.
|
||||
/// </summary>
|
||||
public static string Installer_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Thank you for downloading Bloxstrap.
|
||||
///
|
||||
///You should have gotten it from either {0} or {1}. Those are the only official websites to get it from.
|
||||
///
|
||||
///This installation process will be quick and simple, and you will be able to configure any of Bloxstrap's settings after installation..
|
||||
/// </summary>
|
||||
public static string Installer_Welcome_MainText {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Welcome.MainText", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please click 'Next' to continue..
|
||||
/// </summary>
|
||||
public static string Installer_Welcome_NextToContinue {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Welcome.NextToContinue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Welcome.
|
||||
/// </summary>
|
||||
public static string Installer_Welcome_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Welcome.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You are trying to install version {0} of Bloxstrap, but the latest version available is {1}. Would you like to download it?.
|
||||
/// </summary>
|
||||
public static string Installer_Welcome_UpdateNotice {
|
||||
get {
|
||||
return ResourceManager.GetString("Installer.Welcome.UpdateNotice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Configure settings.
|
||||
/// </summary>
|
||||
public static string LaunchMenu_ConfigureSettings {
|
||||
get {
|
||||
return ResourceManager.GetString("LaunchMenu.ConfigureSettings", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Launch Roblox.
|
||||
/// </summary>
|
||||
public static string LaunchMenu_LaunchRoblox {
|
||||
get {
|
||||
return ResourceManager.GetString("LaunchMenu.LaunchRoblox", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to What do you want to do?.
|
||||
/// </summary>
|
||||
public static string LaunchMenu_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("LaunchMenu.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to See the Wiki for help.
|
||||
/// </summary>
|
||||
public static string LaunchMenu_Wiki_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("LaunchMenu.Wiki.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Having an issue?.
|
||||
/// </summary>
|
||||
public static string LaunchMenu_Wiki_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("LaunchMenu.Wiki.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No log file will be written for this launch because Bloxstrap is unable to write to the folder at '{0}'.
|
||||
/// </summary>
|
||||
@ -2103,87 +2356,6 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install.
|
||||
/// </summary>
|
||||
public static string Menu_Install {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Install", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Configure how Bloxstrap/Roblox is installed..
|
||||
/// </summary>
|
||||
public static string Menu_Installation_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose where Bloxstrap should be installed to..
|
||||
/// </summary>
|
||||
public static string Menu_Installation_InstallLocation_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.InstallLocation.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install Location.
|
||||
/// </summary>
|
||||
public static string Menu_Installation_InstallLocation_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.InstallLocation.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Where Bloxstrap is currently installed to..
|
||||
/// </summary>
|
||||
public static string Menu_Installation_OpenInstallFolder_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.OpenInstallFolder.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open Installation Folder.
|
||||
/// </summary>
|
||||
public static string Menu_Installation_OpenInstallFolder_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.OpenInstallFolder.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Installation.
|
||||
/// </summary>
|
||||
public static string Menu_Installation_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Here's a guide on how to uninstall Bloxstrap..
|
||||
/// </summary>
|
||||
public static string Menu_Installation_UninstallGuide_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.UninstallGuide.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Looking to uninstall?.
|
||||
/// </summary>
|
||||
public static string Menu_Installation_UninstallGuide_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Installation.UninstallGuide.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bloxstrap cannot be installed here. Please choose a different location, or resort to using the default location by clicking the reset button..
|
||||
/// </summary>
|
||||
@ -2523,15 +2695,6 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bloxstrap must first be installed..
|
||||
/// </summary>
|
||||
public static string Menu_Mods_OpenModsFolder_MustBeInstalled {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Mods.OpenModsFolder.MustBeInstalled", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open Mods Folder.
|
||||
/// </summary>
|
||||
@ -2649,42 +2812,6 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There's just a few things you first should know about..
|
||||
/// </summary>
|
||||
public static string Menu_PreInstall_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.PreInstall.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to After installation has finished, the Bloxstrap Menu will be registered as an application in the Start menu. If you ever need to access it again to re-adjust your settings, or access resources such as Fast Flag management, you can find it there..
|
||||
/// </summary>
|
||||
public static string Menu_PreInstall_Info_1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.PreInstall.Info.1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to If you ever need help or guidance with anything, be sure to check the [Wiki]({0}). If you still need something, open an [issue]({1}) on GitHub, or join our [Discord server]({2})..
|
||||
/// </summary>
|
||||
public static string Menu_PreInstall_Info_2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.PreInstall.Info.2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Before you install....
|
||||
/// </summary>
|
||||
public static string Menu_PreInstall_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.PreInstall.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save.
|
||||
/// </summary>
|
||||
@ -2713,12 +2840,63 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bloxstrap Menu.
|
||||
/// Looks up a localized string similar to Bloxstrap Settings.
|
||||
/// </summary>
|
||||
public static string Menu_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to They'll be kept where Bloxstrap was installed, and will automatically be restored on a reinstall..
|
||||
/// </summary>
|
||||
public static string Uninstaller_KeepData_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Uninstaller.KeepData.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep my settings and mods.
|
||||
/// </summary>
|
||||
public static string Uninstaller_KeepData_Label {
|
||||
get {
|
||||
return ResourceManager.GetString("Uninstaller.KeepData.Label", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Uninstalling will remove Bloxstrap from your system, and automatically reconfigure the default Roblox launcher if it's still installed.
|
||||
///
|
||||
///If you're uninstalling or reinstalling because you are having issues with Roblox, read [this help page]({0}) first.
|
||||
///
|
||||
///The uninstall process may not be able to fully clean up itself, so you may need to manually clean up leftover files where Bloxstrap was installed.
|
||||
///
|
||||
///Bloxstrap was installed at "{1}"..
|
||||
/// </summary>
|
||||
public static string Uninstaller_Text {
|
||||
get {
|
||||
return ResourceManager.GetString("Uninstaller.Text", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Uninstall Bloxstrap.
|
||||
/// </summary>
|
||||
public static string Uninstaller_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Uninstaller.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Uninstall.
|
||||
/// </summary>
|
||||
public static string Uninstaller_Uninstall {
|
||||
get {
|
||||
return ResourceManager.GetString("Uninstaller.Uninstall", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -707,33 +707,6 @@ Do NOT use this to import large "flag lists" made by other people that promise t
|
||||
<data name="Menu.IconFiles" xml:space="preserve">
|
||||
<value>Icon files</value>
|
||||
</data>
|
||||
<data name="Menu.Install" xml:space="preserve">
|
||||
<value>Install</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.Description" xml:space="preserve">
|
||||
<value>Configure how Bloxstrap/Roblox is installed.</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.InstallLocation.Description" xml:space="preserve">
|
||||
<value>Choose where Bloxstrap should be installed to.</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.InstallLocation.Title" xml:space="preserve">
|
||||
<value>Install Location</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.OpenInstallFolder.Description" xml:space="preserve">
|
||||
<value>Where Bloxstrap is currently installed to.</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.OpenInstallFolder.Title" xml:space="preserve">
|
||||
<value>Open Installation Folder</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.Title" xml:space="preserve">
|
||||
<value>Installation</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.UninstallGuide.Description" xml:space="preserve">
|
||||
<value>Here's a guide on how to uninstall Bloxstrap.</value>
|
||||
</data>
|
||||
<data name="Menu.Installation.UninstallGuide.Title" xml:space="preserve">
|
||||
<value>Looking to uninstall?</value>
|
||||
</data>
|
||||
<data name="Menu.InstallLocation.CantInstall" xml:space="preserve">
|
||||
<value>Bloxstrap cannot be installed here. Please choose a different location, or resort to using the default location by clicking the reset button.</value>
|
||||
</data>
|
||||
@ -852,9 +825,6 @@ Selecting 'No' will ignore this warning and continue installation.</value>
|
||||
<data name="Menu.Mods.OpenModsFolder.Description" xml:space="preserve">
|
||||
<value>Manage custom Roblox mods here.</value>
|
||||
</data>
|
||||
<data name="Menu.Mods.OpenModsFolder.MustBeInstalled" xml:space="preserve">
|
||||
<value>Bloxstrap must first be installed.</value>
|
||||
</data>
|
||||
<data name="Menu.Mods.OpenModsFolder.Title" xml:space="preserve">
|
||||
<value>Open Mods Folder</value>
|
||||
</data>
|
||||
@ -894,18 +864,6 @@ Selecting 'No' will ignore this warning and continue installation.</value>
|
||||
<data name="Menu.MoreInfo" xml:space="preserve">
|
||||
<value>Click for more information on this option.</value>
|
||||
</data>
|
||||
<data name="Menu.PreInstall.Description" xml:space="preserve">
|
||||
<value>There's just a few things you first should know about.</value>
|
||||
</data>
|
||||
<data name="Menu.PreInstall.Info.1" xml:space="preserve">
|
||||
<value>After installation has finished, the Bloxstrap Menu will be registered as an application in the Start menu. If you ever need to access it again to re-adjust your settings, or access resources such as Fast Flag management, you can find it there.</value>
|
||||
</data>
|
||||
<data name="Menu.PreInstall.Info.2" xml:space="preserve">
|
||||
<value>If you ever need help or guidance with anything, be sure to check the [Wiki]({0}). If you still need something, open an [issue]({1}) on GitHub, or join our [Discord server]({2}).</value>
|
||||
</data>
|
||||
<data name="Menu.PreInstall.Title" xml:space="preserve">
|
||||
<value>Before you install...</value>
|
||||
</data>
|
||||
<data name="Menu.Save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
@ -916,7 +874,7 @@ Selecting 'No' will ignore this warning and continue installation.</value>
|
||||
<value>Settings saved!</value>
|
||||
</data>
|
||||
<data name="Menu.Title" xml:space="preserve">
|
||||
<value>Bloxstrap Menu</value>
|
||||
<value>Bloxstrap Settings</value>
|
||||
</data>
|
||||
<data name="Dialog.LanguageSelector.Header" xml:space="preserve">
|
||||
<value>Choose preferred language</value>
|
||||
@ -1027,4 +985,116 @@ Selecting 'No' will ignore this warning and continue installation.</value>
|
||||
<data name="ContextMenu.RobloxNotRunning" xml:space="preserve">
|
||||
<value>Roblox is still launching. A log file will only be available once Roblox launches.</value>
|
||||
</data>
|
||||
<data name="Installer.Title" xml:space="preserve">
|
||||
<value>Bloxstrap Installer</value>
|
||||
</data>
|
||||
<data name="Installer.Welcome.Title" xml:space="preserve">
|
||||
<value>Welcome</value>
|
||||
</data>
|
||||
<data name="Installer.Install.Title" xml:space="preserve">
|
||||
<value>Install</value>
|
||||
</data>
|
||||
<data name="Installer.Completion.Title" xml:space="preserve">
|
||||
<value>Completion</value>
|
||||
</data>
|
||||
<data name="Common.Navigation.Back" xml:space="preserve">
|
||||
<value>Back</value>
|
||||
</data>
|
||||
<data name="Common.Navigation.Next" xml:space="preserve">
|
||||
<value>Next</value>
|
||||
</data>
|
||||
<data name="Installer.Welcome.MainText" xml:space="preserve">
|
||||
<value>Thank you for downloading Bloxstrap.
|
||||
|
||||
You should have gotten it from either {0} or {1}. Those are the only official websites to get it from.
|
||||
|
||||
This installation process will be quick and simple, and you will be able to configure any of Bloxstrap's settings after installation.</value>
|
||||
</data>
|
||||
<data name="Installer.Welcome.NextToContinue" xml:space="preserve">
|
||||
<value>Please click 'Next' to continue.</value>
|
||||
</data>
|
||||
<data name="Installer.Welcome.UpdateNotice" xml:space="preserve">
|
||||
<value>You are trying to install version {0} of Bloxstrap, but the latest version available is {1}. Would you like to download it?</value>
|
||||
</data>
|
||||
<data name="Installer.Install.Location.Title" xml:space="preserve">
|
||||
<value>Choose where to install to</value>
|
||||
</data>
|
||||
<data name="Installer.Install.Location.Text" xml:space="preserve">
|
||||
<value>Roblox will also be installed to this path. Change this if you prefer to install all your games to a separate drive. Otherwise, it's recommended that you keep this as it is.</value>
|
||||
</data>
|
||||
<data name="Installer.Install.Location.DataFound" xml:space="preserve">
|
||||
<value>Existing data found. Your mods and settings will be restored.</value>
|
||||
</data>
|
||||
<data name="Installer.Install.Shortcuts.Title" xml:space="preserve">
|
||||
<value>Shortcuts</value>
|
||||
</data>
|
||||
<data name="Installer.Install.Shortcuts.Desktop" xml:space="preserve">
|
||||
<value>Create Desktop shortcuts</value>
|
||||
</data>
|
||||
<data name="Installer.Install.Shortcuts.StartMenu" xml:space="preserve">
|
||||
<value>Create Start Menu shortcuts</value>
|
||||
</data>
|
||||
<data name="Installer.Completion.Text" xml:space="preserve">
|
||||
<value>Bloxstrap has successfully been installed.
|
||||
|
||||
Roblox has not yet been installed, that will happen when you launch it with Bloxstrap for the first time. However, before you do that, you may want to configure Bloxstrap's settings first.
|
||||
|
||||
Also, to keep Bloxstrap registered as the website launch handler, avoid using the "Roblox Player" shortcut to launch Roblox. If you don't see Bloxstrap show when launching from the website, simply launch Roblox with Bloxstrap once from the desktop to fix it.
|
||||
|
||||
What would you like to do?</value>
|
||||
</data>
|
||||
<data name="Installer.Completion.Settings.Title" xml:space="preserve">
|
||||
<value>Configure Bloxstrap's settings</value>
|
||||
</data>
|
||||
<data name="Installer.Completion.Settings.Description" xml:space="preserve">
|
||||
<value>Tweak with all the features it has to offer</value>
|
||||
</data>
|
||||
<data name="Installer.Completion.Launch.Title" xml:space="preserve">
|
||||
<value>Install and launch Roblox</value>
|
||||
</data>
|
||||
<data name="Installer.Completion.Launch.Description" xml:space="preserve">
|
||||
<value>Will drop you into the desktop app once everything's done</value>
|
||||
</data>
|
||||
<data name="Uninstaller.Title" xml:space="preserve">
|
||||
<value>Uninstall Bloxstrap</value>
|
||||
</data>
|
||||
<data name="Uninstaller.Text" xml:space="preserve">
|
||||
<value>Uninstalling will remove Bloxstrap from your system, and automatically reconfigure the default Roblox launcher if it's still installed.
|
||||
|
||||
If you're uninstalling or reinstalling because you are having issues with Roblox, read [this help page]({0}) first.
|
||||
|
||||
The uninstall process may not be able to fully clean up itself, so you may need to manually clean up leftover files where Bloxstrap was installed.
|
||||
|
||||
Bloxstrap was installed at "{1}".</value>
|
||||
</data>
|
||||
<data name="Uninstaller.KeepData.Label" xml:space="preserve">
|
||||
<value>Keep my settings and mods</value>
|
||||
</data>
|
||||
<data name="Uninstaller.KeepData.Description" xml:space="preserve">
|
||||
<value>They'll be kept where Bloxstrap was installed, and will automatically be restored on a reinstall.</value>
|
||||
</data>
|
||||
<data name="Uninstaller.Uninstall" xml:space="preserve">
|
||||
<value>Uninstall</value>
|
||||
</data>
|
||||
<data name="LaunchMenu.Title" xml:space="preserve">
|
||||
<value>What do you want to do?</value>
|
||||
</data>
|
||||
<data name="LaunchMenu.LaunchRoblox" xml:space="preserve">
|
||||
<value>Launch Roblox</value>
|
||||
</data>
|
||||
<data name="LaunchMenu.ConfigureSettings" xml:space="preserve">
|
||||
<value>Configure settings</value>
|
||||
</data>
|
||||
<data name="LaunchMenu.Wiki.Title" xml:space="preserve">
|
||||
<value>Having an issue?</value>
|
||||
</data>
|
||||
<data name="LaunchMenu.Wiki.Description" xml:space="preserve">
|
||||
<value>See the Wiki for help</value>
|
||||
</data>
|
||||
<data name="Bootstrapper.WebView2NotFound" xml:space="preserve">
|
||||
<value>You currently do not have the WebView2 runtime installed. Some Roblox features will not work properly without it, such as the desktop app. Would you like to download it now?</value>
|
||||
</data>
|
||||
<data name="Dialog.CannotCreateShortcuts" xml:space="preserve">
|
||||
<value>Bloxstrap was unable to create shortcuts for the Desktop and Start menu. Try creating them later through Bloxstrap Settings.</value>
|
||||
</data>
|
||||
</root>
|
@ -1,5 +1,6 @@
|
||||
namespace Bloxstrap
|
||||
{
|
||||
// TODO: this is a mess and desperately needs refactoring
|
||||
public static class RobloxDeployment
|
||||
{
|
||||
public const string DefaultChannel = "LIVE";
|
||||
@ -52,7 +53,7 @@
|
||||
// this function serves double duty as the setup mirror enumerator, and as our connectivity check
|
||||
// since we're basically asking four different urls for the exact same thing, if all four fail, then it has to be a user-side problem
|
||||
|
||||
// this should be checked for in the installer, in the menu, and in the bootstrapper, as each of those have a dedicated spot they show in
|
||||
// this should be checked for in the installer and in the bootstrapper
|
||||
|
||||
// returns null for success
|
||||
|
||||
@ -168,7 +169,7 @@
|
||||
{
|
||||
var defaultClientVersion = await GetInfo(DefaultChannel);
|
||||
|
||||
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == -1)
|
||||
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == VersionComparison.LessThan)
|
||||
clientVersion.IsBehindDefaultChannel = true;
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
mc:Ignorable="d"
|
||||
Title="Bloxstrap"
|
||||
Title="{x:Static resources:Strings.Installer_Title}"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Width="380"
|
||||
@ -24,10 +24,10 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" Title="Bloxstrap" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" />
|
||||
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" Title="{x:Static resources:Strings.Installer_Title}" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" Icon="pack://application:,,,/Bloxstrap.ico" />
|
||||
|
||||
<StackPanel Grid.Row="1" Margin="12">
|
||||
<TextBlock Text="{x:Static resources:Strings.Dialog_LanguageSelector_Header}" FontSize="18" FontWeight="Medium" />
|
||||
<TextBlock Text="{x:Static resources:Strings.Dialog_LanguageSelector_Header}" FontSize="20" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{x:Static resources:Strings.Dialog_LanguageSelector_Subtext}" TextWrapping="Wrap" Margin="0,0,0,12" />
|
||||
<ComboBox ItemsSource="{Binding Languages, Mode=OneTime}" Text="{Binding SelectedLanguage, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
54
Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml
Normal file
54
Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml
Normal file
@ -0,0 +1,54 @@
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.LaunchMenuDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
mc:Ignorable="d"
|
||||
Title="Bloxstrap"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Width="320"
|
||||
SizeToContent="Height"
|
||||
ResizeMode="NoResize"
|
||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" Title="Bloxstrap" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" Icon="pack://application:,,,/Bloxstrap.ico" />
|
||||
|
||||
<StackPanel Grid.Row="1" Margin="12">
|
||||
<TextBlock FontSize="24" Text="{x:Static resources:Strings.LaunchMenu_Title}" HorizontalAlignment="Center" Margin="0,0,0,16" />
|
||||
|
||||
<ui:CardAction Icon="ArrowRight12" Command="{Binding LaunchRobloxCommand, Mode=OneTime}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.LaunchMenu_LaunchRoblox}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
|
||||
<ui:CardAction Margin="0,8,0,0" Icon="Settings28" Command="{Binding LaunchSettingsCommand, Mode=OneTime}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.LaunchMenu_ConfigureSettings}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
|
||||
<ui:CardAction Margin="0,8,0,0" Icon="BookQuestionMark24" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.LaunchMenu_Wiki_Title}" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.LaunchMenu_Wiki_Description}" Padding="0,0,16,0" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
|
||||
<TextBlock Margin="0,16,0,0" FontSize="12" Text="{Binding Version, Mode=OneTime}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</base:WpfUiWindow>
|
42
Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml.cs
Normal file
42
Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Bloxstrap.UI.ViewModels.Dialogs;
|
||||
using Bloxstrap.UI.ViewModels.Installer;
|
||||
using Wpf.Ui.Mvvm.Interfaces;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LaunchMenuDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LaunchMenuDialog
|
||||
{
|
||||
public NextAction CloseAction = NextAction.Terminate;
|
||||
|
||||
public LaunchMenuDialog()
|
||||
{
|
||||
var viewModel = new LaunchMenuViewModel();
|
||||
viewModel.CloseWindowRequest += (_, closeAction) =>
|
||||
{
|
||||
CloseAction = closeAction;
|
||||
Close();
|
||||
};
|
||||
|
||||
DataContext = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
}
|
56
Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml
Normal file
56
Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml
Normal file
@ -0,0 +1,56 @@
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.UninstallerDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:dmodels="clr-namespace:Bloxstrap.UI.ViewModels.Dialogs"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance dmodels:UninstallerViewModel, IsDesignTimeCreatable=True}"
|
||||
Title="Bloxstrap"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Width="480"
|
||||
SizeToContent="Height"
|
||||
ResizeMode="NoResize"
|
||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" Title="Bloxstrap" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" Icon="pack://application:,,,/Bloxstrap.ico" />
|
||||
|
||||
<StackPanel Grid.Row="1" Margin="12">
|
||||
<TextBlock FontSize="20" FontWeight="SemiBold" Text="{x:Static resources:Strings.Uninstaller_Title}" />
|
||||
<controls:MarkdownTextBlock FontSize="14" Margin="0,0,0,16" MarkdownText="{Binding Text, Mode=OneTime}" TextWrapping="Wrap" />
|
||||
<CheckBox Content="{x:Static resources:Strings.Uninstaller_KeepData_Label}" IsChecked="{Binding KeepData, Mode=TwoWay}" />
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Uninstaller_KeepData_Description}" TextWrapping="Wrap">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding KeepData, Mode=OneWay}" Value="False">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="2" Margin="0,10,0,0" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
||||
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
|
||||
<Button MinWidth="100" Content="{x:Static resources:Strings.Uninstaller_Uninstall}" Command="{Binding ConfirmUninstallCommand}" />
|
||||
<Button MinWidth="100" Margin="12,0,0,0" Content="{x:Static resources:Strings.Common_Cancel}" IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</base:WpfUiWindow>
|
45
Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml.cs
Normal file
45
Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Bloxstrap.UI.ViewModels.Dialogs;
|
||||
using Bloxstrap.UI.ViewModels.Installer;
|
||||
using Wpf.Ui.Mvvm.Interfaces;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for UninstallerDialog.xaml
|
||||
/// </summary>
|
||||
public partial class UninstallerDialog
|
||||
{
|
||||
public bool Confirmed { get; private set; } = false;
|
||||
|
||||
public bool KeepData { get; private set; } = true;
|
||||
|
||||
public UninstallerDialog()
|
||||
{
|
||||
var viewModel = new UninstallerViewModel();
|
||||
viewModel.ConfirmUninstallRequest += (_, _) =>
|
||||
{
|
||||
Confirmed = true;
|
||||
KeepData = viewModel.KeepData;
|
||||
Close();
|
||||
};
|
||||
|
||||
DataContext = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
}
|
79
Bloxstrap/UI/Elements/Installer/MainWindow.xaml
Normal file
79
Bloxstrap/UI/Elements/Installer/MainWindow.xaml
Normal file
@ -0,0 +1,79 @@
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Installer.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:pages="clr-namespace:Bloxstrap.UI.Elements.Installer.Pages"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Installer"
|
||||
mc:Ignorable="d"
|
||||
Title="{x:Static resources:Strings.Installer_Title}"
|
||||
Height="540" Width="840"
|
||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowBackdropType="Mica"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Padding="8" x:Name="RootTitleBar" Grid.Row="0" ForceShutdown="False" MinimizeToTray="False" UseSnapLayout="True" Title="{x:Static resources:Strings.Installer_Title}" Icon="pack://application:,,,/Bloxstrap.ico" />
|
||||
|
||||
<Grid x:Name="RootGrid" Grid.Row="1" Margin="12,12,0,0" Visibility="Visible">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:NavigationFluent x:Name="RootNavigation" Grid.Row="1" Grid.Column="0" Margin="0,0,12,0" Frame="{Binding ElementName=RootFrame}" SelectedPageIndex="0">
|
||||
<ui:NavigationFluent.Items>
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Installer_Welcome_Title}" PageType="{x:Type pages:WelcomePage}" Icon="ArrowCircleRight48" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Installer_Install_Title}" PageType="{x:Type pages:InstallPage}" Icon="ArrowCircleRight48" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Installer_Completion_Title}" PageType="{x:Type pages:CompletionPage}" Icon="ArrowCircleRight48" />
|
||||
</ui:NavigationFluent.Items>
|
||||
</ui:NavigationFluent>
|
||||
|
||||
<Grid Grid.Row="0" Grid.RowSpan="2" Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ui:Breadcrumb Grid.Row="0" Margin="0,0,0,5" Navigation="{Binding ElementName=RootNavigation}" />
|
||||
<Frame x:Name="RootFrame" Grid.Row="1" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<StatusBar x:Name="RootStatusBar" Grid.Row="2" Padding="14,10" Background="{ui:ThemeResource ApplicationBackgroundBrush}" BorderThickness="0,1,0,0">
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<StatusBarItem Grid.Column="1" Padding="0,0,4,0">
|
||||
<ui:Button Content="{x:Static resources:Strings.Common_Navigation_Back}" Width="96" Command="{Binding BackPageCommand, Mode=OneWay}" IsEnabled="{Binding BackButtonEnabled, Mode=OneWay}" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="2" Padding="4,0,4,0">
|
||||
<ui:Button Name="NextButton" Content="{Binding NextButtonText, Mode=OneWay}" Width="96" Command="{Binding NextPageCommand, Mode=OneWay}" IsEnabled="{Binding NextButtonEnabled, Mode=OneWay}" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="3" Padding="12,0,0,0">
|
||||
<ui:Button Content="{x:Static resources:Strings.Common_Close}" Width="96" Command="{Binding CloseWindowCommand, Mode=OneWay}" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</base:WpfUiWindow>
|
137
Bloxstrap/UI/Elements/Installer/MainWindow.xaml.cs
Normal file
137
Bloxstrap/UI/Elements/Installer/MainWindow.xaml.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using System.Windows.Controls;
|
||||
using Wpf.Ui.Controls.Interfaces;
|
||||
using Wpf.Ui.Mvvm.Contracts;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
|
||||
using Bloxstrap.UI.ViewModels.Installer;
|
||||
using Bloxstrap.UI.Elements.Installer.Pages;
|
||||
using Bloxstrap.UI.Elements.Base;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Installer
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
///
|
||||
/// The logic behind this wizard-like interface is full of gross hacks, but there's no easy way to do this and I've tried to
|
||||
/// make it as nice and MVVM-"""conformant""" as can possibly be ¯\_(ツ)_/¯
|
||||
///
|
||||
/// Page ViewModels can request changing of navigation button states through the following call flow:
|
||||
/// - Page ViewModel holds event for requesting button state change
|
||||
/// - Page CodeBehind subscribes to event on page creation
|
||||
/// - Page ViewModel invokes event when ready
|
||||
/// - Page CodeBehind receives it, gets MainWindow, and directly calls MainWindow.SetButtonEnabled()
|
||||
/// - MainWindow.SetButtonEnabled() directly calls MainWindowViewModel.SetButtonEnabled() which does the thing a voila
|
||||
///
|
||||
/// Page ViewModels can also be notified of when the next page button has been pressed and stop progression if needed through a callback
|
||||
/// - MainWindow has a single-set Func<bool> property named NextPageCallback which is reset on every page load
|
||||
/// - This callback is called when the next page button is pressed
|
||||
/// - Page CodeBehind gets MainWindow and sets the callback to its own local function on page load
|
||||
/// - CodeBehind's local function then directly calls the ViewModel to do whatever it needs to do
|
||||
///
|
||||
/// TODO: theme selection
|
||||
|
||||
public partial class MainWindow : WpfUiWindow, INavigationWindow
|
||||
{
|
||||
internal readonly MainWindowViewModel _viewModel = new();
|
||||
|
||||
private Type _currentPage = typeof(WelcomePage);
|
||||
|
||||
private List<Type> _pages = new() { typeof(WelcomePage), typeof(InstallPage), typeof(CompletionPage) };
|
||||
|
||||
public Func<bool>? NextPageCallback;
|
||||
|
||||
public NextAction CloseAction = NextAction.Terminate;
|
||||
|
||||
public bool Finished => _currentPage == _pages.Last();
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
_viewModel.CloseWindowRequest += (_, _) => CloseWindow();
|
||||
|
||||
_viewModel.PageRequest += (_, type) =>
|
||||
{
|
||||
if (type == "next")
|
||||
NextPage();
|
||||
else if (type == "back")
|
||||
BackPage();
|
||||
};
|
||||
|
||||
DataContext = _viewModel;
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
App.Logger.WriteLine("MainWindow::MainWindow", "Initializing installer");
|
||||
|
||||
Closing += new CancelEventHandler(MainWindow_Closing);
|
||||
}
|
||||
|
||||
void NextPage()
|
||||
{
|
||||
if (NextPageCallback is not null && !NextPageCallback())
|
||||
return;
|
||||
|
||||
if (_currentPage == _pages.Last())
|
||||
return;
|
||||
|
||||
var page = _pages[_pages.IndexOf(_currentPage) + 1];
|
||||
|
||||
Navigate(page);
|
||||
|
||||
SetButtonEnabled("next", page != _pages.Last());
|
||||
SetButtonEnabled("back", true);
|
||||
}
|
||||
|
||||
void BackPage()
|
||||
{
|
||||
if (_currentPage == _pages.First())
|
||||
return;
|
||||
|
||||
var page = _pages[_pages.IndexOf(_currentPage) - 1];
|
||||
|
||||
Navigate(page);
|
||||
|
||||
SetButtonEnabled("next", true);
|
||||
SetButtonEnabled("back", page != _pages.First());
|
||||
}
|
||||
|
||||
void MainWindow_Closing(object? sender, CancelEventArgs e)
|
||||
{
|
||||
if (Finished)
|
||||
return;
|
||||
|
||||
var result = Frontend.ShowMessageBox("Are you sure you want to cancel the installation?", MessageBoxImage.Warning, MessageBoxButton.YesNo);
|
||||
|
||||
if (result != MessageBoxResult.Yes)
|
||||
e.Cancel = true;
|
||||
}
|
||||
|
||||
public void SetNextButtonText(string text) => _viewModel.SetNextButtonText(text);
|
||||
|
||||
public void SetButtonEnabled(string type, bool state) => _viewModel.SetButtonEnabled(type, state);
|
||||
|
||||
#region INavigationWindow methods
|
||||
|
||||
public Frame GetFrame() => RootFrame;
|
||||
|
||||
public INavigation GetNavigation() => RootNavigation;
|
||||
|
||||
public bool Navigate(Type pageType)
|
||||
{
|
||||
_currentPage = pageType;
|
||||
NextPageCallback = null;
|
||||
return RootNavigation.Navigate(pageType);
|
||||
}
|
||||
|
||||
public void SetPageService(IPageService pageService) => RootNavigation.PageService = pageService;
|
||||
|
||||
public void ShowWindow() => Show();
|
||||
|
||||
public void CloseWindow() => Close();
|
||||
|
||||
#endregion INavigationWindow methods
|
||||
}
|
||||
}
|
32
Bloxstrap/UI/Elements/Installer/Pages/CompletionPage.xaml
Normal file
32
Bloxstrap/UI/Elements/Installer/Pages/CompletionPage.xaml
Normal file
@ -0,0 +1,32 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Installer.Pages.CompletionPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Installer.Pages"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Title="CompletionPage"
|
||||
Scrollable="True"
|
||||
Loaded="UiPage_Loaded">
|
||||
|
||||
<StackPanel Margin="0,0,14,14">
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Installer_Completion_Text}" TextWrapping="Wrap" Margin="0,8,0,0" />
|
||||
|
||||
<ui:CardAction Margin="0,16,0,0" Icon="Settings28" Command="{Binding LaunchSettingsCommand, Mode=OneTime}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Installer_Completion_Settings_Title}" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Installer_Completion_Settings_Description}" Padding="0,0,16,0" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
|
||||
<ui:CardAction Margin="0,8,0,0" Icon="ArrowRight12" Command="{Binding LaunchRobloxCommand, Mode=OneTime}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Installer_Completion_Launch_Title}" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Installer_Completion_Launch_Description}" Padding="0,0,16,0" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
36
Bloxstrap/UI/Elements/Installer/Pages/CompletionPage.xaml.cs
Normal file
36
Bloxstrap/UI/Elements/Installer/Pages/CompletionPage.xaml.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Windows;
|
||||
using Bloxstrap.UI.ViewModels.Installer;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Installer.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for CompletionPage.xaml
|
||||
/// </summary>
|
||||
public partial class CompletionPage
|
||||
{
|
||||
private readonly CompletionViewModel _viewModel = new();
|
||||
public CompletionPage()
|
||||
{
|
||||
_viewModel.CloseWindowRequest += (_, closeAction) =>
|
||||
{
|
||||
if (Window.GetWindow(this) is MainWindow window)
|
||||
{
|
||||
window.CloseAction = closeAction;
|
||||
window.Close();
|
||||
}
|
||||
};
|
||||
|
||||
DataContext = _viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void UiPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Window.GetWindow(this) is MainWindow window)
|
||||
{
|
||||
window.SetNextButtonText("Next");
|
||||
window.SetButtonEnabled("back", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
Bloxstrap/UI/Elements/Installer/Pages/InstallPage.xaml
Normal file
62
Bloxstrap/UI/Elements/Installer/Pages/InstallPage.xaml
Normal file
@ -0,0 +1,62 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Installer.Pages.InstallPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Installer.Pages"
|
||||
xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Title="InstallPage"
|
||||
Scrollable="True"
|
||||
Loaded="UiPage_Loaded">
|
||||
|
||||
<StackPanel Margin="0,0,14,14">
|
||||
<TextBlock FontSize="20" FontWeight="SemiBold" Text="{x:Static resources:Strings.Installer_Install_Location_Title}" TextWrapping="Wrap" />
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Installer_Install_Location_Text}" TextWrapping="Wrap" />
|
||||
|
||||
<ui:Card Margin="0,8,0,0" Padding="12">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Grid.Column="0" Margin="0,0,4,0" Text="{Binding InstallLocation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<ui:Button Grid.Column="1" Margin="4,0,4,0" Height="35" Icon="Folder24" Content="{x:Static resources:Strings.Common_Browse}" Command="{Binding BrowseInstallLocationCommand}" />
|
||||
<ui:Button Grid.Column="2" Margin="4,0,0,0" Height="35" Icon="ArrowCounterclockwise24" Content="{x:Static resources:Strings.Common_Reset}" Command="{Binding ResetInstallLocationCommand}" />
|
||||
</Grid>
|
||||
</ui:Card>
|
||||
|
||||
<TextBlock Margin="0,8,0,0" FontSize="14" Text="{x:Static resources:Strings.Installer_Install_Location_DataFound}" Visibility="{Binding DataFoundMessageVisibility, Mode=OneWay}" TextWrapping="Wrap" />
|
||||
<TextBlock FontSize="14" Text="{Binding ErrorMessage, Mode=OneWay}" Foreground="{DynamicResource SystemFillColorCriticalBrush}" TextWrapping="Wrap" Margin="0,4,0,0">
|
||||
<TextBlock.Style>
|
||||
<Style>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Text}" Value="">
|
||||
<Setter Property="UIElement.Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock FontSize="20" FontWeight="SemiBold" Text="{x:Static resources:Strings.Installer_Install_Shortcuts_Title}" TextWrapping="Wrap" Margin="0,16,0,0" />
|
||||
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Installer_Install_Shortcuts_Desktop}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding CreateDesktopShortcuts, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Installer_Install_Shortcuts_StartMenu}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding CreateStartMenuShortcuts, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
39
Bloxstrap/UI/Elements/Installer/Pages/InstallPage.xaml.cs
Normal file
39
Bloxstrap/UI/Elements/Installer/Pages/InstallPage.xaml.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
using Bloxstrap.UI.ViewModels.Installer;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Installer.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for WelcomePage.xaml
|
||||
/// </summary>
|
||||
public partial class InstallPage
|
||||
{
|
||||
private readonly InstallViewModel _viewModel = new();
|
||||
|
||||
public InstallPage()
|
||||
{
|
||||
DataContext = _viewModel;
|
||||
|
||||
_viewModel.SetCanContinueEvent += (_, state) =>
|
||||
{
|
||||
if (Window.GetWindow(this) is MainWindow window)
|
||||
window.SetButtonEnabled("next", state);
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void UiPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Window.GetWindow(this) is MainWindow window)
|
||||
{
|
||||
window.SetNextButtonText("Install");
|
||||
window.NextPageCallback += NextPageCallback;
|
||||
}
|
||||
}
|
||||
|
||||
public bool NextPageCallback() => _viewModel.DoInstall();
|
||||
}
|
||||
}
|
55
Bloxstrap/UI/Elements/Installer/Pages/WelcomePage.xaml
Normal file
55
Bloxstrap/UI/Elements/Installer/Pages/WelcomePage.xaml
Normal file
@ -0,0 +1,55 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Installer.Pages.WelcomePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Installer.Pages"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
|
||||
xmlns:dmodels="clr-namespace:Bloxstrap.UI.ViewModels.Installer"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls"
|
||||
xmlns:enums="clr-namespace:Bloxstrap.Enums"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance dmodels:WelcomeViewModel, IsDesignTimeCreatable=True}"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Title="WelcomePage"
|
||||
Scrollable="True"
|
||||
Loaded="UiPage_Loaded">
|
||||
|
||||
<StackPanel Margin="0,0,14,14">
|
||||
<controls:MarkdownTextBlock FontSize="14" MarkdownText="{Binding MainText, Mode=OneWay}" TextWrapping="Wrap" />
|
||||
|
||||
<Grid Margin="0,24,0,0">
|
||||
<Grid.Style>
|
||||
<Style TargetType="Grid">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding VersionNotice, Mode=OneWay}" Value="">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Column="0" Width="32" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Warning.png" />
|
||||
<TextBlock Grid.Column="1" FontSize="14" Margin="12,0,12,0" Text="{Binding VersionNotice, Mode=OneWay}" TextWrapping="Wrap" />
|
||||
<ui:Button Grid.Column="2" Icon="ArrowDownload16" Content="Download" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/releases/latest" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Margin="0,24,0,0" FontSize="14" Text="{x:Static resources:Strings.Installer_Welcome_NextToContinue}" TextWrapping="Wrap">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding CanContinue, Mode=OneWay}" Value="False">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
33
Bloxstrap/UI/Elements/Installer/Pages/WelcomePage.xaml.cs
Normal file
33
Bloxstrap/UI/Elements/Installer/Pages/WelcomePage.xaml.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Windows;
|
||||
using Bloxstrap.UI.ViewModels.Installer;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Installer.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for WelcomePage.xaml
|
||||
/// </summary>
|
||||
public partial class WelcomePage
|
||||
{
|
||||
private readonly WelcomeViewModel _viewModel = new();
|
||||
|
||||
public WelcomePage()
|
||||
{
|
||||
_viewModel.CanContinueEvent += (_, _) =>
|
||||
{
|
||||
if (Window.GetWindow(this) is MainWindow window)
|
||||
window.SetButtonEnabled("next", true);
|
||||
};
|
||||
|
||||
DataContext = _viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void UiPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Window.GetWindow(this) is MainWindow window)
|
||||
window.SetNextButtonText("Next");
|
||||
|
||||
_viewModel.DoChecks();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.InstallationPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Menu.Pages"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
|
||||
xmlns:viewmodels="clr-namespace:Bloxstrap.UI.ViewModels.Menu"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
d:DataContext="{d:DesignInstance Type=viewmodels:InstallationViewModel}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Title="InstallationPage">
|
||||
|
||||
<StackPanel Margin="0,0,14,14">
|
||||
<TextBlock Text="{x:Static resources:Strings.Menu_Installation_Description}" FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
|
||||
<ui:CardExpander Margin="0,16,0,0" IsExpanded="True">
|
||||
<ui:CardExpander.Style>
|
||||
<Style TargetType="ui:CardExpander" BasedOn="{StaticResource {x:Type ui:CardExpander}}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Source={x:Static models:GlobalViewModel.IsNotFirstRun}}" Value="False">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ui:CardExpander.Style>
|
||||
<ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_Installation_InstallLocation_Title}" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Menu_Installation_InstallLocation_Description}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardExpander.Header>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Grid.Column="0" Margin="0,0,4,0" Text="{Binding InstallLocation, Mode=TwoWay}" />
|
||||
<ui:Button Grid.Column="1" Margin="4,0,4,0" Height="35" Icon="Folder24" Content="{x:Static resources:Strings.Common_Browse}" Command="{Binding BrowseInstallLocationCommand}" />
|
||||
<ui:Button Grid.Column="2" Margin="4,0,0,0" Height="35" Icon="ArrowCounterclockwise24" Content="{x:Static resources:Strings.Common_Reset}" Command="{Binding ResetInstallLocationCommand}" />
|
||||
</Grid>
|
||||
</ui:CardExpander>
|
||||
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.Style>
|
||||
<Style TargetType="Grid">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Source={x:Static models:GlobalViewModel.IsNotFirstRun}}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:CardAction Grid.Column="0" x:Name="OpenFolderCardAction" Margin="0,0,4,0" Icon="Folder24" Command="{Binding OpenFolderCommand}" >
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_Installation_OpenInstallFolder_Title}" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Menu_Installation_OpenInstallFolder_Description}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
|
||||
<ui:CardAction Grid.Column="1" Margin="4,0,0,0" Icon="UninstallApp24" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/Uninstalling-Bloxstrap">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_Installation_UninstallGuide_Title}" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Menu_Installation_UninstallGuide_Description}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for InstallationPage.xaml
|
||||
/// </summary>
|
||||
public partial class InstallationPage
|
||||
{
|
||||
public InstallationPage()
|
||||
{
|
||||
DataContext = new InstallationViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.PreInstallPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Menu.Pages"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Title="PreInstallPage"
|
||||
Scrollable="True">
|
||||
|
||||
<Grid Margin="0,0,14,14">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="420" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,0,16" Text="{x:Static resources:Strings.Menu_PreInstall_Description}" FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
|
||||
<Border Grid.Row="1" Grid.Column="0" Margin="0,0,16,0" BorderThickness="1" BorderBrush="{DynamicResource TextFillColorPrimaryBrush}">
|
||||
<Image RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/Menu/StartMenu.png" />
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="1">
|
||||
<TextBlock FontSize="14" TextWrapping="Wrap" Text="{x:Static resources:Strings.Menu_PreInstall_Info_1}" />
|
||||
<controls:MarkdownTextBlock Margin="0,16,0,0" FontSize="14" TextWrapping="Wrap" MarkdownText="{Binding Info2Text, Mode=OneTime}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ui:UiPage>
|
@ -1,16 +0,0 @@
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for PreInstallPage.xaml
|
||||
/// </summary>
|
||||
public partial class PreInstallPage
|
||||
{
|
||||
public PreInstallPage()
|
||||
{
|
||||
DataContext = new PreInstallViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Menu.MainWindow"
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Settings.MainWindow"
|
||||
x:Name="ConfigurationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:pages="clr-namespace:Bloxstrap.UI.Elements.Menu.Pages"
|
||||
xmlns:pages="clr-namespace:Bloxstrap.UI.Elements.Settings.Pages"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
@ -48,17 +48,15 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:NavigationFluent x:Name="RootNavigation" Grid.Row="1" Grid.Column="0" Margin="0,0,12,0" Frame="{Binding ElementName=RootFrame}" SelectedPageIndex="0" Visibility="{Binding NavigationVisibility, Mode=OneWay}">
|
||||
<ui:NavigationFluent x:Name="RootNavigation" Grid.Row="1" Grid.Column="0" Margin="0,0,12,0" Frame="{Binding ElementName=RootFrame}" SelectedPageIndex="0">
|
||||
<ui:NavigationFluent.Items>
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_Integrations_Title}" PageType="{x:Type pages:IntegrationsPage}" Icon="Add28" Tag="integrations" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_Mods_Title}" PageType="{x:Type pages:ModsPage}" Icon="WrenchScrewdriver20" Tag="mods" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_FastFlags_Title}" PageType="{x:Type pages:FastFlagsPage}" Icon="Flag24" Tag="fastflags" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_Appearance_Title}" PageType="{x:Type pages:AppearancePage}" Icon="PaintBrush24" Tag="appearance" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_Behaviour_Title}" PageType="{x:Type pages:BehaviourPage}" Icon="Settings24" Tag="behaviour" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_Installation_Title}" PageType="{x:Type pages:InstallationPage}" Icon="HardDrive20" Tag="installation" />
|
||||
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_FastFlagEditor_Title}" PageType="{x:Type pages:FastFlagEditorPage}" Tag="fastflageditor" Visibility="Collapsed" />
|
||||
<ui:NavigationItem Content="{x:Static resources:Strings.Menu_PreInstall_Title}" PageType="{x:Type pages:PreInstallPage}" Tag="preinstall" Visibility="Collapsed" x:Name="PreInstallNavItem" />
|
||||
<ui:NavigationItem Content="" PageType="{x:Type pages:FastFlagEditorWarningPage}" Tag="fastflageditorwarning" Visibility="Collapsed" x:Name="EditorWarningNavItem" />
|
||||
</ui:NavigationFluent.Items>
|
||||
<ui:NavigationFluent.Footer>
|
||||
@ -92,10 +90,10 @@
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<StatusBarItem Grid.Column="1" Padding="0,0,4,0">
|
||||
<ui:Button Content="{Binding ConfirmButtonText, Mode=OneTime}" Appearance="Primary" Command="{Binding ConfirmSettingsCommand, Mode=OneWay}" IsEnabled="{Binding ConfirmButtonEnabled, Mode=OneWay}" />
|
||||
<ui:Button Content="{x:Static resources:Strings.Menu_Save}" Appearance="Primary" Command="{Binding SaveSettingsCommand, Mode=OneWay}" IsEnabled="{Binding ConfirmButtonEnabled, Mode=OneWay}" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="2" Padding="4,0,0,0">
|
||||
<ui:Button Content="{Binding CloseButtonText, Mode=OneTime}" Command="{Binding CloseWindowCommand, Mode=OneWay}" />
|
||||
<ui:Button Content="{x:Static resources:Strings.Common_Close}" IsCancel="True" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
@ -1,9 +1,9 @@
|
||||
using System.Windows.Controls;
|
||||
using Wpf.Ui.Controls.Interfaces;
|
||||
using Wpf.Ui.Mvvm.Contracts;
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu
|
||||
namespace Bloxstrap.UI.Elements.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
@ -12,30 +12,30 @@ namespace Bloxstrap.UI.Elements.Menu
|
||||
{
|
||||
public MainWindow(bool showAlreadyRunningWarning)
|
||||
{
|
||||
var viewModel = new MainWindowViewModel();
|
||||
viewModel.RequestSaveNoticeEvent += (_, _) => SettingsSavedSnackbar.Show();
|
||||
|
||||
DataContext = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
App.Logger.WriteLine("MainWindow::MainWindow", "Initializing menu");
|
||||
|
||||
DataContext = new MainWindowViewModel(this);
|
||||
|
||||
#if DEBUG // easier access
|
||||
PreInstallNavItem.Visibility = System.Windows.Visibility.Visible;
|
||||
EditorWarningNavItem.Visibility = System.Windows.Visibility.Visible;
|
||||
#endif
|
||||
|
||||
if (showAlreadyRunningWarning)
|
||||
_ = ShowAlreadyRunningSnackbar();
|
||||
ShowAlreadyRunningSnackbar();
|
||||
}
|
||||
|
||||
private async Task ShowAlreadyRunningSnackbar()
|
||||
private async void ShowAlreadyRunningSnackbar()
|
||||
{
|
||||
await Task.Delay(500); // wait for everything to finish loading
|
||||
AlreadyRunningSnackbar.Show();
|
||||
}
|
||||
|
||||
public void OpenWiki(object? sender, EventArgs e) => Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki");
|
||||
|
||||
#region INavigationWindow methods
|
||||
|
||||
public Frame GetFrame() => RootFrame;
|
@ -1,4 +1,4 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.AboutPage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.AboutPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
@ -1,6 +1,6 @@
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AboutPage.xaml
|
@ -1,4 +1,4 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.AppearancePage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.AppearancePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@ -28,16 +28,6 @@
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Menu_Appearance_Language_Title}"
|
||||
Description="{x:Static resources:Strings.Menu_Appearance_Language_Description}">
|
||||
<controls:OptionControl.Style>
|
||||
<Style TargetType="controls:OptionControl">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Source={x:Static models:GlobalViewModel.IsNotFirstRun}, Mode=OneTime}" Value="False">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</controls:OptionControl.Style>
|
||||
<ComboBox Width="200" Padding="10,5,10,5" ItemsSource="{Binding Languages, Mode=OneTime}" Text="{Binding SelectedLanguage, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AppearancePage.xaml
|
@ -1,12 +1,12 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.BehaviourPage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.BehaviourPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Menu.Pages"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Settings.Pages"
|
||||
xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.Menu"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.Settings"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
d:DataContext="{d:DesignInstance Type=models:BehaviourViewModel}"
|
||||
mc:Ignorable="d"
|
@ -13,9 +13,9 @@ using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for BehaviourPage.xaml
|
@ -1,10 +1,10 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.FastFlagEditorPage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.FastFlagEditorPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Menu.Pages"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Settings.Pages"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
@ -9,7 +9,7 @@ using Bloxstrap.UI.Elements.Dialogs;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for FastFlagEditorPage.xaml
|
@ -1,11 +1,11 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.FastFlagEditorWarningPage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.FastFlagEditorWarningPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.Menu"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.Settings"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Scrollable="True"
|
@ -1,7 +1,7 @@
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
using System.Windows;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for FastFlagEditorWarningPage.xaml
|
@ -1,4 +1,4 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.FastFlagsPage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.FastFlagsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
@ -1,9 +1,9 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for FastFlagsPage.xaml
|
@ -1,4 +1,4 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.IntegrationsPage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.IntegrationsPage"
|
||||
x:Name="IntegrationsPageView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
@ -1,8 +1,8 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for IntegrationsPage.xaml
|
@ -1,4 +1,4 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Menu.Pages.ModsPage"
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Elements.Settings.Pages.ModsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@ -23,34 +23,10 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:CardAction Grid.Row="0" Grid.Column="0" x:Name="OpenModFolderCardAction" Margin="0,0,4,0" Icon="Folder24" Command="{Binding OpenModsFolderCommand}" IsEnabled="{Binding Source={x:Static models:GlobalViewModel.IsNotFirstRun}, Mode=OneTime}">
|
||||
<ui:CardAction Grid.Row="0" Grid.Column="0" x:Name="OpenModFolderCardAction" Margin="0,0,4,0" Icon="Folder24" Command="{Binding OpenModsFolderCommand}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_Mods_OpenModsFolder_Title}" TextWrapping="Wrap">
|
||||
<!--this is so fucking stupid the disabled state of the cardaction doesnt change the header text colour-->
|
||||
<TextBlock.Style>
|
||||
<Style>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ElementName=OpenModFolderCardAction, Path=IsEnabled, Mode=OneTime}" Value="False">
|
||||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TextFillColorDisabledBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Foreground="{DynamicResource TextFillColorTertiaryBrush}" TextWrapping="Wrap">
|
||||
<TextBlock.Style>
|
||||
<Style>
|
||||
<Setter Property="TextBlock.Text" Value="{x:Static resources:Strings.Menu_Mods_OpenModsFolder_Description}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ElementName=OpenModFolderCardAction, Path=IsEnabled, Mode=OneTime}" Value="False">
|
||||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TextFillColorDisabledBrush}" />
|
||||
<Setter Property="TextBlock.Text" Value="{x:Static resources:Strings.Menu_Mods_OpenModsFolder_MustBeInstalled}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_Mods_OpenModsFolder_Title}" TextWrapping="Wrap" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Menu_Mods_OpenModsFolder_Description}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
<ui:CardAction Grid.Row="0" Grid.Column="1" Margin="4,0,0,0" Icon="BookQuestionMark24" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/Adding-custom-mods">
|
@ -1,8 +1,8 @@
|
||||
using System.Windows;
|
||||
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
using Bloxstrap.UI.ViewModels.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||
namespace Bloxstrap.UI.Elements.Settings.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ModsPage.xaml
|
@ -2,16 +2,14 @@
|
||||
|
||||
using Bloxstrap.UI.Elements.Bootstrapper;
|
||||
using Bloxstrap.UI.Elements.Dialogs;
|
||||
using Bloxstrap.UI.Elements.Menu;
|
||||
using Bloxstrap.UI.Elements.Settings;
|
||||
using Bloxstrap.UI.Elements.Installer;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Bloxstrap.UI
|
||||
{
|
||||
static class Frontend
|
||||
{
|
||||
public static void ShowLanguageSelection() => new LanguageSelectorDialog().ShowDialog();
|
||||
|
||||
public static void ShowMenu(bool showAlreadyRunningWarning = false) => new MainWindow(showAlreadyRunningWarning).ShowDialog();
|
||||
|
||||
public static MessageBoxResult ShowMessageBox(string message, MessageBoxImage icon = MessageBoxImage.None, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxResult defaultResult = MessageBoxResult.None)
|
||||
{
|
||||
App.Logger.WriteLine("Frontend::ShowMessageBox", message);
|
||||
@ -19,18 +17,16 @@ namespace Bloxstrap.UI
|
||||
if (App.LaunchSettings.IsQuiet)
|
||||
return defaultResult;
|
||||
|
||||
if (!App.LaunchSettings.IsRobloxLaunch)
|
||||
return ShowFluentMessageBox(message, icon, buttons);
|
||||
|
||||
switch (App.Settings.Prop.BootstrapperStyle)
|
||||
{
|
||||
case BootstrapperStyle.FluentDialog:
|
||||
case BootstrapperStyle.ClassicFluentDialog:
|
||||
case BootstrapperStyle.FluentAeroDialog:
|
||||
case BootstrapperStyle.ByfronDialog:
|
||||
return Application.Current.Dispatcher.Invoke(new Func<MessageBoxResult>(() =>
|
||||
{
|
||||
var messagebox = new FluentMessageBox(message, icon, buttons);
|
||||
messagebox.ShowDialog();
|
||||
return messagebox.Result;
|
||||
}));
|
||||
return ShowFluentMessageBox(message, icon, buttons);
|
||||
|
||||
default:
|
||||
return MessageBox.Show(message, App.ProjectName, buttons, icon);
|
||||
@ -68,5 +64,15 @@ namespace Bloxstrap.UI
|
||||
_ => new FluentDialog(false)
|
||||
};
|
||||
}
|
||||
|
||||
private static MessageBoxResult ShowFluentMessageBox(string message, MessageBoxImage icon, MessageBoxButton buttons)
|
||||
{
|
||||
return Application.Current.Dispatcher.Invoke(new Func<MessageBoxResult>(() =>
|
||||
{
|
||||
var messagebox = new FluentMessageBox(message, icon, buttons);
|
||||
messagebox.ShowDialog();
|
||||
return messagebox.Result;
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
Bloxstrap/UI/ViewModels/Dialogs/LaunchMenuViewModel.cs
Normal file
23
Bloxstrap/UI/ViewModels/Dialogs/LaunchMenuViewModel.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
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);
|
||||
|
||||
public ICommand LaunchSettingsCommand => new RelayCommand(LaunchSettings);
|
||||
|
||||
public ICommand LaunchRobloxCommand => new RelayCommand(LaunchRoblox);
|
||||
|
||||
public event EventHandler<NextAction>? CloseWindowRequest;
|
||||
|
||||
private void LaunchSettings() => CloseWindowRequest?.Invoke(this, NextAction.LaunchSettings);
|
||||
|
||||
private void LaunchRoblox() => CloseWindowRequest?.Invoke(this, NextAction.LaunchRoblox);
|
||||
}
|
||||
}
|
24
Bloxstrap/UI/ViewModels/Dialogs/UninstallerViewModel.cs
Normal file
24
Bloxstrap/UI/ViewModels/Dialogs/UninstallerViewModel.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Dialogs
|
||||
{
|
||||
public class UninstallerViewModel
|
||||
{
|
||||
public string Text => String.Format(
|
||||
Strings.Uninstaller_Text,
|
||||
"https://github.com/pizzaboxer/bloxstrap/wiki/Roblox-crashes-or-does-not-launch",
|
||||
Paths.Base
|
||||
);
|
||||
|
||||
public bool KeepData { get; set; } = true;
|
||||
|
||||
public ICommand ConfirmUninstallCommand => new RelayCommand(ConfirmUninstall);
|
||||
|
||||
public event EventHandler? ConfirmUninstallRequest;
|
||||
|
||||
private void ConfirmUninstall() => ConfirmUninstallRequest?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
@ -7,8 +7,6 @@ namespace Bloxstrap.UI.ViewModels
|
||||
{
|
||||
public static ICommand OpenWebpageCommand => new RelayCommand<string>(OpenWebpage);
|
||||
|
||||
public static bool IsNotFirstRun => !App.IsFirstRun;
|
||||
|
||||
private static void OpenWebpage(string? location)
|
||||
{
|
||||
if (location is null)
|
||||
|
23
Bloxstrap/UI/ViewModels/Installer/CompletionViewModel.cs
Normal file
23
Bloxstrap/UI/ViewModels/Installer/CompletionViewModel.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Installer
|
||||
{
|
||||
public class CompletionViewModel
|
||||
{
|
||||
public ICommand LaunchSettingsCommand => new RelayCommand(LaunchSettings);
|
||||
|
||||
public ICommand LaunchRobloxCommand => new RelayCommand(LaunchRoblox);
|
||||
|
||||
public event EventHandler<NextAction>? CloseWindowRequest;
|
||||
|
||||
private void LaunchSettings() => CloseWindowRequest?.Invoke(this, NextAction.LaunchSettings);
|
||||
|
||||
private void LaunchRoblox() => CloseWindowRequest?.Invoke(this, NextAction.LaunchRoblox);
|
||||
}
|
||||
}
|
111
Bloxstrap/UI/ViewModels/Installer/InstallViewModel.cs
Normal file
111
Bloxstrap/UI/ViewModels/Installer/InstallViewModel.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
using Microsoft.Win32;
|
||||
using Wpf.Ui.Mvvm.Interfaces;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Installer
|
||||
{
|
||||
public class InstallViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
private readonly Bloxstrap.Installer installer = new();
|
||||
|
||||
private readonly string _originalInstallLocation;
|
||||
|
||||
public EventHandler<bool>? SetCanContinueEvent;
|
||||
|
||||
public string InstallLocation
|
||||
{
|
||||
get => installer.InstallLocation;
|
||||
set
|
||||
{
|
||||
if (!String.IsNullOrEmpty(ErrorMessage))
|
||||
{
|
||||
SetCanContinueEvent?.Invoke(this, true);
|
||||
|
||||
installer.InstallLocationError = "";
|
||||
OnPropertyChanged(nameof(ErrorMessage));
|
||||
}
|
||||
|
||||
installer.InstallLocation = value;
|
||||
CheckExistingData();
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility DataFoundMessageVisibility { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public string ErrorMessage => installer.InstallLocationError;
|
||||
|
||||
public bool CreateDesktopShortcuts
|
||||
{
|
||||
get => installer.CreateDesktopShortcuts;
|
||||
set => installer.CreateDesktopShortcuts = value;
|
||||
}
|
||||
|
||||
public bool CreateStartMenuShortcuts
|
||||
{
|
||||
get => installer.CreateStartMenuShortcuts;
|
||||
set => installer.CreateStartMenuShortcuts = value;
|
||||
}
|
||||
|
||||
public ICommand BrowseInstallLocationCommand => new RelayCommand(BrowseInstallLocation);
|
||||
|
||||
public ICommand ResetInstallLocationCommand => new RelayCommand(ResetInstallLocation);
|
||||
|
||||
public ICommand OpenFolderCommand => new RelayCommand(OpenFolder);
|
||||
|
||||
public InstallViewModel()
|
||||
{
|
||||
_originalInstallLocation = installer.InstallLocation;
|
||||
CheckExistingData();
|
||||
}
|
||||
|
||||
public bool DoInstall()
|
||||
{
|
||||
if (!installer.CheckInstallLocation())
|
||||
{
|
||||
SetCanContinueEvent?.Invoke(this, false);
|
||||
|
||||
OnPropertyChanged(nameof(ErrorMessage));
|
||||
return false;
|
||||
}
|
||||
|
||||
installer.DoInstall();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CheckExistingData()
|
||||
{
|
||||
if (File.Exists(Path.Combine(InstallLocation, "Settings.json")))
|
||||
DataFoundMessageVisibility = Visibility.Visible;
|
||||
else
|
||||
DataFoundMessageVisibility = Visibility.Collapsed;
|
||||
|
||||
OnPropertyChanged(nameof(DataFoundMessageVisibility));
|
||||
}
|
||||
|
||||
private void BrowseInstallLocation()
|
||||
{
|
||||
using var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
|
||||
if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
|
||||
return;
|
||||
|
||||
InstallLocation = dialog.SelectedPath;
|
||||
OnPropertyChanged(nameof(InstallLocation));
|
||||
}
|
||||
|
||||
private void ResetInstallLocation()
|
||||
{
|
||||
InstallLocation = _originalInstallLocation;
|
||||
OnPropertyChanged(nameof(InstallLocation));
|
||||
}
|
||||
|
||||
private void OpenFolder() => Process.Start("explorer.exe", Paths.Base);
|
||||
}
|
||||
}
|
52
Bloxstrap/UI/ViewModels/Installer/MainWindowViewModel.cs
Normal file
52
Bloxstrap/UI/ViewModels/Installer/MainWindowViewModel.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Installer
|
||||
{
|
||||
public class MainWindowViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
public string NextButtonText { get; private set; } = Strings.Common_Navigation_Next;
|
||||
|
||||
public bool BackButtonEnabled { get; private set; } = false;
|
||||
|
||||
public bool NextButtonEnabled { get; private set; } = false;
|
||||
|
||||
public ICommand BackPageCommand => new RelayCommand(BackPage);
|
||||
|
||||
public ICommand NextPageCommand => new RelayCommand(NextPage);
|
||||
|
||||
public ICommand CloseWindowCommand => new RelayCommand(CloseWindow);
|
||||
|
||||
public event EventHandler<string>? PageRequest;
|
||||
|
||||
public event EventHandler? CloseWindowRequest;
|
||||
|
||||
public void SetButtonEnabled(string type, bool state)
|
||||
{
|
||||
if (type == "next")
|
||||
{
|
||||
NextButtonEnabled = state;
|
||||
OnPropertyChanged(nameof(NextButtonEnabled));
|
||||
}
|
||||
else if (type == "back")
|
||||
{
|
||||
BackButtonEnabled = state;
|
||||
OnPropertyChanged(nameof(BackButtonEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetNextButtonText(string text)
|
||||
{
|
||||
NextButtonText = text;
|
||||
OnPropertyChanged(nameof(NextButtonText));
|
||||
}
|
||||
|
||||
private void BackPage() => PageRequest?.Invoke(this, "back");
|
||||
|
||||
private void NextPage() => PageRequest?.Invoke(this, "next");
|
||||
|
||||
private void CloseWindow() => CloseWindowRequest?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
56
Bloxstrap/UI/ViewModels/Installer/WelcomeViewModel.cs
Normal file
56
Bloxstrap/UI/ViewModels/Installer/WelcomeViewModel.cs
Normal file
@ -0,0 +1,56 @@
|
||||
namespace Bloxstrap.UI.ViewModels.Installer
|
||||
{
|
||||
// TODO: administrator check?
|
||||
public class WelcomeViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
// formatting is done here instead of in xaml, it's just a bit easier
|
||||
public string MainText => String.Format(
|
||||
Resources.Strings.Installer_Welcome_MainText,
|
||||
"[github.com/pizzaboxer/bloxstrap](https://github.com/pizzaboxer/bloxstrap)",
|
||||
"[bloxstrap.pizzaboxer.xyz](https://bloxstrap.pizzaboxer.xyz)"
|
||||
);
|
||||
|
||||
public string VersionNotice { get; private set; } = "";
|
||||
|
||||
public bool CanContinue { get; set; } = false;
|
||||
|
||||
public event EventHandler? CanContinueEvent;
|
||||
|
||||
// called by codebehind on page load
|
||||
public async void DoChecks()
|
||||
{
|
||||
const string LOG_IDENT = "WelcomeViewModel::DoChecks";
|
||||
|
||||
// TODO: move into unified function that bootstrapper can use too
|
||||
GithubRelease? releaseInfo = null;
|
||||
|
||||
try
|
||||
{
|
||||
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
|
||||
|
||||
if (releaseInfo is null || releaseInfo.Assets is null)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Encountered invalid data when fetching GitHub releases");
|
||||
}
|
||||
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;
|
||||
OnPropertyChanged(nameof(CanContinue));
|
||||
|
||||
CanContinueEvent?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
{
|
||||
public class InstallationViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
private string _originalInstallLocation = App.BaseDirectory;
|
||||
|
||||
public ICommand BrowseInstallLocationCommand => new RelayCommand(BrowseInstallLocation);
|
||||
public ICommand ResetInstallLocationCommand => new RelayCommand(ResetInstallLocation);
|
||||
public ICommand OpenFolderCommand => new RelayCommand(OpenFolder);
|
||||
|
||||
private void BrowseInstallLocation()
|
||||
{
|
||||
using var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
|
||||
if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
|
||||
return;
|
||||
|
||||
InstallLocation = dialog.SelectedPath;
|
||||
OnPropertyChanged(nameof(InstallLocation));
|
||||
}
|
||||
|
||||
private void ResetInstallLocation()
|
||||
{
|
||||
InstallLocation = _originalInstallLocation;
|
||||
OnPropertyChanged(nameof(InstallLocation));
|
||||
}
|
||||
|
||||
private void OpenFolder()
|
||||
{
|
||||
Process.Start("explorer.exe", Paths.Base);
|
||||
}
|
||||
|
||||
public string InstallLocation
|
||||
{
|
||||
get => App.BaseDirectory;
|
||||
set => App.BaseDirectory = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Wpf.Ui.Mvvm.Contracts;
|
||||
|
||||
using Bloxstrap.UI.Elements.Menu;
|
||||
using Bloxstrap.UI.Elements.Menu.Pages;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
{
|
||||
public class MainWindowViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
private readonly MainWindow _window;
|
||||
|
||||
public ICommand CloseWindowCommand => new RelayCommand(CloseWindow);
|
||||
public ICommand ConfirmSettingsCommand => new RelayCommand(ConfirmSettings);
|
||||
|
||||
public Visibility NavigationVisibility { get; set; } = Visibility.Visible;
|
||||
public string ConfirmButtonText => App.IsFirstRun ? Resources.Strings.Menu_Install : Resources.Strings.Menu_Save;
|
||||
public string CloseButtonText => App.IsFirstRun ? Resources.Strings.Common_Cancel : Resources.Strings.Common_Close;
|
||||
public bool ConfirmButtonEnabled { get; set; } = true;
|
||||
|
||||
public MainWindowViewModel(MainWindow window)
|
||||
{
|
||||
_window = window;
|
||||
}
|
||||
|
||||
private void CloseWindow() => _window.Close();
|
||||
|
||||
private void ConfirmSettings()
|
||||
{
|
||||
if (!App.IsFirstRun)
|
||||
{
|
||||
App.ShouldSaveConfigs = true;
|
||||
App.Settings.Save();
|
||||
App.State.Save();
|
||||
App.FastFlags.Save();
|
||||
App.ShouldSaveConfigs = false;
|
||||
|
||||
_window.SettingsSavedSnackbar.Show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(App.BaseDirectory))
|
||||
{
|
||||
Frontend.ShowMessageBox(Resources.Strings.Menu_InstallLocation_NotSet, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (NavigationVisibility == Visibility.Visible)
|
||||
{
|
||||
try
|
||||
{
|
||||
// check if we can write to the directory (a bit hacky but eh)
|
||||
string testFile = Path.Combine(App.BaseDirectory, $"{App.ProjectName}WriteTest.txt");
|
||||
|
||||
Directory.CreateDirectory(App.BaseDirectory);
|
||||
File.WriteAllText(testFile, "hi");
|
||||
File.Delete(testFile);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
Frontend.ShowMessageBox(
|
||||
Resources.Strings.Menu_InstallLocation_NoWritePerms,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Frontend.ShowMessageBox(ex.Message, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!App.BaseDirectory.EndsWith(App.ProjectName) && Directory.Exists(App.BaseDirectory) && Directory.EnumerateFileSystemEntries(App.BaseDirectory).Any())
|
||||
{
|
||||
string suggestedChange = Path.Combine(App.BaseDirectory, App.ProjectName);
|
||||
|
||||
MessageBoxResult result = Frontend.ShowMessageBox(
|
||||
string.Format(Resources.Strings.Menu_InstallLocation_NotEmpty, suggestedChange),
|
||||
MessageBoxImage.Warning,
|
||||
MessageBoxButton.YesNoCancel,
|
||||
MessageBoxResult.Yes
|
||||
);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
App.BaseDirectory = suggestedChange;
|
||||
else if (result == MessageBoxResult.Cancel)
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
App.BaseDirectory.Length <= 3 || // prevent from installing to the root of a drive
|
||||
App.BaseDirectory.StartsWith("\\\\") || // i actually haven't encountered anyone doing this and i dont even know if this is possible but this is just to be safe lmao
|
||||
App.BaseDirectory.ToLowerInvariant().Contains("onedrive") || // prevent from installing to a onedrive folder
|
||||
Directory.GetParent(App.BaseDirectory)!.ToString().ToLowerInvariant() == Paths.UserProfile.ToLowerInvariant() // prevent from installing to an essential user profile folder
|
||||
)
|
||||
{
|
||||
Frontend.ShowMessageBox(
|
||||
Resources.Strings.Menu_InstallLocation_CantInstall,
|
||||
MessageBoxImage.Error,
|
||||
MessageBoxButton.OK
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (NavigationVisibility == Visibility.Visible)
|
||||
{
|
||||
((INavigationWindow)_window).Navigate(typeof(PreInstallPage));
|
||||
|
||||
NavigationVisibility = Visibility.Collapsed;
|
||||
OnPropertyChanged(nameof(NavigationVisibility));
|
||||
|
||||
ConfirmButtonEnabled = false;
|
||||
OnPropertyChanged(nameof(ConfirmButtonEnabled));
|
||||
|
||||
Task.Run(async delegate
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
|
||||
ConfirmButtonEnabled = true;
|
||||
OnPropertyChanged(nameof(ConfirmButtonEnabled));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
App.IsSetupComplete = true;
|
||||
CloseWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
{
|
||||
class PreInstallViewModel
|
||||
{
|
||||
public string Info2Text
|
||||
{
|
||||
get => string.Format(
|
||||
Resources.Strings.Menu_PreInstall_Info_2,
|
||||
"https://www.github.com/pizzaboxer/bloxstrap/wiki",
|
||||
"https://www.github.com/pizzaboxer/bloxstrap/issues",
|
||||
"https://discord.gg/nKjV3mGq6R");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
public class AboutViewModel
|
||||
{
|
@ -7,9 +7,9 @@ using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
using Bloxstrap.UI.Elements.Menu;
|
||||
using Bloxstrap.UI.Elements.Settings;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
public class AppearanceViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
public class BehaviourViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
@ -5,9 +5,9 @@ using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Wpf.Ui.Mvvm.Contracts;
|
||||
|
||||
using Bloxstrap.UI.Elements.Menu.Pages;
|
||||
using Bloxstrap.UI.Elements.Settings.Pages;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
internal class FastFlagEditorWarningViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
@ -6,10 +6,10 @@ using Wpf.Ui.Mvvm.Contracts;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Bloxstrap.UI.Elements.Menu.Pages;
|
||||
using Bloxstrap.UI.Elements.Settings.Pages;
|
||||
using Bloxstrap.Enums.FlagPresets;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
public class FastFlagsViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
@ -7,7 +7,7 @@ using Microsoft.Win32;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
public class IntegrationsViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
22
Bloxstrap/UI/ViewModels/Settings/MainWindowViewModel.cs
Normal file
22
Bloxstrap/UI/ViewModels/Settings/MainWindowViewModel.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
public class MainWindowViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
public ICommand SaveSettingsCommand => new RelayCommand(SaveSettings);
|
||||
|
||||
public EventHandler? RequestSaveNoticeEvent;
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
App.Settings.Save();
|
||||
App.State.Save();
|
||||
App.FastFlags.Save();
|
||||
|
||||
RequestSaveNoticeEvent?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
}
|
@ -5,13 +5,13 @@ using Microsoft.Win32;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Bloxstrap.UI.ViewModels.Menu
|
||||
namespace Bloxstrap.UI.ViewModels.Settings
|
||||
{
|
||||
public class ModsViewModel : NotifyPropertyChangedViewModel
|
||||
{
|
||||
private void OpenModsFolder() => Process.Start("explorer.exe", Paths.Modifications);
|
||||
|
||||
private bool _usingCustomFont => App.IsFirstRun && App.CustomFontLocation is not null || !App.IsFirstRun && File.Exists(Paths.CustomFont);
|
||||
private bool _usingCustomFont => File.Exists(Paths.CustomFont);
|
||||
|
||||
private readonly Dictionary<string, byte[]> FontHeaders = new()
|
||||
{
|
||||
@ -24,15 +24,8 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
||||
{
|
||||
if (_usingCustomFont)
|
||||
{
|
||||
if (App.IsFirstRun)
|
||||
{
|
||||
App.CustomFontLocation = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Filesystem.AssertReadOnly(Paths.CustomFont);
|
||||
File.Delete(Paths.CustomFont);
|
||||
}
|
||||
Filesystem.AssertReadOnly(Paths.CustomFont);
|
||||
File.Delete(Paths.CustomFont);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -51,17 +44,10 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
||||
Frontend.ShowMessageBox(Resources.Strings.Menu_Mods_Misc_CustomFont_Invalid, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (App.IsFirstRun)
|
||||
{
|
||||
App.CustomFontLocation = dialog.FileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Paths.CustomFont)!);
|
||||
File.Copy(dialog.FileName, Paths.CustomFont);
|
||||
Filesystem.AssertReadOnly(Paths.CustomFont);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Paths.CustomFont)!);
|
||||
File.Copy(dialog.FileName, Paths.CustomFont);
|
||||
Filesystem.AssertReadOnly(Paths.CustomFont);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(ChooseCustomFontVisibility));
|
@ -49,12 +49,12 @@ namespace Bloxstrap
|
||||
/// 0: version1 == version2 <br />
|
||||
/// 1: version1 > version2
|
||||
/// </returns>
|
||||
public static int CompareVersions(string versionStr1, string versionStr2)
|
||||
public static VersionComparison CompareVersions(string versionStr1, string versionStr2)
|
||||
{
|
||||
var version1 = new Version(versionStr1.Replace("v", ""));
|
||||
var version2 = new Version(versionStr2.Replace("v", ""));
|
||||
|
||||
return version1.CompareTo(version2);
|
||||
return (VersionComparison)version1.CompareTo(version2);
|
||||
}
|
||||
|
||||
public static string GetRobloxVersion(bool studio)
|
||||
|
@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bloxstrap
|
||||
namespace Bloxstrap.Utility
|
||||
{
|
||||
public class InterProcessLock : IDisposable
|
||||
{
|
@ -1,4 +1,5 @@
|
||||
using System.Windows;
|
||||
using Bloxstrap.Resources;
|
||||
|
||||
namespace Bloxstrap.Utility
|
||||
{
|
||||
@ -30,10 +31,7 @@ namespace Bloxstrap.Utility
|
||||
|
||||
_loadStatus = GenericTriState.Failed;
|
||||
|
||||
Frontend.ShowMessageBox(
|
||||
$"{App.ProjectName} was unable to create shortcuts for the Desktop and Start menu. They will be created the next time Roblox is launched.",
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
Frontend.ShowMessageBox(Strings.Dialog_CannotCreateShortcuts, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user