Skip to content

Commit

Permalink
Update Epic Online Services (EOS) Protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
BattlefieldDuck committed Jan 22, 2024
1 parent 6c0bc74 commit 85bc784
Show file tree
Hide file tree
Showing 8 changed files with 1,637 additions and 51 deletions.
117 changes: 81 additions & 36 deletions OpenGSQ/Protocols/EOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,42 +20,47 @@ public class EOS : ProtocolBase
/// <inheritdoc/>
public override string FullName => "Epic Online Services (EOS) Protocol";

private readonly string _apiUrl = "https://api.epicgames.dev";
private readonly string _clientId;
private readonly string _clientSecret;
private static readonly string _apiUrl = "https://api.epicgames.dev";
private readonly string _deploymentId;
private string _accessToken;
private readonly string _accessToken;

/// <summary>
/// Initializes a new instance of the EOS class.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <param name="timeout">The connection timeout in milliseconds. Default is 5 seconds.</param>
/// <param name="clientId">The client ID for authentication.</param>
/// <param name="clientSecret">The client secret for authentication.</param>
/// <param name="deploymentId">The deployment ID for authentication.</param>
public EOS(string host, int port, int timeout = 5000, string clientId = null, string clientSecret = null, string deploymentId = null) : base(host, port, timeout)
/// <param name="host">The host name of the server.</param>
/// <param name="port">The port number of the server.</param>
/// <param name="timeout">The timeout value for the connection, in milliseconds. Default is 5000.</param>
/// <param name="deploymentId">The deployment ID for the application.</param>
/// <param name="accessToken">The access token for the application.</param>
/// <exception cref="ArgumentException">Thrown when either deploymentId or accessToken is null.</exception>
public EOS(string host, int port, int timeout = 5000, string deploymentId = null, string accessToken = null) : base(host, port, timeout)
{
if (clientId == null || clientSecret == null || deploymentId == null)
if (deploymentId == null || accessToken == null)
{
throw new ArgumentException("clientId, clientSecret, and deploymentId must not be null");
throw new ArgumentException("deploymentId, and accessToken must not be null");
}

_clientId = clientId;
_clientSecret = clientSecret;
_deploymentId = deploymentId;
_accessToken = accessToken;
}

/// <summary>
/// Asynchronously gets an access token.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains the access token.</returns>
protected async Task<string> GetAccessTokenAsync()
public static async Task<string> GetAccessTokenAsync(string clientId, string clientSecret, string deploymentId, string grantType, string externalAuthType, string externalAuthToken)
{
string url = $"{_apiUrl}/auth/v1/oauth/token";
string body = $"grant_type=client_credentials&deployment_id={_deploymentId}";
string authInfo = $"{_clientId}:{_clientSecret}";

var queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

Check failure on line 55 in OpenGSQ/Protocols/EOS.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'HttpUtility' could not be found in the namespace 'System.Web'. This type has been forwarded to assembly 'System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' Consider adding a reference to that assembly.

Check failure on line 55 in OpenGSQ/Protocols/EOS.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'HttpUtility' could not be found in the namespace 'System.Web'. This type has been forwarded to assembly 'System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' Consider adding a reference to that assembly.
queryString.Add("grant_type", grantType);
queryString.Add("external_auth_type", externalAuthType);
queryString.Add("external_auth_token", externalAuthToken);
queryString.Add("nonce", "opengsq");
queryString.Add("deployment_id", deploymentId);
queryString.Add("display_name", "User");

string authInfo = $"{clientId}:{clientSecret}";
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));

