@ -38,11 +38,10 @@ public class EpicVoiceChatTest : MonoBehaviour
// This also allows multiple clients to connect to the same lobby without having to communicate the ID among each other.
private const string DebugLobbyId = "a0f6a51f-6b61-4c95-9ed8-a1d508fe4eb6" ;
[SerializeField]
private string devAuthAddress ;
[SerializeField] private string devAuthAddress ;
private readonly StringBuilder status = new StringBuilder ( "Status:\n" ) ;
private PlatformInterface platformInterface ;
private AuthInterface authInterface ;
private ConnectInterface connectInterface ;
@ -64,7 +63,7 @@ public class EpicVoiceChatTest : MonoBehaviour
#else
Path . Combine ( Application . dataPath , @"Plugins\x86_64\xaudio2_9redist.dll" ) ;
#endif
IEnumerator Start ( )
{
#if UNITY_EDITOR && UNITY_STANDALONE
@ -76,7 +75,7 @@ public class EpicVoiceChatTest : MonoBehaviour
Debug . LogError ( $"Error initializing game runtime, hresult = 0x{hresult:X}" ) ;
yield break ;
}
Debug . Log ( "GXDK game runtime initialized." ) ;
hresult = SDK . XBL . XblInitialize ( GameCoreSettings . SCID ) ;
@ -125,8 +124,8 @@ public class EpicVoiceChatTest : MonoBehaviour
#if !UNITY_STANDALONE
EOSNativeHelper . GetMemoryFunctions ( out var allocFunc , out var reallocFunc , out var releaseFunc ) ;
#endif
var result = PlatformInterface . Initialize ( new InitializeOptions
var initOptions = new InitializeOptions
{
ProductName = "WW1Test" ,
ProductVersion = "1.0.0.0" ,
@ -136,18 +135,19 @@ public class EpicVoiceChatTest : MonoBehaviour
ReallocateMemoryFunction = reallocFunc ,
ReleaseMemoryFunction = releaseFunc ,
#endif
} ) ;
} ;
var result = PlatformInterface . Initialize ( ref initOptions ) ;
if ( result ! = Result . Success )
{
Debug . LogError ( "Failed to initialize EOS, result = " + result ) ;
status . AppendLine ( "EOS initialization failed..." ) ;
yield break ;
}
LoggingInterface . SetLogLevel ( LogCategory . AllCategories , LogLevel . Warning ) ;
LoggingInterface . SetCallback ( OnEOSLogMessage ) ;
#if UNITY_STANDALONE_WIN
Debug . Log ( "XAudio library path: " + XAudio29DllPath ) ;
@ -184,25 +184,25 @@ public class EpicVoiceChatTest : MonoBehaviour
ProductId = "b21a28c2c5404c8099d72f5a28c59c16" ,
SandboxId = "b630f2c3933a4838a971ce53d5b0db3b" ,
DeploymentId = "9a36f589572c492fbee14bd299173c12" ,
ClientCredentials = new ClientCredentials
{
ClientId = "xyza7891UOFoUhfvfbKgO2xRCIiuAIjH" ,
ClientSecret = "NArwIQT1laFfsS7fdcN1MKDdgwy490w9MBJsAlHN4QI" ,
} ,
Flags = PlatformFlags . DisableOverlay ,
CacheDirectory = null ,
EncryptionKey = null ,
IsServer = false ,
RTCOptions = new RTCOptions ( ) ,
} ;
#endif
platformInterface = PlatformInterface . Create ( options ) ;
platformInterface = PlatformInterface . Create ( ref options ) ;
if ( platformInterface = = null )
{
@ -210,32 +210,36 @@ public class EpicVoiceChatTest : MonoBehaviour
status . AppendLine ( "Failed to create EOS platform interface" ) ;
yield break ;
}
Debug . Log ( "EOS platform interface successfully created!" ) ;
status . AppendLine ( "EOS platform interface created" ) ;
authInterface = platformInterface . GetAuthInterface ( ) ;
status . AppendLine ( "Auth interface: " + authInterface ) ;
connectInterface = platformInterface . GetConnectInterface ( ) ;
status . AppendLine ( "Connect interface: " + connectInterface ) ;
lobbyInterface = platformInterface . GetLobbyInterface ( ) ;
status . AppendLine ( "Lobby interface: " + lobbyInterface ) ;
rtcInterface = platformInterface . GetRTCInterface ( ) ; // Real-time communication, needed for audio
rtcInterface = platformInterface . GetRTCInterface ( ) ; // Real-time communication, needed for audio
status . AppendLine ( "RTC interface: " + rtcInterface ) ;
audioInterface = rtcInterface . GetAudioInterface ( ) ;
status . AppendLine ( "Audio interface: " + audioInterface ) ;
voiceChat = new EOSVoiceChat ( lobbyInterface , rtcInterface , audioInterface , ( ) = > localProductUserId ) ;
voiceChat . OnChatConnected + = ( ) = > status . AppendLine ( "Chat lobby successfully connected!" ) ;
voiceChat . OnChatConnectionFailed + = ( ) = > status . AppendLine ( "Chat lobby connection failed..." ) ;
voiceChat . OnChatDisconnected + = ( ) = > status . AppendLine ( "Chat lobby disconnected" ) ;
voiceChat . OnChatUserJoined + = userId = > status . AppendLine ( $"Chat user {userId} joined" ) ;
voiceChat . OnChatUserJoined + = userId = >
{
status . AppendLine ( $"Chat user {userId} joined" ) ;
HandleChatUserJoined ( userId ) ;
} ;
voiceChat . OnChatUserLeft + = userId = > status . AppendLine ( $"Chat user {userId} left" ) ;
#if UNITY_GAMECORE
SDK . XUserAddAsync ( XUserAddOptions . AddDefaultUserAllowingUI , ( hr , userHandle ) = >
{
@ -246,21 +250,25 @@ public class EpicVoiceChatTest : MonoBehaviour
return ;
}
status . AppendLine ( "Xbox user added" ) ;
SDK . XUserGetId ( userHandle , out ulong xuid ) ;
Debug . Log ( $"Xbox user added with XUID: {xuid}" ) ;
status . AppendLine ( $"Xbox user added" ) ;
SDK . XUserGetTokenAndSignatureUtf16Async ( userHandle , XUserGetTokenAndSignatureOptions . None ,
"GET" , "https://api.epicgames.dev/" , null , null , HandleTokenAndSignature ) ;
} ) ;
yield return new WaitUntil ( ( ) = > ! string . IsNullOrEmpty ( xstsToken ) ) ;
connectInterface . Login ( new Epic . OnlineServices . Connect . LoginOptions
var loginOptions = new Epic . OnlineServices . Connect . LoginOptions
{
Credentials = new Epic . OnlineServices . Connect . Credentials
{
Type = ExternalCredentialType . XblXstsToken ,
Token = xstsToken ,
} ,
} , null , HandleConnectResult ) ;
} ;
connectInterface . Login ( ref loginOptions , null , HandleConnectResult ) ;
#elif UNITY_PS4 || UNITY_PS5
var loggedInUser = PSInput . RefreshUsersDetails ( 0 ) ;
@ -292,23 +300,25 @@ public class EpicVoiceChatTest : MonoBehaviour
yield return new WaitUntil ( ( ) = > ! string . IsNullOrEmpty ( psnIdToken ) ) ;
connectInterface . Login ( new Epic . OnlineServices . Connect . LoginOptions
var loginOptions = new Epic . OnlineServices . Connect . LoginOptions
{
Credentials = new Epic . OnlineServices . Connect . Credentials
{
Type = ExternalCredentialType . PsnIdToken ,
Token = psnIdToken ,
} ,
} , null , HandleConnectResult ) ;
} ;
connectInterface . Login ( ref loginOptions , null , HandleConnectResult ) ;
#else
authInterface . Login ( new LoginOptions
var loginOptions = new LoginOptions
{
Credentials = GetEpicCredentials ( ) ,
ScopeFlags = AuthScopeFlags . BasicProfile ,
} , null , HandleLoginResult ) ;
} ;
authInterface . Login ( ref loginOptions , null , HandleLoginResult ) ;
#endif
}
#if UNITY_GAMECORE
private void HandleTokenAndSignature ( int hresult , XUserGetTokenAndSignatureUtf16Data tokenAndSignature )
{
@ -318,13 +328,13 @@ public class EpicVoiceChatTest : MonoBehaviour
status . AppendLine ( "Xbox Live authentication failed..." ) ;
return ;
}
Debug . Log ( $"Xbox Live authenticated, XSTS token = {tokenAndSignature.Token}" ) ;
status . AppendLine ( "Xbox Live successfully authenticated" ) ;
xstsToken = tokenAndSignature . Token ;
}
#endif
#if UNITY_STANDALONE
private Credentials GetEpicCredentials ( ) // This is platform-specific (actually it's not, we can skip this on consoles)
{
@ -344,7 +354,7 @@ public class EpicVoiceChatTest : MonoBehaviour
} ;
}
private void HandleLoginResult ( LoginCallbackInfo data )
private void HandleLoginResult ( ref LoginCallbackInfo data )
{
switch ( data . ResultCode )
{
@ -352,18 +362,27 @@ public class EpicVoiceChatTest : MonoBehaviour
localEpicAccountId = data . LocalUserId ;
Debug . Log ( "EOS login successful: " + localEpicAccountId ) ;
status . AppendLine ( "EOS login successful: " + localEpicAccountId ) ;
var copyTokenOptions = new CopyUserAuthTokenOptions ( ) ;
authInterface . CopyUserAuthToken ( ref copyTokenOptions , localEpicAccountId , out Token ? token ) ;
if ( ! token . HasValue )
{
Debug . Log ( "EOS login failed, invalid token!" ) ;
status . AppendLine ( "EOS login failed, invalid token!" ) ;
break ;
}
authInterface . CopyUserAuthToken ( new CopyUserAuthTokenOptions ( ) , localEpicAccountId , out Token token ) ;
Debug . Log ( $"User auth access token: {token.AccessToken}" ) ;
connectInterface . Login ( new Epic . OnlineServices . Connect . LoginOptions
Debug . Log ( $"User auth access token: {token.Value.AccessToken}" ) ;
var loginOptions = new Epic . OnlineServices . Connect . LoginOptions
{
Credentials = new Epic . OnlineServices . Connect . Credentials
{
Type = ExternalCredentialType . Epic , // Can be XSTS or PSN ID as well, platform-specific
Token = token . AccessToken ,
Token = token . Value . AccessToken ,
} ,
} , null , HandleConnectResult ) ;
} ;
connectInterface . Login ( ref loginOptions , null , HandleConnectResult ) ;
break ;
default :
Debug . Log ( "EOS login failed, result code = " + data . ResultCode ) ;
@ -373,7 +392,7 @@ public class EpicVoiceChatTest : MonoBehaviour
}
#endif
private void HandleConnectResult ( Epic . OnlineServices . Connect . LoginCallbackInfo data )
private void HandleConnectResult ( ref Epic . OnlineServices . Connect . LoginCallbackInfo data )
{
switch ( data . ResultCode )
{
@ -381,18 +400,16 @@ public class EpicVoiceChatTest : MonoBehaviour
localProductUserId = data . LocalUserId ;
Debug . Log ( "Connect successful: " + localProductUserId ) ;
status . AppendLine ( "Connect successful: " + localProductUserId ) ;
CreateOrJoinVoiceLobby ( ) ;
break ;
case Result . InvalidUser :
Debug . Log ( "Invalid user, creating user..." ) ;
status . AppendLine ( "Invalid user, creating user..." ) ;
connectInterface . CreateUser ( new CreateUserOptions
{
ContinuanceToken = data . ContinuanceToken ,
} , null , HandleUserCreated ) ;
var createUserOptions = new CreateUserOptions { ContinuanceToken = data . ContinuanceToken } ;
connectInterface . CreateUser ( ref createUserOptions , null , HandleUserCreated ) ;
break ;
default :
Debug . Log ( "Connect failed, result code = " + data . ResultCode ) ;
@ -401,7 +418,7 @@ public class EpicVoiceChatTest : MonoBehaviour
}
}
private void HandleUserCreated ( CreateUserCallbackInfo data )
private void HandleUserCreated ( ref CreateUserCallbackInfo data )
{
switch ( data . ResultCode )
{
@ -409,7 +426,7 @@ public class EpicVoiceChatTest : MonoBehaviour
localProductUserId = data . LocalUserId ;
Debug . Log ( "User creation successful: " + localProductUserId ) ;
status . AppendLine ( "User creation successful: " + localProductUserId ) ;
CreateOrJoinVoiceLobby ( ) ;
break ;
default :
@ -428,7 +445,7 @@ public class EpicVoiceChatTest : MonoBehaviour
{
if ( platformInterface ! = null )
platformInterface . Tick ( ) ;
#if UNITY_GAMECORE
SDK . XTaskQueueDispatch ( ) ;
#elif UNITY_PS4 || UNITY_PS5
@ -449,9 +466,9 @@ public class EpicVoiceChatTest : MonoBehaviour
platformInterface . Release ( ) ;
platformInterface = null ;
}
PlatformInterface . Shutdown ( ) ;
#if UNITY_EDITOR && UNITY_STANDALONE
UnloadLibrary ( ) ;
#endif
@ -464,7 +481,8 @@ public class EpicVoiceChatTest : MonoBehaviour
{
// EOS SDK 1.13+ uses dynamic library binding in the Editor but does not provide any system functions to actually load dynamic libraries,
// so we need to provide those ourselves.
eosLbraryHandle = EOSNativeHelper . LoadLibrary ( $@"Assets\Plugins\EpicOnlineServices\Bin\{Config.LibraryName}.dll" ) ;
eosLbraryHandle =
EOSNativeHelper . LoadLibrary ( $@"Assets\Plugins\EpicOnlineServices\Bin\{Config.LibraryName}.dll" ) ;
if ( eosLbraryHandle = = IntPtr . Zero )
{
throw new Exception ( "Could not load EOS library!" ) ;
@ -490,7 +508,7 @@ public class EpicVoiceChatTest : MonoBehaviour
}
#endif
private void OnEOSLogMessage ( LogMessage message )
private void OnEOSLogMessage ( ref LogMessage message )
{
switch ( message . Level )
{
@ -515,7 +533,93 @@ public class EpicVoiceChatTest : MonoBehaviour
float screenScale = screenHeight / 7 2 0f ;
GUI . matrix = Matrix4x4 . Scale ( new Vector3 ( screenScale , screenScale , 1 ) ) ;
GUILayout . Label ( status . ToString ( ) ) ;
}
private void HandleChatUserJoined ( ProductUserId productUserId )
{
if ( connectInterface = = null | | localProductUserId = = null )
return ;
Debug . Log ( $"Chat product user ID {productUserId} joined, querying account mapping..." ) ;
// We need to know how to map from remote account GUIDs to Epic's ProductUserId so we can query other players' chat status
var queryMappingsOptions = new QueryProductUserIdMappingsOptions
{
LocalUserId = localProductUserId ,
ProductUserIds = new [ ] { productUserId } ,
} ;
connectInterface . QueryProductUserIdMappings ( ref queryMappingsOptions , productUserId , HandleProductUserIdMappings ) ;
var queryExternalOptions = new QueryExternalAccountMappingsOptions
{
LocalUserId = localProductUserId ,
AccountIdType = ExternalAccountType . Xbl ,
ExternalAccountIds = new Utf8String [ ] { "2814644373145756" , "2814654765419620" } ,
} ;
connectInterface . QueryExternalAccountMappings ( ref queryExternalOptions , null , HandleExternalAccountMappings ) ;
}
private void HandleExternalAccountMappings ( ref QueryExternalAccountMappingsCallbackInfo data )
{
switch ( data . ResultCode )
{
case Result . Success :
foreach ( var accountId in
new [ ] { "2814644373145756" , "2814654765419620" } )
{
var getMappingOptions = new GetExternalAccountMappingsOptions
{
AccountIdType = ExternalAccountType . Xbl ,
LocalUserId = localProductUserId ,
TargetExternalUserId = accountId
} ;
var productUserId = connectInterface . GetExternalAccountMapping ( ref getMappingOptions ) ;
if ( productUserId ! = null )
{
Debug . Log ( $"Mapped XBL account ID {accountId} to product user ID: {productUserId}" ) ;
}
else
{
Debug . Log ( $"Failed to map XBL account ID {accountId} to any product user ID" ) ;
}
}
break ;
default :
Debug . LogError ( $"External account mapping query failed with result code: {data.ResultCode}" ) ;
break ;
}
}
private readonly Dictionary < string , ProductUserId > steamAccountMappings = new Dictionary < string , ProductUserId > ( ) ;
private readonly Dictionary < string , ProductUserId > epicAccountMappings = new Dictionary < string , ProductUserId > ( ) ;
private readonly Dictionary < string , ProductUserId > xblAccountMappings = new Dictionary < string , ProductUserId > ( ) ;
private readonly Dictionary < string , ProductUserId > psnAccountMappings = new Dictionary < string , ProductUserId > ( ) ;
private void HandleProductUserIdMappings ( ref QueryProductUserIdMappingsCallbackInfo data )
{
switch ( data . ResultCode )
{
case Result . Success :
var productUserId = ( ProductUserId ) data . ClientData ;
var copyOptions = new CopyProductUserInfoOptions { TargetUserId = productUserId } ;
var result = connectInterface . CopyProductUserInfo ( ref copyOptions , out var externalAccountInfo ) ;
if ( result = = Result . Success )
{
Debug . Log ( $"External account info for product user {productUserId}: type = {externalAccountInfo?.AccountIdType}, account id = {externalAccountInfo?.AccountId}, display name = {externalAccountInfo?.DisplayName}" ) ;
}
else
{
Debug . LogError ( $"Failed to query external account info for product user {productUserId}" ) ;
}
break ;
default :
Debug . LogError ( $"Product user ID mapping query failed with result code: {data.ResultCode}" ) ;
break ;
}
}
}