Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/audience/Assets/link.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ hooks fire under IL2CPP with stripping High.
<assembly fullname="Facepunch.Steamworks.Posix" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win64" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win32" preserve="all" />

<!-- Epic auto-detection; mirrors SDK link.xml. Ignored if EOS plugin is not present. -->
<assembly fullname="EOSSDK" preserve="all" />
<assembly fullname="PlayEveryWare.EpicOnlineServices" preserve="all" />
</linker>
94 changes: 94 additions & 0 deletions src/Packages/Audience/Runtime/ImmutableAudience.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ public static void Init(AudienceConfig config)

FireGameLaunch(config, consentAtInit, skanRegistered, attributionContext);
TryIdentifySteamUser();
TryIdentifyEpicUser();

CheckAndFireAttStatusChanged(config, consentAtInit);

Expand Down Expand Up @@ -1214,6 +1215,98 @@ private static bool TryGetFacepunchId(out string? id)
return true;
}

// Resolves PlayEveryWare.EpicOnlineServices.EOSManager across install methods.
// Returns null when the EOS Unity plugin is not present.
private static System.Type? ResolveEosManagerType() =>
System.Type.GetType("PlayEveryWare.EpicOnlineServices.EOSManager, PlayEveryWare.EpicOnlineServices")
?? System.Type.GetType("PlayEveryWare.EpicOnlineServices.EOSManager, Assembly-CSharp");

// Gets the initialised PlatformInterface handle from EOSManager.Instance.
// Returns null when the EOS plugin is absent or EOS has not been initialised.
private static object? GetEosPlatformInterface()
{
var managerType = ResolveEosManagerType();
if (managerType == null) return null;
// Use the compiled getter name (get_Instance) for IL2CPP compatibility;
// property metadata can be stripped even when the method body survives.
var instance = managerType.GetMethod("get_Instance")?.Invoke(null, null)
?? managerType.GetProperty("Instance")?.GetValue(null);
if (instance == null) return null;
return instance.GetType().GetMethod("GetEOSPlatformInterface")?.Invoke(instance, null);
}

// Sets distribution_platform = "epic" when EOS is active at launch.
// Config override wins afterward.
private static void TryDetectEpicPlatform(Dictionary<string, object> properties)
{
try
{
if (GetEosPlatformInterface() != null)
properties["distribution_platform"] = DistributionPlatforms.Epic;
}
catch (Exception ex)
{
Log.Warn(AudienceLogs.EpicPlatformDetectionFailed(ex));
}
}

// Calls Identify with the logged-in EOS ProductUserId.
// No-op if EOS is not present, not initialised, no user is logged in,
// or consent is below Full.
private static void TryIdentifyEpicUser()
{
try
{
if (!TryGetEpicAccountId(out var id))
return;
Log.Debug(AudienceLogs.EpicAutoIdentified(id!));
Identify(id!, IdentityType.Epic);
}
catch (Exception ex)
{
Log.Warn(AudienceLogs.EpicIdentityCollectionFailed(ex));
}
}

// Reads the EOS ProductUserId via ConnectInterface.GetLoggedInUserByIndex(0).
// Requires the game to have already initialised EOS via EOSManager.
private static bool TryGetEpicAccountId(out string? id)
{
id = null;

// Guard: EOSSDK types must be present.
if (System.Type.GetType("Epic.OnlineServices.Connect.ConnectInterface, EOSSDK") == null)
return false;

var platformInterface = GetEosPlatformInterface();
if (platformInterface == null) return false;

var connectInterface = platformInterface.GetType()
.GetMethod("GetConnectInterface")?.Invoke(platformInterface, null);
if (connectInterface == null) return false;

// Skip if no users are logged in.
var countResult = connectInterface.GetType()
.GetMethod("GetLoggedInUsersCount")?.Invoke(connectInterface, null);
if (!(countResult is int count && count > 0)) return false;

var optionsType = System.Type.GetType(
"Epic.OnlineServices.Connect.GetLoggedInUserByIndexOptions, EOSSDK");
if (optionsType == null) return false;

// Default-constructed options: UserIndex = 0 (first logged-in user).
var options = Activator.CreateInstance(optionsType);
var productUserId = connectInterface.GetType()
.GetMethod("GetLoggedInUserByIndex")?.Invoke(connectInterface, new[] { options });
if (productUserId == null) return false;

if (productUserId.GetType().GetMethod("IsValid")?.Invoke(productUserId, null) as bool? != true)
return false;

id = productUserId.ToString();
return !string.IsNullOrEmpty(id);
}

// consentAtInit only gates the launch; Track still checks live _state via CanTrack.
private static void FireGameLaunch(
AudienceConfig config,
Expand Down Expand Up @@ -1246,6 +1339,7 @@ private static void FireGameLaunch(

// Auto-detect distribution platform via reflection. Config override wins below.
TryDetectSteamPlatform(properties);
TryDetectEpicPlatform(properties);

// Config-supplied distributionPlatform overrides the auto-detected value.
if (config.DistributionPlatform != null)
Expand Down
13 changes: 13 additions & 0 deletions src/Packages/Audience/Runtime/Utility/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,18 @@ internal static string SteamAutoIdentified(string steamId) =>
internal static string SteamIdentityCollectionFailed(Exception ex) =>
$"Steam identity collection threw {ex.GetType().Name}: {ex.Message}. " +
"Steam user ID will not be auto-collected.";

// ---- Epic auto-detection ----

internal static string EpicPlatformDetectionFailed(Exception ex) =>
$"Epic platform detection threw {ex.GetType().Name}: {ex.Message}. " +
"distribution_platform will not be auto-set.";

internal static string EpicAutoIdentified(string epicId) =>
$"auto-identified epic user: {epicId}";

internal static string EpicIdentityCollectionFailed(Exception ex) =>
$"Epic identity collection threw {ex.GetType().Name}: {ex.Message}. " +
"Epic user ID will not be auto-collected.";
}
}
7 changes: 7 additions & 0 deletions src/Packages/Audience/link.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ framework dependency.
<assembly fullname="Facepunch.Steamworks.Posix" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win64" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win32" preserve="all" />

<!--
Epic platform auto-detection. The SDK resolves these types via reflection
at runtime; entries for missing assemblies are silently ignored.
-->
<assembly fullname="EOSSDK" preserve="all" />
<assembly fullname="PlayEveryWare.EpicOnlineServices" preserve="all" />
</linker>
Loading