v1.3.0.1 (Epic Store release)

This commit is contained in:
Regalis11
2024-03-28 18:34:33 +02:00
parent 81ca8637be
commit 3791670c42
269 changed files with 13160 additions and 2966 deletions
@@ -0,0 +1,65 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
public enum AuthenticationTicketKind
{
SteamAuthTicketForSteamHost = 0,
SteamAuthTicketForEosHost = 1,
EgsOwnershipToken = 2
}
[NetworkSerialize(ArrayMaxSize = UInt16.MaxValue)]
readonly record struct AuthenticationTicket(
AuthenticationTicketKind Kind,
ImmutableArray<byte> Data) : INetSerializableStruct
{
public static async Task<Option<AuthenticationTicket>> Create(Endpoint serverEndpoint)
{
if (SteamManager.IsInitialized && SteamManager.GetSteamId().TryUnwrap(out var steamId))
{
if (serverEndpoint is EosP2PEndpoint)
{
var authTicket = await SteamManager.GetAuthTicketForEosHostAuth();
return authTicket
.Bind(t => t.Data != null ? Option.Some(t.Data) : Option.None)
.Select(data => new AuthenticationTicket(AuthenticationTicketKind.SteamAuthTicketForEosHost, data.ToImmutableArray()));
}
else
{
var authTicket = SteamManager.GetAuthSessionTicketForSteamHost(serverEndpoint);
var steamIdBytes = BitConverter.GetBytes(steamId.Value);
return authTicket
.Bind(t => t.Data != null ? Option.Some(t.Data) : Option.None)
.Select(data => new AuthenticationTicket(
AuthenticationTicketKind.SteamAuthTicketForSteamHost,
steamIdBytes.Concat(data).ToImmutableArray()));
}
}
if (EosInterface.IdQueries.GetLoggedInPuids() is { Length: > 0 } puids)
{
var externalAccountIdsResult = await EosInterface.IdQueries.GetSelfExternalAccountIds(puids[0]);
if (externalAccountIdsResult.TryUnwrapSuccess(out var externalAccountIds))
{
var epicAccountIdOption = externalAccountIds.OfType<EpicAccountId>().FirstOrNone();
if (epicAccountIdOption.TryUnwrap(out var epicAccountId))
{
return (await EosInterface.Ownership.GetGameOwnershipToken(epicAccountId))
.Select(t => new AuthenticationTicket(
AuthenticationTicketKind.EgsOwnershipToken,
Encoding.UTF8.GetBytes(t.Jwt.ToString()).ToImmutableArray()));
}
}
}
return Option.None;
}
}
@@ -0,0 +1,38 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
abstract class Authenticator
{
public abstract Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket);
public abstract void EndAuthSession(AccountId accountId);
public static ImmutableDictionary<AuthenticationTicketKind, Authenticator> GetAuthenticatorsForHost(Option<Endpoint> ownerEndpointOption)
{
var authenticators = new Dictionary<AuthenticationTicketKind, Authenticator>();
if (EosInterface.Core.IsInitialized)
{
// Every kind of host should be able to do EOS ID Token authentication if they have EOS enabled
authenticators.Add(AuthenticationTicketKind.EgsOwnershipToken, new EgsOwnershipTokenAuthenticator());
if (ownerEndpointOption.TryUnwrap(out var ownerEndpoint) && ownerEndpoint is EosP2PEndpoint)
{
// EOS P2P hosts do not have access to Steamworks
authenticators.Add(AuthenticationTicketKind.SteamAuthTicketForEosHost, new SteamAuthTicketForEosHostAuthenticator());
}
}
if (!(ownerEndpointOption.TryUnwrap(out var ownerEndpoint2) && ownerEndpoint2 is EosP2PEndpoint) && SteamManager.IsInitialized)
{
// Steam P2P hosts and dedicated servers have access to Steamworks
authenticators.Add(AuthenticationTicketKind.SteamAuthTicketForSteamHost, new SteamAuthTicketForSteamHostAuthenticator());
}
return authenticators.ToImmutableDictionary();
}
}
@@ -0,0 +1,21 @@
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma.Networking;
sealed class EgsOwnershipTokenAuthenticator : Authenticator
{
public override async Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket)
{
var jwtOption = JsonWebToken.Parse(Encoding.UTF8.GetString(ticket.Data.AsSpan()));
if (!jwtOption.TryUnwrap(out var jwt)) { return AccountInfo.None; }
var ownershipToken = new EosInterface.Ownership.Token(jwt);
var accountIdOption = await ownershipToken.Verify();
if (!accountIdOption.TryUnwrap(out var accountId)) { return AccountInfo.None; }
return new AccountInfo(accountId);
}
public override void EndAuthSession(AccountId accountId) { /* do nothing */ }
}
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using RestSharp;
namespace Barotrauma.Networking;
sealed class SteamAuthTicketForEosHostAuthenticator : Authenticator
{
#warning FIXME change URL back to the non-experimental one once this passes QA
private const string consentServerUrl = "https://barotraumagame.com/baromaster/experimental/";
private const string consentServerFile = "getOwnerSteamId.php";
private const int RemoteRequestVersion = 1;
public override async Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket)
{
string ticketData = ToolBoxCore.ByteArrayToHexString(ticket.Data);
var client = new RestClient(consentServerUrl);
var request = new RestRequest(consentServerFile, Method.GET);
request.AddParameter("authticket", ticketData);
request.AddParameter("request_version", RemoteRequestVersion);
var response = await client.ExecuteAsync(request, Method.GET);
if (!response.IsSuccessful) { return AccountInfo.None; }
try
{
var jsonDoc = JsonDocument.Parse(response.Content);
Option<SteamId> steamId = Option.None;
Option<SteamId> ownerSteamId = Option.None;
foreach (var property in jsonDoc.RootElement.EnumerateObject())
{
if (!property.Name.ToIdentifier().Contains("SteamId")) { continue; }
var accountIdOption = SteamId.Parse(property.Value.GetString() ?? "");
if (accountIdOption.IsNone()) { continue; }
if (property.Name.ToIdentifier() == "SteamId")
{
steamId = accountIdOption;
}
else if (property.Name.ToIdentifier() == "OwnerSteamId")
{
ownerSteamId = accountIdOption;
}
}
var otherIds = ownerSteamId.TryUnwrap(out var id) ? new AccountId[] { id } : Array.Empty<AccountId>();
return new AccountInfo(steamId.Select(static id => (AccountId)id), otherIds);
}
catch
{
return AccountInfo.None;
}
}
public override void EndAuthSession(AccountId accountId) { /* do nothing */ }
}
@@ -0,0 +1,77 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Barotrauma.Steam;
namespace Barotrauma.Networking;
#if CLIENT
using SteamAuthSessionInterface = Steamworks.SteamUser;
#else
using SteamAuthSessionInterface = Steamworks.SteamServer;
#endif
sealed class SteamAuthTicketForSteamHostAuthenticator : Authenticator
{
private static Steamworks.BeginAuthResult BeginAuthSession(Steamworks.AuthTicket authTicket, SteamId clientSteamId)
{
if (!SteamManager.IsInitialized) { return Steamworks.BeginAuthResult.ServerNotConnectedToSteam; }
if (authTicket.Data is null) { return Steamworks.BeginAuthResult.InvalidTicket; }
DebugConsole.Log("Authenticating Steam client " + clientSteamId);
Steamworks.BeginAuthResult startResult = SteamAuthSessionInterface.BeginAuthSession(authTicket.Data, clientSteamId.Value);
if (startResult != Steamworks.BeginAuthResult.OK)
{
DebugConsole.Log($"Steam authentication failed: failed to start auth session ({startResult})");
}
return startResult;
}
private static void EndAuthSession(SteamId clientSteamId)
{
if (!SteamManager.IsInitialized) { return; }
DebugConsole.Log($"Ending auth session with Steam client {clientSteamId}");
SteamAuthSessionInterface.EndAuthSession(clientSteamId.Value);
}
public override async Task<AccountInfo> VerifyTicket(AuthenticationTicket ticket)
{
if (ticket.Data.Length < 8) { return AccountInfo.None; }
var ticketData = ticket.Data.ToArray();
var steamAuthTicket = new Steamworks.AuthTicket { Data = ticketData[8..] };
var steamId = new SteamId(BitConverter.ToUInt64(ticketData.AsSpan()[..8]));
using var janitor = Janitor.Start();
(Steamworks.AuthResponse AuthResponse, SteamId OwnerSteamId)? authResult = null;
void onValidateAuthTicketResponse(Steamworks.SteamId clientId, Steamworks.SteamId ownerClientId, Steamworks.AuthResponse response)
{
if (clientId != steamId.Value) { response = Steamworks.AuthResponse.AuthTicketInvalid; }
authResult = (response, new SteamId(ownerClientId));
}
SteamAuthSessionInterface.OnValidateAuthTicketResponse += onValidateAuthTicketResponse;
janitor.AddAction(() => SteamAuthSessionInterface.OnValidateAuthTicketResponse -= onValidateAuthTicketResponse);
var beginAuthSessionResult = BeginAuthSession(steamAuthTicket, steamId);
if (beginAuthSessionResult != Steamworks.BeginAuthResult.OK) { return AccountInfo.None; }
while (authResult is null)
{
await Task.Delay(32);
}
if (authResult.Value.AuthResponse != Steamworks.AuthResponse.OK) { return AccountInfo.None; }
return new AccountInfo(steamId, authResult.Value.OwnerSteamId);
}
public override void EndAuthSession(AccountId accountId)
{
if (accountId is not SteamId steamId) { return; }
SteamAuthSessionInterface.EndAuthSession(steamId.Value);
}
}