mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-21 10:01:27 -07:00
Improve watcher/failed launch handling
Bloxstrap now attempts to identify the log file *when* Roblox launches, then passes it to the watcher It does this so that it can determine if Roblox fails to launch
This commit is contained in:
parent
b918d27452
commit
aac6ec3d4c
@ -5,8 +5,6 @@ using System.Windows.Threading;
|
|||||||
|
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
|
||||||
using Bloxstrap.Models.SettingTasks.Base;
|
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -59,6 +59,8 @@ namespace Bloxstrap
|
|||||||
private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath);
|
private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath);
|
||||||
private bool _noConnection = false;
|
private bool _noConnection = false;
|
||||||
|
|
||||||
|
private AsyncMutex? _mutex;
|
||||||
|
|
||||||
private int _appPid = 0;
|
private int _appPid = 0;
|
||||||
|
|
||||||
public IBootstrapperDialog? Dialog = null;
|
public IBootstrapperDialog? Dialog = null;
|
||||||
@ -191,6 +193,8 @@ namespace Bloxstrap
|
|||||||
await using var mutex = new AsyncMutex(false, "Bloxstrap-Bootstrapper");
|
await using var mutex = new AsyncMutex(false, "Bloxstrap-Bootstrapper");
|
||||||
await mutex.AcquireAsync(_cancelTokenSource.Token);
|
await mutex.AcquireAsync(_cancelTokenSource.Token);
|
||||||
|
|
||||||
|
_mutex = mutex;
|
||||||
|
|
||||||
// reload our configs since they've likely changed by now
|
// reload our configs since they've likely changed by now
|
||||||
if (mutexExists)
|
if (mutexExists)
|
||||||
{
|
{
|
||||||
@ -230,11 +234,14 @@ namespace Bloxstrap
|
|||||||
else
|
else
|
||||||
WindowsRegistry.RegisterPlayer();
|
WindowsRegistry.RegisterPlayer();
|
||||||
|
|
||||||
|
if (_launchMode != LaunchMode.Player)
|
||||||
await mutex.ReleaseAsync();
|
await mutex.ReleaseAsync();
|
||||||
|
|
||||||
if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested)
|
if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested)
|
||||||
StartRoblox();
|
StartRoblox();
|
||||||
|
|
||||||
|
await mutex.ReleaseAsync();
|
||||||
|
|
||||||
Dialog?.CloseBootstrapper();
|
Dialog?.CloseBootstrapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,13 +343,28 @@ namespace Bloxstrap
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool startEventSignalled;
|
string? logFileName = null;
|
||||||
|
|
||||||
// TODO: figure out why this is causing roblox to block for some users
|
|
||||||
using (var startEvent = new EventWaitHandle(false, EventResetMode.ManualReset, AppData.StartEvent))
|
using (var startEvent = new EventWaitHandle(false, EventResetMode.ManualReset, AppData.StartEvent))
|
||||||
{
|
{
|
||||||
startEvent.Reset();
|
startEvent.Reset();
|
||||||
|
|
||||||
|
var logWatcher = new FileSystemWatcher()
|
||||||
|
{
|
||||||
|
Path = Path.Combine(Paths.LocalAppData, "Roblox\\logs"),
|
||||||
|
Filter = "*.log",
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var logCreatedEvent = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
logWatcher.Created += (_, e) =>
|
||||||
|
{
|
||||||
|
logWatcher.EnableRaisingEvents = false;
|
||||||
|
logFileName = e.FullPath;
|
||||||
|
logCreatedEvent.Set();
|
||||||
|
};
|
||||||
|
|
||||||
// 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
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -358,11 +380,26 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {_appPid}), waiting for start event");
|
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {_appPid}), waiting for start event");
|
||||||
|
|
||||||
startEventSignalled = startEvent.WaitOne(TimeSpan.FromSeconds(5));
|
if (startEvent.WaitOne(TimeSpan.FromSeconds(5)))
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Start event signalled");
|
||||||
|
else
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Start event not signalled, implying successful launch");
|
||||||
|
|
||||||
|
logCreatedEvent.WaitOne(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
if (String.IsNullOrEmpty(logFileName))
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Unable to identify log file");
|
||||||
|
Frontend.ShowPlayerErrorDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Got log file as {logFileName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startEventSignalled)
|
_mutex?.ReleaseAsync();
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Start event signalled");
|
}
|
||||||
|
|
||||||
if (IsStudioLaunch)
|
if (IsStudioLaunch)
|
||||||
return;
|
return;
|
||||||
@ -391,23 +428,27 @@ namespace Bloxstrap
|
|||||||
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)
|
if (integration.AutoClose && pid != 0)
|
||||||
autoclosePids.Add(pid);
|
autoclosePids.Add(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
string argPids = _appPid.ToString();
|
|
||||||
|
|
||||||
if (autoclosePids.Any())
|
|
||||||
argPids += $";{String.Join(',', autoclosePids)}";
|
|
||||||
|
|
||||||
if (App.Settings.Prop.EnableActivityTracking || App.LaunchSettings.TestModeFlag.Active || autoclosePids.Any())
|
if (App.Settings.Prop.EnableActivityTracking || App.LaunchSettings.TestModeFlag.Active || autoclosePids.Any())
|
||||||
{
|
{
|
||||||
using var ipl = new InterProcessLock("Watcher", TimeSpan.FromSeconds(5));
|
using var ipl = new InterProcessLock("Watcher", TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
string args = $"-watcher \"{argPids}\"";
|
var watcherData = new WatcherData
|
||||||
|
{
|
||||||
|
ProcessId = _appPid,
|
||||||
|
LogFile = logFileName,
|
||||||
|
AutoclosePids = autoclosePids
|
||||||
|
};
|
||||||
|
|
||||||
|
string watcherDataArg = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(watcherData)));
|
||||||
|
|
||||||
|
string args = $"-watcher \"{watcherDataArg}\"";
|
||||||
|
|
||||||
if (App.LaunchSettings.TestModeFlag.Active)
|
if (App.LaunchSettings.TestModeFlag.Active)
|
||||||
args += " -testmode";
|
args += " -testmode";
|
||||||
|
@ -51,6 +51,12 @@
|
|||||||
|
|
||||||
public bool IsDisposed = false;
|
public bool IsDisposed = false;
|
||||||
|
|
||||||
|
public ActivityWatcher(string? logFile = null)
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrEmpty(logFile))
|
||||||
|
LogLocation = logFile;
|
||||||
|
}
|
||||||
|
|
||||||
public async void Start()
|
public async void Start()
|
||||||
{
|
{
|
||||||
const string LOG_IDENT = "ActivityWatcher::Start";
|
const string LOG_IDENT = "ActivityWatcher::Start";
|
||||||
@ -66,13 +72,15 @@
|
|||||||
//
|
//
|
||||||
// we'll tail the log file continuously, monitoring for any log entries that we need to determine the current game activity
|
// we'll tail the log file continuously, monitoring for any log entries that we need to determine the current game activity
|
||||||
|
|
||||||
|
FileInfo logFileInfo;
|
||||||
|
|
||||||
|
if (String.IsNullOrEmpty(LogLocation))
|
||||||
|
{
|
||||||
string logDirectory = Path.Combine(Paths.LocalAppData, "Roblox\\logs");
|
string logDirectory = Path.Combine(Paths.LocalAppData, "Roblox\\logs");
|
||||||
|
|
||||||
if (!Directory.Exists(logDirectory))
|
if (!Directory.Exists(logDirectory))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FileInfo logFileInfo;
|
|
||||||
|
|
||||||
// we need to make sure we're fetching the absolute latest log file
|
// we need to make sure we're fetching the absolute latest log file
|
||||||
// if roblox doesn't start quickly enough, we can wind up fetching the previous log file
|
// if roblox doesn't start quickly enough, we can wind up fetching the previous log file
|
||||||
// good rule of thumb is to find a log file that was created in the last 15 seconds or so
|
// good rule of thumb is to find a log file that was created in the last 15 seconds or so
|
||||||
@ -97,26 +105,24 @@
|
|||||||
OnLogOpen?.Invoke(this, EventArgs.Empty);
|
OnLogOpen?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
LogLocation = logFileInfo.FullName;
|
LogLocation = logFileInfo.FullName;
|
||||||
FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logFileInfo = new FileInfo(LogLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}");
|
App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}");
|
||||||
|
|
||||||
var logUpdatedEvent = new AutoResetEvent(false);
|
using var streamReader = new StreamReader(logFileStream);
|
||||||
var logWatcher = new FileSystemWatcher()
|
|
||||||
{
|
|
||||||
Path = logDirectory,
|
|
||||||
Filter = Path.GetFileName(logFileInfo.FullName),
|
|
||||||
EnableRaisingEvents = true
|
|
||||||
};
|
|
||||||
logWatcher.Changed += (s, e) => logUpdatedEvent.Set();
|
|
||||||
|
|
||||||
using var sr = new StreamReader(logFileStream);
|
|
||||||
|
|
||||||
while (!IsDisposed)
|
while (!IsDisposed)
|
||||||
{
|
{
|
||||||
string? log = await sr.ReadLineAsync();
|
string? log = await streamReader.ReadLineAsync();
|
||||||
|
|
||||||
if (log is null)
|
if (log is null)
|
||||||
logUpdatedEvent.WaitOne(250);
|
await Task.Delay(1000);
|
||||||
else
|
else
|
||||||
ReadLogEntry(log);
|
ReadLogEntry(log);
|
||||||
}
|
}
|
||||||
@ -265,7 +271,7 @@
|
|||||||
InGame = true;
|
InGame = true;
|
||||||
Data.TimeJoined = DateTime.Now;
|
Data.TimeJoined = DateTime.Now;
|
||||||
|
|
||||||
OnGameJoin?.Invoke(this, new EventArgs());
|
OnGameJoin?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (InGame && Data.PlaceId != 0)
|
else if (InGame && Data.PlaceId != 0)
|
||||||
@ -282,7 +288,7 @@
|
|||||||
InGame = false;
|
InGame = false;
|
||||||
Data = new();
|
Data = new();
|
||||||
|
|
||||||
OnGameLeave?.Invoke(this, new EventArgs());
|
OnGameLeave?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
else if (entry.Contains(GameTeleportingEntry))
|
else if (entry.Contains(GameTeleportingEntry))
|
||||||
{
|
{
|
||||||
|
11
Bloxstrap/Models/WatcherData.cs
Normal file
11
Bloxstrap/Models/WatcherData.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Bloxstrap.Models
|
||||||
|
{
|
||||||
|
internal class WatcherData
|
||||||
|
{
|
||||||
|
public int ProcessId { get; set; }
|
||||||
|
|
||||||
|
public string? LogFile { get; set; }
|
||||||
|
|
||||||
|
public List<int>? AutoclosePids { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
using Bloxstrap.UI.Elements.Bootstrapper;
|
using Bloxstrap.UI.Elements.Bootstrapper;
|
||||||
using Bloxstrap.UI.Elements.Dialogs;
|
using Bloxstrap.UI.Elements.Dialogs;
|
||||||
using Bloxstrap.UI.Elements.Settings;
|
|
||||||
using Bloxstrap.UI.Elements.Installer;
|
|
||||||
using System.Drawing;
|
|
||||||
|
|
||||||
namespace Bloxstrap.UI
|
namespace Bloxstrap.UI
|
||||||
{
|
{
|
||||||
@ -31,7 +28,8 @@ namespace Bloxstrap.UI
|
|||||||
topLine = Strings.Dialog_PlayerError_Crash;
|
topLine = Strings.Dialog_PlayerError_Crash;
|
||||||
|
|
||||||
ShowMessageBox($"{topLine}\n\n{Strings.Dialog_PlayerError_HelpInformation}", MessageBoxImage.Error);
|
ShowMessageBox($"{topLine}\n\n{Strings.Dialog_PlayerError_HelpInformation}", MessageBoxImage.Error);
|
||||||
Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Roblox-crashes-or-does-not-launch");
|
|
||||||
|
// Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Roblox-crashes-or-does-not-launch");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ShowExceptionDialog(Exception exception)
|
public static void ShowExceptionDialog(Exception exception)
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
using Bloxstrap.Integrations;
|
using Bloxstrap.Integrations;
|
||||||
|
using Bloxstrap.Models;
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
public class Watcher : IDisposable
|
public class Watcher : IDisposable
|
||||||
{
|
{
|
||||||
private int _gameClientPid = 0;
|
|
||||||
|
|
||||||
private readonly InterProcessLock _lock = new("Watcher");
|
private readonly InterProcessLock _lock = new("Watcher");
|
||||||
|
|
||||||
private readonly List<int> _autoclosePids = new();
|
private readonly WatcherData? _watcherData;
|
||||||
|
|
||||||
private readonly NotifyIconWrapper? _notifyIcon;
|
private readonly NotifyIconWrapper? _notifyIcon;
|
||||||
|
|
||||||
@ -26,53 +25,37 @@ namespace Bloxstrap
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? watcherData = App.LaunchSettings.WatcherFlag.Data;
|
string? watcherDataArg = App.LaunchSettings.WatcherFlag.Data;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (String.IsNullOrEmpty(watcherData))
|
if (String.IsNullOrEmpty(watcherDataArg))
|
||||||
{
|
{
|
||||||
string path = Path.Combine(Paths.Roblox, "Player", "RobloxPlayerBeta.exe");
|
string path = Path.Combine(Paths.Roblox, "Player", "RobloxPlayerBeta.exe");
|
||||||
using var gameClientProcess = Process.Start(path);
|
using var gameClientProcess = Process.Start(path);
|
||||||
_gameClientPid = gameClientProcess.Id;
|
|
||||||
|
_watcherData = new() { ProcessId = gameClientProcess.Id };
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (String.IsNullOrEmpty(watcherData))
|
if (String.IsNullOrEmpty(watcherDataArg))
|
||||||
throw new Exception("Watcher data not specified");
|
throw new Exception("Watcher data not specified");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(watcherData) && _gameClientPid == 0)
|
if (!String.IsNullOrEmpty(watcherDataArg))
|
||||||
{
|
_watcherData = JsonSerializer.Deserialize<WatcherData>(Encoding.UTF8.GetString(Convert.FromBase64String(watcherDataArg)));
|
||||||
var split = watcherData.Split(';');
|
|
||||||
|
|
||||||
if (split.Length == 0)
|
if (_watcherData is null)
|
||||||
_ = int.TryParse(watcherData, out _gameClientPid);
|
|
||||||
|
|
||||||
if (split.Length >= 1)
|
|
||||||
_ = int.TryParse(split[0], out _gameClientPid);
|
|
||||||
|
|
||||||
if (split.Length >= 2)
|
|
||||||
{
|
|
||||||
foreach (string strPid in split[1].Split(','))
|
|
||||||
{
|
|
||||||
if (int.TryParse(strPid, out int pid) && pid != 0)
|
|
||||||
_autoclosePids.Add(pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_gameClientPid == 0)
|
|
||||||
throw new Exception("Watcher data is invalid");
|
throw new Exception("Watcher data is invalid");
|
||||||
|
|
||||||
if (App.Settings.Prop.EnableActivityTracking)
|
if (App.Settings.Prop.EnableActivityTracking)
|
||||||
{
|
{
|
||||||
ActivityWatcher = new();
|
ActivityWatcher = new(_watcherData.LogFile);
|
||||||
|
|
||||||
if (App.Settings.Prop.UseDisableAppPatch)
|
if (App.Settings.Prop.UseDisableAppPatch)
|
||||||
{
|
{
|
||||||
ActivityWatcher.OnAppClose += delegate
|
ActivityWatcher.OnAppClose += delegate
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Received desktop app exit, closing Roblox");
|
App.Logger.WriteLine(LOG_IDENT, "Received desktop app exit, closing Roblox");
|
||||||
using var process = Process.GetProcessById(_gameClientPid);
|
using var process = Process.GetProcessById(_watcherData.ProcessId);
|
||||||
process.CloseMainWindow();
|
process.CloseMainWindow();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -84,11 +67,12 @@ namespace Bloxstrap
|
|||||||
_notifyIcon = new(this);
|
_notifyIcon = new(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void KillRobloxProcess() => CloseProcess(_gameClientPid, true);
|
public void KillRobloxProcess() => CloseProcess(_watcherData!.ProcessId, true);
|
||||||
|
|
||||||
public void CloseProcess(int pid, bool force = false)
|
public void CloseProcess(int pid, bool force = false)
|
||||||
{
|
{
|
||||||
const string LOG_IDENT = "Watcher::CloseProcess";
|
const string LOG_IDENT = "Watcher::CloseProcess";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var process = Process.GetProcessById(pid);
|
using var process = Process.GetProcessById(pid);
|
||||||
@ -115,16 +99,19 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
public async Task Run()
|
public async Task Run()
|
||||||
{
|
{
|
||||||
if (!_lock.IsAcquired)
|
if (!_lock.IsAcquired || _watcherData is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ActivityWatcher?.Start();
|
ActivityWatcher?.Start();
|
||||||
|
|
||||||
while (Utilities.GetProcessesSafe().Any(x => x.Id == _gameClientPid))
|
while (Utilities.GetProcessesSafe().Any(x => x.Id == _watcherData.ProcessId))
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
foreach (int pid in _autoclosePids)
|
if (_watcherData.AutoclosePids is not null)
|
||||||
|
{
|
||||||
|
foreach (int pid in _watcherData.AutoclosePids)
|
||||||
CloseProcess(pid);
|
CloseProcess(pid);
|
||||||
|
}
|
||||||
|
|
||||||
if (App.LaunchSettings.TestModeFlag.Active)
|
if (App.LaunchSettings.TestModeFlag.Active)
|
||||||
Process.Start(Paths.Process, "-settings -testmode");
|
Process.Start(Paths.Process, "-settings -testmode");
|
||||||
|
Loading…
Reference in New Issue
Block a user