Skip to content

Commit

Permalink
Improve watcher/failed launch handling
Browse files Browse the repository at this point in the history
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
  • Loading branch information
pizzaboxer committed Oct 12, 2024
1 parent b918d27 commit aac6ec3
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 91 deletions.
2 changes: 0 additions & 2 deletions Bloxstrap/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

using Microsoft.Win32;

using Bloxstrap.Models.SettingTasks.Base;

namespace Bloxstrap
{
/// <summary>
Expand Down
69 changes: 55 additions & 14 deletions Bloxstrap/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class Bootstrapper
private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath);
private bool _noConnection = false;

private AsyncMutex? _mutex;

private int _appPid = 0;

public IBootstrapperDialog? Dialog = null;
Expand Down Expand Up @@ -191,6 +193,8 @@ public async Task Run()
await using var mutex = new AsyncMutex(false, "Bloxstrap-Bootstrapper");
await mutex.AcquireAsync(_cancelTokenSource.Token);

_mutex = mutex;

// reload our configs since they've likely changed by now
if (mutexExists)
{
Expand Down Expand Up @@ -230,11 +234,14 @@ public async Task Run()
else
WindowsRegistry.RegisterPlayer();

await mutex.ReleaseAsync();
if (_launchMode != LaunchMode.Player)
await mutex.ReleaseAsync();

if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested)
StartRoblox();

await mutex.ReleaseAsync();

Dialog?.CloseBootstrapper();
}

Expand Down Expand Up @@ -336,12 +343,27 @@ private void StartRoblox()
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))
{
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
try
Expand All @@ -358,11 +380,26 @@ private void StartRoblox()

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");

if (startEventSignalled)
App.Logger.WriteLine(LOG_IDENT, "Start event signalled");
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}");
}

_mutex?.ReleaseAsync();
}

if (IsStudioLaunch)
return;
Expand Down Expand Up @@ -391,23 +428,27 @@ private void StartRoblox()
catch (Exception ex)
{
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);
}

string argPids = _appPid.ToString();

if (autoclosePids.Any())
argPids += $";{String.Join(',', autoclosePids)}";

if (App.Settings.Prop.EnableActivityTracking || App.LaunchSettings.TestModeFlag.Active || autoclosePids.Any())
{
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)
args += " -testmode";
Expand Down
78 changes: 42 additions & 36 deletions Bloxstrap/Integrations/ActivityWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public class ActivityWatcher : IDisposable

public bool IsDisposed = false;

public ActivityWatcher(string? logFile = null)
{
if (!String.IsNullOrEmpty(logFile))
LogLocation = logFile;
}

public async void Start()
{
const string LOG_IDENT = "ActivityWatcher::Start";
Expand All @@ -65,58 +71,58 @@ public async void Start()
// - check for leaves/disconnects with 'Time to disconnect replication data: {{TIME}}' entry
//
// we'll tail the log file continuously, monitoring for any log entries that we need to determine the current game activity

FileInfo logFileInfo;

string logDirectory = Path.Combine(Paths.LocalAppData, "Roblox\\logs");
if (String.IsNullOrEmpty(LogLocation))
{
string logDirectory = Path.Combine(Paths.LocalAppData, "Roblox\\logs");

if (!Directory.Exists(logDirectory))
return;
if (!Directory.Exists(logDirectory))
return;

FileInfo logFileInfo;
// 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
// good rule of thumb is to find a log file that was created in the last 15 seconds or so

App.Logger.WriteLine(LOG_IDENT, "Opening Roblox 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
// good rule of thumb is to find a log file that was created in the last 15 seconds or so
while (true)
{
logFileInfo = new DirectoryInfo(logDirectory)
.GetFiles()
.Where(x => x.Name.Contains("Player", StringComparison.OrdinalIgnoreCase) && x.CreationTime <= DateTime.Now)
.OrderByDescending(x => x.CreationTime)
.First();

App.Logger.WriteLine(LOG_IDENT, "Opening Roblox log file...");
if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
break;

while (true)
{
logFileInfo = new DirectoryInfo(logDirectory)
.GetFiles()
.Where(x => x.Name.Contains("Player", StringComparison.OrdinalIgnoreCase) && x.CreationTime <= DateTime.Now)
.OrderByDescending(x => x.CreationTime)
.First();
App.Logger.WriteLine(LOG_IDENT, $"Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})");
await Task.Delay(1000);
}

if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
break;
OnLogOpen?.Invoke(this, EventArgs.Empty);

App.Logger.WriteLine(LOG_IDENT, $"Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})");
await Task.Delay(1000);
LogLocation = logFileInfo.FullName;
}
else
{
logFileInfo = new FileInfo(LogLocation);
}

OnLogOpen?.Invoke(this, EventArgs.Empty);
var logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

LogLocation = logFileInfo.FullName;
FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}");

var logUpdatedEvent = new AutoResetEvent(false);
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);
using var streamReader = new StreamReader(logFileStream);

while (!IsDisposed)
{
string? log = await sr.ReadLineAsync();
string? log = await streamReader.ReadLineAsync();

if (log is null)
logUpdatedEvent.WaitOne(250);
await Task.Delay(1000);
else
ReadLogEntry(log);
}
Expand Down Expand Up @@ -265,7 +271,7 @@ private void ReadLogEntry(string entry)
InGame = true;
Data.TimeJoined = DateTime.Now;

OnGameJoin?.Invoke(this, new EventArgs());
OnGameJoin?.Invoke(this, EventArgs.Empty);
}
}
else if (InGame && Data.PlaceId != 0)
Expand All @@ -282,7 +288,7 @@ private void ReadLogEntry(string entry)
InGame = false;
Data = new();

OnGameLeave?.Invoke(this, new EventArgs());
OnGameLeave?.Invoke(this, EventArgs.Empty);
}
else if (entry.Contains(GameTeleportingEntry))
{
Expand Down
11 changes: 11 additions & 0 deletions Bloxstrap/Models/WatcherData.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
6 changes: 2 additions & 4 deletions Bloxstrap/UI/Frontend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

using Bloxstrap.UI.Elements.Bootstrapper;
using Bloxstrap.UI.Elements.Dialogs;
using Bloxstrap.UI.Elements.Settings;
using Bloxstrap.UI.Elements.Installer;
using System.Drawing;

namespace Bloxstrap.UI
{
Expand All @@ -31,7 +28,8 @@ public static void ShowPlayerErrorDialog(bool crash = false)
topLine = Strings.Dialog_PlayerError_Crash;

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)
Expand Down
Loading

0 comments on commit aac6ec3

Please sign in to comment.