Skip to content

Commit

Permalink
Draft: game history (+ other minor fixes)
Browse files Browse the repository at this point in the history
  • Loading branch information
pizzaboxer committed Sep 3, 2024
1 parent b4a9710 commit dfcd5b6
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 24 deletions.
3 changes: 2 additions & 1 deletion Bloxstrap/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ private void StartRoblox()
int gameClientPid;
bool startEventSignalled;

// 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();
Expand Down Expand Up @@ -387,7 +388,7 @@ private void StartRoblox()

if (App.Settings.Prop.EnableActivityTracking || autoclosePids.Any())
{
using var ipl = new InterProcessLock("Watcher");
using var ipl = new InterProcessLock("Watcher", TimeSpan.FromSeconds(5));

if (ipl.IsAcquired)
Process.Start(Paths.Process, $"-watcher \"{args}\"");
Expand Down
46 changes: 44 additions & 2 deletions Bloxstrap/Integrations/ActivityWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class ActivityWatcher : IDisposable
private const string GameJoiningEntry = "[FLog::Output] ! Joining game";
private const string GameJoiningPrivateServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::joinGamePostPrivateServer";
private const string GameJoiningReservedServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::initiateTeleportToReservedServer";
private const string GameJoiningUniverseEntry = "[FLog::GameJoinLoadTime] Report game_join_loadtime:";
private const string GameJoiningUDMUXEntry = "[FLog::Network] UDMUX Address = ";
private const string GameJoinedEntry = "[FLog::Network] serverId:";
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
Expand All @@ -17,6 +18,7 @@ public class ActivityWatcher : IDisposable
private const string GameLeavingEntry = "[FLog::SingleSurfaceApp] leaveUGCGameInternal";

private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
private const string GameJoiningUniversePattern = @"universeid:([0-9]+)";
private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|[0-9]+";
private const string GameMessageEntryPattern = @"\[BloxstrapRPC\] (.*)";
Expand All @@ -39,15 +41,19 @@ public class ActivityWatcher : IDisposable

// these are values to use assuming the player isn't currently in a game
// hmm... do i move this to a model?
public DateTime ActivityTimeJoined;
public bool ActivityInGame = false;
public long ActivityPlaceId = 0;
public long ActivityUniverseId = 0;
public string ActivityJobId = "";
public string ActivityMachineAddress = "";
public bool ActivityMachineUDMUX = false;
public bool ActivityIsTeleport = false;
public string ActivityLaunchData = "";
public ServerType ActivityServerType = ServerType.Public;

public List<ActivityHistoryEntry> ActivityHistory = new();

public bool IsDisposed = false;

public async void Start()
Expand Down Expand Up @@ -131,6 +137,7 @@ public string GetActivityDeeplink()
return deeplink;
}

// TODO: i need to double check how this handles failed game joins (connection error, invalid permissions, etc)
private void ReadLogEntry(string entry)
{
const string LOG_IDENT = "ActivityWatcher::ReadLogEntry";
Expand All @@ -151,6 +158,8 @@ private void ReadLogEntry(string entry)

if (!ActivityInGame && ActivityPlaceId == 0)
{
// We are not in a game, nor are in the process of joining one

if (entry.Contains(GameJoiningPrivateServerEntry))
{
// we only expect to be joining a private server if we're not already in a game
Expand Down Expand Up @@ -189,13 +198,28 @@ private void ReadLogEntry(string entry)
}
else if (!ActivityInGame && ActivityPlaceId != 0)
{
if (entry.Contains(GameJoiningUDMUXEntry))
// We are not confirmed to be in a game, but we are in the process of joining one

if (entry.Contains(GameJoiningUniverseEntry))
{
var match = Regex.Match(entry, GameJoiningUniversePattern);

if (match.Groups.Count != 2)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join universe entry");
App.Logger.WriteLine(LOG_IDENT, entry);
return;
}

ActivityUniverseId = long.Parse(match.Groups[1].Value);
}
else if (entry.Contains(GameJoiningUDMUXEntry))
{
Match match = Regex.Match(entry, GameJoiningUDMUXPattern);

if (match.Groups.Count != 3 || match.Groups[2].Value != ActivityMachineAddress)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game join UDMUX entry");
App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join UDMUX entry");
App.Logger.WriteLine(LOG_IDENT, entry);
return;
}
Expand All @@ -219,17 +243,35 @@ private void ReadLogEntry(string entry)
App.Logger.WriteLine(LOG_IDENT, $"Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");

ActivityInGame = true;
ActivityTimeJoined = DateTime.Now;

OnGameJoin?.Invoke(this, new EventArgs());
}
}
else if (ActivityInGame && ActivityPlaceId != 0)
{
// We are confirmed to be in a game

if (entry.Contains(GameDisconnectedEntry))
{
App.Logger.WriteLine(LOG_IDENT, $"Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");

// TODO: should this be including launchdata?
if (ActivityServerType != ServerType.Reserved)
{
ActivityHistory.Insert(0, new ActivityHistoryEntry
{
PlaceId = ActivityPlaceId,
UniverseId = ActivityUniverseId,
JobId = ActivityJobId,
TimeJoined = ActivityTimeJoined,
TimeLeft = DateTime.Now
});
}

ActivityInGame = false;
ActivityPlaceId = 0;
ActivityUniverseId = 0;
ActivityJobId = "";
ActivityMachineAddress = "";
ActivityMachineUDMUX = false;
Expand Down
11 changes: 2 additions & 9 deletions Bloxstrap/Integrations/DiscordRichPresence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,8 @@ public async Task<bool> SetCurrentGame()

// 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");
if (universeIdResponse is null)
{
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe ID!");
return false;
}

long universeId = universeIdResponse.UniverseId;
App.Logger.WriteLine(LOG_IDENT, $"Got Universe ID as {universeId}");
long universeId = _activityWatcher.ActivityUniverseId;

// preserve time spent playing if we're teleporting between places in the same universe
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
Expand Down Expand Up @@ -247,7 +240,7 @@ public async Task<bool> SetCurrentGame()
buttons.Add(new Button
{
Label = "Join server",
Url = $"roblox://experiences/start?placeId={placeId}&gameInstanceId={_activityWatcher.ActivityJobId}"
Url = _activityWatcher.GetActivityDeeplink()
});
}

Expand Down
2 changes: 1 addition & 1 deletion Bloxstrap/LaunchHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public static void LaunchRoblox()
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
if (t.Exception is not null)
App.FinalizeExceptionHandling(t.Exception, false);
App.FinalizeExceptionHandling(t.Exception);
}
App.Terminate();
Expand Down
38 changes: 38 additions & 0 deletions Bloxstrap/Models/ActivityHistoryEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using CommunityToolkit.Mvvm.Input;
using System.Windows.Input;

