Skip to content
Merged
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
61 changes: 54 additions & 7 deletions MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Sigil"
mc:Ignorable="d"
Title="Sigil" Height="580" Width="700"
Title="Sigil" Height="680" Width="820"
MinHeight="580" MinWidth="700"
WindowStartupLocation="CenterScreen"
Background="#1a1a2e">

Expand Down Expand Up @@ -154,7 +155,7 @@
<!-- Main Content -->
<Grid Grid.Row="0" Margin="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="280"/>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

Expand Down Expand Up @@ -205,6 +206,34 @@
<TextBlock Text="{Binding SelectedAccount.DisplayName, FallbackValue='No account selected'}"
FontSize="24" FontWeight="Bold"
Foreground="{StaticResource TextPrimary}"/>
<Border Background="{StaticResource BackgroundCard}"
BorderBrush="{StaticResource BorderColor}"
BorderThickness="1" CornerRadius="6"
Padding="10,6" Margin="0,8,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Proxy"
FontSize="11" FontWeight="SemiBold"
Foreground="{StaticResource TextSecondary}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding ProxyDisplayText}"
FontSize="11"
Foreground="{StaticResource TextPrimary}"
VerticalAlignment="Center"
Margin="10,0,0,0"/>
<Button Grid.Column="2"
Content="Configure"
Style="{StaticResource SmallButton}"
Click="OnConfigureProxy"
Padding="10,3"
FontSize="10"/>
</Grid>
</Border>
</StackPanel>

<!-- Characters header row -->
Expand All @@ -229,20 +258,20 @@
VerticalAlignment="Center"/>

<StackPanel Grid.Column="2" Orientation="Horizontal">
<Button Content="Refresh Char"
<Button Content="Refresh"
Click="OnRefreshCharacters"
Style="{StaticResource SmallButton}"
Margin="4,0,0,0"
Margin="0,0,4,0"
ToolTip="Refresh the characters for this account"/>
<Button Content="+ Create"
Style="{StaticResource SmallButton}"
IsEnabled="{Binding CanCreateCharacter}"
Click="OnCreateCharacter"/>
Click="OnCreateCharacter"
Margin="0,0,4,0"/>
<Button Content="Auto"
Style="{StaticResource SmallButton}"
IsEnabled="{Binding CanAutoCreate}"
Click="OnAutoCreateCharacters"
Margin="4,0,0,0"
ToolTip="Automatically fill this account to 20 characters (30s between each)"/>
</StackPanel>
</Grid>
Expand Down Expand Up @@ -328,6 +357,24 @@
VerticalAlignment="Center" Margin="8,0,0,0"/>
</Grid>

<!-- ProxInject -->
<TextBlock Text="Game Proxy (ProxInject)"
FontSize="12"
Foreground="{StaticResource TextSecondary}"
Margin="0,4,0,6"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,12">
<TextBlock x:Name="ProxInjectStatusText"
Text="Not installed"
FontSize="11"
Foreground="{StaticResource TextSecondary}"
VerticalAlignment="Center"
Margin="0,0,8,0"/>
<Button x:Name="ProxInjectDownloadBtn"
Content="Download ProxInject"
Style="{StaticResource SmallButton}"
Click="OnDownloadProxInject"/>
</StackPanel>

<!-- Actions -->
<StackPanel Orientation="Horizontal">
<Button Content="Refresh Token" Style="{StaticResource SmallButton}"
Expand All @@ -345,7 +392,7 @@
<!-- Log Panel -->
<Border Grid.Row="1" Background="{StaticResource BackgroundCard}"
BorderBrush="{StaticResource BorderColor}" BorderThickness="0,1,0,0"
Height="100">
Height="120">
<ScrollViewer x:Name="LogScrollViewer"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Expand Down
90 changes: 88 additions & 2 deletions MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public partial class MainWindow : Window, INotifyPropertyChanged
private readonly AuthService _authService = new();
private readonly LauncherService _launcherService = new();
private readonly JagexAccountService _jagexAccountService = new();
private readonly ProxInjectService _proxInjectService = new();
private readonly CharacterCreationQueueService _creationQueue;

private AccountProfile? _selectedAccount;
Expand Down Expand Up @@ -70,6 +71,8 @@ public AccountProfile? SelectedAccount

