quantum-space-buddies/EpicOnlineTransport/EOSSDKComponent.cs
2022-04-03 22:22:19 -07:00

465 lines
16 KiB
C#

using Epic.OnlineServices;
using Epic.OnlineServices.Logging;
using Epic.OnlineServices.Platform;
using UnityEngine;
/// <summary>
/// Manages the Epic Online Services SDK
/// Do not destroy this component!
/// The Epic Online Services SDK can only be initialized once,
/// after releasing the SDK the game has to be restarted in order to initialize the SDK again.
/// In the unity editor the OnDestroy function will not run so that we dont have to restart the editor after play.
/// </summary>
namespace EpicTransport;
[DefaultExecutionOrder(-32000)]
public class EOSSDKComponent : MonoBehaviour
{
// Unity Inspector shown variables
[SerializeField]
public EosApiKey apiKeys;
[Header("User Login")]
public bool authInterfaceLogin = false;
public Epic.OnlineServices.Auth.LoginCredentialType authInterfaceCredentialType = Epic.OnlineServices.Auth.LoginCredentialType.AccountPortal;
public uint devAuthToolPort = 7878;
public string devAuthToolCredentialName = "";
public ExternalCredentialType connectInterfaceCredentialType = ExternalCredentialType.DeviceidAccessToken;
public string deviceModel = "PC Windows 64bit";
[SerializeField] private string displayName = "User";
public static string DisplayName
{
get => Instance.displayName;
set => Instance.displayName = value;
}
[Header("Misc")]
public LogLevel epicLoggerLevel = LogLevel.Error;
[SerializeField]
private bool collectPlayerMetrics = true;
public static bool CollectPlayerMetrics => Instance.collectPlayerMetrics;
public bool checkForEpicLauncherAndRestart = false;
public bool delayedInitialization = false;
public float platformTickIntervalInSeconds = 0.0f;
private float platformTickTimer = 0f;
public uint tickBudgetInMilliseconds = 0;
// End Unity Inspector shown variables
private ulong authExpirationHandle;
private string authInterfaceLoginCredentialId = null;
public static void SetAuthInterfaceLoginCredentialId(string credentialId) => Instance.authInterfaceLoginCredentialId = credentialId;
private string authInterfaceCredentialToken = null;
public static void SetAuthInterfaceCredentialToken(string credentialToken) => Instance.authInterfaceCredentialToken = credentialToken;
private string connectInterfaceCredentialToken = null;
public static void SetConnectInterfaceCredentialToken(string credentialToken) => Instance.connectInterfaceCredentialToken = credentialToken;
private PlatformInterface EOS;
// Interfaces
public static Epic.OnlineServices.Achievements.AchievementsInterface GetAchievementsInterface() => Instance.EOS.GetAchievementsInterface();
public static Epic.OnlineServices.Auth.AuthInterface GetAuthInterface() => Instance.EOS.GetAuthInterface();
public static Epic.OnlineServices.Connect.ConnectInterface GetConnectInterface() => Instance.EOS.GetConnectInterface();
public static Epic.OnlineServices.Ecom.EcomInterface GetEcomInterface() => Instance.EOS.GetEcomInterface();
public static Epic.OnlineServices.Friends.FriendsInterface GetFriendsInterface() => Instance.EOS.GetFriendsInterface();
public static Epic.OnlineServices.Leaderboards.LeaderboardsInterface GetLeaderboardsInterface() => Instance.EOS.GetLeaderboardsInterface();
public static Epic.OnlineServices.Lobby.LobbyInterface GetLobbyInterface() => Instance.EOS.GetLobbyInterface();
public static Epic.OnlineServices.Metrics.MetricsInterface GetMetricsInterface() => Instance.EOS.GetMetricsInterface(); // Handled by the transport automatically, only use this interface if Mirror is not used for singleplayer
public static Epic.OnlineServices.Mods.ModsInterface GetModsInterface() => Instance.EOS.GetModsInterface();
public static Epic.OnlineServices.P2P.P2PInterface GetP2PInterface() => Instance.EOS.GetP2PInterface();
public static Epic.OnlineServices.PlayerDataStorage.PlayerDataStorageInterface GetPlayerDataStorageInterface() => Instance.EOS.GetPlayerDataStorageInterface();
public static Epic.OnlineServices.Presence.PresenceInterface GetPresenceInterface() => Instance.EOS.GetPresenceInterface();
public static Epic.OnlineServices.Sessions.SessionsInterface GetSessionsInterface() => Instance.EOS.GetSessionsInterface();
public static Epic.OnlineServices.TitleStorage.TitleStorageInterface GetTitleStorageInterface() => Instance.EOS.GetTitleStorageInterface();
public static Epic.OnlineServices.UI.UIInterface GetUIInterface() => Instance.EOS.GetUIInterface();
public static Epic.OnlineServices.UserInfo.UserInfoInterface GetUserInfoInterface() => Instance.EOS.GetUserInfoInterface();
protected EpicAccountId localUserAccountId;
public static EpicAccountId LocalUserAccountId => Instance.localUserAccountId;
protected string localUserAccountIdString;
public static string LocalUserAccountIdString => Instance.localUserAccountIdString;
protected ProductUserId localUserProductId;
public static ProductUserId LocalUserProductId => Instance.localUserProductId;
protected string localUserProductIdString;
public static string LocalUserProductIdString => Instance.localUserProductIdString;
protected bool initialized;
public static bool Initialized => Instance.initialized;
protected bool isConnecting;
public static bool IsConnecting => Instance.isConnecting;
protected static EOSSDKComponent instance;
protected static EOSSDKComponent Instance
{
get
{
if (instance == null)
{
return new GameObject("EOSSDKComponent").AddComponent<EOSSDKComponent>();
}
else
{
return instance;
}
}
}
public static void Tick()
{
instance.platformTickTimer -= Time.deltaTime;
instance.EOS.Tick();
}
// If we're in editor, we should dynamically load and unload the SDK between play sessions.
// This allows us to initialize the SDK each time the game is run in editor.
#if UNITY_EDITOR_WIN
[DllImport("Kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("Kernel32.dll")]
private static extern int FreeLibrary(IntPtr hLibModule);
[DllImport("Kernel32.dll")]
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
private IntPtr libraryPointer;
#endif
#if UNITY_EDITOR_LINUX
[DllImport("libdl.so", EntryPoint = "dlopen")]
private static extern IntPtr LoadLibrary(String lpFileName, int flags = 2);
[DllImport("libdl.so", EntryPoint = "dlclose")]
private static extern int FreeLibrary(IntPtr hLibModule);
[DllImport("libdl.so")]
private static extern IntPtr dlsym(IntPtr handle, String symbol);
[DllImport("libdl.so")]
private static extern IntPtr dlerror();
private static IntPtr GetProcAddress(IntPtr hModule, string lpProcName) {
// clear previous errors if any
dlerror();
var res = dlsym(hModule, lpProcName);
var errPtr = dlerror();
if (errPtr != IntPtr.Zero) {
throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr));
}
return res;
}
private IntPtr libraryPointer;
#endif
private void Awake()
{
// Initialize Java version of the SDK with a reference to the VM with JNI
// See https://eoshelp.epicgames.com/s/question/0D54z00006ufJBNCA2/cant-get-createdeviceid-to-work-in-unity-android-c-sdk?language=en_US
if (Application.platform == RuntimePlatform.Android)
{
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var context = activity.Call<AndroidJavaObject>("getApplicationContext");
var EOS_SDK_JAVA = new AndroidJavaClass("com.epicgames.mobile.eossdk.EOSSDK");
EOS_SDK_JAVA.CallStatic("init", context);
}
// Prevent multiple instances
if (instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
#if UNITY_EDITOR
var libraryPath = "Assets/Mirror/Runtime/Transport/EpicOnlineTransport/EOSSDK/" + Config.LibraryName;
libraryPointer = LoadLibrary(libraryPath);
if (libraryPointer == IntPtr.Zero) {
throw new Exception("Failed to load library" + libraryPath);
}
Bindings.Hook(libraryPointer, GetProcAddress);
#endif
if (!delayedInitialization)
{
Initialize();
}
}
protected void InitializeImplementation()
{
isConnecting = true;
var initializeOptions = new InitializeOptions()
{
ProductName = apiKeys.epicProductName,
ProductVersion = apiKeys.epicProductVersion
};
var initializeResult = PlatformInterface.Initialize(initializeOptions);
// This code is called each time the game is run in the editor, so we catch the case where the SDK has already been initialized in the editor.
var isAlreadyConfiguredInEditor = Application.isEditor && initializeResult == Result.AlreadyConfigured;
if (initializeResult != Result.Success && !isAlreadyConfiguredInEditor)
{
throw new System.Exception("Failed to initialize platform: " + initializeResult);
}
// The SDK outputs lots of information that is useful for debugging.
// Make sure to set up the logging interface as early as possible: after initializing.
LoggingInterface.SetLogLevel(LogCategory.AllCategories, epicLoggerLevel);
LoggingInterface.SetCallback(message => Logger.EpicDebugLog(message));
var options = new Options()
{
ProductId = apiKeys.epicProductId,
SandboxId = apiKeys.epicSandboxId,
DeploymentId = apiKeys.epicDeploymentId,
ClientCredentials = new ClientCredentials()
{
ClientId = apiKeys.epicClientId,
ClientSecret = apiKeys.epicClientSecret
},
TickBudgetInMilliseconds = tickBudgetInMilliseconds
};
EOS = PlatformInterface.Create(options);
if (EOS == null)
{
throw new System.Exception("Failed to create platform");
}
if (checkForEpicLauncherAndRestart)
{
var result = EOS.CheckForLauncherAndRestart();
// If not started through epic launcher the app will be restarted and we can quit
if (result != Result.NoChange)
{
// Log error if launcher check failed, but still quit to prevent hacking
if (result == Result.UnexpectedError)
{
Debug.LogError("Unexpected Error while checking if app was started through epic launcher");
}
Application.Quit();
}
}
// If we use the Auth interface then only login into the Connect interface after finishing the auth interface login
// If we don't use the Auth interface we can directly login to the Connect interface
if (authInterfaceLogin)
{
if (authInterfaceCredentialType == Epic.OnlineServices.Auth.LoginCredentialType.Developer)
{
authInterfaceLoginCredentialId = "localhost:" + devAuthToolPort;
authInterfaceCredentialToken = devAuthToolCredentialName;
}
// Login to Auth Interface
var loginOptions = new Epic.OnlineServices.Auth.LoginOptions()
{
Credentials = new Epic.OnlineServices.Auth.Credentials()
{
Type = authInterfaceCredentialType,
Id = authInterfaceLoginCredentialId,
Token = authInterfaceCredentialToken
},
ScopeFlags = Epic.OnlineServices.Auth.AuthScopeFlags.BasicProfile | Epic.OnlineServices.Auth.AuthScopeFlags.FriendsList | Epic.OnlineServices.Auth.AuthScopeFlags.Presence
};
EOS.GetAuthInterface().Login(loginOptions, null, OnAuthInterfaceLogin);
}
else
{
// Login to Connect Interface
if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
var createDeviceIdOptions = new Epic.OnlineServices.Connect.CreateDeviceIdOptions();
createDeviceIdOptions.DeviceModel = deviceModel;
EOS.GetConnectInterface().CreateDeviceId(createDeviceIdOptions, null, OnCreateDeviceId);
}
else
{
ConnectInterfaceLogin();
}
}
}
public static void Initialize()
{
if (Instance.initialized || Instance.isConnecting)
{
return;
}
Instance.InitializeImplementation();
}
private void OnAuthInterfaceLogin(Epic.OnlineServices.Auth.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
Debug.Log("Auth Interface Login succeeded");
string accountIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out accountIdString);
if (Result.Success == result)
{
Debug.Log("EOS User ID:" + accountIdString);
localUserAccountIdString = accountIdString;
localUserAccountId = loginCallbackInfo.LocalUserId;
}
ConnectInterfaceLogin();
}
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
Debug.Log("Login returned " + loginCallbackInfo.ResultCode);
}
}
private void OnCreateDeviceId(Epic.OnlineServices.Connect.CreateDeviceIdCallbackInfo createDeviceIdCallbackInfo)
{
if (createDeviceIdCallbackInfo.ResultCode == Result.Success || createDeviceIdCallbackInfo.ResultCode == Result.DuplicateNotAllowed)
{
ConnectInterfaceLogin();
}
else if (Epic.OnlineServices.Common.IsOperationComplete(createDeviceIdCallbackInfo.ResultCode))
{
Debug.Log("Device ID creation returned " + createDeviceIdCallbackInfo.ResultCode);
}
}
private void ConnectInterfaceLogin()
{
var loginOptions = new Epic.OnlineServices.Connect.LoginOptions();
if (connectInterfaceCredentialType == ExternalCredentialType.Epic)
{
Epic.OnlineServices.Auth.Token token;
var result = EOS.GetAuthInterface().CopyUserAuthToken(new Epic.OnlineServices.Auth.CopyUserAuthTokenOptions(), localUserAccountId, out token);
if (result == Result.Success)
{
connectInterfaceCredentialToken = token.AccessToken;
}
else
{
Debug.LogError("Failed to retrieve User Auth Token");
}
}
else if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
loginOptions.UserLoginInfo = new Epic.OnlineServices.Connect.UserLoginInfo();
loginOptions.UserLoginInfo.DisplayName = displayName;
}
loginOptions.Credentials = new Epic.OnlineServices.Connect.Credentials();
loginOptions.Credentials.Type = connectInterfaceCredentialType;
loginOptions.Credentials.Token = connectInterfaceCredentialToken;
EOS.GetConnectInterface().Login(loginOptions, null, OnConnectInterfaceLogin);
}
private void OnConnectInterfaceLogin(Epic.OnlineServices.Connect.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
Debug.Log("Connect Interface Login succeeded");
string productIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out productIdString);
if (Result.Success == result)
{
Debug.Log("EOS User Product ID:" + productIdString);
localUserProductIdString = productIdString;
localUserProductId = loginCallbackInfo.LocalUserId;
}
initialized = true;
isConnecting = false;
var authExpirationOptions = new Epic.OnlineServices.Connect.AddNotifyAuthExpirationOptions();
authExpirationHandle = EOS.GetConnectInterface().AddNotifyAuthExpiration(authExpirationOptions, null, OnAuthExpiration);
}
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
Debug.Log("Login returned " + loginCallbackInfo.ResultCode + "\nRetrying...");
EOS.GetConnectInterface().CreateUser(new Epic.OnlineServices.Connect.CreateUserOptions() { ContinuanceToken = loginCallbackInfo.ContinuanceToken }, null, (Epic.OnlineServices.Connect.CreateUserCallbackInfo cb) =>
{
if (cb.ResultCode != Result.Success)
{
Debug.Log(cb.ResultCode);
return;
}
localUserProductId = cb.LocalUserId;
ConnectInterfaceLogin();
});
}
}
private void OnAuthExpiration(Epic.OnlineServices.Connect.AuthExpirationCallbackInfo authExpirationCallbackInfo)
{
Debug.Log("AuthExpiration callback");
EOS.GetConnectInterface().RemoveNotifyAuthExpiration(authExpirationHandle);
ConnectInterfaceLogin();
}
// Calling tick on a regular interval is required for callbacks to work.
private void LateUpdate()
{
if (EOS != null)
{
platformTickTimer += Time.deltaTime;
if (platformTickTimer >= platformTickIntervalInSeconds)
{
platformTickTimer = 0;
EOS.Tick();
}
}
}
private void OnApplicationQuit()
{
if (EOS != null)
{
EOS.Release();
EOS = null;
PlatformInterface.Shutdown();
}
// Unhook the library in the editor, this makes it possible to load the library again after stopping to play
#if UNITY_EDITOR
if (libraryPointer != IntPtr.Zero) {
Bindings.Unhook();
// Free until the module ref count is 0
while (FreeLibrary(libraryPointer) != 0) { }
libraryPointer = IntPtr.Zero;
}
#endif
}
}