namespace Bloxstrap.Models
{
public class ActivityHistoryEntry
{
public long UniverseId { get; set; }

public long PlaceId { get; set; }

public string JobId { get; set; } = String.Empty;

public DateTime TimeJoined { get; set; }

public DateTime TimeLeft { get; set; }

public string TimeJoinedFriendly => String.Format("{0} - {1}", TimeJoined.ToString("h:mm tt"), TimeLeft.ToString("h:mm tt"));

public bool DetailsLoaded = false;

public string GameName { get; set; } = String.Empty;

public string GameThumbnail { get; set; } = String.Empty;

public ICommand RejoinServerCommand => new RelayCommand(RejoinServer);

private void RejoinServer()
{
string playerPath = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
string deeplink = $"roblox://experiences/start?placeId={PlaceId}&gameInstanceId={JobId}";

// start RobloxPlayerBeta.exe directly since Roblox can reuse the existing window
// ideally, i'd like to find out how roblox is doing it
Process.Start(playerPath, deeplink);
}
}
}
21 changes: 15 additions & 6 deletions Bloxstrap/Resources/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Bloxstrap/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,6 @@ Your ReShade configuration files will still be saved, and you can locate them by
<data name="ContextMenu.CopyDeeplinkInvite" xml:space="preserve">
<value>Copy invite deeplink</value>
</data>
<data name="ContextMenu.SeeServerDetails" xml:space="preserve">
<value>See server details</value>
</data>
<data name="ContextMenu.ServerInformation.CopyInstanceId" xml:space="preserve">
<value>Copy Instance ID</value>
</data>
Expand Down Expand Up @@ -1168,4 +1165,10 @@ Are you sure you want to continue?</value>
<data name="JsonManager.FastFlagsLoadFailed" xml:space="preserve">
<value>Your Fast Flags could not be loaded. They have been reset to the default configuration.</value>
</data>
<data name="ContextMenu.GameHistory.Title" xml:space="preserve">
<value>Game history</value>
</data>
<data name="ContextMenu.GameHistory.Rejoin" xml:space="preserve">
<value>Rejoin</value>
</data>
</root>
14 changes: 13 additions & 1 deletion Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,19 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:SymbolIcon Grid.Column="0" Symbol="Info28"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_SeeServerDetails}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_ServerInformation_Title}" />
</Grid>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="JoinLastServerMenuItem" Visibility="Collapsed" Click="JoinLastServerMenuItem_Click">
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:SymbolIcon Grid.Column="0" Symbol="History24"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_GameHistory_Title}" />
</Grid>
</MenuItem.Header>
</MenuItem>
Expand Down
9 changes: 9 additions & 0 deletions Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public void ActivityWatcher_OnGameJoin(object? sender, EventArgs e)
public void ActivityWatcher_OnGameLeave(object? sender, EventArgs e)
{
Dispatcher.Invoke(() => {
JoinLastServerMenuItem.Visibility = Visibility.Visible;
InviteDeeplinkMenuItem.Visibility = Visibility.Collapsed;
ServerDetailsMenuItem.Visibility = Visibility.Collapsed;
Expand Down Expand Up @@ -129,5 +130,13 @@ private void CloseRobloxMenuItem_Click(object sender, RoutedEventArgs e)

_watcher.KillRobloxProcess();
}

private void JoinLastServerMenuItem_Click(object sender, RoutedEventArgs e)
{
if (_activityWatcher is null)
throw new ArgumentNullException(nameof(_activityWatcher));

new ServerHistory(_activityWatcher).ShowDialog();
}
}
}
Loading

0 comments on commit dfcd5b6

Please sign in to comment.