Skip to content

Commit f48472f

Browse files
committed
Adds blackbox bypass and refactors authentication methods
Introduces a blackbox bypass option to help mitigate 403 errors by allowing the skip of certain security payloads. Renames the connection flow to `AuthenticateAsync` for better clarity and refines the internal logic for registry access and byte encoding.
1 parent c304518 commit f48472f

4 files changed

Lines changed: 33 additions & 20 deletions

File tree

src/GameforgeConnector/Blackbox.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ private static string EncodeString(string inputStr)
3636
return "tra:";
3737

3838
var blackbox = new byte[uriEncoded.Length];
39+
3940
blackbox[0] = uriEncoded[0];
4041

4142
for (var i = 1; i < uriEncoded.Length; i++)
@@ -46,18 +47,18 @@ private static string EncodeString(string inputStr)
4647
}
4748
}
4849

49-
var base64 = Convert.ToBase64String(blackbox);
50-
base64 = base64.Replace('/', '_').Replace('+', '-').Replace("=", "");
50+
var base64 = Convert.ToBase64String(blackbox)
51+
.Replace('/', '_')
52+
.Replace('+', '-')
53+
.Replace("=", "");
5154

5255
return "tra:" + base64;
5356
}
5457

5558
private static byte[] ToPercentEncodingBytes(string input, string exclude)
5659
{
5760
var utf8 = Encoding.UTF8.GetBytes(input);
58-
5961
var excludeSet = new HashSet<byte>(Encoding.ASCII.GetBytes(exclude));
60-
6162
var outBytes = new List<byte>(utf8.Length * 3);
6263

6364
foreach (var b in utf8)
@@ -83,7 +84,7 @@ static bool IsUnreserved(byte b)
8384
return b switch
8485
{
8586
>= (byte)'a' and <= (byte)'z' or >= (byte)'A' and <= (byte)'Z' or >= (byte)'0' and <= (byte)'9' => true,
86-
_ => b == (byte)'-' || b == (byte)'.' || b == (byte)'_' || b == (byte)'~'
87+
_ => b is (byte)'-' or (byte)'.' or (byte)'_' or (byte)'~'
8788
};
8889
}
8990
}

src/GameforgeConnector/GameforgeClient.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
namespace GameforgeConnector;
88

99
public record GameforgeClientSettings(
10-
string InstallationId,
11-
string ChromeVersion,
10+
string InstallationId,
11+
string ChromeVersion,
1212
string GameforgeVersion,
1313
string Certificate,
14+
bool BlackboxBypass,
1415
Fingerprint Fingerprint);
1516

1617
public class GameforgeClient(GameforgeClientSettings settings)
@@ -21,11 +22,11 @@ public class GameforgeClient(GameforgeClientSettings settings)
2122
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
2223
});
2324

24-
public async Task<GameforgeClientSession?> ConnectAsync(string email, string password)
25+
public async Task<GameforgeClientSession?> AuthenticateAsync(string email, string password)
2526
{
2627
var fp = await settings.Fingerprint.CopyAndRefresh();
2728

28-
if (!await SendConnectOptions())
29+
if (!await SendAuthenticateOptions())
2930
return null;
3031

3132
var payload = new JObject
@@ -69,7 +70,7 @@ static string ExtractToken(string body)
6970
}
7071
}
7172

72-
private async Task<bool> SendConnectOptions()
73+
private static async Task<bool> SendAuthenticateOptions()
7374
{
7475
var request = new HttpRequestMessage(HttpMethod.Options, "https://spark.gameforge.com/api/v2/authProviders/credentials/sessions");
7576

src/GameforgeConnector/GameforgeClientBuilder.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Text;
2-
using Microsoft.Win32;
32
using Newtonsoft.Json.Linq;
43

54
namespace GameforgeConnector;
@@ -11,6 +10,7 @@ public class GameforgeClientBuilder
1110
private string? gameforgeVersion;
1211
private string? certificate;
1312
private Fingerprint? fingerprint;
13+
private bool withBlackboxBypass;
1414

1515
private static readonly HttpClient client = new();
1616

@@ -53,6 +53,13 @@ public GameforgeClientBuilder WithCertificateFromFile(string path)
5353
return this;
5454
}
5555