using (var client = new HttpClient()
Expand All @@ -67,7 +72,7 @@ protected async Task<string> GetAccessTokenAsync()
}
})
{
HttpResponseMessage response = await client.PostAsync(url, new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded"));
HttpResponseMessage response = await client.PostAsync(url, new StringContent(queryString.ToString(), Encoding.UTF8, "application/x-www-form-urlencoded"));
response.EnsureSuccessStatusCode();

var data = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>();
Expand All @@ -77,41 +82,81 @@ protected async Task<string> GetAccessTokenAsync()
}

/// <summary>
/// Retrieves matchmaking data asynchronously.
/// Asynchronously retrieves an external authentication token.
/// </summary>
/// <param name="data">The data to be sent to the server.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains the matchmaking data.
/// </returns>
/// <exception cref="AuthenticationException">Thrown when there is a failure in getting the access token.</exception>
public async Task<Matchmaking> GetMatchmakingAsync(Dictionary<string, object> data)
/// <param name="clientId">The client ID.</param>
/// <param name="clientSecret">The client secret.</param>
/// <param name="externalAuthType">The type of external authentication.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the access token.</returns>
/// <exception cref="ArgumentException">Thrown when either clientId or clientSecret is null.</exception>
/// <exception cref="NotImplementedException">Thrown when the provided externalAuthType hasn't been implemented yet.</exception>
public static async Task<string> GetExternalAuthTokenAsync(string clientId, string clientSecret, string externalAuthType)
{
if (_accessToken == null)
if (externalAuthType == "deviceid_access_token")
{
try
string url = $"{_apiUrl}/auth/v1/accounts/deviceid";

var queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

Check failure on line 99 in OpenGSQ/Protocols/EOS.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'HttpUtility' could not be found in the namespace 'System.Web'. This type has been forwarded to assembly 'System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' Consider adding a reference to that assembly.

Check failure on line 99 in OpenGSQ/Protocols/EOS.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'HttpUtility' could not be found in the namespace 'System.Web'. This type has been forwarded to assembly 'System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' Consider adding a reference to that assembly.
queryString.Add("deviceModel", "PC");

string authInfo = $"{clientId}:{clientSecret}";
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));

using (var client = new HttpClient()
{
_accessToken = await GetAccessTokenAsync();
}
catch (Exception ex)
BaseAddress = new Uri(url),
DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue("Basic", authInfo)
}
})
{
throw new AuthenticationException($"Failed to get access token due to an error: {ex.Message}");
HttpResponseMessage response = await client.PostAsync(url, new StringContent(queryString.ToString(), Encoding.UTF8, "application/x-www-form-urlencoded"));
response.EnsureSuccessStatusCode();

var data = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>();

return data["access_token"].ToString();
}
}

string url = $"{_apiUrl}/matchmaking/v1/{_deploymentId}/filter";
throw new NotImplementedException($"The external authentication type '{externalAuthType}' is not supported. Please provide a supported authentication type.");
}

/// <summary>
/// Asynchronously retrieves matchmaking data without any filter parameters.
/// </summary>
/// <param name="deploymentId">The deployment ID.</param>
/// <param name="accessToken">The access token.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the matchmaking data.</returns>
public static Task<Matchmaking> GetMatchmakingAsync(string deploymentId, string accessToken)
{
return GetMatchmakingAsync(deploymentId, accessToken, new Dictionary<string, object>());
}

/// <summary>
/// Asynchronously retrieves matchmaking data.
/// </summary>
/// <param name="deploymentId">The deployment ID.</param>
/// <param name="accessToken">The access token.</param>
/// <param name="filter">The filter parameters for the matchmaking request.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the matchmaking data.</returns>
public static async Task<Matchmaking> GetMatchmakingAsync(string deploymentId, string accessToken, Dictionary<string, object> filter)
{
string url = $"{_apiUrl}/matchmaking/v1/{deploymentId}/filter";

using (var client = new HttpClient()
{
BaseAddress = new Uri(url),
DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue("Bearer", _accessToken)
Authorization = new AuthenticationHeaderValue("Bearer", accessToken)
}
})
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

