mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-21 10:01:27 -07:00
Move activity watcher to separate process (#810)
this was done to: - ensure robloxplayerbeta launches as an orphaned process - help alleviate problems with multiple instances - alleviate problems with the notifyicon causing blocking conflicts on the bootstrapper ui thread - help reduce functional dependency on the bootstrapper, makes it less monolithic and more maintainable ive always wanted to do this for a long while, but have always put it off because of how painful it would be this may genuinely be the most painful refactoring i've ever had to do, but after 2 days, i managed to do it, and it works great!
This commit is contained in:
parent
cf45d9c808
commit
fd290f9ff7
@ -8,6 +8,7 @@ using Microsoft.Win32;
|
|||||||
using Bloxstrap.Models.SettingTasks.Base;
|
using Bloxstrap.Models.SettingTasks.Base;
|
||||||
using Bloxstrap.UI.Elements.About.Pages;
|
using Bloxstrap.UI.Elements.About.Pages;
|
||||||
using Bloxstrap.UI.Elements.About;
|
using Bloxstrap.UI.Elements.About;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
@ -37,8 +38,6 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
public static readonly MD5 MD5Provider = MD5.Create();
|
public static readonly MD5 MD5Provider = MD5.Create();
|
||||||
|
|
||||||
public static NotifyIconWrapper? NotifyIcon { get; set; }
|
|
||||||
|
|
||||||
public static readonly Logger Logger = new();
|
public static readonly Logger Logger = new();
|
||||||
|
|
||||||
public static readonly Dictionary<string, BaseTask> PendingSettingTasks = new();
|
public static readonly Dictionary<string, BaseTask> PendingSettingTasks = new();
|
||||||
@ -55,19 +54,23 @@ namespace Bloxstrap
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
#if RELEASE
|
|
||||||
private static bool _showingExceptionDialog = false;
|
private static bool _showingExceptionDialog = false;
|
||||||
#endif
|
|
||||||
|
private static bool _terminating = false;
|
||||||
|
|
||||||
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
|
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
|
if (_terminating)
|
||||||
|
return;
|
||||||
|
|
||||||
int exitCodeNum = (int)exitCode;
|
int exitCodeNum = (int)exitCode;
|
||||||
|
|
||||||
Logger.WriteLine("App::Terminate", $"Terminating with exit code {exitCodeNum} ({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)
|
void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||||
@ -79,24 +82,28 @@ namespace Bloxstrap
|
|||||||
FinalizeExceptionHandling(e.Exception);
|
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)
|
if (log)
|
||||||
Logger.WriteException("App::FinalizeExceptionHandling", exception);
|
Logger.WriteException("App::FinalizeExceptionHandling", ex);
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
throw exception;
|
|
||||||
#else
|
|
||||||
if (_showingExceptionDialog)
|
if (_showingExceptionDialog)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_showingExceptionDialog = true;
|
_showingExceptionDialog = true;
|
||||||
|
|
||||||
if (!LaunchSettings.QuietFlag.Active)
|
if (!LaunchSettings.QuietFlag.Active)
|
||||||
Frontend.ShowExceptionDialog(exception);
|
Frontend.ShowExceptionDialog(ex);
|
||||||
|
|
||||||
Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
@ -208,10 +215,6 @@ namespace Bloxstrap
|
|||||||
State.Load();
|
State.Load();
|
||||||
FastFlags.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))
|
if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale))
|
||||||
{
|
{
|
||||||
Settings.Prop.Locale = "nil";
|
Settings.Prop.Locale = "nil";
|
||||||
@ -228,7 +231,7 @@ namespace Bloxstrap
|
|||||||
LaunchHandler.ProcessLaunchArgs();
|
LaunchHandler.ProcessLaunchArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
Terminate();
|
// you must *explicitly* call terminate when everything is done, it won't be called implicitly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ using System.Windows.Forms;
|
|||||||
|
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
|
||||||
using Bloxstrap.Integrations;
|
|
||||||
using Bloxstrap.Resources;
|
|
||||||
using Bloxstrap.AppData;
|
using Bloxstrap.AppData;
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
@ -289,9 +287,6 @@ namespace Bloxstrap
|
|||||||
_launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}", StringComparison.InvariantCultureIgnoreCase);
|
_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()
|
var startInfo = new ProcessStartInfo()
|
||||||
{
|
{
|
||||||
FileName = _playerLocation,
|
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
|
// 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;
|
int gameClientPid;
|
||||||
using (Process gameClient = Process.Start(startInfo)!)
|
using (var gameClient = Process.Start(startInfo)!)
|
||||||
{
|
{
|
||||||
gameClientPid = gameClient.Id;
|
gameClientPid = gameClient.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Process?> autocloseProcesses = new();
|
|
||||||
ActivityWatcher? activityWatcher = null;
|
|
||||||
DiscordRichPresence? richPresence = null;
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})");
|
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})");
|
||||||
|
|
||||||
using (var startEvent = new SystemEvent(AppData.StartEvent))
|
using (var startEvent = new SystemEvent(AppData.StartEvent))
|
||||||
{
|
{
|
||||||
|
// TODO: get rid of this
|
||||||
bool startEventFired = await startEvent.WaitForEvent();
|
bool startEventFired = await startEvent.WaitForEvent();
|
||||||
|
|
||||||
startEvent.Close();
|
startEvent.Close();
|
||||||
@ -330,40 +322,14 @@ namespace Bloxstrap
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (App.Settings.Prop.EnableActivityTracking && _launchMode == LaunchMode.Player)
|
var autoclosePids = new List<int>();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// launch custom integrations now
|
// 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})");
|
App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})");
|
||||||
|
|
||||||
|
int pid = 0;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var process = Process.Start(new ProcessStartInfo
|
var process = Process.Start(new ProcessStartInfo
|
||||||
@ -372,48 +338,34 @@ namespace Bloxstrap
|
|||||||
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
|
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
|
||||||
WorkingDirectory = Path.GetDirectoryName(integration.Location),
|
WorkingDirectory = Path.GetDirectoryName(integration.Location),
|
||||||
UseShellExecute = true
|
UseShellExecute = true
|
||||||
});
|
})!;
|
||||||
|
|
||||||
if (integration.AutoClose)
|
pid = process.Id;
|
||||||
{
|
|
||||||
shouldWait = true;
|
|
||||||
autocloseProcesses.Add(process);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}'!");
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}'!");
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"{ex.Message}");
|
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
|
// event fired, wait for 3 seconds then close
|
||||||
await Task.Delay(3000);
|
await Task.Delay(3000);
|
||||||
Dialog?.CloseBootstrapper();
|
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()
|
public void CancelInstall()
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|[0-9]+";
|
private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|[0-9]+";
|
||||||
private const string GameMessageEntryPattern = @"\[BloxstrapRPC\] (.*)";
|
private const string GameMessageEntryPattern = @"\[BloxstrapRPC\] (.*)";
|
||||||
|
|
||||||
private int _gameClientPid;
|
|
||||||
private int _logEntriesRead = 0;
|
private int _logEntriesRead = 0;
|
||||||
private bool _teleportMarker = false;
|
private bool _teleportMarker = false;
|
||||||
private bool _reservedTeleportMarker = false;
|
private bool _reservedTeleportMarker = false;
|
||||||
@ -27,6 +26,7 @@
|
|||||||
public event EventHandler<string>? OnLogEntry;
|
public event EventHandler<string>? OnLogEntry;
|
||||||
public event EventHandler? OnGameJoin;
|
public event EventHandler? OnGameJoin;
|
||||||
public event EventHandler? OnGameLeave;
|
public event EventHandler? OnGameLeave;
|
||||||
|
public event EventHandler? OnLogOpen;
|
||||||
public event EventHandler? OnAppClose;
|
public event EventHandler? OnAppClose;
|
||||||
public event EventHandler<Message>? OnRPCMessage;
|
public event EventHandler<Message>? OnRPCMessage;
|
||||||
|
|
||||||
@ -47,14 +47,9 @@
|
|||||||
|
|
||||||
public bool IsDisposed = false;
|
public bool IsDisposed = false;
|
||||||
|
|
||||||
public ActivityWatcher(int gameClientPid)
|
public async void Start()
|
||||||
{
|
{
|
||||||
_gameClientPid = gameClientPid;
|
const string LOG_IDENT = "ActivityWatcher::Start";
|
||||||
}
|
|
||||||
|
|
||||||
public async void StartWatcher()
|
|
||||||
{
|
|
||||||
const string LOG_IDENT = "ActivityWatcher::StartWatcher";
|
|
||||||
|
|
||||||
// okay, here's the process:
|
// okay, here's the process:
|
||||||
//
|
//
|
||||||
@ -84,23 +79,26 @@
|
|||||||
{
|
{
|
||||||
logFileInfo = new DirectoryInfo(logDirectory)
|
logFileInfo = new DirectoryInfo(logDirectory)
|
||||||
.GetFiles()
|
.GetFiles()
|
||||||
.Where(x => x.CreationTime <= DateTime.Now)
|
.Where(x => x.Name.Contains("Player", StringComparison.OrdinalIgnoreCase) && x.CreationTime <= DateTime.Now)
|
||||||
.OrderByDescending(x => x.CreationTime)
|
.OrderByDescending(x => x.CreationTime)
|
||||||
.First();
|
.First();
|
||||||
|
|
||||||
if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
|
if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
|
||||||
break;
|
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})");
|
App.Logger.WriteLine(LOG_IDENT, $"Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})");
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnLogOpen?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
LogLocation = logFileInfo.FullName;
|
LogLocation = logFileInfo.FullName;
|
||||||
FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}");
|
App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}");
|
||||||
|
|
||||||
AutoResetEvent logUpdatedEvent = new(false);
|
var logUpdatedEvent = new AutoResetEvent(false);
|
||||||
FileSystemWatcher logWatcher = new()
|
var logWatcher = new FileSystemWatcher()
|
||||||
{
|
{
|
||||||
Path = logDirectory,
|
Path = logDirectory,
|
||||||
Filter = Path.GetFileName(logFileInfo.FullName),
|
Filter = Path.GetFileName(logFileInfo.FullName),
|
||||||
@ -108,7 +106,7 @@
|
|||||||
};
|
};
|
||||||
logWatcher.Changed += (s, e) => logUpdatedEvent.Set();
|
logWatcher.Changed += (s, e) => logUpdatedEvent.Set();
|
||||||
|
|
||||||
using StreamReader sr = new(logFileStream);
|
using var sr = new StreamReader(logFileStream);
|
||||||
|
|
||||||
while (!IsDisposed)
|
while (!IsDisposed)
|
||||||
{
|
{
|
||||||
@ -117,13 +115,13 @@
|
|||||||
if (log is null)
|
if (log is null)
|
||||||
logUpdatedEvent.WaitOne(250);
|
logUpdatedEvent.WaitOne(250);
|
||||||
else
|
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);
|
OnLogEntry?.Invoke(this, entry);
|
||||||
|
|
||||||
@ -302,7 +300,7 @@
|
|||||||
var ipInfo = await Http.GetJson<IPInfoResponse>($"https://ipinfo.io/{ActivityMachineAddress}/json");
|
var ipInfo = await Http.GetJson<IPInfoResponse>($"https://ipinfo.io/{ActivityMachineAddress}/json");
|
||||||
|
|
||||||
if (ipInfo is null)
|
if (ipInfo is null)
|
||||||
return $"? ({Resources.Strings.ActivityTracker_LookupFailed})";
|
return $"? ({Strings.ActivityTracker_LookupFailed})";
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(ipInfo.Country))
|
if (string.IsNullOrEmpty(ipInfo.Country))
|
||||||
location = "?";
|
location = "?";
|
||||||
@ -312,7 +310,7 @@
|
|||||||
location = $"{ipInfo.City}, {ipInfo.Region}, {ipInfo.Country}";
|
location = $"{ipInfo.City}, {ipInfo.Region}, {ipInfo.Country}";
|
||||||
|
|
||||||
if (!ActivityInGame)
|
if (!ActivityInGame)
|
||||||
return $"? ({Resources.Strings.ActivityTracker_LeftGame})";
|
return $"? ({Strings.ActivityTracker_LeftGame})";
|
||||||
|
|
||||||
GeolocationCache[ActivityMachineAddress] = location;
|
GeolocationCache[ActivityMachineAddress] = location;
|
||||||
|
|
||||||
@ -323,7 +321,7 @@
|
|||||||
App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {ActivityMachineAddress}");
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {ActivityMachineAddress}");
|
||||||
App.Logger.WriteException(LOG_IDENT, ex);
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
|
|
||||||
return $"? ({Resources.Strings.ActivityTracker_LookupFailed})";
|
return $"? ({Strings.ActivityTracker_LookupFailed})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +194,8 @@ namespace Bloxstrap.Integrations
|
|||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Setting presence for Place ID {placeId}");
|
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<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{placeId}/universe");
|
var universeIdResponse = await Http.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{placeId}/universe");
|
||||||
if (universeIdResponse is null)
|
if (universeIdResponse is null)
|
||||||
{
|
{
|
||||||
@ -282,6 +284,7 @@ namespace Bloxstrap.Integrations
|
|||||||
// this is used for configuration from BloxstrapRPC
|
// this is used for configuration from BloxstrapRPC
|
||||||
_currentPresenceCopy = _currentPresence.Clone();
|
_currentPresenceCopy = _currentPresence.Clone();
|
||||||
|
|
||||||
|
// TODO: use queue for stashing messages
|
||||||
if (_stashedRPCMessage is not null)
|
if (_stashedRPCMessage is not null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Found stashed RPC message, invoking presence set command now");
|
App.Logger.WriteLine(LOG_IDENT, "Found stashed RPC message, invoking presence set command now");
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
using Bloxstrap.UI.Elements.Dialogs;
|
|
||||||
|
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Windows.Win32;
|
using Windows.Win32;
|
||||||
using Windows.Win32.Foundation;
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
|
using Bloxstrap.UI.Elements.Dialogs;
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
public static class LaunchHandler
|
public static class LaunchHandler
|
||||||
@ -19,6 +19,7 @@ namespace Bloxstrap
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NextAction.LaunchRoblox:
|
case NextAction.LaunchRoblox:
|
||||||
|
App.LaunchSettings.RobloxLaunchMode = LaunchMode.Player;
|
||||||
LaunchRoblox();
|
LaunchRoblox();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -120,6 +121,8 @@ namespace Bloxstrap
|
|||||||
Installer.DoUninstall(keepData);
|
Installer.DoUninstall(keepData);
|
||||||
|
|
||||||
Frontend.ShowMessageBox(Strings.Bootstrapper_SuccessfullyUninstalled, MessageBoxImage.Information);
|
Frontend.ShowMessageBox(Strings.Bootstrapper_SuccessfullyUninstalled, MessageBoxImage.Information);
|
||||||
|
|
||||||
|
App.Terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LaunchSettings()
|
public static void LaunchSettings()
|
||||||
@ -131,7 +134,7 @@ namespace Bloxstrap
|
|||||||
if (interlock.IsAcquired)
|
if (interlock.IsAcquired)
|
||||||
{
|
{
|
||||||
bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1;
|
bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1;
|
||||||
new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).ShowDialog();
|
new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).Show();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -156,7 +159,6 @@ namespace Bloxstrap
|
|||||||
{
|
{
|
||||||
const string LOG_IDENT = "LaunchHandler::LaunchRoblox";
|
const string LOG_IDENT = "LaunchHandler::LaunchRoblox";
|
||||||
|
|
||||||
|
|
||||||
if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll")))
|
if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll")))
|
||||||
{
|
{
|
||||||
Frontend.ShowMessageBox(Strings.Bootstrapper_WMFNotFound, MessageBoxImage.Error);
|
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
|
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
||||||
var bootstrapper = new Bootstrapper(installWebView2);
|
var bootstrapper = new Bootstrapper(installWebView2);
|
||||||
@ -206,45 +206,53 @@ namespace Bloxstrap
|
|||||||
dialog.Bootstrapper = bootstrapper;
|
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");
|
App.Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished");
|
||||||
|
|
||||||
// notifyicon is blocking main thread, must be disposed here
|
|
||||||
App.NotifyIcon?.Dispose();
|
|
||||||
|
|
||||||
if (t.IsFaulted)
|
if (t.IsFaulted)
|
||||||
|
{
|
||||||
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
||||||
|
|
||||||
if (t.Exception is null)
|
if (t.Exception is not null)
|
||||||
return;
|
App.FinalizeExceptionHandling(t.Exception, false);
|
||||||
|
}
|
||||||
|
|
||||||
App.Logger.WriteException(LOG_IDENT, t.Exception);
|
App.Terminate();
|
||||||
|
|
||||||
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();
|
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()
|
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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
public LaunchFlag StudioFlag { get; } = new("studio");
|
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; } = "";
|
public string RobloxLaunchArgs { get; private set; } = "";
|
||||||
|
|
||||||
|
@ -26,6 +26,10 @@
|
|||||||
"Bloxstrap (Studio Launch)": {
|
"Bloxstrap (Studio Launch)": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "-studio"
|
"commandLineArgs": "-studio"
|
||||||
|
},
|
||||||
|
"Bloxstrap (Watcher)": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "-watcher"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -61,7 +61,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</MenuItem.Header>
|
</MenuItem.Header>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem x:Name="CloseRobloxMenuItem" Visibility="Collapsed" Click="CloseRobloxMenuItem_Click">
|
<MenuItem Click="CloseRobloxMenuItem_Click">
|
||||||
<MenuItem.Header>
|
<MenuItem.Header>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@ -73,7 +73,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</MenuItem.Header>
|
</MenuItem.Header>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem x:Name="LogTracerMenuItem" Click="LogTracerMenuItem_Click">
|
<MenuItem x:Name="LogTracerMenuItem" Visibility="Collapsed" Click="LogTracerMenuItem_Click">
|
||||||
<MenuItem.Header>
|
<MenuItem.Header>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
@ -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
|
// 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 Watcher _watcher;
|
||||||
private readonly DiscordRichPresence? _richPresenceHandler;
|
|
||||||
|
private ActivityWatcher? _activityWatcher => _watcher.ActivityWatcher;
|
||||||
|
|
||||||
private ServerInformation? _serverInformationWindow;
|
private ServerInformation? _serverInformationWindow;
|
||||||
private int? _processId;
|
|
||||||
|
|
||||||
public MenuContainer(ActivityWatcher? activityWatcher, DiscordRichPresence? richPresenceHandler, int? processId)
|
public MenuContainer(Watcher watcher)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
_activityWatcher = activityWatcher;
|
_watcher = watcher;
|
||||||
_richPresenceHandler = richPresenceHandler;
|
|
||||||
_processId = processId;
|
|
||||||
|
|
||||||
if (_activityWatcher is not null)
|
if (_activityWatcher is not null)
|
||||||
{
|
{
|
||||||
|
_activityWatcher.OnLogOpen += ActivityWatcher_OnLogOpen;
|
||||||
_activityWatcher.OnGameJoin += ActivityWatcher_OnGameJoin;
|
_activityWatcher.OnGameJoin += ActivityWatcher_OnGameJoin;
|
||||||
_activityWatcher.OnGameLeave += ActivityWatcher_OnGameLeave;
|
_activityWatcher.OnGameLeave += ActivityWatcher_OnGameLeave;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_richPresenceHandler is not null)
|
if (_watcher.RichPresence is not null)
|
||||||
RichPresenceMenuItem.Visibility = Visibility.Visible;
|
RichPresenceMenuItem.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
if (_processId is not null)
|
|
||||||
CloseRobloxMenuItem.Visibility = Visibility.Visible;
|
|
||||||
|
|
||||||
VersionTextBlock.Text = $"{App.ProjectName} v{App.Version}";
|
VersionTextBlock.Text = $"{App.ProjectName} v{App.Version}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +51,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
{
|
{
|
||||||
if (_serverInformationWindow is null)
|
if (_serverInformationWindow is null)
|
||||||
{
|
{
|
||||||
_serverInformationWindow = new ServerInformation(_activityWatcher!);
|
_serverInformationWindow = new ServerInformation(_watcher);
|
||||||
_serverInformationWindow.Closed += (_, _) => _serverInformationWindow = null;
|
_serverInformationWindow.Closed += (_, _) => _serverInformationWindow = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,17 +61,23 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
_serverInformationWindow.Activate();
|
_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(() => {
|
Dispatcher.Invoke(() => {
|
||||||
if (_activityWatcher?.ActivityServerType == ServerType.Public)
|
if (_activityWatcher.ActivityServerType == ServerType.Public)
|
||||||
InviteDeeplinkMenuItem.Visibility = Visibility.Visible;
|
InviteDeeplinkMenuItem.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
ServerDetailsMenuItem.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(() => {
|
Dispatcher.Invoke(() => {
|
||||||
InviteDeeplinkMenuItem.Visibility = Visibility.Collapsed;
|
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 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}");
|
private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetDataObject($"roblox://experiences/start?placeId={_activityWatcher?.ActivityPlaceId}&gameInstanceId={_activityWatcher?.ActivityJobId}");
|
||||||
|
|
||||||
@ -110,12 +112,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
{
|
{
|
||||||
string? location = _activityWatcher?.LogLocation;
|
string? location = _activityWatcher?.LogLocation;
|
||||||
|
|
||||||
if (location is null)
|
if (location is not null)
|
||||||
{
|
|
||||||
Frontend.ShowMessageBox(Strings.ContextMenu_RobloxNotRunning, MessageBoxImage.Information);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utilities.ShellExecute(location);
|
Utilities.ShellExecute(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,9 +127,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
if (result != MessageBoxResult.Yes)
|
if (result != MessageBoxResult.Yes)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using Process process = Process.GetProcessById((int)_processId!);
|
_watcher.KillRobloxProcess();
|
||||||
process.Kill();
|
|
||||||
process.Close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,13 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ServerInformation
|
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();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
<ui:Button Content="{x:Static resources:Strings.Menu_Save}" Appearance="Primary" Command="{Binding SaveSettingsCommand, Mode=OneWay}" />
|
<ui:Button Content="{x:Static resources:Strings.Menu_Save}" Appearance="Primary" Command="{Binding SaveSettingsCommand, Mode=OneWay}" />
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
<StatusBarItem Grid.Column="2" Padding="4,0,0,0">
|
<StatusBarItem Grid.Column="2" Padding="4,0,0,0">
|
||||||
<ui:Button Content="{x:Static resources:Strings.Common_Close}" IsCancel="True" />
|
<ui:Button Content="{x:Static resources:Strings.Common_Close}" Command="{Binding CloseWindowCommand, Mode=OneWay}" />
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
</StatusBar>
|
</StatusBar>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -17,7 +17,9 @@ namespace Bloxstrap.UI.Elements.Settings
|
|||||||
public MainWindow(bool showAlreadyRunningWarning)
|
public MainWindow(bool showAlreadyRunningWarning)
|
||||||
{
|
{
|
||||||
var viewModel = new MainWindowViewModel();
|
var viewModel = new MainWindowViewModel();
|
||||||
|
|
||||||
viewModel.RequestSaveNoticeEvent += (_, _) => SettingsSavedSnackbar.Show();
|
viewModel.RequestSaveNoticeEvent += (_, _) => SettingsSavedSnackbar.Show();
|
||||||
|
viewModel.RequestCloseWindowEvent += (_, _) => Close();
|
||||||
|
|
||||||
DataContext = viewModel;
|
DataContext = viewModel;
|
||||||
|
|
||||||
@ -64,6 +66,9 @@ namespace Bloxstrap.UI.Elements.Settings
|
|||||||
if (result != MessageBoxResult.Yes)
|
if (result != MessageBoxResult.Yes)
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!e.Cancel)
|
||||||
|
App.Terminate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bloxstrap.Integrations;
|
using Bloxstrap.Integrations;
|
||||||
|
using Bloxstrap.UI.Elements.About;
|
||||||
using Bloxstrap.UI.Elements.ContextMenu;
|
using Bloxstrap.UI.Elements.ContextMenu;
|
||||||
|
|
||||||
namespace Bloxstrap.UI
|
namespace Bloxstrap.UI
|
||||||
@ -10,18 +11,21 @@ namespace Bloxstrap.UI
|
|||||||
private bool _disposing = false;
|
private bool _disposing = false;
|
||||||
|
|
||||||
private readonly System.Windows.Forms.NotifyIcon _notifyIcon;
|
private readonly System.Windows.Forms.NotifyIcon _notifyIcon;
|
||||||
private MenuContainer? _menuContainer;
|
|
||||||
|
|
||||||
private ActivityWatcher? _activityWatcher;
|
private readonly MenuContainer _menuContainer;
|
||||||
private DiscordRichPresence? _richPresenceHandler;
|
|
||||||
private int? _processId;
|
private readonly Watcher _watcher;
|
||||||
|
|
||||||
|
private ActivityWatcher? _activityWatcher => _watcher.ActivityWatcher;
|
||||||
|
|
||||||
EventHandler? _alertClickHandler;
|
EventHandler? _alertClickHandler;
|
||||||
|
|
||||||
public NotifyIconWrapper()
|
public NotifyIconWrapper(Watcher watcher)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine("NotifyIconWrapper::NotifyIconWrapper", "Initializing notification area icon");
|
App.Logger.WriteLine("NotifyIconWrapper::NotifyIconWrapper", "Initializing notification area icon");
|
||||||
|
|
||||||
|
_watcher = watcher;
|
||||||
|
|
||||||
_notifyIcon = new()
|
_notifyIcon = new()
|
||||||
{
|
{
|
||||||
Icon = Properties.Resources.IconBloxstrap,
|
Icon = Properties.Resources.IconBloxstrap,
|
||||||
@ -30,52 +34,18 @@ namespace Bloxstrap.UI
|
|||||||
};
|
};
|
||||||
|
|
||||||
_notifyIcon.MouseClick += MouseClickEventHandler;
|
_notifyIcon.MouseClick += MouseClickEventHandler;
|
||||||
}
|
|
||||||
|
|
||||||
#region Handler registers
|
|
||||||
public void SetRichPresenceHandler(DiscordRichPresence richPresenceHandler)
|
|
||||||
{
|
|
||||||
if (_richPresenceHandler is not null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_richPresenceHandler = richPresenceHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetActivityWatcher(ActivityWatcher activityWatcher)
|
|
||||||
{
|
|
||||||
if (_activityWatcher is not null)
|
if (_activityWatcher is not null)
|
||||||
return;
|
_activityWatcher.OnGameJoin += OnGameJoin;
|
||||||
|
|
||||||
_activityWatcher = activityWatcher;
|
_menuContainer = new(_watcher);
|
||||||
|
_menuContainer.Show();
|
||||||
if (App.Settings.Prop.ShowServerDetails)
|
|
||||||
_activityWatcher.OnGameJoin += (_, _) => Task.Run(OnGameJoin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetProcessId(int processId)
|
|
||||||
{
|
|
||||||
if (_processId is not null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_processId = processId;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Context menu
|
#region Context menu
|
||||||
public void InitializeContextMenu()
|
|
||||||
{
|
|
||||||
if (_menuContainer is not null || _disposing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
App.Logger.WriteLine("NotifyIconWrapper::InitializeContextMenu", "Initializing context menu");
|
|
||||||
|
|
||||||
_menuContainer = new(_activityWatcher, _richPresenceHandler, _processId);
|
|
||||||
_menuContainer.ShowDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MouseClickEventHandler(object? sender, System.Windows.Forms.MouseEventArgs e)
|
public void MouseClickEventHandler(object? sender, System.Windows.Forms.MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Button != System.Windows.Forms.MouseButtons.Right || _menuContainer is null)
|
if (e.Button != System.Windows.Forms.MouseButtons.Right)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_menuContainer.Activate();
|
_menuContainer.Activate();
|
||||||
@ -84,9 +54,12 @@ namespace Bloxstrap.UI
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Activity handlers
|
#region Activity handlers
|
||||||
public async void OnGameJoin()
|
public async void OnGameJoin(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
string serverLocation = await _activityWatcher!.GetServerLocation();
|
if (_activityWatcher is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string serverLocation = await _activityWatcher.GetServerLocation();
|
||||||
string title = _activityWatcher.ActivityServerType switch
|
string title = _activityWatcher.ActivityServerType switch
|
||||||
{
|
{
|
||||||
ServerType.Public => Strings.ContextMenu_ServerInformation_Notification_Title_Public,
|
ServerType.Public => Strings.ContextMenu_ServerInformation_Notification_Title_Public,
|
||||||
@ -99,7 +72,7 @@ namespace Bloxstrap.UI
|
|||||||
title,
|
title,
|
||||||
String.Format(Strings.ContextMenu_ServerInformation_Notification_Text, serverLocation),
|
String.Format(Strings.ContextMenu_ServerInformation_Notification_Text, serverLocation),
|
||||||
10,
|
10,
|
||||||
(_, _) => _menuContainer?.ShowServerInformationWindow()
|
(_, _) => _menuContainer.ShowServerInformationWindow()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@ -151,9 +124,8 @@ namespace Bloxstrap.UI
|
|||||||
|
|
||||||
App.Logger.WriteLine("NotifyIconWrapper::Dispose", "Disposing NotifyIcon");
|
App.Logger.WriteLine("NotifyIconWrapper::Dispose", "Disposing NotifyIcon");
|
||||||
|
|
||||||
_menuContainer?.Dispatcher.Invoke(_menuContainer.Close);
|
_menuContainer.Dispatcher.Invoke(_menuContainer.Close);
|
||||||
_notifyIcon?.Dispose();
|
_notifyIcon.Dispose();
|
||||||
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
@ -7,20 +7,23 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
|
|||||||
{
|
{
|
||||||
internal class ServerInformationViewModel : NotifyPropertyChangedViewModel
|
internal class ServerInformationViewModel : NotifyPropertyChangedViewModel
|
||||||
{
|
{
|
||||||
private readonly Window _window;
|
|
||||||
private readonly ActivityWatcher _activityWatcher;
|
private readonly ActivityWatcher _activityWatcher;
|
||||||
|
|
||||||
public string InstanceId => _activityWatcher.ActivityJobId;
|
public string InstanceId => _activityWatcher.ActivityJobId;
|
||||||
public string ServerType => Resources.Strings.ResourceManager.GetStringSafe($"Enums.ServerType.{_activityWatcher.ActivityServerType}");
|
|
||||||
public string ServerLocation { get; private set; } = Resources.Strings.ContextMenu_ServerInformation_Loading;
|
public string ServerType => Strings.ResourceManager.GetStringSafe($"Enums.ServerType.{_activityWatcher.ActivityServerType}");
|
||||||
|
|
||||||
|
public string ServerLocation { get; private set; } = Strings.ContextMenu_ServerInformation_Loading;
|
||||||
|
|
||||||
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
|
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
|
||||||
public ICommand CloseWindowCommand => new RelayCommand(_window.Close);
|
|
||||||
|
|
||||||
public ServerInformationViewModel(Window window, ActivityWatcher activityWatcher)
|
public ICommand CloseWindowCommand => new RelayCommand(RequestClose);
|
||||||
|
|
||||||
|
public EventHandler? RequestCloseEvent;
|
||||||
|
|
||||||
|
public ServerInformationViewModel(Watcher watcher)
|
||||||
{
|
{
|
||||||
_window = window;
|
_activityWatcher = watcher.ActivityWatcher!;
|
||||||
_activityWatcher = activityWatcher;
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@ -30,5 +33,7 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void CopyInstanceId() => Clipboard.SetDataObject(InstanceId);
|
private void CopyInstanceId() => Clipboard.SetDataObject(InstanceId);
|
||||||
|
|
||||||
|
private void RequestClose() => RequestCloseEvent?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,6 @@ namespace Bloxstrap.UI.ViewModels.Installer
|
|||||||
|
|
||||||
private void LaunchRoblox() => CloseWindowRequest?.Invoke(this, NextAction.LaunchRoblox);
|
private void LaunchRoblox() => CloseWindowRequest?.Invoke(this, NextAction.LaunchRoblox);
|
||||||
|
|
||||||
private void LaunchAbout() => new MainWindow().Show();
|
private void LaunchAbout() => new MainWindow().ShowDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,16 @@ namespace Bloxstrap.UI.ViewModels.Settings
|
|||||||
|
|
||||||
public ICommand SaveSettingsCommand => new RelayCommand(SaveSettings);
|
public ICommand SaveSettingsCommand => new RelayCommand(SaveSettings);
|
||||||
|
|
||||||
|
public ICommand CloseWindowCommand => new RelayCommand(CloseWindow);
|
||||||
|
|
||||||
public EventHandler? RequestSaveNoticeEvent;
|
public EventHandler? RequestSaveNoticeEvent;
|
||||||
|
|
||||||
|
public EventHandler? RequestCloseWindowEvent;
|
||||||
|
|
||||||
private void OpenAbout() => new MainWindow().ShowDialog();
|
private void OpenAbout() => new MainWindow().ShowDialog();
|
||||||
|
|
||||||
|
private void CloseWindow() => RequestCloseWindowEvent?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
private void SaveSettings()
|
private void SaveSettings()
|
||||||
{
|
{
|
||||||
const string LOG_IDENT = "MainWindowViewModel::SaveSettings";
|
const string LOG_IDENT = "MainWindowViewModel::SaveSettings";
|
||||||
@ -35,7 +41,7 @@ namespace Bloxstrap.UI.ViewModels.Settings
|
|||||||
|
|
||||||
App.PendingSettingTasks.Clear();
|
App.PendingSettingTasks.Clear();
|
||||||
|
|
||||||
RequestSaveNoticeEvent?.Invoke(this, new EventArgs());
|
RequestSaveNoticeEvent?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
147
Bloxstrap/Watcher.cs
Normal file
147
Bloxstrap/Watcher.cs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
using Bloxstrap.Integrations;
|
||||||
|
using System.CodeDom;
|
||||||
|
using System.Security.Permissions;
|
||||||
|
|
||||||
|
namespace Bloxstrap
|
||||||
|
{
|
||||||
|
public class Watcher : IDisposable
|
||||||
|
{
|
||||||
|
private int _gameClientPid = 0;
|
||||||
|
|
||||||
|
private readonly InterProcessLock _lock = new("Watcher");
|
||||||
|
|
||||||
|
private readonly List<int> _autoclosePids = new();
|
||||||
|
|
||||||
|
private readonly NotifyIconWrapper? _notifyIcon;
|
||||||
|
|
||||||
|
public readonly ActivityWatcher? ActivityWatcher;
|
||||||
|
|
||||||
|
public readonly DiscordRichPresence? RichPresence;
|
||||||
|
|
||||||
|
public Watcher()
|
||||||
|
{
|
||||||
|
const string LOG_IDENT = "Watcher";
|
||||||
|
|
||||||
|
if (!_lock.IsAcquired)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Watcher instance already exists");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? watcherData = App.LaunchSettings.WatcherFlag.Data;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if (String.IsNullOrEmpty(watcherData))
|
||||||
|
{
|
||||||
|
string path = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
|
||||||
|
using var gameClientProcess = Process.Start(path);
|
||||||
|
_gameClientPid = gameClientProcess.Id;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (String.IsNullOrEmpty(watcherData))
|
||||||
|
throw new Exception("Watcher data not specified");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(watcherData) && _gameClientPid == 0)
|
||||||
|
{
|
||||||
|
var split = watcherData.Split(';');
|
||||||
|
|
||||||
|
if (split.Length == 0)
|
||||||
|
_ = int.TryParse(watcherData, out _gameClientPid);
|
||||||
|
|
||||||
|
if (split.Length >= 1)
|
||||||
|
_ = int.TryParse(split[0], out _gameClientPid);
|
||||||
|
|
||||||
|
if (split.Length >= 2)
|
||||||
|
{
|
||||||
|
foreach (string strPid in split[0].Split(';'))
|
||||||
|
{
|
||||||
|
if (int.TryParse(strPid, out int pid) && pid != 0)
|
||||||
|
_autoclosePids.Add(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gameClientPid == 0)
|
||||||
|
throw new Exception("Watcher data is invalid");
|
||||||
|
|
||||||
|
if (App.Settings.Prop.EnableActivityTracking)
|
||||||
|
{
|
||||||
|
ActivityWatcher = new();
|
||||||
|
|
||||||
|
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)
|
||||||
|
RichPresence = new(ActivityWatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
_notifyIcon = new(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KillRobloxProcess() => KillProcess(_gameClientPid);
|
||||||
|
|
||||||
|
public void KillProcess(int pid)
|
||||||
|
{
|
||||||
|
using var process = Process.GetProcessById(pid);
|
||||||
|
|
||||||
|
App.Logger.WriteLine("Watcher::KillProcess", $"Killing process '{process.ProcessName}' (PID {process.Id})");
|
||||||
|
|
||||||
|
if (process.HasExited)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine("Watcher::KillProcess", $"PID {process.Id} has already exited");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
process.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseProcess(int pid)
|
||||||
|
{
|
||||||
|
using var process = Process.GetProcessById(pid);
|
||||||
|
|
||||||
|
App.Logger.WriteLine("Watcher::CloseProcess", $"Closing process '{process.ProcessName}' (PID {process.Id})");
|
||||||
|
|
||||||
|
if (process.HasExited)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine("Watcher::CloseProcess", $"PID {process.Id} has already exited");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.CloseMainWindow();
|
||||||
|
process.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Run()
|
||||||
|
{
|
||||||
|
if (!_lock.IsAcquired)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ActivityWatcher?.Start();
|
||||||
|
|
||||||
|
while (Utilities.GetProcessesSafe().Any(x => x.Id == _gameClientPid))
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
foreach (int pid in _autoclosePids)
|
||||||
|
CloseProcess(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine("Watcher::Dispose", "Disposing Watcher");
|
||||||
|
|
||||||
|
_notifyIcon?.Dispose();
|
||||||
|
RichPresence?.Dispose();
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user