@@ -257,6 +257,7 @@ public static void Init(AudienceConfig config)
257257
258258 FireGameLaunch ( config , consentAtInit , skanRegistered , attributionContext ) ;
259259 TryIdentifySteamUser ( ) ;
260+ TryIdentifyEpicUser ( ) ;
260261
261262 CheckAndFireAttStatusChanged ( config , consentAtInit ) ;
262263
@@ -1214,6 +1215,116 @@ private static bool TryGetFacepunchId(out string? id)
12141215 return true ;
12151216 }
12161217
1218+ // Resolves PlayEveryWare.EpicOnlineServices.EOSManager across install methods.
1219+ // Returns null when the EOS Unity plugin is not present.
1220+ private static System . Type ? ResolveEosManagerType ( ) =>
1221+ System . Type . GetType ( "PlayEveryWare.EpicOnlineServices.EOSManager, PlayEveryWare.EpicOnlineServices" )
1222+ ?? System . Type . GetType ( "PlayEveryWare.EpicOnlineServices.EOSManager, com.playeveryware.eos.core" )
1223+ ?? System . Type . GetType ( "PlayEveryWare.EpicOnlineServices.EOSManager, Assembly-CSharp" ) ;
1224+
1225+ // Gets the initialised PlatformInterface handle from EOSManager.Instance.
1226+ // Returns null when the EOS plugin is absent or EOS has not been initialised.
1227+ private static object ? GetEosPlatformInterface ( )
1228+ {
1229+ var managerType = ResolveEosManagerType ( ) ;
1230+ if ( managerType == null ) return null ;
1231+ // Use the compiled getter name (get_Instance) for IL2CPP compatibility;
1232+ // property metadata can be stripped even when the method body survives.
1233+ var instance = managerType . GetMethod ( "get_Instance" ) ? . Invoke ( null , null )
1234+ ?? managerType . GetProperty ( "Instance" ) ? . GetValue ( null ) ;
1235+ if ( instance == null ) return null ;
1236+ return instance . GetType ( ) . GetMethod ( "GetEOSPlatformInterface" ) ? . Invoke ( instance , null ) ;
1237+ }
1238+
1239+ // Sets distribution_platform = "epic" when the game was launched from the Epic
1240+ // Games Store launcher. Uses command-line args injected by the EGS launcher
1241+ // (-epicenv=, -epicapp=) rather than EOS-being-initialised, which would
1242+ // misattribute Steam games that use EOS purely for cross-play.
1243+ // Config override wins afterward.
1244+ private static void TryDetectEpicPlatform ( Dictionary < string , object > properties )
1245+ {
1246+ try
1247+ {
1248+ if ( IsLaunchedFromEpicGamesStore ( ) )
1249+ properties [ "distribution_platform" ] = DistributionPlatforms . Epic ;
1250+ }
1251+ catch ( Exception ex )
1252+ {
1253+ Log . Warn ( AudienceLogs . EpicPlatformDetectionFailed ( ex ) ) ;
1254+ }
1255+ }
1256+
1257+ // EGS launcher injects these args into every game it launches, regardless of
1258+ // whether the game integrates EOS. Absent when EOS is used for cross-play only.
1259+ // -EpicPortal is the strongest signal (bare flag, no value); -epicapp= and
1260+ // -epicenv= are the environment/artifact identifiers also always present.
1261+ private static bool IsLaunchedFromEpicGamesStore ( )
1262+ {
1263+ var args = Environment . GetCommandLineArgs ( ) ;
1264+ foreach ( var arg in args )
1265+ {
1266+ if ( arg . Equals ( "-EpicPortal" , StringComparison . OrdinalIgnoreCase ) ||
1267+ arg . StartsWith ( "-epicenv=" , StringComparison . OrdinalIgnoreCase ) ||
1268+ arg . StartsWith ( "-epicapp=" , StringComparison . OrdinalIgnoreCase ) )
1269+ return true ;
1270+ }
1271+ return false ;
1272+ }
1273+
1274+ // Calls Identify with the logged-in EOS ProductUserId.
1275+ // No-op if EOS is not present, not initialised, no user is logged in,
1276+ // or consent is below Full.
1277+ private static void TryIdentifyEpicUser ( )
1278+ {
1279+ try
1280+ {
1281+ if ( ! TryGetEpicAccountId ( out var id ) )
1282+ return ;
1283+ Log . Debug ( AudienceLogs . EpicAutoIdentified ( id ! ) ) ;
1284+ Identify ( id ! , IdentityType . Epic ) ;
1285+ }
1286+ catch ( Exception ex )
1287+ {
1288+ Log . Warn ( AudienceLogs . EpicIdentityCollectionFailed ( ex ) ) ;
1289+ }
1290+ }
1291+
1292+ // Reads the EOS EpicAccountId via AuthInterface.GetLoggedInAccountByIndex(0).
1293+ // EpicAccountId is the player's Epic Games Account — consistent across all products.
1294+ // Requires the game to have already initialised EOS via EOSManager.
1295+ private static bool TryGetEpicAccountId ( out string ? id )
1296+ {
1297+ id = null ;
1298+
1299+ // Guard: EOS C# bindings must be present (assembly name varies by install method).
1300+ if ( System . Type . GetType ( "Epic.OnlineServices.Auth.AuthInterface, com.Epic.OnlineServices" ) == null
1301+ && System . Type . GetType ( "Epic.OnlineServices.Auth.AuthInterface, EOSSDK" ) == null )
1302+ return false ;
1303+
1304+ var platformInterface = GetEosPlatformInterface ( ) ;
1305+ if ( platformInterface == null ) return false ;
1306+
1307+ var authInterface = platformInterface . GetType ( )
1308+ . GetMethod ( "GetAuthInterface" ) ? . Invoke ( platformInterface , null ) ;
1309+ if ( authInterface == null ) return false ;
1310+
1311+ // Skip if no accounts are logged in.
1312+ var countResult = authInterface . GetType ( )
1313+ . GetMethod ( "GetLoggedInAccountsCount" ) ? . Invoke ( authInterface , null ) ;
1314+ if ( ! ( countResult is int count && count > 0 ) ) return false ;
1315+
1316+ // C# binding takes a plain int index, not an options struct.
1317+ var epicAccountId = authInterface . GetType ( )
1318+ . GetMethod ( "GetLoggedInAccountByIndex" ) ? . Invoke ( authInterface , new object [ ] { 0 } ) ;
1319+ if ( epicAccountId == null ) return false ;
1320+
1321+ if ( epicAccountId . GetType ( ) . GetMethod ( "IsValid" ) ? . Invoke ( epicAccountId , null ) as bool ? != true )
1322+ return false ;
1323+
1324+ id = epicAccountId . ToString ( ) ;
1325+ return ! string . IsNullOrEmpty ( id ) ;
1326+ }
1327+
12171328 // consentAtInit only gates the launch; Track still checks live _state via CanTrack.
12181329 private static void FireGameLaunch (
12191330 AudienceConfig config ,
@@ -1246,6 +1357,7 @@ private static void FireGameLaunch(
12461357
12471358 // Auto-detect distribution platform via reflection. Config override wins below.
12481359 TryDetectSteamPlatform ( properties ) ;
1360+ TryDetectEpicPlatform ( properties ) ;
12491361
12501362 // Config-supplied distributionPlatform overrides the auto-detected value.
12511363 if ( config . DistributionPlatform != null )
0 commit comments