Skip to content
Merged
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
7 changes: 7 additions & 0 deletions examples/audience/Assets/link.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@ hooks fire under IL2CPP with stripping High.
<type fullname="System.IO.Compression.GZipStream" preserve="all" />
<type fullname="System.IO.Compression.CompressionLevel" preserve="all" />
</assembly>

<!-- Steam auto-detection; mirrors SDK link.xml. Ignored if Steamworks is not present. -->
<assembly fullname="com.rlabrecque.steamworks.net" preserve="all" />
<assembly fullname="Steamworks.NET" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Posix" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win64" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win32" preserve="all" />
</linker>
130 changes: 129 additions & 1 deletion src/Packages/Audience/Runtime/ImmutableAudience.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ public static void Init(AudienceConfig config)
}

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

CheckAndFireAttStatusChanged(config, consentAtInit);

Expand Down Expand Up @@ -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<string, object> 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,
Expand Down Expand Up @@ -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;

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 @@ -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.";
}
}
12 changes: 12 additions & 0 deletions src/Packages/Audience/link.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,16 @@ framework dependency.
<type fullname="System.IO.Compression.GZipStream" preserve="all" />
<type fullname="System.IO.Compression.CompressionLevel" preserve="all" />
</assembly>

<!--
Steam platform auto-detection. The SDK resolves these types via reflection
at runtime so the linker cannot see the dependency and strips them under
IL2CPP High stripping. Entries for missing assemblies are silently ignored,
so games without Steamworks are unaffected.
-->
<assembly fullname="com.rlabrecque.steamworks.net" preserve="all" />
<assembly fullname="Steamworks.NET" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Posix" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win64" preserve="all" />
<assembly fullname="Facepunch.Steamworks.Win32" preserve="all" />
</linker>
Loading