HttpResponseMessage response = await client.PostAsync(url, new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json"));
HttpResponseMessage response = await client.PostAsync(url, new StringContent(JsonSerializer.Serialize(filter), Encoding.UTF8, "application/json"));
response.EnsureSuccessStatusCode();

var responseData = await response.Content.ReadFromJsonAsync<Matchmaking>();
Expand All @@ -133,7 +178,7 @@ public async Task<Dictionary<string, object>> GetInfo()
string address = await GetIPAddress();
string addressBoundPort = $":{Port}";

var data = await GetMatchmakingAsync(new Dictionary<string, object>
var data = await GetMatchmakingAsync(_deploymentId, _accessToken, new Dictionary<string, object>
{
{ "criteria", new List<Dictionary<string, object>>
{
Expand Down
33 changes: 26 additions & 7 deletions OpenGSQTests/Protocols/EOSTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ namespace OpenGSQ.Protocols.Tests
[TestClass()]
public class EOSTests : TestBase
{
private readonly static string clientId = "xyza7891muomRmynIIHaJB9COBKkwj6n";
private readonly static string clientSecret = "PP5UGxysEieNfSrEicaD1N2Bb3TdXuD7xHYcsdUHZ7s";
private readonly static string deploymentId = "ad9a8feffb3b4b2ca315546f038c3ae2";

// arksa
public EOS eos = new("5.62.115.46", 7783, 5000, clientId, clientSecret, deploymentId);

public EOSTests() : base(nameof(EOSTests))
{
// EnableSave = true;
Expand All @@ -22,7 +15,33 @@ public EOSTests() : base(nameof(EOSTests))
[TestMethod()]
public async Task GetInfoTest()
{
// Ark: Survival Ascended
string clientId = "xyza7891muomRmynIIHaJB9COBKkwj6n";
string clientSecret = "PP5UGxysEieNfSrEicaD1N2Bb3TdXuD7xHYcsdUHZ7s";
string deploymentId = "ad9a8feffb3b4b2ca315546f038c3ae2";
string grantType = "client_credentials";
string externalAuthType = "";
string externalAuthToken = "";
string accessToken = await EOS.GetAccessTokenAsync(clientId, clientSecret, deploymentId, grantType, externalAuthType, externalAuthToken);

EOS eos = new("5.62.115.46", 7783, 5000, deploymentId, accessToken);

SaveResult(nameof(GetInfoTest), await eos.GetInfo());
}

[TestMethod()]
public async Task GetMatchmakingAsyncTest()
{
// Palworld
string clientId = "xyza78916PZ5DF0fAahu4tnrKKyFpqRE";
string clientSecret = "j0NapLEPm3R3EOrlQiM8cRLKq3Rt02ZVVwT0SkZstSg";
string deploymentId = "0a18471f93d448e2a1f60e47e03d3413";
string grantType = "external_auth";
string externalAuthType = "deviceid_access_token";
string externalAuthToken = await EOS.GetExternalAuthTokenAsync(clientId, clientSecret, externalAuthType);
string accessToken = await EOS.GetAccessTokenAsync(clientId, clientSecret, deploymentId, grantType, externalAuthType, externalAuthToken);

SaveResult(nameof(GetMatchmakingAsyncTest), await EOS.GetMatchmakingAsync(deploymentId, accessToken));
}
}
}
8 changes: 4 additions & 4 deletions OpenGSQTests/Results/EOSTests/GetInfoTest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"rejoinAfterKick": "",
"platforms": null
},
"totalPlayers": 39,
"openPublicPlayers": 31,
"totalPlayers": 45,
"openPublicPlayers": 25,
"publicPlayers": [],
"started": false,
"lastUpdated": null,
Expand All @@ -29,11 +29,11 @@
"SERVERPASSWORD_b": false,
"MATCHTIMEOUT_d": 120.0,
"ENABLEDMODSFILEIDS_s": "4979340",
"DAYTIME_s": "327",
"DAYTIME_s": "328",
"SOTFMATCHSTARTED_b": false,
"STEELSHIELDENABLED_l": 1,
"SERVERUSESBATTLEYE_b": true,
"EOSSERVERPING_l": 252,
"EOSSERVERPING_l": 246,
"ALLOWDOWNLOADCHARS_l": 1,
"OFFICIALSERVER_s": "1",
"GAMEMODE_s": "TestGameMode_C",
Expand Down
Loading

0 comments on commit 85bc784

Please sign in to comment.