#region copyright // --------------------------------------------------------------- // Copyright (C) Dmitriy Yukhanov - focus [https://codestage.net] // --------------------------------------------------------------- #endregion namespace CodeStage.AntiCheat.Time { using Common; using Detectors; using UnityEngine; using UnityEngine.SceneManagement; using Utils; /// /// Speed-hack resistant %Time.* alternative. /// Does proxies to the regular %Time.* APIs until actual speed hack is detected. /// /// Requires running \ref CodeStage.AntiCheat.Detectors.SpeedHackDetector "SpeedHackDetector" to operate properly. Make sure to start SpeedHackDetector before calling Init().
/// Uses Unity's %Time.* APIs until speed hack is detected and switches to the speed-hack resistant time since then. [AddComponentMenu("")] [DisallowMultipleComponent] public class SpeedHackProofTime : KeepAliveBehaviour { private static bool inited; private static bool speedHackDetected; private static float reliableTime; private static float reliableDeltaTime; private static float reliableUnscaledTime; private static float reliableUnscaledDeltaTime; private static float reliableRealtimeSinceStartup; private static float reliableTimeSinceLevelLoad; private static bool warningShot; private long currentReliableTicks; private long lastFrameReliableTicks; private long reliableTicksDelta; #region Unity Events protected override string GetComponentName() { return "SpeedHackProofTime"; } private void Update() { if (!speedHackDetected) { UpdateTimeValuesFromUnityTime(); } else { currentReliableTicks = TimeUtils.GetReliableTicks(); reliableTicksDelta = currentReliableTicks - lastFrameReliableTicks; UpdateReliableTimeValues(); } } #endregion /// /// Call to add to the scene and force internal initialization. Gets called automatically when necessary if not initialized. /// /// Make sure to call it after you setup and run \ref CodeStage.AntiCheat.Detectors.SpeedHackDetector "SpeedHackDetector". public static void Init() { inited = GetOrCreateInstance.InitInternal(); } /// /// Call to remove from scene and clean internal resources. /// public static void Dispose() { inited = false; if (Instance == null) { return; } var detectorInstance = SpeedHackDetector.Instance; if (detectorInstance != null) { detectorInstance.CheatDetected -= Instance.OnSpeedHackDetected; } Destroy(Instance.gameObject); } /// /// Speed-hack resistant analogue on Unity's %Time.time API. /// public static float time { get { if (!inited) { Init(); } return speedHackDetected ? reliableTime : Time.time; } } /// /// Speed-hack resistant analogue on Unity's %Time.unscaledTime API. /// public static float unscaledTime { get { if (!inited) { Init(); } return speedHackDetected ? reliableUnscaledTime : Time.unscaledTime; } } /// /// Speed-hack resistant analogue on Unity's %Time.deltaTime API. /// public static float deltaTime { get { if (!inited) { Init(); } return speedHackDetected ? reliableDeltaTime : Time.deltaTime; } } /// /// Speed-hack resistant analogue on Unity's %Time.unscaledDeltaTime API. /// public static float unscaledDeltaTime { get { if (!inited) { Init(); } return speedHackDetected ? reliableUnscaledDeltaTime : Time.unscaledDeltaTime; } } /// /// Speed-hack resistant analogue on Unity's %Time.realtimeSinceStartup API. /// public static float realtimeSinceStartup { get { if (!inited) { Init(); } return speedHackDetected ? reliableRealtimeSinceStartup : Time.realtimeSinceStartup; } } /// /// Speed-hack resistant analogue on Unity's %Time.timeSinceLevelLoad API. /// public static float timeSinceLevelLoad { get { if (!inited) { Init(); } return speedHackDetected ? reliableTimeSinceLevelLoad : Time.timeSinceLevelLoad; } } private bool InitInternal() { var detectorInstance = SpeedHackDetector.Instance; if (detectorInstance == null) { if (!warningShot) { Debug.LogWarning(ACTkConstants.LogPrefix + "Can't initialize SpeedHackProofTime class since it requires running SpeedHackDetector instance which was not found. " + "Did you started SpeedHackDetector before using SpeedHackProofTime?\n" + "SpeedHackProofTime will use unreliable vanilla Time.* APIs until you start SpeedHackDetector."); warningShot = true; } return false; } if (!detectorInstance.IsRunning) { if (!warningShot) { Debug.LogWarning(ACTkConstants.LogPrefix + "Can't initialize SpeedHackProofTime class since it requires running SpeedHackDetector instance but only idle instance was found. " + "Did you started SpeedHackDetector before using SpeedHackProofTime?\n" + "SpeedHackProofTime will use unreliable vanilla Time.* APIs until you start SpeedHackDetector."); warningShot = true; } return false; } detectorInstance.CheatDetected += OnSpeedHackDetected; return true; } private void UpdateTimeValuesFromUnityTime() { reliableTime = Time.time; reliableDeltaTime = Time.deltaTime; reliableUnscaledTime = Time.unscaledTime; reliableUnscaledDeltaTime = Time.unscaledDeltaTime; reliableTimeSinceLevelLoad = Time.timeSinceLevelLoad; reliableRealtimeSinceStartup = Time.realtimeSinceStartup; } private void UpdateReliableTimeValues() { lastFrameReliableTicks = currentReliableTicks; reliableUnscaledDeltaTime = (float)reliableTicksDelta / TimeUtils.TicksPerSecond; reliableDeltaTime = reliableUnscaledDeltaTime * Time.timeScale; reliableTime += reliableDeltaTime; reliableUnscaledTime += reliableUnscaledDeltaTime; reliableRealtimeSinceStartup += reliableUnscaledDeltaTime; reliableTimeSinceLevelLoad += reliableDeltaTime; } private void OnSpeedHackDetected() { speedHackDetected = true; lastFrameReliableTicks = TimeUtils.GetReliableTicks(); } protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode) { base.OnSceneLoaded(scene, mode); reliableTimeSinceLevelLoad = 0; } } }