v1.3.0.1 (Epic Store release)
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public enum AchievementUnlockError
|
||||
{
|
||||
Unknown,
|
||||
InvalidUser,
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
InvalidParameters,
|
||||
NotFound
|
||||
}
|
||||
|
||||
public enum IngestStatError
|
||||
{
|
||||
Unknown,
|
||||
InvalidUser,
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
InvalidParameters,
|
||||
NotFound
|
||||
}
|
||||
|
||||
public enum QueryStatsError
|
||||
{
|
||||
Unknown,
|
||||
InvalidUser,
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
InvalidParameters,
|
||||
NotFound
|
||||
}
|
||||
|
||||
public enum QueryAchievementsError
|
||||
{
|
||||
Unknown,
|
||||
InvalidUser,
|
||||
InvalidProductUserID,
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
InvalidParameters,
|
||||
NotFound
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class Achievements
|
||||
{
|
||||
private static Implementation? LoadedImplementation => Core.LoadedImplementation;
|
||||
|
||||
public static async Task<Result<uint, AchievementUnlockError>> UnlockAchievements(
|
||||
params Identifier[] achievementIds)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.UnlockAchievements(achievementIds)
|
||||
: Result.Failure(AchievementUnlockError.EosNotInitialized);
|
||||
|
||||
public static async Task<Result<Unit, IngestStatError>> IngestStats(
|
||||
params (AchievementStat Stat, int IngestAmount)[] stats)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.IngestStats(stats)
|
||||
: Result.Failure(IngestStatError.EosNotInitialized);
|
||||
|
||||
public static Task<Result<ImmutableDictionary<AchievementStat, int>, QueryStatsError>> QueryStats(
|
||||
params AchievementStat[] stats)
|
||||
=> QueryStats(stats.ToImmutableArray());
|
||||
|
||||
public static async Task<Result<ImmutableDictionary<AchievementStat, int>, QueryStatsError>> QueryStats(
|
||||
ImmutableArray<AchievementStat> stats)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.QueryStats(stats)
|
||||
: Result.Failure(QueryStatsError.EosNotInitialized);
|
||||
|
||||
public static async Task<Result<ImmutableDictionary<Identifier, double>, QueryAchievementsError>>
|
||||
QueryPlayerAchievements()
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.QueryPlayerAchievements()
|
||||
: Result.Failure(QueryAchievementsError.EosNotInitialized);
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Task<Result<uint, AchievementUnlockError>> UnlockAchievements(
|
||||
params Identifier[] achievementIds);
|
||||
|
||||
public abstract Task<Result<Unit, IngestStatError>> IngestStats(
|
||||
params (AchievementStat Stat, int IngestAmount)[] stats);
|
||||
|
||||
public abstract Task<Result<ImmutableDictionary<AchievementStat, int>, QueryStatsError>> QueryStats(
|
||||
ImmutableArray<AchievementStat> stats);
|
||||
|
||||
public abstract Task<Result<ImmutableDictionary<Identifier, double>, QueryAchievementsError>>
|
||||
QueryPlayerAchievements();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public enum ApplicationCredentials
|
||||
{
|
||||
Client,
|
||||
Server
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("EosInterface.Implementation.Win64")]
|
||||
[assembly: InternalsVisibleTo("EosInterface.Implementation.MacOS")]
|
||||
[assembly: InternalsVisibleTo("EosInterface.Implementation.Linux")]
|
||||
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class Core
|
||||
{
|
||||
internal static Implementation? LoadedImplementation { get; private set; } = null;
|
||||
private static AssemblyLoadContext? assemblyLoadContext = null;
|
||||
|
||||
private static bool hasShutDown = false;
|
||||
private static bool failedToInitialize = false;
|
||||
|
||||
private static string GetAssemblyPath(string assemblyName)
|
||||
=> Path.Combine(
|
||||
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
|
||||
$"{assemblyName}.dll");
|
||||
|
||||
private static bool resolvingDependency;
|
||||
|
||||
private static Assembly? ResolveDependency(AssemblyLoadContext context, AssemblyName dependencyName)
|
||||
{
|
||||
if (resolvingDependency)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
resolvingDependency = true;
|
||||
Assembly dependency =
|
||||
context.LoadFromAssemblyPath(
|
||||
GetAssemblyPath(dependencyName.Name ?? throw new Exception("Dependency name was null")));
|
||||
resolvingDependency = false;
|
||||
return dependency;
|
||||
}
|
||||
|
||||
public enum InitError
|
||||
{
|
||||
PlatformInterfaceNotCreated,
|
||||
AlreadyInitialized,
|
||||
UnknownOsPlatform,
|
||||
ImplementationDllLoadFailed,
|
||||
ImplementationDllHasNoValidClasses,
|
||||
ImplementationFailedToInstantiate,
|
||||
NativeDllLoadFailed,
|
||||
CannotRestartAfterShutdown,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public enum Status
|
||||
{
|
||||
NotInitialized,
|
||||
InitializationError,
|
||||
ShutDown,
|
||||
InitializedButOffline,
|
||||
Online
|
||||
}
|
||||
|
||||
public static bool IsInitialized
|
||||
=> LoadedImplementation != null && LoadedImplementation.IsInitialized();
|
||||
|
||||
public static Status CurrentStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
if (hasShutDown)
|
||||
{
|
||||
return Status.ShutDown;
|
||||
}
|
||||
|
||||
if (failedToInitialize)
|
||||
{
|
||||
return Status.InitializationError;
|
||||
}
|
||||
|
||||
if (LoadedImplementation is { CurrentStatus: var status })
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
return Status.NotInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
public static Result<Unit, InitError> Init(ApplicationCredentials applicationCredentials, bool enableOverlay)
|
||||
{
|
||||
var (success, failure) = Result<Unit, InitError>.GetFactoryMethods();
|
||||
if (LoadedImplementation != null)
|
||||
{
|
||||
return !LoadedImplementation.IsInitialized()
|
||||
? LoadedImplementation.Init(applicationCredentials, enableOverlay)
|
||||
: failure(InitError.AlreadyInitialized);
|
||||
}
|
||||
|
||||
if (hasShutDown)
|
||||
{
|
||||
return failure(InitError.CannotRestartAfterShutdown);
|
||||
}
|
||||
|
||||
string platformSuffix;
|
||||
string nativeDllName;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
platformSuffix = "Win64";
|
||||
nativeDllName = "./EOSSDK-Win64-Shipping.dll";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
platformSuffix = "MacOS";
|
||||
nativeDllName = "./libEOSSDK-Mac-Shipping.dylib";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
platformSuffix = "Linux";
|
||||
nativeDllName = "./libEOSSDK-Linux-Shipping.so";
|
||||
}
|
||||
else
|
||||
{
|
||||
failedToInitialize = true;
|
||||
return failure(InitError.UnknownOsPlatform);
|
||||
}
|
||||
|
||||
if (!NativeLibrary.TryLoad(nativeDllName, out var nativeLib))
|
||||
{
|
||||
failedToInitialize = true;
|
||||
return failure(InitError.NativeDllLoadFailed);
|
||||
}
|
||||
|
||||
NativeLibrary.Free(nativeLib);
|
||||
|
||||
string assemblyName = $"EosInterface.Implementation.{platformSuffix}";
|
||||
|
||||
assemblyLoadContext = new AssemblyLoadContext(assemblyName, isCollectible: true);
|
||||
assemblyLoadContext.Resolving += ResolveDependency;
|
||||
|
||||
Assembly implementationAssembly;
|
||||
try
|
||||
{
|
||||
implementationAssembly = assemblyLoadContext.LoadFromAssemblyPath(GetAssemblyPath(assemblyName));
|
||||
}
|
||||
catch
|
||||
{
|
||||
failedToInitialize = true;
|
||||
return failure(InitError.ImplementationDllLoadFailed);
|
||||
}
|
||||
|
||||
var implementationTypes =
|
||||
implementationAssembly.DefinedTypes
|
||||
.Where(t => t.IsSubclassOf(typeof(Implementation)))
|
||||
.Where(t => t is { IsAbstract: false, IsGenericType: false })
|
||||
.ToArray();
|
||||
if (!implementationTypes.Any())
|
||||
{
|
||||
failedToInitialize = true;
|
||||
return failure(InitError.ImplementationDllHasNoValidClasses);
|
||||
}
|
||||
|
||||
Implementation implementationInstance;
|
||||
try
|
||||
{
|
||||
var implementationInstanceNullable =
|
||||
(Implementation?)Activator.CreateInstance(implementationTypes.First());
|
||||
if (implementationInstanceNullable is null)
|
||||
{
|
||||
failedToInitialize = true;
|
||||
return failure(InitError.ImplementationFailedToInstantiate);
|
||||
}
|
||||
|
||||
implementationInstance = implementationInstanceNullable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
failedToInitialize = true;
|
||||
return failure(InitError.ImplementationFailedToInstantiate);
|
||||
}
|
||||
|
||||
LoadedImplementation = implementationInstance;
|
||||
|
||||
var initResult = implementationInstance.Init(applicationCredentials, enableOverlay);
|
||||
if (initResult.IsFailure)
|
||||
{
|
||||
failedToInitialize = true;
|
||||
}
|
||||
|
||||
return initResult;
|
||||
}
|
||||
|
||||
public enum WillRestartThroughLauncher
|
||||
{
|
||||
No,
|
||||
Yes
|
||||
}
|
||||
|
||||
public enum CheckForLauncherAndRestartError
|
||||
{
|
||||
EosNotInitialized,
|
||||
UnexpectedError,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static Result<WillRestartThroughLauncher, CheckForLauncherAndRestartError> CheckForLauncherAndRestart()
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? LoadedImplementation.CheckForLauncherAndRestart()
|
||||
: Result.Failure(CheckForLauncherAndRestartError.EosNotInitialized);
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (LoadedImplementation.IsInitialized())
|
||||
{
|
||||
LoadedImplementation.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public static void CleanupAndQuit()
|
||||
{
|
||||
var loadedImplementation = LoadedImplementation;
|
||||
if (!loadedImplementation.IsInitialized())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TaskPool.Add(
|
||||
"CleanupAndQuit",
|
||||
loadedImplementation.CloseAllOwnedSessions(),
|
||||
_ => QuitNow());
|
||||
}
|
||||
|
||||
private static void QuitNow()
|
||||
{
|
||||
hasShutDown = CurrentStatus != Status.NotInitialized;
|
||||
LoadedImplementation?.Quit();
|
||||
LoadedImplementation = null;
|
||||
assemblyLoadContext?.Unload();
|
||||
assemblyLoadContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Core.Status CurrentStatus { get; }
|
||||
public abstract string NativeLibraryName { get; }
|
||||
|
||||
public abstract Result<Unit, Core.InitError> Init(ApplicationCredentials applicationCredentials,
|
||||
bool enableOverlay);
|
||||
|
||||
public abstract Result<Core.WillRestartThroughLauncher, Core.CheckForLauncherAndRestartError>
|
||||
CheckForLauncherAndRestart();
|
||||
|
||||
public abstract void Update();
|
||||
public abstract void Quit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static class EosStatusExtensions
|
||||
{
|
||||
public static bool IsInitialized(this EosInterface.Core.Status status)
|
||||
=> status is EosInterface.Core.Status.InitializedButOffline or EosInterface.Core.Status.Online;
|
||||
|
||||
internal static bool IsInitialized(
|
||||
[NotNullWhen(returnValue: true)] this EosInterface.Implementation? implementation)
|
||||
=> implementation is { CurrentStatus: var status } && status.IsInitialized();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BarotraumaCore\BarotraumaCore.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public readonly record struct EgsFriend(
|
||||
string DisplayName,
|
||||
EpicAccountId EpicAccountId,
|
||||
FriendStatus Status,
|
||||
string ConnectCommand,
|
||||
string ServerName);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class Friends
|
||||
{
|
||||
private static Implementation? LoadedImplementation => Core.LoadedImplementation;
|
||||
|
||||
public enum GetFriendsError
|
||||
{
|
||||
EosNotInitialized,
|
||||
|
||||
EgsFriendsQueryTimedOut,
|
||||
EgsFriendsQueryFailed,
|
||||
|
||||
UserInfoQueryTimedOut,
|
||||
UserInfoQueryFailed,
|
||||
CopyUserInfoFailed,
|
||||
DisplayNameIsEmpty,
|
||||
|
||||
EgsPresenceQueryTimedOut,
|
||||
EgsPresenceQueryFailed,
|
||||
CopyPresenceFailed,
|
||||
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<EgsFriend, GetFriendsError>> GetFriend(
|
||||
EpicAccountId selfEaid,
|
||||
EpicAccountId friendEaid)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.GetFriend(selfEaid, friendEaid)
|
||||
: Result.Failure(GetFriendsError.EosNotInitialized);
|
||||
|
||||
public static async Task<Result<ImmutableArray<EgsFriend>, GetFriendsError>> GetFriends(
|
||||
EpicAccountId epicAccountId)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.GetFriends(epicAccountId)
|
||||
: Result.Failure(GetFriendsError.EosNotInitialized);
|
||||
|
||||
public static async Task<Result<EgsFriend, GetFriendsError>> GetSelfUserInfo(EpicAccountId epicAccountId)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.GetSelfUserInfo(epicAccountId)
|
||||
: Result.Failure(GetFriendsError.EosNotInitialized);
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Task<Result<EgsFriend, Friends.GetFriendsError>> GetFriend(
|
||||
EpicAccountId selfEaid,
|
||||
EpicAccountId friendEaid);
|
||||
|
||||
public abstract Task<Result<ImmutableArray<EgsFriend>, Friends.GetFriendsError>> GetFriends(
|
||||
EpicAccountId epicAccountId);
|
||||
|
||||
public abstract Task<Result<EgsFriend, Friends.GetFriendsError>> GetSelfUserInfo(EpicAccountId epicAccountId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class Presence
|
||||
{
|
||||
public readonly record struct JoinGameInfo(
|
||||
EpicAccountId RecipientId,
|
||||
string JoinCommand);
|
||||
|
||||
public readonly record struct AcceptInviteInfo(
|
||||
EpicAccountId RecipientId,
|
||||
string JoinCommand);
|
||||
|
||||
public readonly record struct ReceiveInviteInfo(
|
||||
EpicAccountId RecipientId,
|
||||
EpicAccountId SenderId,
|
||||
string JoinCommand);
|
||||
|
||||
private static readonly NamedEvent<JoinGameInfo> dummyJoinGameEvent =
|
||||
new NamedEvent<JoinGameInfo>();
|
||||
|
||||
private static readonly NamedEvent<AcceptInviteInfo> dummyAcceptInviteEvent =
|
||||
new NamedEvent<AcceptInviteInfo>();
|
||||
|
||||
private static readonly NamedEvent<ReceiveInviteInfo> dummyReceiveInviteEvent =
|
||||
new NamedEvent<ReceiveInviteInfo>();
|
||||
|
||||
public static NamedEvent<JoinGameInfo> OnJoinGame
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? Core.LoadedImplementation.OnJoinGame
|
||||
: dummyJoinGameEvent;
|
||||
|
||||
public static NamedEvent<AcceptInviteInfo> OnInviteAccepted
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? Core.LoadedImplementation.OnInviteAccepted
|
||||
: dummyAcceptInviteEvent;
|
||||
|
||||
public static NamedEvent<ReceiveInviteInfo> OnInviteReceived
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? Core.LoadedImplementation.OnInviteReceived
|
||||
: dummyReceiveInviteEvent;
|
||||
|
||||
public enum SetJoinCommandError
|
||||
{
|
||||
EosNotInitialized,
|
||||
FailedToSetCustomInvite,
|
||||
FailedToCreatePresenceModification,
|
||||
JoinCommandTooLong,
|
||||
ServerNameTooLong,
|
||||
FailedToSetJoinInfo,
|
||||
FailedToGetPuid,
|
||||
DescTooLong,
|
||||
FailedToSetRichText,
|
||||
FailedToSetRecords,
|
||||
SetPresenceTimedOut,
|
||||
FailedToSetPresence
|
||||
}
|
||||
|
||||
public static async Task<Result<Unit, SetJoinCommandError>> SetJoinCommand(
|
||||
EpicAccountId epicAccountId, string desc, string serverName, string joinCommand)
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? await Core.LoadedImplementation.SetJoinCommand(epicAccountId, desc, serverName, joinCommand)
|
||||
: Result.Failure(SetJoinCommandError.EosNotInitialized);
|
||||
|
||||
public enum SendInviteError
|
||||
{
|
||||
EosNotInitialized,
|
||||
FailedToGetSelfPuid,
|
||||
FailedToGetRemotePuid,
|
||||
TimedOut,
|
||||
InternalError
|
||||
}
|
||||
|
||||
public static async Task<Result<Unit, SendInviteError>> SendInvite(
|
||||
EpicAccountId selfEpicAccountId, EpicAccountId remoteEpicAccountId)
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? await Core.LoadedImplementation.SendInvite(selfEpicAccountId, remoteEpicAccountId)
|
||||
: Result.Failure(SendInviteError.EosNotInitialized);
|
||||
|
||||
public static void DeclineInvite(EpicAccountId selfEpicAccountId, EpicAccountId senderEpicAccountId)
|
||||
{
|
||||
if (Core.LoadedImplementation.IsInitialized())
|
||||
{
|
||||
Core.LoadedImplementation.DeclineInvite(selfEpicAccountId, senderEpicAccountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract NamedEvent<Presence.JoinGameInfo> OnJoinGame { get; }
|
||||
public abstract NamedEvent<Presence.AcceptInviteInfo> OnInviteAccepted { get; }
|
||||
public abstract NamedEvent<Presence.ReceiveInviteInfo> OnInviteReceived { get; }
|
||||
|
||||
public abstract Task<Result<Unit, Presence.SetJoinCommandError>> SetJoinCommand(
|
||||
EpicAccountId epicAccountId, string desc, string joinCommand, string s);
|
||||
public abstract Task<Result<Unit, Presence.SendInviteError>> SendInvite(
|
||||
EpicAccountId selfEpicAccountId, EpicAccountId remoteEpicAccountId);
|
||||
public abstract void DeclineInvite(EpicAccountId selfEpicAccountId, EpicAccountId senderEpicAccountId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public sealed class EgsAuthContinuanceToken
|
||||
{
|
||||
// Got this number by checking a decoded continuance token, may be subject to change
|
||||
public static readonly TimeSpan Duration = TimeSpan.FromMinutes(30);
|
||||
|
||||
public readonly DateTime ExpiryTime;
|
||||
public bool IsValid => value != IntPtr.Zero && DateTime.Now < ExpiryTime;
|
||||
|
||||
private IntPtr value;
|
||||
|
||||
public EgsAuthContinuanceToken(IntPtr value, DateTime expiryTime)
|
||||
{
|
||||
this.value = value;
|
||||
ExpiryTime = expiryTime;
|
||||
}
|
||||
|
||||
public IntPtr Spend()
|
||||
{
|
||||
var retVal = IsValid ? value : IntPtr.Zero;
|
||||
value = IntPtr.Zero;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{(IsValid ? "Valid" : "Invalid")} EGS ContinuanceToken"
|
||||
+ (IsValid ? $" (expires on {ExpiryTime})" : "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public enum GetEgsSelfIdTokenError
|
||||
{
|
||||
EosNotInitialized,
|
||||
NotLoggedIn,
|
||||
InvalidToken,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public enum VerifyEgsIdTokenResult
|
||||
{
|
||||
Verified,
|
||||
Failed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Epic Games ID Token, used to authenticate an Epic Account ID.
|
||||
/// This is distinct from <see cref="EosIdToken" />, which represents an EOS ID Token.
|
||||
/// </summary>
|
||||
public abstract class EgsIdToken
|
||||
{
|
||||
public abstract EpicAccountId AccountId { get; }
|
||||
|
||||
public static Option<EgsIdToken> Parse(string str)
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? Core.LoadedImplementation.ParseEgsIdToken(str)
|
||||
: Option.None;
|
||||
|
||||
public static Result<EgsIdToken, GetEgsSelfIdTokenError> FromEpicAccountId(EpicAccountId accountId)
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? Core.LoadedImplementation.GetEgsIdTokenForEpicAccountId(accountId)
|
||||
: Result.Failure(GetEgsSelfIdTokenError.EosNotInitialized);
|
||||
|
||||
public abstract override string ToString();
|
||||
|
||||
public abstract Task<VerifyEgsIdTokenResult> Verify(AccountId accountId);
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Option<EgsIdToken> ParseEgsIdToken(string str);
|
||||
|
||||
public abstract Result<EgsIdToken, GetEgsSelfIdTokenError> GetEgsIdTokenForEpicAccountId(
|
||||
EpicAccountId accountId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public sealed class EosConnectContinuanceToken
|
||||
{
|
||||
// Got this number by checking a decoded continuance token, may be subject to change
|
||||
public static readonly TimeSpan Duration = TimeSpan.FromMinutes(30);
|
||||
|
||||
public readonly AccountId ExternalAccountId;
|
||||
public readonly DateTime ExpiryTime;
|
||||
public bool IsValid => value != IntPtr.Zero && DateTime.Now < ExpiryTime;
|
||||
|
||||
private IntPtr value;
|
||||
|
||||
public EosConnectContinuanceToken(IntPtr value, AccountId externalAccountId, DateTime expiryTime)
|
||||
{
|
||||
this.value = value;
|
||||
this.ExternalAccountId = externalAccountId;
|
||||
ExpiryTime = expiryTime;
|
||||
}
|
||||
|
||||
public IntPtr Spend()
|
||||
{
|
||||
var retVal = IsValid ? value : IntPtr.Zero;
|
||||
value = IntPtr.Zero;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{(IsValid ? "Valid" : "Invalid")} {ExternalAccountId} ContinuanceToken"
|
||||
+ (IsValid ? $" (expires on {ExpiryTime})" : "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public enum GetEosSelfIdTokenError
|
||||
{
|
||||
EosNotInitialized,
|
||||
NotLoggedIn,
|
||||
InvalidToken,
|
||||
CouldNotParseJwt,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public enum VerifyEosIdTokenError
|
||||
{
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
ProductIdDidNotMatch,
|
||||
CouldNotParseExternalAccountId,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an EOS ID Token, used to authenticate a Product User ID.
|
||||
/// This is distinct from <see cref="EgsIdToken" />, which represents an Epic Games ID Token.
|
||||
/// </summary>
|
||||
public readonly record struct EosIdToken(
|
||||
ProductUserId ProductUserId,
|
||||
JsonWebToken JsonWebToken)
|
||||
{
|
||||
public async Task<Result<AccountId, VerifyEosIdTokenError>> Verify()
|
||||
=> Core.LoadedImplementation is { } loadedImplementation
|
||||
? await loadedImplementation.VerifyEosIdToken(this)
|
||||
: Result.Failure(VerifyEosIdTokenError.EosNotInitialized);
|
||||
|
||||
public static Option<EosIdToken> Parse(string str)
|
||||
{
|
||||
var jsonReader = new Utf8JsonReader(Encoding.UTF8.GetBytes(str));
|
||||
JsonDocument? jsonDoc = null;
|
||||
try
|
||||
{
|
||||
if (!JsonDocument.TryParseValue(ref jsonReader, out jsonDoc))
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
if (!jsonDoc.RootElement.TryGetProperty(nameof(ProductUserId), out var puidElement))
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
if (!jsonDoc.RootElement.TryGetProperty(nameof(JsonWebToken), out var jwtElement))
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
var puidStr = puidElement.ToString();
|
||||
if (!puidStr.IsHexString())
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
var puid = new ProductUserId(puidStr);
|
||||
|
||||
var jwtStr = jwtElement.ToString();
|
||||
if (!JsonWebToken.Parse(jwtStr).TryUnwrap(out var jsonWebToken))
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
var newToken = new EosIdToken(puid, jsonWebToken);
|
||||
|
||||
return Option.Some(newToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Option.None;
|
||||
}
|
||||
finally
|
||||
{
|
||||
jsonDoc?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static Result<EosIdToken, GetEosSelfIdTokenError> FromProductUserId(ProductUserId puid)
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? Core.LoadedImplementation.GetEosIdTokenForProductUserId(puid)
|
||||
: Result.Failure(GetEosSelfIdTokenError.EosNotInitialized);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
using var memoryStream = new System.IO.MemoryStream();
|
||||
using var jsonWriter = new Utf8JsonWriter(memoryStream);
|
||||
jsonWriter.WriteStartObject();
|
||||
jsonWriter.WriteString(nameof(ProductUserId), ProductUserId.Value);
|
||||
jsonWriter.WriteString(nameof(JsonWebToken), JsonWebToken.ToString());
|
||||
jsonWriter.WriteEndObject();
|
||||
jsonWriter.Flush();
|
||||
memoryStream.Flush();
|
||||
return Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Task<Result<AccountId, VerifyEosIdTokenError>> VerifyEosIdToken(EosIdToken token);
|
||||
public abstract Result<EosIdToken, GetEosSelfIdTokenError> GetEosIdTokenForProductUserId(ProductUserId puid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class IdQueries
|
||||
{
|
||||
private static Implementation? LoadedImplementation => Core.LoadedImplementation;
|
||||
|
||||
public static bool IsLoggedIntoEosConnect
|
||||
=> GetLoggedInPuids() is { Length: > 0 };
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the <see cref="Barotrauma.EosInterface.ProductUserId" />s the player has logged in with.
|
||||
/// For most players, this is expected to return one ID.
|
||||
/// It may return two IDs if a Steam user has chosen to link their account to an Epic Account.
|
||||
/// </summary>
|
||||
public static ImmutableArray<ProductUserId> GetLoggedInPuids()
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? LoadedImplementation.GetLoggedInPuids()
|
||||
: ImmutableArray<ProductUserId>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the <see cref="Barotrauma.Networking.EpicAccountId" />s the player has logged in with.
|
||||
/// This is expected to return at most one ID.
|
||||
/// <br /><br />
|
||||
/// This should return exactly one ID for any Epic Games Store player.
|
||||
/// <br />
|
||||
/// Steam players may choose to link their account to only one Epic Games account.
|
||||
/// </summary>
|
||||
public static ImmutableArray<EpicAccountId> GetLoggedInEpicIds()
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? LoadedImplementation.GetLoggedInEpicIds()
|
||||
: ImmutableArray<EpicAccountId>.Empty;
|
||||
|
||||
public enum GetSelfExternalIdError
|
||||
{
|
||||
EosNotInitialized,
|
||||
Inaccessible,
|
||||
Timeout,
|
||||
InvalidUser,
|
||||
ParseError,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<ImmutableArray<AccountId>, GetSelfExternalIdError>> GetSelfExternalAccountIds(
|
||||
ProductUserId puid)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.GetSelfExternalAccountIds(puid)
|
||||
: Result.Failure(GetSelfExternalIdError.EosNotInitialized);
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract ImmutableArray<ProductUserId> GetLoggedInPuids();
|
||||
public abstract ImmutableArray<EpicAccountId> GetLoggedInEpicIds();
|
||||
|
||||
public abstract Task<Result<ImmutableArray<AccountId>, IdQueries.GetSelfExternalIdError>>
|
||||
GetSelfExternalAccountIds(ProductUserId puid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class Login
|
||||
{
|
||||
private static Implementation? LoadedImplementation => Core.LoadedImplementation;
|
||||
|
||||
public enum CreateProductAccountError
|
||||
{
|
||||
EosNotInitialized,
|
||||
InvalidContinuanceToken,
|
||||
Timeout,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<ProductUserId, CreateProductAccountError>> CreateProductAccount(
|
||||
EosConnectContinuanceToken eosContinuanceToken)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.CreateProductAccount(eosContinuanceToken)
|
||||
: Result.Failure(CreateProductAccountError.EosNotInitialized);
|
||||
|
||||
public enum LinkExternalAccountError
|
||||
{
|
||||
EosNotInitialized,
|
||||
InvalidContinuanceToken,
|
||||
Timeout,
|
||||
CannotLink,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<Unit, LinkExternalAccountError>> LinkExternalAccount(ProductUserId puid,
|
||||
EosConnectContinuanceToken eosContinuanceToken)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.LinkExternalAccount(puid, eosContinuanceToken)
|
||||
: Result.Failure(LinkExternalAccountError.EosNotInitialized);
|
||||
|
||||
public enum UnlinkExternalAccountError
|
||||
{
|
||||
EosNotInitialized,
|
||||
FailedToGetExternalAccounts,
|
||||
NotLoggedInToGivenAccount,
|
||||
Timeout,
|
||||
CannotLink,
|
||||
InvalidUser,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<Unit, UnlinkExternalAccountError>> UnlinkExternalAccount(ProductUserId puid)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.UnlinkExternalAccount(puid)
|
||||
: Result.Failure(UnlinkExternalAccountError.EosNotInitialized);
|
||||
|
||||
public enum LoginError
|
||||
{
|
||||
EosNotInitialized,
|
||||
|
||||
SteamNotLoggedIn,
|
||||
FailedToGetSteamSessionTicket,
|
||||
|
||||
EgsLoginTimeout,
|
||||
EgsAccountNotFound,
|
||||
FailedToParseEgsId,
|
||||
FailedToGetEgsIdToken,
|
||||
AuthExchangeCodeNotFound,
|
||||
AuthRequiresOpeningBrowser,
|
||||
|
||||
Timeout,
|
||||
InvalidUser,
|
||||
EgsAccessDenied,
|
||||
EosAccessDenied,
|
||||
UnexpectedContinuanceToken,
|
||||
|
||||
UnhandledFailureCondition
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum LoginEpicFlags
|
||||
{
|
||||
None = 0x0,
|
||||
FailWithoutOpeningBrowser = 0x1
|
||||
}
|
||||
|
||||
public static async
|
||||
Task<Result<OneOf<ProductUserId, EosConnectContinuanceToken, EgsAuthContinuanceToken>, LoginError>>
|
||||
LoginEpicWithLinkedSteamAccount(LoginEpicFlags flags)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.LoginEpicWithLinkedSteamAccount(flags)
|
||||
: Result.Failure(LoginError.EosNotInitialized);
|
||||
|
||||
public static async Task<Result<Either<ProductUserId, EosConnectContinuanceToken>, LoginError>>
|
||||
LoginEpicExchangeCode(string exchangeCode)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.LoginEpicExchangeCode(exchangeCode)
|
||||
: Result.Failure(LoginError.EosNotInitialized);
|
||||
|
||||
public static async Task<Result<Either<ProductUserId, EosConnectContinuanceToken>, LoginError>>
|
||||
LoginEpicIdToken(EgsIdToken token)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.LoginEpicIdToken(token)
|
||||
: Result.Failure(LoginError.EosNotInitialized);
|
||||
|
||||
public static async Task<Result<Either<ProductUserId, EosConnectContinuanceToken>, LoginError>> LoginSteam()
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.LoginSteam()
|
||||
: Result.Failure(LoginError.EosNotInitialized);
|
||||
|
||||
public enum LinkExternalAccountToEpicAccountError
|
||||
{
|
||||
EosNotInitialized,
|
||||
|
||||
TimedOut,
|
||||
FailedToParseEgsAccountId,
|
||||
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<EpicAccountId, LinkExternalAccountToEpicAccountError>>
|
||||
LinkExternalAccountToEpicAccount(EgsAuthContinuanceToken continuanceToken)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.LinkExternalAccountToEpicAccount(continuanceToken)
|
||||
: Result.Failure(LinkExternalAccountToEpicAccountError.EosNotInitialized);
|
||||
|
||||
public enum LogoutEpicAccountError
|
||||
{
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<Unit, LogoutEpicAccountError>> LogoutEpicAccount(EpicAccountId egsId)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.LogoutEpicAccount(egsId)
|
||||
: Result.Failure(LogoutEpicAccountError.EosNotInitialized);
|
||||
|
||||
/// <summary>
|
||||
/// This is essentially a function for logging out, except EOS has no EOS_Connect_Logout function
|
||||
/// so instead we have this to fake it. Once you use this, no methods should return this PUID
|
||||
/// until you log into it again.
|
||||
/// </summary>
|
||||
public static void MarkAsInaccessible(ProductUserId puid)
|
||||
{
|
||||
if (LoadedImplementation.IsInitialized())
|
||||
{
|
||||
LoadedImplementation.MarkAsInaccessible(puid);
|
||||
}
|
||||
}
|
||||
|
||||
public static Option<string> ParseEgsExchangeCode(IReadOnlyList<string> args)
|
||||
{
|
||||
if (args.Contains("-AUTH_TYPE=exchangecode", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return args.FirstOrNone(arg =>
|
||||
arg.StartsWith("-AUTH_PASSWORD=", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(arg => arg["-AUTH_PASSWORD=".Length..]);
|
||||
}
|
||||
|
||||
return Option.None;
|
||||
}
|
||||
|
||||
public static void TestEosSessionTimeoutRecovery(ProductUserId puid)
|
||||
{
|
||||
if (LoadedImplementation.IsInitialized())
|
||||
{
|
||||
LoadedImplementation.TestEosSessionTimeoutRecovery(puid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Task<Result<ProductUserId, Login.CreateProductAccountError>> CreateProductAccount(
|
||||
EosConnectContinuanceToken eosContinuanceToken);
|
||||
|
||||
public abstract Task<Result<Unit, Login.LinkExternalAccountError>> LinkExternalAccount(ProductUserId puid,
|
||||
EosConnectContinuanceToken eosContinuanceToken);
|
||||
|
||||
public abstract Task<Result<Unit, Login.UnlinkExternalAccountError>> UnlinkExternalAccount(ProductUserId puid);
|
||||
|
||||
public abstract Task<Result<Either<ProductUserId, EosConnectContinuanceToken>, Login.LoginError>>
|
||||
LoginEpicExchangeCode(string exchangeCode);
|
||||
|
||||
public abstract
|
||||
Task<Result<OneOf<ProductUserId, EosConnectContinuanceToken, EgsAuthContinuanceToken>, Login.LoginError>>
|
||||
LoginEpicWithLinkedSteamAccount(Login.LoginEpicFlags flags);
|
||||
|
||||
public abstract Task<Result<Either<ProductUserId, EosConnectContinuanceToken>, Login.LoginError>>
|
||||
LoginEpicIdToken(EgsIdToken token);
|
||||
|
||||
public abstract Task<Result<Either<ProductUserId, EosConnectContinuanceToken>, Login.LoginError>> LoginSteam();
|
||||
|
||||
public abstract Task<Result<EpicAccountId, Login.LinkExternalAccountToEpicAccountError>>
|
||||
LinkExternalAccountToEpicAccount(EgsAuthContinuanceToken continuanceToken);
|
||||
|
||||
public abstract Task<Result<Unit, Login.LogoutEpicAccountError>> LogoutEpicAccount(EpicAccountId egsId);
|
||||
public abstract void MarkAsInaccessible(ProductUserId puid);
|
||||
public abstract void TestEosSessionTimeoutRecovery(ProductUserId puid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class Ownership
|
||||
{
|
||||
private static Implementation? LoadedImplementation => Core.LoadedImplementation;
|
||||
|
||||
public static async Task<Option<Ownership.Token>> GetGameOwnershipToken(EpicAccountId selfEpicAccountId)
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.GetGameOwnershipToken(selfEpicAccountId)
|
||||
: Option.None;
|
||||
|
||||
public readonly record struct Token(JsonWebToken Jwt)
|
||||
{
|
||||
public async Task<Option<EpicAccountId>> Verify()
|
||||
=> LoadedImplementation.IsInitialized()
|
||||
? await LoadedImplementation.VerifyGameOwnershipToken(this)
|
||||
: Option.None;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Task<Option<Ownership.Token>> GetGameOwnershipToken(EpicAccountId selfEpicAccountId);
|
||||
|
||||
public abstract Task<Option<EpicAccountId>> VerifyGameOwnershipToken(Ownership.Token token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// A Product User ID is an EOS-specific ID that's linked to the SteamID or the Epic Account ID of a player.
|
||||
/// It is used to identify players in many of EOS' interfaces, most notably the P2P networking interface.
|
||||
/// <br /><br />
|
||||
/// A Product User ID used by Barotrauma is only valid for Barotrauma; other games that use EOS get their
|
||||
/// own separate set of Product User IDs.
|
||||
/// </summary>
|
||||
public readonly record struct ProductUserId(string Value);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public abstract class P2PSocket : IDisposable
|
||||
{
|
||||
public enum CreationError
|
||||
{
|
||||
EosNotInitialized,
|
||||
UserNotLoggedIn,
|
||||
RequestBindFailed,
|
||||
CloseBindFailed
|
||||
}
|
||||
|
||||
public readonly record struct IncomingConnectionRequest(
|
||||
P2PSocket Socket,
|
||||
ProductUserId RemoteUserId)
|
||||
{
|
||||
public void Accept()
|
||||
=> Socket.AcceptConnectionRequest(this);
|
||||
}
|
||||
|
||||
public readonly record struct RemoteConnectionClosed(
|
||||
ProductUserId RemoteUserId,
|
||||
RemoteConnectionClosed.ConnectionClosedReason Reason)
|
||||
{
|
||||
public enum ConnectionClosedReason
|
||||
{
|
||||
Unknown,
|
||||
ClosedByLocalUser,
|
||||
ClosedByPeer,
|
||||
TimedOut,
|
||||
TooManyConnections,
|
||||
InvalidMessage,
|
||||
InvalidData,
|
||||
ConnectionFailed,
|
||||
ConnectionClosed,
|
||||
NegotiationFailed,
|
||||
UnexpectedError,
|
||||
Unhandled
|
||||
}
|
||||
}
|
||||
|
||||
public readonly NamedEvent<IncomingConnectionRequest> HandleIncomingConnection
|
||||
= new NamedEvent<IncomingConnectionRequest>();
|
||||
|
||||
public readonly NamedEvent<RemoteConnectionClosed> HandleClosedConnection
|
||||
= new NamedEvent<RemoteConnectionClosed>();
|
||||
|
||||
public static Result<P2PSocket, CreationError> Create(ProductUserId puid, SocketId socketId)
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? Core.LoadedImplementation.CreateP2PSocket(puid, socketId)
|
||||
: Result.Failure(CreationError.EosNotInitialized);
|
||||
|
||||
public abstract void AcceptConnectionRequest(IncomingConnectionRequest request);
|
||||
|
||||
public abstract void CloseConnection(ProductUserId remoteUserId);
|
||||
|
||||
public readonly record struct IncomingMessage(
|
||||
byte[] Buffer,
|
||||
int ByteLength,
|
||||
ProductUserId Sender);
|
||||
|
||||
public abstract IEnumerable<IncomingMessage> GetMessageBatch();
|
||||
|
||||
public readonly record struct OutgoingMessage(
|
||||
byte[] Buffer,
|
||||
int ByteLength,
|
||||
ProductUserId Destination,
|
||||
DeliveryMethod DeliveryMethod);
|
||||
|
||||
public enum SendError
|
||||
{
|
||||
EosNotInitialized,
|
||||
InvalidParameters,
|
||||
LimitExceeded,
|
||||
NoConnection,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public abstract Result<Unit, SendError> SendMessage(OutgoingMessage msg);
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Result<P2PSocket, P2PSocket.CreationError> CreateP2PSocket(ProductUserId puid,
|
||||
SocketId socketId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public readonly record struct SocketId(string SocketName);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static partial class EosInterface
|
||||
{
|
||||
public static class Sessions
|
||||
{
|
||||
public const string DefaultBucketName = "BBucket";
|
||||
public const int MinBucketIndex = 0;
|
||||
public const int MaxBucketIndex = 9;
|
||||
|
||||
public sealed record OwnedSession(
|
||||
string BucketId,
|
||||
Identifier InternalId,
|
||||
Identifier GlobalId,
|
||||
Dictionary<Identifier, string> Attributes) : IDisposable
|
||||
{
|
||||
public Option<string> HostAddress = Option.None;
|
||||
|
||||
public ImmutableDictionary<Identifier, string> SyncedAttributes =
|
||||
ImmutableDictionary<Identifier, string>.Empty;
|
||||
|
||||
public async Task<Result<Unit, AttributeUpdateError>> UpdateAttributes()
|
||||
=> Core.LoadedImplementation is { } implementation
|
||||
? await implementation.UpdateOwnedSessionAttributes(this)
|
||||
: Result.Failure(AttributeUpdateError.EosNotInitialized);
|
||||
|
||||
public async Task<Result<Unit, CloseError>> Close()
|
||||
=> Core.LoadedImplementation is { } implementation
|
||||
? await implementation.CloseOwnedSession(this)
|
||||
: Result.Failure(CloseError.EosNotInitialized);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Core.IsInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var _ = Close();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct RemoteSession(
|
||||
string SessionId,
|
||||
string HostAddress,
|
||||
int CurrentPlayers,
|
||||
int MaxPlayers,
|
||||
ImmutableDictionary<Identifier, string> Attributes,
|
||||
string BucketId)
|
||||
{
|
||||
public readonly record struct Query(
|
||||
int BucketIndex,
|
||||
ProductUserId LocalUserId,
|
||||
uint MaxResults,
|
||||
ImmutableDictionary<Identifier, string> Attributes)
|
||||
{
|
||||
public enum Error
|
||||
{
|
||||
EosNotInitialized,
|
||||
|
||||
ExceededMaxAllowedResults,
|
||||
|
||||
InvalidParameters,
|
||||
TimedOut,
|
||||
NotFound,
|
||||
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public async Task<Result<ImmutableArray<RemoteSession>, Error>> Run()
|
||||
=> Core.LoadedImplementation is { } loadedImplementation
|
||||
? await loadedImplementation.RunRemoteSessionQuery(this)
|
||||
: Result.Failure(Error.EosNotInitialized);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CreateError
|
||||
{
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
|
||||
SessionAlreadyExists,
|
||||
|
||||
InvalidParametersForAddAttribute,
|
||||
IncompatibleVersionForAddAttribute,
|
||||
UnhandledErrorConditionForAddAttribute,
|
||||
|
||||
InvalidUser,
|
||||
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public enum AttributeUpdateError
|
||||
{
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
|
||||
FailedToCreateSessionModificationHandle,
|
||||
|
||||
InvalidParametersForRemoveAttribute,
|
||||
IncompatibleVersionForRemoveAttribute,
|
||||
UnhandledErrorConditionForRemoveAttribute,
|
||||
|
||||
InvalidParametersForAddAttribute,
|
||||
IncompatibleVersionForAddAttribute,
|
||||
UnhandledErrorConditionForAddAttribute,
|
||||
|
||||
InvalidParametersForSessionUpdate,
|
||||
SessionsOutOfSync,
|
||||
SessionNotFound,
|
||||
NoConnection,
|
||||
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public enum CloseError
|
||||
{
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
|
||||
InvalidParameters,
|
||||
AlreadyPending,
|
||||
NotFound,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public enum RegisterError
|
||||
{
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public enum UnregisterError
|
||||
{
|
||||
EosNotInitialized,
|
||||
TimedOut,
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public static async Task<Result<OwnedSession, CreateError>> CreateSession(Option<ProductUserId> puidOption,
|
||||
Identifier internalId, int maxPlayers)
|
||||
=> Core.LoadedImplementation.IsInitialized()
|
||||
? await Core.LoadedImplementation.CreateSession(puidOption, internalId, maxPlayers)
|
||||
: Result.Failure(CreateError.EosNotInitialized);
|
||||
}
|
||||
|
||||
internal abstract partial class Implementation
|
||||
{
|
||||
public abstract Task<Result<Sessions.OwnedSession, Sessions.CreateError>> CreateSession(
|
||||
Option<ProductUserId> selfUserIdOption, Identifier internalId, int maxPlayers);
|
||||
|
||||
public abstract Task<Result<Unit, Sessions.AttributeUpdateError>> UpdateOwnedSessionAttributes(
|
||||
Sessions.OwnedSession session);
|
||||
|
||||
public abstract Task<Result<Unit, Sessions.CloseError>> CloseOwnedSession(Sessions.OwnedSession session);
|
||||
public abstract Task CloseAllOwnedSessions();
|
||||
|
||||
public abstract Task<Result<ImmutableArray<Sessions.RemoteSession>, Sessions.RemoteSession.Query.Error>>
|
||||
RunRemoteSessionQuery(Sessions.RemoteSession.Query query);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user