56+
[Obsolete("This method should not be used, it's only here to avoid 403 errors when using the Gameforge API and it's probably not safe to use it.")]
57+
public GameforgeClientBuilder WithBlackboxBypass()
58+
{
59+
withBlackboxBypass = true;
60+
return this;
61+
}
62+
5663
public GameforgeClientBuilder WithCertificateFromResources()
5764
{
5865
return WithCertificateFromFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "certificate.pem"));
@@ -90,19 +97,17 @@ public GameforgeClientBuilder WithChromeVersionFromApi()
9097
public GameforgeClientBuilder WithInstallationIdFromRegistry()
9198
{
9299
if (!OperatingSystem.IsWindows())
93-
throw new InvalidOperationException("Installation ID is required on non-Windows platforms");
100+
throw new InvalidOperationException("InstallationIdFromRegistry is only available on Windows platforms");
94101

95-
using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Gameforge4d\GameforgeClient\MainApp"))
102+
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Gameforge4d\GameforgeClient\MainApp"))
96103
{
97104
installationId = key?.GetValue("InstallationId")?.ToString();
98105
}
99106

100107
if (string.IsNullOrWhiteSpace(installationId))
101108
{
102-
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Gameforge4d\GameforgeClient\MainApp"))
103-
{
104-
installationId = key?.GetValue("InstallationId")?.ToString();
105-
}
109+
using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Gameforge4d\GameforgeClient\MainApp");
110+
installationId = key?.GetValue("InstallationId")?.ToString();
106111
}
107112

108113
return this;
@@ -138,6 +143,6 @@ public GameforgeClient Build()
138143
if (certificate is null)
139144
throw new InvalidOperationException("Certificate is required");
140145

141-
return new GameforgeClient(new GameforgeClientSettings(installationId, chromeVersion, gameforgeVersion, certificate!, fingerprint!));
146+
return new GameforgeClient(new GameforgeClientSettings(installationId, chromeVersion, gameforgeVersion, certificate!, withBlackboxBypass, fingerprint!));
142147
}
143148
}

src/GameforgeConnector/GameforgeClientSession.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public async Task<List<GameAccount>> GetAccountsAsync()
8686
{
8787
["platformGameAccountId"] = account.Id,
8888
["gsid"] = id,
89-
["blackbox"] = Blackbox.Encrypt(id, account.Id, fp),
89+
["blackbox"] = settings.BlackboxBypass ? new JObject() : Blackbox.Encrypt(id, account.Id, fp),
9090
["gameId"] = GetGameId(account.Game)
9191
};
9292

@@ -161,10 +161,14 @@ private async Task<bool> SendIovationOptions()
161161

162162
private async Task<bool> SendIovation(string accountId)
163163
{
164+
var fp = await settings.Fingerprint.CopyAndRefresh();
165+
164166
if (!await SendIovationOptions())
165167
return false;
166168

167-
var fp = await settings.Fingerprint.CopyAndRefresh();
169+
if (settings.BlackboxBypass)
170+
return true;
171+
168172
var content = new JObject
169173
{
170174
["accountId"] = accountId,
@@ -176,6 +180,8 @@ private async Task<bool> SendIovation(string accountId)
176180

177181
request.Content = new StringContent(content.ToString(Formatting.None), Encoding.UTF8, "application/json");
178182

183+
request.Headers.TryAddWithoutValidation("Accept", "*/*");
184+
request.Headers.TryAddWithoutValidation("Accept-Language", "en-US,en,q=0.9");
179185
request.Headers.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36");
180186
request.Headers.TryAddWithoutValidation("TNT-Installation-Id", settings.InstallationId);
181187
request.Headers.TryAddWithoutValidation("Origin", "spark://www.gameforge.com");

0 commit comments

Comments
 (0)