_selectedAccount = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ProxyDisplayText));
ApplyProxyToServices();
UpdateSelectedCharacters();
Settings.LastSelectedAccountId = _selectedAccount?.AccountId;
_ = _settingsStore.SaveAsync(Settings);
Expand Down Expand Up @@ -115,6 +118,9 @@ public GameAccount? SelectedCharacter
public bool CanAutoCreate =>
_selectedAccount != null && _selectedAccount.GameAccounts.Count < 20;

public string ProxyDisplayText =>
_selectedAccount?.Proxy?.ToDisplayString() ?? "None";

public string QueueStatusText
{
get => _queueStatusText;
Expand Down Expand Up @@ -147,6 +153,7 @@ private async void OnLoaded(object sender, RoutedEventArgs e)
SelectedAccount = Accounts.FirstOrDefault(a => a.AccountId == Settings.LastSelectedAccountId)
?? Accounts.FirstOrDefault();

UpdateProxInjectStatus();
await UpdateStatusAsync();
}

Expand All @@ -155,7 +162,7 @@ private async void OnAddAccount(object sender, RoutedEventArgs e)
try
{
StatusText = "Opening browser for login...";
var authWindow = new AuthWindow(_authService, Settings, null)
var authWindow = new AuthWindow(_authService, Settings, null, SelectedAccount?.Proxy)
{
Owner = this
};
Expand Down Expand Up @@ -282,7 +289,8 @@ private async void OnLaunch(object sender, RoutedEventArgs e)
await _accountStore.SaveAsync(Accounts);

var character = SelectedCharacter ?? SelectedAccount.GameAccounts.FirstOrDefault();
await _launcherService.LaunchAsync(Settings, token, character);
await _launcherService.LaunchAsync(Settings, token, character, SelectedAccount.Proxy,
msg => Dispatcher.Invoke(() => AppendLog(msg)));

StatusText = "Game launched";
}
Expand Down Expand Up @@ -550,6 +558,84 @@ private void OnCancelQueue(object sender, RoutedEventArgs e)
QueueStatusText = string.Empty;
}

private void ApplyProxyToServices()
{
var proxy = _selectedAccount?.Proxy;
_authService.SetProxy(proxy);
_jagexAccountService.SetProxy(proxy);
}

private async void OnConfigureProxy(object sender, RoutedEventArgs e)
{
if (SelectedAccount == null) return;
var dialog = new ProxyDialog(SelectedAccount.Proxy ?? new ProxyConfig()) { Owner = this };
if (dialog.ShowDialog() == true)
{
SelectedAccount.Proxy = dialog.Result;
await _accountStore.SaveAsync(Accounts);
OnPropertyChanged(nameof(ProxyDisplayText));
ApplyProxyToServices();
StatusText = dialog.Result.Enabled
? $"Proxy set: {dialog.Result.ToDisplayString()}"
: "Proxy disabled";
}
}

private async void OnDownloadProxInject(object sender, RoutedEventArgs e)
{
if (ProxInjectService.IsInstalled)
{
MessageBox.Show("ProxInject is already installed.", "Sigil");
return;
}

var result = MessageBox.Show(
"Sigil needs to download ProxInject (a third-party open-source tool) to proxy game client traffic.\n\n" +
$"Download URL:\n{ProxInjectService.DownloadUrl}\n\n" +
"Source: https://github.com/PragmaTwice/proxinject\n\n" +
"Do you want to download and install it now?",
"Download ProxInject",
MessageBoxButton.YesNo,
MessageBoxImage.Question);

if (result != MessageBoxResult.Yes) return;

try
{
ProxInjectDownloadBtn.IsEnabled = false;
ProxInjectStatusText.Text = "Downloading...";
StatusText = "Downloading ProxInject...";

await _proxInjectService.DownloadAsync(msg => Dispatcher.Invoke(() => AppendLog(msg)));

ProxInjectStatusText.Text = "Installed";
ProxInjectDownloadBtn.Content = "Reinstall";
ProxInjectDownloadBtn.IsEnabled = true;
StatusText = "ProxInject installed successfully";
}
catch (Exception ex)
{
ProxInjectStatusText.Text = "Failed";
ProxInjectDownloadBtn.IsEnabled = true;
StatusText = $"ProxInject download failed: {ex.Message}";
MessageBox.Show($"Download failed:\n{ex.Message}", "Sigil");
}
}

