Files
recnet-plugin/Patches/UnityTelemetryPatch.cs
2026-08-05 12:44:31 -04:00

79 lines
4.1 KiB
C#

using System;
namespace RecNetPlugin.Patches;
// Unity's own telemetry — the uploads to perf-events.cloud.unity3d.com.
//
// !! THIS DOES NOT WORK on the 20230414 build, and that is a known, accepted limitation — don't spend
// another afternoon on it. Confirmed at runtime: the setters below are refused, `enabled` reads back
// True on all five attempts, and perf-events uploads keep flowing. It's left in because it costs
// nothing, is the correct thing to do if a future build stops refusing, and the read-back logs the
// truth either way rather than pretending. The parts of `Disable Telemetry` that carry the actual win
// are Amplitude, the collector and Backtrace — all confirmed dropping — and they take out the bulk of
// the noise. If perf-events ever has to go for real, it needs a hosts-file/DNS block or a native hook;
// there is no managed lever.
//
// There is nothing to hook here, and that's the point: `UnityEngine.Analytics.Analytics` and
// `PerformanceReporting` are thin managed shims over native engine code, and the uploads happen inside
// the player, not on any managed send path a Harmony prefix could sit on. Blocking this one at the HTTP
// layer is equally hopeless — it never touches BestHTTP or UnityWebRequest. What it *does* have is a
// documented opt-out, so we flip the switches at startup and read them back.
//
// Performance Reporting is the exception/crash reporter, Analytics is the event stream; both feed
// perf-events, so both go off. `limitUserTracking` and `deviceStatsEnabled` cover the case where
// something re-enables the event stream behind our back — with those set, what it can collect is
// nothing worth sending.
internal static class UnityTelemetryPatch
{
// Applied from Plugin.Load and again on each scene load until it sticks — these are native
// properties whose setters can be refused (service not initialised yet, build flags), so
// "set it once at load and assume" is exactly how this silently does nothing.
private const int MaxAttempts = 5;
private static bool _done;
private static int _attempts;
public static void Apply()
{
if (_done || !Plugin.DisableTelemetry.Value || _attempts >= MaxAttempts)
return;
_attempts++;
try
{
UnityEngine.Analytics.PerformanceReporting.enabled = false;
UnityEngine.Analytics.Analytics.enabled = false;
UnityEngine.Analytics.Analytics.deviceStatsEnabled = false;
UnityEngine.Analytics.Analytics.limitUserTracking = true;
}
catch (Exception e)
{
// No _done: a later scene load gets another go, up to MaxAttempts.
if (_attempts >= MaxAttempts)
Plugin.Log.LogWarning($"[UNITY-TELEMETRY] gave up flipping the opt-out switches after {_attempts} attempts: {e.Message}");
return;
}
// The read-back is the proof, not the assignment above. A refused setter is silent.
var perf = UnityEngine.Analytics.PerformanceReporting.enabled;
var analytics = UnityEngine.Analytics.Analytics.enabled;
_done = !perf && !analytics;
if (_done)
Plugin.Log.LogInfo(
$"[UNITY-TELEMETRY] disabled — PerformanceReporting.enabled={perf} Analytics.enabled={analytics} " +
$"deviceStats={UnityEngine.Analytics.Analytics.deviceStatsEnabled} " +
$"limitUserTracking={UnityEngine.Analytics.Analytics.limitUserTracking}");
else if (_attempts >= MaxAttempts)
// Info, not a warning: this is the known outcome on this build (see the header), not a
// fault to go chasing. It stays logged so a build that *does* accept the switches is
// visible as a change rather than a surprise.
Plugin.Log.LogInfo(
$"[UNITY-TELEMETRY] switches refused after {_attempts} attempts — " +
$"PerformanceReporting.enabled={perf} Analytics.enabled={analytics}. " +
"Known limitation: perf-events.cloud.unity3d.com uploads continue. The rest of Disable Telemetry is unaffected.");
}
}