diff --git a/examples/audience/Assets/link.xml b/examples/audience/Assets/link.xml
index 05aa338c..c90feed3 100644
--- a/examples/audience/Assets/link.xml
+++ b/examples/audience/Assets/link.xml
@@ -27,4 +27,11 @@ hooks fire under IL2CPP with stripping High.
+
+
+
+
+
+
+
diff --git a/src/Packages/Audience/Runtime/ImmutableAudience.cs b/src/Packages/Audience/Runtime/ImmutableAudience.cs
index 0df90fc3..14c1ba4c 100644
--- a/src/Packages/Audience/Runtime/ImmutableAudience.cs
+++ b/src/Packages/Audience/Runtime/ImmutableAudience.cs
@@ -256,6 +256,7 @@ public static void Init(AudienceConfig config)
}
FireGameLaunch(config, consentAtInit, skanRegistered, attributionContext);
+ TryIdentifySteamUser();
CheckAndFireAttStatusChanged(config, consentAtInit);
@@ -1089,6 +1090,130 @@ private static void RescheduleSendTimer(HttpTransport transport)
timer.Change(nextMs, sendIntervalMs);
}
+ // Resolves Steamworks.SteamAPI (Steamworks.NET) across all common install methods:
+ // UPM / OpenUPM package → com.rlabrecque.steamworks.net
+ // .dll plugin in Assets → Steamworks.NET
+ // source files in Assets → Assembly-CSharp
+ // Returns null when Steamworks.NET is not present.
+ private static System.Type? ResolveSteamApiType() =>
+ System.Type.GetType("Steamworks.SteamAPI, com.rlabrecque.steamworks.net")
+ ?? System.Type.GetType("Steamworks.SteamAPI, Steamworks.NET")
+ ?? System.Type.GetType("Steamworks.SteamAPI, Assembly-CSharp");
+
+ // Resolves Steamworks.SteamClient (Facepunch.Steamworks) across common install methods.
+ // Facepunch ships platform-specific DLLs; the assembly name encodes the platform:
+ // macOS/Linux → Facepunch.Steamworks.Posix
+ // Windows 64 → Facepunch.Steamworks.Win64
+ // Windows 32 → Facepunch.Steamworks.Win32
+ // Source in Assets → Assembly-CSharp
+ // Returns null when Facepunch.Steamworks is not present.
+ private static System.Type? ResolveFacepunchSteamClientType() =>
+ System.Type.GetType("Steamworks.SteamClient, Facepunch.Steamworks.Posix")
+ ?? System.Type.GetType("Steamworks.SteamClient, Facepunch.Steamworks.Win64")
+ ?? System.Type.GetType("Steamworks.SteamClient, Facepunch.Steamworks.Win32")
+ ?? System.Type.GetType("Steamworks.SteamClient, Assembly-CSharp");
+
+ // Sets distribution_platform = "steam" when any supported Steam wrapper is
+ // detected as active. Config override wins afterward (line ~1195).
+ private static void TryDetectSteamPlatform(Dictionary properties)
+ {
+ try
+ {
+ if (IsSteamworksNetRunning() || IsFacepunchSteamValid())
+ properties["distribution_platform"] = DistributionPlatforms.Steam;
+ }
+ catch (Exception ex)
+ {
+ Log.Warn(AudienceLogs.SteamPlatformDetectionFailed(ex));
+ }
+ }
+
+ // Calls Identify with the logged-in SteamID64. Tries Steamworks.NET first,
+ // then Facepunch.Steamworks. No-ops if neither is present, Steam is not
+ // running, the ID is invalid, or consent is below Full.
+ private static void TryIdentifySteamUser()
+ {
+ try
+ {
+ if (!TryGetSteamworksNetId(out var id) && !TryGetFacepunchId(out id))
+ return;
+ Log.Debug(AudienceLogs.SteamAutoIdentified(id!));
+ Identify(id!, IdentityType.Steam);
+ }
+ catch (Exception ex)
+ {
+ Log.Warn(AudienceLogs.SteamIdentityCollectionFailed(ex));
+ }
+ }
+
+ // Returns true if Steamworks.NET's SteamAPI.IsSteamRunning() is true.
+ private static bool IsSteamworksNetRunning()
+ {
+ var steamApi = ResolveSteamApiType();
+ if (steamApi == null) return false;
+ return steamApi.GetMethod("IsSteamRunning")?.Invoke(null, null) as bool? == true;
+ }
+
+ // Returns true if Facepunch.Steamworks's SteamClient.IsValid is true.
+ private static bool IsFacepunchSteamValid()
+ {
+ var steamClient = ResolveFacepunchSteamClientType();
+ if (steamClient == null) return false;
+ return steamClient.GetProperty("IsValid")?.GetValue(null) as bool? == true;
+ }
+
+ // Reads the SteamID64 via Steamworks.NET (SteamUser.GetSteamID → m_SteamID ulong).
+ // Attempts SteamAPI.Init() first so the sample app works without manual Steam init;
+ // in shipping games Init() is already called before our SDK runs.
+ private static bool TryGetSteamworksNetId(out string? id)
+ {
+ id = null;
+ var steamApi = ResolveSteamApiType();
+ if (steamApi == null) return false;
+ if (steamApi.GetMethod("IsSteamRunning")?.Invoke(null, null) as bool? != true) return false;
+
+ // Safe to call even if already initialised; returns false but is otherwise a no-op.
+ steamApi.GetMethod("Init")?.Invoke(null, null);
+
+ var steamUserType =
+ System.Type.GetType("Steamworks.SteamUser, com.rlabrecque.steamworks.net")
+ ?? System.Type.GetType("Steamworks.SteamUser, Steamworks.NET")
+ ?? System.Type.GetType("Steamworks.SteamUser, Assembly-CSharp");
+ if (steamUserType == null) return false;
+
+ var steamId = steamUserType.GetMethod("GetSteamID")?.Invoke(null, null);
+ if (steamId == null) return false;
+
+ // CSteamID.m_SteamID == 0 means not logged in / not initialised.
+ var raw = steamId.GetType().GetField("m_SteamID")?.GetValue(steamId);
+ if (raw == null || (ulong)raw == 0) return false;
+
+ id = ((ulong)raw).ToString();
+ return true;
+ }
+
+ // Reads the SteamID64 via Facepunch.Steamworks (SteamClient.SteamId.Value ulong).
+ // Requires SteamClient.Init(appId) to have been called by the game already;
+ // the SDK cannot call it as the appId is unknown.
+ private static bool TryGetFacepunchId(out string? id)
+ {
+ id = null;
+ var steamClient = ResolveFacepunchSteamClientType();
+ if (steamClient == null) return false;
+ if (steamClient.GetProperty("IsValid")?.GetValue(null) as bool? != true) return false;
+
+ var steamIdProp = steamClient.GetProperty("SteamId");
+ var steamId = steamIdProp?.GetValue(null);
+ if (steamId == null) return false;
+
+ // Facepunch SteamId exposes Value as a public ulong field (not a property).
+ var raw = steamId.GetType().GetField("Value")?.GetValue(steamId);
+ if (raw == null || (ulong)raw == 0) return false;
+
+ id = ((ulong)raw).ToString();
+ return true;
+ }
+
// consentAtInit only gates the launch; Track still checks live _state via CanTrack.
private static void FireGameLaunch(
AudienceConfig config,
@@ -1119,7 +1244,10 @@ private static void FireGameLaunch(
}
}
- // Config-supplied distributionPlatform overrides the provider value.
+ // Auto-detect distribution platform via reflection. Config override wins below.
+ TryDetectSteamPlatform(properties);
+
+ // Config-supplied distributionPlatform overrides the auto-detected value.
if (config.DistributionPlatform != null)
properties["distribution_platform"] = config.DistributionPlatform;
diff --git a/src/Packages/Audience/Runtime/Utility/Log.cs b/src/Packages/Audience/Runtime/Utility/Log.cs
index 772f3aee..8fa5eb43 100644
--- a/src/Packages/Audience/Runtime/Utility/Log.cs
+++ b/src/Packages/Audience/Runtime/Utility/Log.cs
@@ -158,5 +158,18 @@ internal static string ATTIDFAProviderThrew(Exception ex) =>
internal static string GAIDFetchThrew(Exception ex) =>
$"GAID fetch threw {ex.GetType().Name}: {ex.Message}. " +
"gaid will not ship on game_launch this session; next launch retries.";
+
+ // ---- Steam auto-detection ----
+
+ internal static string SteamPlatformDetectionFailed(Exception ex) =>
+ $"Steam platform detection threw {ex.GetType().Name}: {ex.Message}. " +
+ "distribution_platform will not be auto-set.";
+
+ internal static string SteamAutoIdentified(string steamId) =>
+ $"auto-identified steam user: {steamId}";
+
+ internal static string SteamIdentityCollectionFailed(Exception ex) =>
+ $"Steam identity collection threw {ex.GetType().Name}: {ex.Message}. " +
+ "Steam user ID will not be auto-collected.";
}
}
diff --git a/src/Packages/Audience/link.xml b/src/Packages/Audience/link.xml
index 5f9428bb..38cd8398 100644
--- a/src/Packages/Audience/link.xml
+++ b/src/Packages/Audience/link.xml
@@ -29,4 +29,16 @@ framework dependency.
+
+
+
+
+
+
+