diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..3d2e4f6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: boxerpizza diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index f2d9a6e..424babb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -11,6 +11,7 @@ body: - If your problem is with Roblox itself (i.e. it crashes or doesn't launch), [check to see if it happens without Bloxstrap](https://github.com/pizzaboxer/bloxstrap/wiki/Roblox-crashes-or-does-not-launch). - Please only open an issue if your problem happens only with Bloxstrap, and state clearly that this is the case, as anything else is out of my control. - If you are getting a Bloxstrap Exception error, please attach a copy of the provided log file. There is a button on the dialog that locates it for you. + - Also, please ensure you are using the [latest version of Bloxstrap](https://github.com/pizzaboxer/bloxstrap/releases/latest). Your report will be null and void if you are not. - If more clarification on the issue is needed, and you don't respond after a month, then your issue will be closed as stale. - type: checkboxes id: terms @@ -19,7 +20,11 @@ body: options: - label: I have read the preliminary instructions, and I am certain that my problem has not already been addressed. required: true - - label: My answer given in the checkbox above is a lie. + - label: I have thoroughly looked through the available Wiki articles and could not find a solution to my problem. + required: true + - label: I am using the latest version of Bloxstrap. + required: true + - label: I did not answer truthfully to all the above checkboxes. - type: textarea id: what-happened attributes: @@ -27,3 +32,10 @@ body: description: Provide a comprehensive description of the problem you're facing. Don't forget to attach any additional resources you may have, such as log files and screenshots. validations: required: true + - type: textarea + id: log + attributes: + label: Bloxstrap Log + description: If you're getting a Bloxstrap Exception error, upload your log file here. Otherwise, just leave it empty. + value: "N/A" + #render: text diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index db72422..651806d 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -6,6 +6,9 @@ using System.Windows.Threading; using Microsoft.Win32; using Bloxstrap.Models.SettingTasks.Base; +using Bloxstrap.UI.Elements.About.Pages; +using Bloxstrap.UI.Elements.About; +using System; namespace Bloxstrap { @@ -29,9 +32,11 @@ namespace Bloxstrap public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2]; - public static readonly MD5 MD5Provider = MD5.Create(); + public static bool IsActionBuild => !String.IsNullOrEmpty(BuildMetadata.CommitRef); - public static NotifyIconWrapper? NotifyIcon { get; set; } + public static bool IsProductionBuild => IsActionBuild && BuildMetadata.CommitRef.StartsWith("tag", StringComparison.Ordinal); + + public static readonly MD5 MD5Provider = MD5.Create(); public static readonly Logger Logger = new(); @@ -49,19 +54,23 @@ namespace Bloxstrap ) ); -#if RELEASE private static bool _showingExceptionDialog = false; -#endif + + private static bool _terminating = false; public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS) { + if (_terminating) + return; + int exitCodeNum = (int)exitCode; Logger.WriteLine("App::Terminate", $"Terminating with exit code {exitCodeNum} ({exitCode})"); - NotifyIcon?.Dispose(); + Current.Dispatcher.Invoke(() => Current.Shutdown(exitCodeNum)); + // Environment.Exit(exitCodeNum); - Environment.Exit(exitCodeNum); + _terminating = true; } void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e) @@ -73,24 +82,28 @@ namespace Bloxstrap FinalizeExceptionHandling(e.Exception); } - public static void FinalizeExceptionHandling(Exception exception, bool log = true) + public static void FinalizeExceptionHandling(AggregateException ex) + { + foreach (var innerEx in ex.InnerExceptions) + Logger.WriteException("App::FinalizeExceptionHandling", innerEx); + + FinalizeExceptionHandling(ex.GetBaseException(), false); + } + + public static void FinalizeExceptionHandling(Exception ex, bool log = true) { if (log) - Logger.WriteException("App::FinalizeExceptionHandling", exception); + Logger.WriteException("App::FinalizeExceptionHandling", ex); -#if DEBUG - throw exception; -#else if (_showingExceptionDialog) return; _showingExceptionDialog = true; if (!LaunchSettings.QuietFlag.Active) - Frontend.ShowExceptionDialog(exception); + Frontend.ShowExceptionDialog(ex); Terminate(ErrorCode.ERROR_INSTALL_FAILURE); -#endif } protected override void OnStartup(StartupEventArgs e) @@ -103,10 +116,10 @@ namespace Bloxstrap Logger.WriteLine(LOG_IDENT, $"Starting {ProjectName} v{Version}"); - if (String.IsNullOrEmpty(BuildMetadata.CommitHash)) - Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from {BuildMetadata.Machine}"); - else + if (IsActionBuild) Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from commit {BuildMetadata.CommitHash} ({BuildMetadata.CommitRef})"); + else + Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from {BuildMetadata.Machine}"); Logger.WriteLine(LOG_IDENT, $"Loaded from {Paths.Process}"); @@ -202,10 +215,6 @@ namespace Bloxstrap State.Load(); FastFlags.Load(); - // we can only parse them now as settings need - // to be loaded first to know what our channel is - // LaunchSettings.ParseRoblox(); - if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale)) { Settings.Prop.Locale = "nil"; @@ -214,13 +223,15 @@ namespace Bloxstrap Locale.Set(Settings.Prop.Locale); +#if !DEBUG if (!LaunchSettings.UninstallFlag.Active) Installer.HandleUpgrade(); +#endif LaunchHandler.ProcessLaunchArgs(); } - Terminate(); + // you must *explicitly* call terminate when everything is done, it won't be called implicitly } } } diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index c23d17d..0fc6171 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -3,8 +3,6 @@ using System.Windows.Forms; using Microsoft.Win32; -using Bloxstrap.Integrations; -using Bloxstrap.Resources; using Bloxstrap.AppData; namespace Bloxstrap @@ -289,9 +287,6 @@ namespace Bloxstrap _launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}", StringComparison.InvariantCultureIgnoreCase); } - // whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes - bool shouldWait = false; - var startInfo = new ProcessStartInfo() { FileName = _playerLocation, @@ -308,19 +303,16 @@ namespace Bloxstrap // v2.2.0 - byfron will trip if we keep a process handle open for over a minute, so we're doing this now int gameClientPid; - using (Process gameClient = Process.Start(startInfo)!) + using (var gameClient = Process.Start(startInfo)!) { gameClientPid = gameClient.Id; } - List autocloseProcesses = new(); - ActivityWatcher? activityWatcher = null; - DiscordRichPresence? richPresence = null; - App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})"); using (var startEvent = new SystemEvent(AppData.StartEvent)) { + // TODO: get rid of this bool startEventFired = await startEvent.WaitForEvent(); startEvent.Close(); @@ -330,40 +322,14 @@ namespace Bloxstrap return; } - if (App.Settings.Prop.EnableActivityTracking && _launchMode == LaunchMode.Player) - App.NotifyIcon?.SetProcessId(gameClientPid); - - if (App.Settings.Prop.EnableActivityTracking) - { - activityWatcher = new(gameClientPid); - shouldWait = true; - - App.NotifyIcon?.SetActivityWatcher(activityWatcher); - - if (App.Settings.Prop.UseDisableAppPatch) - { - activityWatcher.OnAppClose += (_, _) => - { - App.Logger.WriteLine(LOG_IDENT, "Received desktop app exit, closing Roblox"); - using var process = Process.GetProcessById(gameClientPid); - process.CloseMainWindow(); - }; - } - - if (App.Settings.Prop.UseDiscordRichPresence) - { - App.Logger.WriteLine(LOG_IDENT, "Using Discord Rich Presence"); - richPresence = new(activityWatcher); - - App.NotifyIcon?.SetRichPresenceHandler(richPresence); - } - } + var autoclosePids = new List(); // launch custom integrations now - foreach (CustomIntegration integration in App.Settings.Prop.CustomIntegrations) + foreach (var integration in App.Settings.Prop.CustomIntegrations) { App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})"); + int pid = 0; try { var process = Process.Start(new ProcessStartInfo @@ -372,48 +338,34 @@ namespace Bloxstrap Arguments = integration.LaunchArgs.Replace("\r\n", " "), WorkingDirectory = Path.GetDirectoryName(integration.Location), UseShellExecute = true - }); + })!; - if (integration.AutoClose) - { - shouldWait = true; - autocloseProcesses.Add(process); - } + pid = process.Id; } catch (Exception ex) { App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}'!"); App.Logger.WriteLine(LOG_IDENT, $"{ex.Message}"); } + + if (integration.AutoClose && pid != 0) + autoclosePids.Add(pid); + } + + using (var proclock = new InterProcessLock("Watcher")) + { + string args = gameClientPid.ToString(); + + if (autoclosePids.Any()) + args += $";{String.Join(',', autoclosePids)}"; + + if (proclock.IsAcquired) + Process.Start(Paths.Process, $"-watcher \"{args}\""); } // event fired, wait for 3 seconds then close await Task.Delay(3000); Dialog?.CloseBootstrapper(); - - // keep bloxstrap open in the background if needed - if (!shouldWait) - return; - - activityWatcher?.StartWatcher(); - - App.Logger.WriteLine(LOG_IDENT, "Waiting for Roblox to close"); - - while (Utilities.GetProcessesSafe().Any(x => x.Id == gameClientPid)) - await Task.Delay(1000); - - App.Logger.WriteLine(LOG_IDENT, $"Roblox has exited"); - - richPresence?.Dispose(); - - foreach (var process in autocloseProcesses) - { - if (process is null || process.HasExited) - continue; - - App.Logger.WriteLine(LOG_IDENT, $"Autoclosing process '{process.ProcessName}' (PID {process.Id})"); - process.Kill(); - } } public void CancelInstall() @@ -528,7 +480,7 @@ namespace Bloxstrap var versionComparison = Utilities.CompareVersions(App.Version, releaseInfo.TagName); // check if we aren't using a deployed build, so we can update to one if a new version comes out - if (versionComparison == VersionComparison.Equal && App.BuildMetadata.CommitRef.StartsWith("tag") || versionComparison == VersionComparison.GreaterThan) + if (versionComparison == VersionComparison.Equal && App.IsProductionBuild || versionComparison == VersionComparison.GreaterThan) { App.Logger.WriteLine(LOG_IDENT, $"No updates found"); return; diff --git a/Bloxstrap/Enums/FlagPresets/RenderingMode.cs b/Bloxstrap/Enums/FlagPresets/RenderingMode.cs index 769a430..082d301 100644 --- a/Bloxstrap/Enums/FlagPresets/RenderingMode.cs +++ b/Bloxstrap/Enums/FlagPresets/RenderingMode.cs @@ -4,9 +4,7 @@ { [EnumName(FromTranslation = "Common.Automatic")] Default, - // Vulkan, D3D11, D3D10, - // OpenGL } } diff --git a/Bloxstrap/FastFlagManager.cs b/Bloxstrap/FastFlagManager.cs index 492bb68..f211752 100644 --- a/Bloxstrap/FastFlagManager.cs +++ b/Bloxstrap/FastFlagManager.cs @@ -6,6 +6,8 @@ namespace Bloxstrap { public override string FileLocation => Path.Combine(Paths.Modifications, "ClientSettings\\ClientAppSettings.json"); + public bool Changed => !OriginalProp.SequenceEqual(Prop); + public static IReadOnlyDictionary PresetFlags = new Dictionary { { "Network.Log", "FLogNetwork" }, @@ -28,9 +30,6 @@ namespace Bloxstrap { "Rendering.Mode.D3D11", "FFlagDebugGraphicsPreferD3D11" }, { "Rendering.Mode.D3D10", "FFlagDebugGraphicsPreferD3D11FL10" }, - { "Rendering.Mode.Vulkan", "FFlagDebugGraphicsPreferVulkan" }, - { "Rendering.Mode.Vulkan.Fix", "FFlagRenderVulkanFixMinimizeWindow" }, - { "Rendering.Mode.OpenGL", "FFlagDebugGraphicsPreferOpenGL" }, { "Rendering.Lighting.Voxel", "DFFlagDebugRenderForceTechnologyVoxel" }, { "Rendering.Lighting.ShadowMap", "FFlagDebugForceFutureIsBrightPhase2" }, @@ -63,10 +62,8 @@ namespace Bloxstrap public static IReadOnlyDictionary RenderingModes => new Dictionary { { RenderingMode.Default, "None" }, - // { RenderingMode.Vulkan, "Vulkan" }, { RenderingMode.D3D11, "D3D11" }, { RenderingMode.D3D10, "D3D10" }, - // { RenderingMode.OpenGL, "OpenGL" } }; public static IReadOnlyDictionary LightingModes => new Dictionary @@ -228,14 +225,6 @@ namespace Bloxstrap return mapping.First().Key; } - public void CheckManualFullscreenPreset() - { - if (GetPreset("Rendering.Mode.Vulkan") == "True" || GetPreset("Rendering.Mode.OpenGL") == "True") - SetPreset("Rendering.ManualFullscreen", null); - else - SetPreset("Rendering.ManualFullscreen", "False"); - } - public override void Save() { // convert all flag values to strings before saving @@ -244,13 +233,17 @@ namespace Bloxstrap Prop[pair.Key] = pair.Value.ToString()!; base.Save(); + + // clone the dictionary + OriginalProp = new(Prop); } public override void Load() { base.Load(); - CheckManualFullscreenPreset(); + // clone the dictionary + OriginalProp = new(Prop); // TODO - remove when activity tracking has been revamped if (GetPreset("Network.Log") != "7") diff --git a/Bloxstrap/Installer.cs b/Bloxstrap/Installer.cs index ccb458c..849a834 100644 --- a/Bloxstrap/Installer.cs +++ b/Bloxstrap/Installer.cs @@ -340,6 +340,18 @@ namespace Bloxstrap if (MD5Hash.FromFile(Paths.Process) == MD5Hash.FromFile(Paths.Application)) return; + if (currentVer is not null && existingVer is not null && Utilities.CompareVersions(currentVer, existingVer) == VersionComparison.LessThan) + { + var result = Frontend.ShowMessageBox( + Strings.InstallChecker_VersionLessThanInstalled, + MessageBoxImage.Question, + MessageBoxButton.YesNo + ); + + if (result != MessageBoxResult.Yes) + return; + } + // silently upgrade version if the command line flag is set or if we're launching from an auto update if (!App.LaunchSettings.UpgradeFlag.Active && !isAutoUpgrade) { @@ -466,6 +478,9 @@ namespace Bloxstrap App.FastFlags.Save(); } + if (currentVer is null) + return; + if (isAutoUpgrade) { Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Release-notes-for-Bloxstrap-v{currentVer}"); diff --git a/Bloxstrap/Integrations/ActivityWatcher.cs b/Bloxstrap/Integrations/ActivityWatcher.cs index 02548de..6403a8b 100644 --- a/Bloxstrap/Integrations/ActivityWatcher.cs +++ b/Bloxstrap/Integrations/ActivityWatcher.cs @@ -21,7 +21,6 @@ private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|[0-9]+"; private const string GameMessageEntryPattern = @"\[BloxstrapRPC\] (.*)"; - private int _gameClientPid; private int _logEntriesRead = 0; private bool _teleportMarker = false; private bool _reservedTeleportMarker = false; @@ -29,6 +28,7 @@ public event EventHandler? OnLogEntry; public event EventHandler? OnGameJoin; public event EventHandler? OnGameLeave; + public event EventHandler? OnLogOpen; public event EventHandler? OnAppClose; public event EventHandler? OnRPCMessage; @@ -50,14 +50,9 @@ public bool IsDisposed = false; - public ActivityWatcher(int gameClientPid) + public async void Start() { - _gameClientPid = gameClientPid; - } - - public async void StartWatcher() - { - const string LOG_IDENT = "ActivityWatcher::StartWatcher"; + const string LOG_IDENT = "ActivityWatcher::Start"; // okay, here's the process: // @@ -87,23 +82,26 @@ { logFileInfo = new DirectoryInfo(logDirectory) .GetFiles() - .Where(x => x.CreationTime <= DateTime.Now) + .Where(x => x.Name.Contains("Player", StringComparison.OrdinalIgnoreCase) && x.CreationTime <= DateTime.Now) .OrderByDescending(x => x.CreationTime) .First(); if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now) break; + // TODO: report failure after 10 seconds of no log file App.Logger.WriteLine(LOG_IDENT, $"Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})"); await Task.Delay(1000); } + OnLogOpen?.Invoke(this, EventArgs.Empty); + LogLocation = logFileInfo.FullName; FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}"); - AutoResetEvent logUpdatedEvent = new(false); - FileSystemWatcher logWatcher = new() + var logUpdatedEvent = new AutoResetEvent(false); + var logWatcher = new FileSystemWatcher() { Path = logDirectory, Filter = Path.GetFileName(logFileInfo.FullName), @@ -111,7 +109,7 @@ }; logWatcher.Changed += (s, e) => logUpdatedEvent.Set(); - using StreamReader sr = new(logFileStream); + using var sr = new StreamReader(logFileStream); while (!IsDisposed) { @@ -120,13 +118,13 @@ if (log is null) logUpdatedEvent.WaitOne(250); else - ExamineLogEntry(log); + ReadLogEntry(log); } } - private void ExamineLogEntry(string entry) + private void ReadLogEntry(string entry) { - const string LOG_IDENT = "ActivityWatcher::ExamineLogEntry"; + const string LOG_IDENT = "ActivityWatcher::ReadLogEntry"; OnLogEntry?.Invoke(this, entry); @@ -319,7 +317,7 @@ var ipInfo = await Http.GetJson($"https://ipinfo.io/{ActivityMachineAddress}/json"); if (ipInfo is null) - return $"? ({Resources.Strings.ActivityTracker_LookupFailed})"; + return $"? ({Strings.ActivityTracker_LookupFailed})"; if (string.IsNullOrEmpty(ipInfo.Country)) location = "?"; @@ -329,7 +327,7 @@ location = $"{ipInfo.City}, {ipInfo.Region}, {ipInfo.Country}"; if (!ActivityInGame) - return $"? ({Resources.Strings.ActivityTracker_LeftGame})"; + return $"? ({Strings.ActivityTracker_LeftGame})"; GeolocationCache[ActivityMachineAddress] = location; @@ -340,7 +338,7 @@ App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {ActivityMachineAddress}"); App.Logger.WriteException(LOG_IDENT, ex); - return $"? ({Resources.Strings.ActivityTracker_LookupFailed})"; + return $"? ({Strings.ActivityTracker_LookupFailed})"; } } diff --git a/Bloxstrap/Integrations/DiscordRichPresence.cs b/Bloxstrap/Integrations/DiscordRichPresence.cs index b99f8e1..c7b4b8e 100644 --- a/Bloxstrap/Integrations/DiscordRichPresence.cs +++ b/Bloxstrap/Integrations/DiscordRichPresence.cs @@ -198,6 +198,8 @@ namespace Bloxstrap.Integrations App.Logger.WriteLine(LOG_IDENT, $"Setting presence for Place ID {placeId}"); + // TODO: move this to its own function under the activity watcher? + // TODO: show error if information cannot be queried instead of silently failing var universeIdResponse = await Http.GetJson($"https://apis.roblox.com/universes/v1/places/{placeId}/universe"); if (universeIdResponse is null) { @@ -312,6 +314,7 @@ namespace Bloxstrap.Integrations // this is used for configuration from BloxstrapRPC _currentPresenceCopy = _currentPresence.Clone(); + // TODO: use queue for stashing messages if (_stashedRPCMessage is not null) { App.Logger.WriteLine(LOG_IDENT, "Found stashed RPC message, invoking presence set command now"); diff --git a/Bloxstrap/JsonManager.cs b/Bloxstrap/JsonManager.cs index 1a91949..a8e8c3e 100644 --- a/Bloxstrap/JsonManager.cs +++ b/Bloxstrap/JsonManager.cs @@ -4,6 +4,8 @@ namespace Bloxstrap { public class JsonManager where T : class, new() { + public T OriginalProp { get; set; } = new(); + public T Prop { get; set; } = new(); public virtual string FileLocation => Path.Combine(Paths.Base, $"{typeof(T).Name}.json"); diff --git a/Bloxstrap/LaunchHandler.cs b/Bloxstrap/LaunchHandler.cs index 58ef421..a7f5406 100644 --- a/Bloxstrap/LaunchHandler.cs +++ b/Bloxstrap/LaunchHandler.cs @@ -1,11 +1,11 @@ using System.Windows; -using Bloxstrap.UI.Elements.Dialogs; - using Microsoft.Win32; using Windows.Win32; using Windows.Win32.Foundation; +using Bloxstrap.UI.Elements.Dialogs; + namespace Bloxstrap { public static class LaunchHandler @@ -19,6 +19,7 @@ namespace Bloxstrap break; case NextAction.LaunchRoblox: + App.LaunchSettings.RobloxLaunchMode = LaunchMode.Player; LaunchRoblox(); break; @@ -85,7 +86,7 @@ namespace Bloxstrap ProcessNextAction(installer.CloseAction, !installer.Finished); } - + } public static void LaunchUninstaller() @@ -120,6 +121,8 @@ namespace Bloxstrap Installer.DoUninstall(keepData); Frontend.ShowMessageBox(Strings.Bootstrapper_SuccessfullyUninstalled, MessageBoxImage.Information); + + App.Terminate(); } public static void LaunchSettings() @@ -131,12 +134,12 @@ namespace Bloxstrap if (interlock.IsAcquired) { bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1; - new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).ShowDialog(); + new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).Show(); } else { App.Logger.WriteLine(LOG_IDENT, $"Found an already existing menu window"); - + var process = Utilities.GetProcessesSafe().Where(x => x.MainWindowTitle == Strings.Menu_Title).FirstOrDefault(); if (process is not null) @@ -156,7 +159,6 @@ namespace Bloxstrap { const string LOG_IDENT = "LaunchHandler::LaunchRoblox"; - if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll"))) { Frontend.ShowMessageBox(Strings.Bootstrapper_WMFNotFound, MessageBoxImage.Error); @@ -191,8 +193,6 @@ namespace Bloxstrap } } - 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(installWebView2); @@ -206,45 +206,53 @@ namespace Bloxstrap dialog.Bootstrapper = bootstrapper; } - Task bootstrapperTask = Task.Run(async () => await bootstrapper.Run()).ContinueWith(t => + Task.Run(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; + if (t.Exception is not null) + App.FinalizeExceptionHandling(t.Exception, false); + } - 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); + App.Terminate(); }); - // 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.NoLaunchFlag.Active && App.Settings.Prop.EnableActivityTracking) - App.NotifyIcon?.InitializeContextMenu(); - - App.Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish"); - - bootstrapperTask.Wait(); } public static void LaunchWatcher() { + const string LOG_IDENT = "LaunchHandler::LaunchWatcher"; + // this whole topology is a bit confusing, bear with me: + // main thread: strictly UI only, handles showing of the notification area icon, context menu, server details dialog + // - server information task: queries server location, invoked if either the explorer notification is shown or the server details dialog is opened + // - discord rpc thread: handles rpc connection with discord + // - discord rich presence tasks: handles querying and displaying of game information, invoked on activity watcher events + // - watcher task: runs activity watcher + waiting for roblox to close, terminates when it has + + var watcher = new Watcher(); + + Task.Run(watcher.Run).ContinueWith(t => + { + App.Logger.WriteLine(LOG_IDENT, "Watcher task has finished"); + + watcher.Dispose(); + + if (t.IsFaulted) + { + App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the watcher"); + + if (t.Exception is not null) + App.FinalizeExceptionHandling(t.Exception); + } + + App.Terminate(); + }); } } } diff --git a/Bloxstrap/LaunchSettings.cs b/Bloxstrap/LaunchSettings.cs index a217d09..046fd9a 100644 --- a/Bloxstrap/LaunchSettings.cs +++ b/Bloxstrap/LaunchSettings.cs @@ -28,7 +28,7 @@ namespace Bloxstrap public LaunchFlag StudioFlag { get; } = new("studio"); - public LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.None; + public LaunchMode RobloxLaunchMode { get; set; } = LaunchMode.None; public string RobloxLaunchArgs { get; private set; } = ""; diff --git a/Bloxstrap/Logger.cs b/Bloxstrap/Logger.cs index 2eea25d..bbaa40a 100644 --- a/Bloxstrap/Logger.cs +++ b/Bloxstrap/Logger.cs @@ -7,7 +7,7 @@ private readonly SemaphoreSlim _semaphore = new(1, 1); private FileStream? _filestream; - public readonly List Backlog = new(); + public readonly List History = new(); public bool Initialized = false; public bool NoWriteMode = false; public string? FileLocation; @@ -55,7 +55,7 @@ WriteLine(LOG_IDENT, $"Failed to initialize because Bloxstrap cannot write to {directory}"); Frontend.ShowMessageBox( - String.Format(Resources.Strings.Logger_NoWriteMode, directory), + String.Format(Strings.Logger_NoWriteMode, directory), System.Windows.MessageBoxImage.Warning, System.Windows.MessageBoxButton.OK ); @@ -68,8 +68,8 @@ Initialized = true; - if (Backlog.Count > 0) - WriteToLog(string.Join("\r\n", Backlog)); + if (History.Count > 0) + WriteToLog(string.Join("\r\n", History)); WriteLine(LOG_IDENT, "Finished initializing!"); @@ -102,10 +102,12 @@ { string timestamp = DateTime.UtcNow.ToString("s") + "Z"; string outcon = $"{timestamp} {message}"; - string outlog = outcon.Replace(Paths.UserProfile, "%UserProfile%"); + string outlog = outcon.Replace(Paths.UserProfile, "%UserProfile%", StringComparison.InvariantCultureIgnoreCase); Debug.WriteLine(outcon); WriteToLog(outlog); + + History.Add(outlog); } public void WriteLine(string identifier, string message) => WriteLine($"[{identifier}] {message}"); @@ -122,10 +124,7 @@ private async void WriteToLog(string message) { if (!Initialized) - { - Backlog.Add(message); return; - } try { diff --git a/Bloxstrap/Models/SettingTasks/Base/BoolBaseTask.cs b/Bloxstrap/Models/SettingTasks/Base/BoolBaseTask.cs index b22b067..5b5adce 100644 --- a/Bloxstrap/Models/SettingTasks/Base/BoolBaseTask.cs +++ b/Bloxstrap/Models/SettingTasks/Base/BoolBaseTask.cs @@ -29,12 +29,16 @@ namespace Bloxstrap.Models.SettingTasks.Base set { - App.PendingSettingTasks[Name] = this; _newState = value; + + if (Changed) + App.PendingSettingTasks[Name] = this; + else + App.PendingSettingTasks.Remove(Name); } } - public override bool Changed => NewState != OriginalState; + public override bool Changed => _newState != OriginalState; public BoolBaseTask(string prefix, string name) : base(prefix, name) { } } diff --git a/Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs b/Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs index 56a8c47..c64e0a0 100644 --- a/Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs +++ b/Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs @@ -23,12 +23,16 @@ set { - App.PendingSettingTasks[Name] = this; _newState = value; + + if (Changed) + App.PendingSettingTasks[Name] = this; + else + App.PendingSettingTasks.Remove(Name); } } - public override bool Changed => !NewState.Equals(OriginalState); + public override bool Changed => !_newState.Equals(OriginalState); public IEnumerable Selections { get; private set; } = Enum.GetValues(typeof(T)).Cast().OrderBy(x => diff --git a/Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs b/Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs index dd4a07f..6b3ce13 100644 --- a/Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs +++ b/Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs @@ -29,12 +29,16 @@ namespace Bloxstrap.Models.SettingTasks.Base set { - App.PendingSettingTasks[Name] = this; _newState = value; + + if (Changed) + App.PendingSettingTasks[Name] = this; + else + App.PendingSettingTasks.Remove(Name); } } - public override bool Changed => NewState != OriginalState; + public override bool Changed => _newState != OriginalState; public StringBaseTask(string prefix, string name) : base(prefix, name) { } } diff --git a/Bloxstrap/Properties/launchSettings.json b/Bloxstrap/Properties/launchSettings.json index e3f8ff7..d07acf2 100644 --- a/Bloxstrap/Properties/launchSettings.json +++ b/Bloxstrap/Properties/launchSettings.json @@ -26,6 +26,10 @@ "Bloxstrap (Studio Launch)": { "commandName": "Project", "commandLineArgs": "-studio" + }, + "Bloxstrap (Watcher)": { + "commandName": "Project", + "commandLineArgs": "-watcher" } } } \ No newline at end of file diff --git a/Bloxstrap/Resources/Strings.Designer.cs b/Bloxstrap/Resources/Strings.Designer.cs index fb7b569..df5dc57 100644 --- a/Bloxstrap/Resources/Strings.Designer.cs +++ b/Bloxstrap/Resources/Strings.Designer.cs @@ -60,6 +60,33 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to Licenses. + /// + public static string About_Licenses_Title { + get { + return ResourceManager.GetString("About.Licenses.Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About Bloxstrap. + /// + public static string About_Title { + get { + return ResourceManager.GetString("About.Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Translators. + /// + public static string About_Translators_Title { + get { + return ResourceManager.GetString("About.Translators.Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to left game. /// @@ -422,15 +449,6 @@ namespace Bloxstrap.Resources { } } - /// - /// Looks up a localized string similar to Locate log file. - /// - public static string Common_LocateLogFile { - get { - return ResourceManager.GetString("Common.LocateLogFile", resourceCulture); - } - } - /// /// Looks up a localized string similar to Miscellaneous. /// @@ -494,6 +512,15 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to Open log file. + /// + public static string Common_OpenLogFile { + get { + return ResourceManager.GetString("Common.OpenLogFile", resourceCulture); + } + } + /// /// Looks up a localized string similar to Presets. /// @@ -602,15 +629,6 @@ namespace Bloxstrap.Resources { } } - /// - /// Looks up a localized string similar to Open log file. - /// - public static string ContextMenu_OpenLogFile { - get { - return ResourceManager.GetString("ContextMenu.OpenLogFile", resourceCulture); - } - } - /// /// Looks up a localized string similar to Roblox is still launching. A log file will only be available once Roblox launches.. /// @@ -1249,6 +1267,17 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to The version of Bloxstrap you've launched is older than the version you currently have installed. + ///Issues may occur and your settings may be altered. A reinstall is recommended. + ///Are you sure you want to continue?. + /// + public static string InstallChecker_VersionLessThanInstalled { + get { + return ResourceManager.GetString("InstallChecker.VersionLessThanInstalled", resourceCulture); + } + } + /// /// Looks up a localized string similar to Will drop you into the desktop app once everything's done. /// @@ -2960,6 +2989,15 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to You have unsaved changes. Are you sure you want to close without saving?. + /// + public static string Menu_UnsavedChanges { + get { + return ResourceManager.GetString("Menu.UnsavedChanges", resourceCulture); + } + } + /// /// Looks up a localized string similar to They'll be kept where Bloxstrap was installed, and will automatically be restored on a reinstall.. /// diff --git a/Bloxstrap/Resources/Strings.resx b/Bloxstrap/Resources/Strings.resx index f5a90d4..f230b10 100644 --- a/Bloxstrap/Resources/Strings.resx +++ b/Bloxstrap/Resources/Strings.resx @@ -226,8 +226,8 @@ Your ReShade configuration files will still be saved, and you can locate them by Import JSON - - Locate log file + + Open log file Miscellaneous @@ -268,9 +268,6 @@ Your ReShade configuration files will still be saved, and you can locate them by Copy invite deeplink - - Open log file - See server details @@ -1136,4 +1133,21 @@ If not, then please report this exception to the maintainers of this fork. Do NO Please wait for uninstallation to finish. + + About Bloxstrap + + + Licenses + + + Translators + + + You have unsaved changes. Are you sure you want to close without saving? + + + The version of Bloxstrap you've launched is older than the version you currently have installed. +Issues may occur and your settings may be altered. A reinstall is recommended. +Are you sure you want to continue? + diff --git a/Bloxstrap/RobloxDeployment.cs b/Bloxstrap/RobloxDeployment.cs index b350eea..37784a5 100644 --- a/Bloxstrap/RobloxDeployment.cs +++ b/Bloxstrap/RobloxDeployment.cs @@ -4,6 +4,8 @@ { public const string DefaultChannel = "production"; + private const string VersionStudioHash = "version-012732894899482c"; + public static string BaseUrl { get; private set; } = null!; private static readonly Dictionary ClientVersionCache = new(); @@ -18,23 +20,31 @@ { "https://s3.amazonaws.com/setup.roblox.com", 4 } }; - private static async Task TestConnection(string url, int priority) + private static async Task TestConnection(string url, int priority, CancellationToken token) { string LOG_IDENT = $"RobloxDeployment::TestConnection.{url}"; - await Task.Delay(priority * 1000); - - if (BaseUrl is not null) - return null; + await Task.Delay(priority * 1000, token); App.Logger.WriteLine(LOG_IDENT, "Connecting..."); try { - var response = await App.HttpClient.GetAsync($"{url}/version"); + var response = await App.HttpClient.GetAsync($"{url}/versionStudio", token); if (!response.IsSuccessStatusCode) throw new HttpResponseException(response); + + // versionStudio is the version hash for the last MFC studio to be deployed. + // the response body should always be "version-012732894899482c". + string content = await response.Content.ReadAsStringAsync(token); + if (content != VersionStudioHash) + throw new Exception($"versionStudio response does not match (expected \"{VersionStudioHash}\", got \"{content}\")"); + } + catch (TaskCanceledException) + { + App.Logger.WriteLine(LOG_IDENT, "Connectivity test cancelled."); + throw; } catch (Exception ex) { @@ -56,11 +66,11 @@ // returns null for success - if (!String.IsNullOrEmpty(BaseUrl)) - return null; + CancellationTokenSource tokenSource = new CancellationTokenSource(); + CancellationToken token = tokenSource.Token; var exceptions = new List(); - var tasks = (from entry in BaseUrls select TestConnection(entry.Key, entry.Value)).ToList(); + var tasks = (from entry in BaseUrls select TestConnection(entry.Key, entry.Value, token)).ToList(); App.Logger.WriteLine(LOG_IDENT, "Testing connectivity..."); @@ -79,6 +89,9 @@ break; } + // stop other running connectivity tests + tokenSource.Cancel(); + if (String.IsNullOrEmpty(BaseUrl)) return exceptions[0]; diff --git a/Bloxstrap/UI/Elements/About/MainWindow.xaml b/Bloxstrap/UI/Elements/About/MainWindow.xaml new file mode 100644 index 0000000..a334f3a --- /dev/null +++ b/Bloxstrap/UI/Elements/About/MainWindow.xaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bloxstrap/UI/Elements/About/MainWindow.xaml.cs b/Bloxstrap/UI/Elements/About/MainWindow.xaml.cs new file mode 100644 index 0000000..425b995 --- /dev/null +++ b/Bloxstrap/UI/Elements/About/MainWindow.xaml.cs @@ -0,0 +1,35 @@ +using System.Windows.Controls; +using Wpf.Ui.Controls.Interfaces; +using Wpf.Ui.Mvvm.Contracts; + +namespace Bloxstrap.UI.Elements.About +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : INavigationWindow + { + public MainWindow() + { + InitializeComponent(); + + App.Logger.WriteLine("MainWindow::MainWindow", "Initializing menu"); + } + + #region INavigationWindow methods + + public Frame GetFrame() => RootFrame; + + public INavigation GetNavigation() => RootNavigation; + + public bool Navigate(Type pageType) => RootNavigation.Navigate(pageType); + + public void SetPageService(IPageService pageService) => RootNavigation.PageService = pageService; + + public void ShowWindow() => Show(); + + public void CloseWindow() => Close(); + + #endregion INavigationWindow methods + } +} diff --git a/Bloxstrap/UI/Elements/About/Pages/AboutPage.xaml b/Bloxstrap/UI/Elements/About/Pages/AboutPage.xaml new file mode 100644 index 0000000..8e16999 --- /dev/null +++ b/Bloxstrap/UI/Elements/About/Pages/AboutPage.xaml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bloxstrap/UI/Elements/Settings/Pages/AboutPage.xaml.cs b/Bloxstrap/UI/Elements/About/Pages/AboutPage.xaml.cs similarity index 86% rename from Bloxstrap/UI/Elements/Settings/Pages/AboutPage.xaml.cs rename to Bloxstrap/UI/Elements/About/Pages/AboutPage.xaml.cs index 75cbd32..3294258 100644 --- a/Bloxstrap/UI/Elements/Settings/Pages/AboutPage.xaml.cs +++ b/Bloxstrap/UI/Elements/About/Pages/AboutPage.xaml.cs @@ -1,6 +1,6 @@ using Bloxstrap.UI.ViewModels.Settings; -namespace Bloxstrap.UI.Elements.Settings.Pages +namespace Bloxstrap.UI.Elements.About.Pages { /// /// Interaction logic for AboutPage.xaml diff --git a/Bloxstrap/UI/Elements/About/Pages/LicensesPage.xaml b/Bloxstrap/UI/Elements/About/Pages/LicensesPage.xaml new file mode 100644 index 0000000..8a4f984 --- /dev/null +++ b/Bloxstrap/UI/Elements/About/Pages/LicensesPage.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bloxstrap/UI/Elements/About/Pages/LicensesPage.xaml.cs b/Bloxstrap/UI/Elements/About/Pages/LicensesPage.xaml.cs new file mode 100644 index 0000000..7ce2e61 --- /dev/null +++ b/Bloxstrap/UI/Elements/About/Pages/LicensesPage.xaml.cs @@ -0,0 +1,13 @@ +namespace Bloxstrap.UI.Elements.About.Pages +{ + /// + /// Interaction logic for LicensesPage.xaml + /// + public partial class LicensesPage + { + public LicensesPage() + { + InitializeComponent(); + } + } +} diff --git a/Bloxstrap/UI/Elements/About/Pages/TranslatorsPage.xaml b/Bloxstrap/UI/Elements/About/Pages/TranslatorsPage.xaml new file mode 100644 index 0000000..2ad3738 --- /dev/null +++ b/Bloxstrap/UI/Elements/About/Pages/TranslatorsPage.xaml @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Bloxstrap/UI/Elements/About/Pages/TranslatorsPage.xaml.cs b/Bloxstrap/UI/Elements/About/Pages/TranslatorsPage.xaml.cs new file mode 100644 index 0000000..fb41f0a --- /dev/null +++ b/Bloxstrap/UI/Elements/About/Pages/TranslatorsPage.xaml.cs @@ -0,0 +1,13 @@ +namespace Bloxstrap.UI.Elements.About.Pages +{ + /// + /// Interaction logic for TranslatorsPage.xaml + /// + public partial class TranslatorsPage + { + public TranslatorsPage() + { + InitializeComponent(); + } + } +} diff --git a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml index 8bf796c..88a7380 100644 --- a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml +++ b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml @@ -61,7 +61,7 @@ - + @@ -73,7 +73,7 @@ - + @@ -81,7 +81,7 @@ - + diff --git a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs index 9e34a29..c4412ba 100644 --- a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs +++ b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs @@ -22,32 +22,28 @@ namespace Bloxstrap.UI.Elements.ContextMenu { // i wouldve gladly done this as mvvm but turns out that data binding just does not work with menuitems for some reason so idk this sucks - private readonly ActivityWatcher? _activityWatcher; - private readonly DiscordRichPresence? _richPresenceHandler; + private readonly Watcher _watcher; + + private ActivityWatcher? _activityWatcher => _watcher.ActivityWatcher; private ServerInformation? _serverInformationWindow; - private int? _processId; - public MenuContainer(ActivityWatcher? activityWatcher, DiscordRichPresence? richPresenceHandler, int? processId) + public MenuContainer(Watcher watcher) { InitializeComponent(); - _activityWatcher = activityWatcher; - _richPresenceHandler = richPresenceHandler; - _processId = processId; + _watcher = watcher; if (_activityWatcher is not null) { + _activityWatcher.OnLogOpen += ActivityWatcher_OnLogOpen; _activityWatcher.OnGameJoin += ActivityWatcher_OnGameJoin; _activityWatcher.OnGameLeave += ActivityWatcher_OnGameLeave; } - if (_richPresenceHandler is not null) + if (_watcher.RichPresence is not null) RichPresenceMenuItem.Visibility = Visibility.Visible; - if (_processId is not null) - CloseRobloxMenuItem.Visibility = Visibility.Visible; - VersionTextBlock.Text = $"{App.ProjectName} v{App.Version}"; } @@ -55,7 +51,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu { if (_serverInformationWindow is null) { - _serverInformationWindow = new ServerInformation(_activityWatcher!); + _serverInformationWindow = new ServerInformation(_watcher); _serverInformationWindow.Closed += (_, _) => _serverInformationWindow = null; } @@ -65,17 +61,23 @@ namespace Bloxstrap.UI.Elements.ContextMenu _serverInformationWindow.Activate(); } - private void ActivityWatcher_OnGameJoin(object? sender, EventArgs e) + public void ActivityWatcher_OnLogOpen(object? sender, EventArgs e) => + Dispatcher.Invoke(() => LogTracerMenuItem.Visibility = Visibility.Visible); + + public void ActivityWatcher_OnGameJoin(object? sender, EventArgs e) { + if (_activityWatcher is null) + return; + Dispatcher.Invoke(() => { - if (_activityWatcher?.ActivityServerType == ServerType.Public) + if (_activityWatcher.ActivityServerType == ServerType.Public) InviteDeeplinkMenuItem.Visibility = Visibility.Visible; ServerDetailsMenuItem.Visibility = Visibility.Visible; }); } - private void ActivityWatcher_OnGameLeave(object? sender, EventArgs e) + public void ActivityWatcher_OnGameLeave(object? sender, EventArgs e) { Dispatcher.Invoke(() => { InviteDeeplinkMenuItem.Visibility = Visibility.Collapsed; @@ -100,7 +102,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu private void Window_Closed(object sender, EventArgs e) => App.Logger.WriteLine("MenuContainer::Window_Closed", "Context menu container closed"); - private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _richPresenceHandler?.SetVisibility(((MenuItem)sender).IsChecked); + private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _watcher.RichPresence?.SetVisibility(((MenuItem)sender).IsChecked); private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetDataObject($"roblox://experiences/start?placeId={_activityWatcher?.ActivityPlaceId}&gameInstanceId={_activityWatcher?.ActivityJobId}"); @@ -110,13 +112,8 @@ namespace Bloxstrap.UI.Elements.ContextMenu { string? location = _activityWatcher?.LogLocation; - if (location is null) - { - Frontend.ShowMessageBox(Strings.ContextMenu_RobloxNotRunning, MessageBoxImage.Information); - return; - } - - Utilities.ShellExecute(location); + if (location is not null) + Utilities.ShellExecute(location); } private void CloseRobloxMenuItem_Click(object sender, RoutedEventArgs e) @@ -130,9 +127,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu if (result != MessageBoxResult.Yes) return; - using Process process = Process.GetProcessById((int)_processId!); - process.Kill(); - process.Close(); + _watcher.KillRobloxProcess(); } } } diff --git a/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml.cs b/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml.cs index 5d46d85..65b8f1e 100644 --- a/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml.cs +++ b/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml.cs @@ -22,9 +22,13 @@ namespace Bloxstrap.UI.Elements.ContextMenu /// public partial class ServerInformation { - public ServerInformation(ActivityWatcher activityWatcher) + public ServerInformation(Watcher watcher) { - DataContext = new ServerInformationViewModel(this, activityWatcher); + var viewModel = new ServerInformationViewModel(watcher); + + viewModel.RequestCloseEvent += (_, _) => Close(); + + DataContext = viewModel; InitializeComponent(); } } diff --git a/Bloxstrap/UI/Elements/Dialogs/ExceptionDialog.xaml b/Bloxstrap/UI/Elements/Dialogs/ExceptionDialog.xaml index 4c1dd33..1fa1c01 100644 --- a/Bloxstrap/UI/Elements/Dialogs/ExceptionDialog.xaml +++ b/Bloxstrap/UI/Elements/Dialogs/ExceptionDialog.xaml @@ -41,7 +41,7 @@ -