private void UpdateProxInjectStatus()
{
if (ProxInjectService.IsInstalled)
{
ProxInjectStatusText.Text = "Installed";
ProxInjectDownloadBtn.Content = "Reinstall";
}
else
{
ProxInjectStatusText.Text = "Not installed";
ProxInjectDownloadBtn.Content = "Download ProxInject";
}
}

private void OnWindowClosed(object? sender, EventArgs e)
{
_creationQueue.Dispose();
Expand Down
1 change: 1 addition & 0 deletions Models/AccountProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public sealed class AccountProfile
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? LastUsedAt { get; set; }
public List<GameAccount> GameAccounts { get; set; } = new();
public ProxyConfig? Proxy { get; set; }
}
25 changes: 25 additions & 0 deletions Models/ProxyConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Sigil.Models;

public sealed class ProxyConfig
{
public bool Enabled { get; set; }
public string Host { get; set; } = string.Empty;
public int Port { get; set; } = 1080;
public ProxyType Type { get; set; } = ProxyType.Socks5;
public string? Username { get; set; }
public string? Password { get; set; }

public string ToUri() => Type switch
{
ProxyType.Http => $"http://{Host}:{Port}",
ProxyType.Socks5 => $"socks5://{Host}:{Port}",
_ => $"socks5://{Host}:{Port}"
};

public string ToDisplayString() =>
Enabled && !string.IsNullOrWhiteSpace(Host)
? $"{Type} {Host}:{Port}"
: "None";
}

public enum ProxyType { Http, Socks5 }
7 changes: 6 additions & 1 deletion Services/AuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ namespace Sigil.Services;

public sealed class AuthService
{
private readonly HttpClient _httpClient = new();
private HttpClient _httpClient = new();

public void SetProxy(ProxyConfig? proxy)
{
_httpClient = ProxyHttpClientFactory.Create(proxy);
}

public AuthStart BeginLogin(AppSettings settings)
{
Expand Down
7 changes: 6 additions & 1 deletion Services/JagexAccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ namespace Sigil.Services;

public sealed class JagexAccountService
{
private readonly HttpClient _httpClient = new(new HttpClientHandler { UseCookies = false });
private HttpClient _httpClient = new(new HttpClientHandler { UseCookies = false });

public void SetProxy(ProxyConfig? proxy)
{
_httpClient = ProxyHttpClientFactory.Create(proxy, useCookies: false);
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true
Expand Down
27 changes: 25 additions & 2 deletions Services/LauncherService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ namespace Sigil.Services;

public sealed class LauncherService
{
private readonly ProxInjectService _proxInject = new();

/// <summary>
/// Launches the RS3 game client directly with session credentials.
/// The game client reads JX_SESSION_ID, JX_CHARACTER_ID, and JX_DISPLAY_NAME from environment variables.
/// Returns the started process so the caller can inject into it (e.g. agent DLL).
/// If a proxy is configured and ProxInject is installed, injects the proxy into the game process.
/// </summary>
public Task<Process> LaunchAsync(AppSettings settings, OAuthToken? token, GameAccount? selectedCharacter)
public async Task<Process> LaunchAsync(AppSettings settings, OAuthToken? token, GameAccount? selectedCharacter, ProxyConfig? proxy = null, Action<string>? log = null)
{
var exePath = settings.Rs3ClientPath;

Expand Down Expand Up @@ -51,6 +54,26 @@ public Task<Process> LaunchAsync(AppSettings settings, OAuthToken? token, GameAc
var process = Process.Start(startInfo);
if (process == null)
throw new InvalidOperationException("Failed to start the game process.");
return Task.FromResult(process);

// Inject proxy into the game process if configured
if (proxy is { Enabled: true } && !string.IsNullOrWhiteSpace(proxy.Host) && ProxInjectService.IsInstalled)
{
try
{
// Give the process a moment to initialize before injecting
await Task.Delay(2000).ConfigureAwait(false);
await _proxInject.InjectAsync(process.Id, proxy, log).ConfigureAwait(false);
}
catch (Exception ex)
{
log?.Invoke($"Proxy injection warning: {ex.Message}");
}
}
else if (proxy is { Enabled: true } && !ProxInjectService.IsInstalled)
{
log?.Invoke("Proxy configured but ProxInject not installed. Game traffic will NOT be proxied. Download it from Advanced Settings.");
}

return process;
}
}
Loading
Loading