Universal - Device Time - Detector (Pro)
Many games use the device clock for trials, daily rewards, or timers. The player can change that clock. This detector notices the change and gives you a safer UTC time.
Walkthrough: Protecting against device clock cheating.
Detector
DeviceTimeCheatingDetector (namespace GUPS.AntiCheat.Detector) watches for a moved device clock. It listens to DeviceTimeMonitor. It also exposes a safer DateTime.UtcNow through GUPS.AntiCheat.Protected.Time.ProtectedTime. That time can come from the internet or from the device clock.
Observed subject
In Awake, the detector subscribes to DeviceTimeStatus from the DeviceTimeMonitor on the same object. If that monitor is missing, the detector logs a warning and does nothing.
Status
The detector sends a CheatingDetectionStatus:
public struct CheatingDetectionStatus : IDetectorStatus
{
// Probability that the detection is a false positive, in the range [0.0, 1.0].
public float PossibilityOfFalsePositive { get; }
// The threat rating reported with this detection.
public uint ThreatRating { get; }
}
Threat rating and false positives
- PossibilityOfFalsePositive:
0.35(fixed) on each report. - ThreatRating: default
500(recommended 500). False alarms are unlikely, and a moved clock matters a lot.
Lifecycle and timing
- In
Awakeit subscribes to the monitor. It also stores the app start time, from the internet or from the device clock. - In
Startit checks once. If the device clock is more than 15 seconds away from the reference, it reports cheating right away. - It also reports when the monitor sends a deviation that is not
None. - In
FixedUpdateit updatesCurrentUtcTime. When the app gets focus or leaves pause, it fetches the start time again.
Configuration
- Is Active (
isActive, bool, default true): Turns the detector on or off. - Threat Rating (
threatRating, uint, default 500): How much one find adds to the threat score. - On Cheating Detection Event (
OnCheatingDetectionEvent): Functions to call when a cheat is found. - Use Internet Time (
useInternetTime, bool, default true): When on, the start time comes from a web server instead of the device clock. - Server Address (
serverAddress, string, defaulthttps://google.com): The server used to read the current UTC time. The time comes from the responseDateheader. - Server Certificate Hash (
serverCertificateHash, string, optional): A certificate hash used to check the server. If it does not match, that is treated as possible tampering. Optional, but safer.
Supported platforms
Every platform.
Requirements
None, except a DeviceTimeMonitor on the same object. Internet time needs a network connection to the server you set.
How to use
Add DeviceTimeMonitor if it is not there yet, and add DeviceTimeCheatingDetector as a child of the AntiCheat-Monitor. Then choose how you want to react.
Add the monitor
The detector needs the monitor's reports. Put DeviceTimeMonitor on the same object. The detector subscribes to DeviceTimeStatus.
Add the detector
Manual
Add DeviceTimeCheatingDetector from GUPS.AntiCheat.Detector, next to the monitor. A child of the AntiCheat-Monitor is the best place.

Add the DeviceTimeCheatingDetector component.
Prefab
The prefab includes the detector and the monitor.

Add the Device Time Cheating Detector prefab to the AntiCheat-Monitor.
Settings

The DeviceTimeCheatingDetector settings.
- General Settings: Turn the detector on or off.
- Threat Rating Settings: How serious one find is.
- Observable Settings: Functions to call when cheating is found.
- Device Time Settings: How the safer UTC time is built.
Runtime
Use ProtectedTime.UtcNow instead of DateTime.UtcNow:
// Instead of using DateTime.UtcNow, use the UtcNow from GUPS.AntiCheat.Protected.Time.ProtectedTime.
public static class ProtectedTime
{
// The protected Coordinated Universal Time (UTC) DateTime (Read Only). The calculated UTC time, which may differ from the original DateTime.UtcNow
// because it is calculated to be as secure and trustworthy as possible.
public static DateTime UtcNow { get; }
}
Read the detection in code
You can also listen yourself. Subscribe returns an IDisposable. Dispose it when you want to stop.
using System;
using GUPS.AntiCheat;
using GUPS.AntiCheat.Core.Detector;
using GUPS.AntiCheat.Detector;
using UnityEngine;
public class DeviceTimeDetectionLogger : MonoBehaviour, IObserver<IDetectorStatus>
{
private void Start()
{
var detector = AntiCheatMonitor.Instance.GetDetector<DeviceTimeCheatingDetector>();
detector.Subscribe(this);
}
public void OnNext(IDetectorStatus status)
{
Debug.LogWarning($"Device time cheating detected (threat={status.ThreatRating}, fp={status.PossibilityOfFalsePositive}).");
}
public void OnError(Exception error) { }
public void OnCompleted() { }
}
React when a cheat is found
The detector tells the AntiCheat-Monitor. The monitor adds the report to the threat score.
Punisher. Add a punisher prefab as a child of the monitor. It runs when the score reaches its limit.

Built-in punisher prefabs.
Inspector. Add a function to On Cheating Detection Event. It runs when this detector finds a cheat.

Inspector callbacks on a detector.
Code.
var detector = AntiCheatMonitor.Instance
.GetDetector<DeviceTimeCheatingDetector>();
detector.Subscribe(myObserver);
PossibleCheatingDetected stays true after the first find.