Skip to content

Launching & Closing Custom Integrations based on specific games #4032

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Bloxstrap/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
<converters:StringFormatConverter x:Key="StringFormatConverter" />
<converters:RangeConverter x:Key="RangeConverter" />
<converters:EnumNameConverter x:Key="EnumNameConverter" />
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
40 changes: 21 additions & 19 deletions Bloxstrap/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -420,30 +420,32 @@ private void StartRoblox()
// launch custom integrations now
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})");
if (!integration.SpecifyGame) {
App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})");

int pid = 0;
int pid = 0;

try
{
var process = Process.Start(new ProcessStartInfo
try
{
FileName = integration.Location,
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
WorkingDirectory = Path.GetDirectoryName(integration.Location),
UseShellExecute = true
})!;
var process = Process.Start(new ProcessStartInfo
{
FileName = integration.Location,
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
WorkingDirectory = Path.GetDirectoryName(integration.Location),
UseShellExecute = true
})!;

pid = process.Id;
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}'!");
App.Logger.WriteLine(LOG_IDENT, ex.Message);
}
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);
if (integration.AutoClose && pid != 0)
autoclosePids.Add(pid);
}
}

if (App.Settings.Prop.EnableActivityTracking || App.LaunchSettings.TestModeFlag.Active || autoclosePids.Any())
Expand Down
100 changes: 100 additions & 0 deletions Bloxstrap/Integrations/IntegrationWatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
namespace Bloxstrap.Integrations
{
public class IntegrationWatcher : IDisposable
{
private readonly ActivityWatcher _activityWatcher;
private readonly Dictionary<int, CustomIntegration> _activeIntegrations = new();

public IntegrationWatcher(ActivityWatcher activityWatcher)
{
_activityWatcher = activityWatcher;

_activityWatcher.OnGameJoin += OnGameJoin;
_activityWatcher.OnGameLeave += OnGameLeave;
}

private void OnGameJoin(object? sender, EventArgs e)
{
if (!_activityWatcher.InGame)
return;

long currentGameId = _activityWatcher.Data.PlaceId;

foreach (var integration in App.Settings.Prop.CustomIntegrations)
{
if (!integration.SpecifyGame || integration.GameID != currentGameId.ToString())
continue;

LaunchIntegration(integration);
}
}

private void OnGameLeave(object? sender, EventArgs e)
{
foreach (var pid in _activeIntegrations.Keys.ToList())
{
var integration = _activeIntegrations[pid];
if (integration.AutoCloseOnGame)
{
TerminateProcess(pid);
_activeIntegrations.Remove(pid);
}
}
}

private void LaunchIntegration(CustomIntegration integration)
{
const string LOG_IDENT = "IntegrationWatcher::LaunchIntegration";

try
{
var process = Process.Start(new ProcessStartInfo
{
FileName = integration.Location,
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
WorkingDirectory = Path.GetDirectoryName(integration.Location),
UseShellExecute = true
});

if (process != null)
{
App.Logger.WriteLine(LOG_IDENT, $"Integration '{integration.Name}' launched for game ID '{integration.GameID}' (PID {process.Id}).");
_activeIntegrations[process.Id] = integration;
}
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}': {ex.Message}");
}
}

private void TerminateProcess(int pid)
{
const string LOG_IDENT = "IntegrationWatcher::TerminateProcess";

try
{
var process = Process.GetProcessById(pid);
process.Kill();

App.Logger.WriteLine(LOG_IDENT, $"Terminated integration process (PID {pid}).");
}
catch (Exception)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to terminate process (PID {pid}), likely already exited.");
}
}

public void Dispose()
{
foreach (var pid in _activeIntegrations.Keys)
{
TerminateProcess(pid);
}

_activeIntegrations.Clear();
_activityWatcher.Dispose();
GC.SuppressFinalize(this);
}
}
}
3 changes: 3 additions & 0 deletions Bloxstrap/Models/CustomIntegration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public class CustomIntegration
public string Name { get; set; } = "";
public string Location { get; set; } = "";
public string LaunchArgs { get; set; } = "";
public bool SpecifyGame { get; set; } = false;
public string GameID { get; set; } = "";
public bool AutoCloseOnGame { get; set; } = true;
public bool AutoClose { get; set; } = true;
}
}
29 changes: 28 additions & 1 deletion Bloxstrap/Resources/Strings.Designer.cs

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

9 changes: 9 additions & 0 deletions Bloxstrap/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,15 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<data name="Menu.Integrations.Custom.AppLocation" xml:space="preserve">
<value>Application Location</value>
</data>
<data name="Menu.Integrations.Custom.SpecifyGame" xml:space="preserve">
<value>Run on a specific game</value>
</data>
<data name="Menu.Integrations.Custom.GameID" xml:space="preserve">
<value>Game ID</value>
</data>
<data name="Menu.Integrations.Custom.AutoCloseOnGame" xml:space="preserve">
<value>Auto close when the game closes</value>
</data>
<data name="Menu.Integrations.Custom.AutoClose" xml:space="preserve">
<value>Auto close when Roblox closes</value>
</data>
Expand Down
21 changes: 21 additions & 0 deletions Bloxstrap/UI/Converters/BooleanToVisibilityConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Windows;
using System.Windows.Data;

namespace Bloxstrap.UI.Converters
{
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return boolValue ? Visibility.Visible : Visibility.Collapsed;

return Visibility.Collapsed;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
21 changes: 21 additions & 0 deletions Bloxstrap/UI/Converters/InverseBooleanToVisibilityConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Windows;
using System.Windows.Data;

namespace Bloxstrap.UI.Converters
{
public class InverseBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return boolValue ? Visibility.Collapsed : Visibility.Visible;

return Visibility.Visible;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
1 change: 1 addition & 0 deletions Bloxstrap/UI/Elements/About/Pages/AboutPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
<controls:MarkdownTextBlock MarkdownText="[Redusofficial](https://github.com/Redusofficial)" />
<controls:MarkdownTextBlock MarkdownText="[srthMD](https://github.com/srthMD)" />
<controls:MarkdownTextBlock MarkdownText="[axellse](https://github.com/axellse)" />
<controls:MarkdownTextBlock MarkdownText="[CubesterYT](https://github.com/CubesterYT)" />
</StackPanel>
</controls:Expander>

Expand Down
8 changes: 6 additions & 2 deletions Bloxstrap/UI/Elements/Settings/Pages/IntegrationsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@

<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Title}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Description}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<Grid Margin="0,8,0,0">
<Grid Margin="0,8,0,0" MinHeight="325">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
Expand Down Expand Up @@ -109,7 +109,11 @@
</Grid>
<TextBlock Margin="0,8,0,0" Text="{x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<ui:TextBox Margin="0,4,0,0" PlaceholderText="{Binding Source='/k echo {0}', Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs_Placeholder}}" Text="{Binding SelectedCustomIntegration.LaunchArgs}" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoClose}" IsChecked="{Binding SelectedCustomIntegration.AutoClose}" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_SpecifyGame}" IsChecked="{Binding SelectedCustomIntegration.SpecifyGame, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Margin="0,8,0,0" Text="{x:Static resources:Strings.Menu_Integrations_Custom_GameID}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ui:TextBox Margin="0,4,0,0" PlaceholderText="1818" Text="{Binding SelectedCustomIntegration.GameID}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoCloseOnGame}" IsChecked="{Binding SelectedCustomIntegration.AutoCloseOnGame, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoClose}" IsChecked="{Binding SelectedCustomIntegration.AutoClose, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
</StackPanel>
<TextBlock Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Text="{x:Static resources:Strings.Menu_Integrations_Custom_NoneSelected}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Style>
Expand Down
7 changes: 6 additions & 1 deletion Bloxstrap/Watcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ public class Watcher : IDisposable
private readonly InterProcessLock _lock = new("Watcher");

private readonly WatcherData? _watcherData;

private readonly NotifyIconWrapper? _notifyIcon;

public readonly ActivityWatcher? ActivityWatcher;

public readonly DiscordRichPresence? RichPresence;

public readonly IntegrationWatcher? IntegrationWatcher;

public Watcher()
{
const string LOG_IDENT = "Watcher";
Expand Down Expand Up @@ -63,6 +65,8 @@ public Watcher()

if (App.Settings.Prop.UseDiscordRichPresence)
RichPresence = new(ActivityWatcher);

IntegrationWatcher = new IntegrationWatcher(ActivityWatcher);
}

_notifyIcon = new(this);
Expand Down Expand Up @@ -122,6 +126,7 @@ public void Dispose()
{
App.Logger.WriteLine("Watcher::Dispose", "Disposing Watcher");

IntegrationWatcher?.Dispose();
_notifyIcon?.Dispose();
RichPresence?.Dispose();

Expand Down