Android - App Tampering - Detector (Pro)
Players can download an Android app, change it, and share that copy. This detector compares the running app with what you expect.
Walkthrough: Protecting an Android build.
Detector
AndroidPackageTamperingDetector checks several kinds of app changes. If one fails, it tells its listeners.
Observed subject
It listens to IAndroidStatus from these monitors:
- AndroidPackageSourceMonitor: which store installed the app.
- AndroidPackageHashMonitor: a hash of the whole app file.
- AndroidPackageFingerprintMonitor: who signed the app.
- AndroidPackageLibraryMonitor: the code libraries packed in the app.
Status
The detector compares each report with your settings. On a mismatch it sends an AndroidCheatingDetectionStatus:
public struct AndroidCheatingDetectionStatus : IAndroidCheatingDetectionStatus
{
// 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; }
// The type of cheating detected on the Android device.
public EAndroidCheatingType AndroidCheatingType { get; }
// True if the source monitor failed to retrieve its data over the native interface.
public bool MonitorFailedToRetrieveData { get; }
}
- MonitorFailedToRetrieveData: The monitor could not read its data. That can happen on an older device. No real check ran, so the false-alarm chance is higher.
AndroidCheatingType can be:
public enum EAndroidCheatingType : byte
{
UNKNOWN = 0, // Type could not be classified.
PACKAGE_SOURCE = 1, // Installation source is not from an allowed store.
PACKAGE_HASH = 2, // App hash does not match the expected hash.
PACKAGE_FINGERPRINT = 3, // Signing fingerprint does not match the expected fingerprint.
PACKAGE_LIBRARY = 4 // A native library is not allowed (whitelist/blacklist).
}
Threat rating and false positives
- PossibilityOfFalsePositive:
0.01on a real find. It rises to0.75when the monitor could not read its data. - ThreatRating: default
500(recommended 500).
Lifecycle and timing
- In
Awakeit subscribes to whichever of the four monitors are on the same object. A missing monitor simply turns that one check off. - Each monitor report is checked against the project settings. A mismatch is reported.
- In the Editor and in development builds, the checks run only when Verify development builds (
Android_Enable_Development) is on.
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.
Stores, the expected hash, the expected signature, and the library lists are set in Project Settings. See the sections below.
Supported platforms
Android only.
Requirements
Android 4.4 (API level 19) or newer.
How to use
Add one or more of the package monitors, and add AndroidPackageTamperingDetector as a child of the AntiCheat-Monitor. Fill in the settings. Then choose how you want to react.
Add the monitors
This detector can use:
AndroidPackageSourceMonitorAndroidPackageHashMonitorAndroidPackageFingerprintMonitorAndroidPackageLibraryMonitor
Put them on the same object as the detector.
Add the detector
Manual
Add AndroidPackageTamperingDetector from GUPS.AntiCheat.Detector.Android. A child of the monitor is the best place.

Add the AndroidPackageTamperingDetector component.
Prefab
The prefab includes the detector and the monitors.

Add the Android Package Cheating Detector prefab to the AntiCheat-Monitor.
Settings

The AndroidPackageTamperingDetector 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.
Runtime
In Awake the detector subscribes to the package monitors on the same object:
- Source: which store installed the app, for example Google Play.
- Hash: the hash of the whole app, as text.
- Fingerprint: the signature, as text.
- Libraries: the library names in the app.
Check the install source
Cheaters often share an edited app as a direct download. This check allows only the stores you trust.
Monitor
Add AndroidPackageSourceMonitor. The detector checks that store against your allow list.
Project settings
Open Edit > Project Settings > GuardingPearSoftware > AntiCheat. Go to Android - App Store - Settings.

Allowed install sources.
- Allow all installation sources: On means every store is allowed. Off means only the list below is allowed.
- Allow following sources: The stores you trust. A store that is not in the list is reported.
- Allow custom sources: A store that is not in the built-in list. Enter its package name. Google Play is
com.android.vending.
If the store is not allowed, that is a cheat report. See React when a cheat is found.
Check the app hash
The hash shows whether the app file was changed. That includes a new package name, edited code, or other changed files.
Monitor
AndroidPackageHashMonitor calculates the hash. The detector compares it with a hash from your server.
Project settings
Open Android - App Hash - Settings.

Hash settings.
- Verify app hash: Turn the check on. After a build, AntiCheat prints the hash in the Editor log. Put that text on a server the app can reach. At start, the app downloads it and compares. If they differ, this is not the build you shipped.
- Used hash algorithm: SHA-256 is the recommended choice.
- Remote hash location: A web address that returns only the hash text.
{version}is replaced withApplication.version. Examples:https://yourserver.com/yourapp/hash/{version}orhttps://yourserver.com/yourapp/hash?version={version}. Set the version under Edit > Project Settings > Player. That isPlayerSettings.bundleVersion.
After an Android build, the hash appears in the console. You can also calculate it yourself.

The app hash in the console after a build.
Copy that text to your server and return it from a GET request.
Note
The hash changes on every build, even when the game looks the same. Return the hash of the build you shipped. Use {version} in the address if you keep more than one build online.
Set the version under Edit > Project Settings > Player.

Set Application.version under Player settings.
The server should return only the hash text. A small example:
import express from 'express';
const app = express();
app.get('/hash', (req, res) => {
if(req.query.version === '0.2') {
res.send('00:E4:C4:13:2F:09:91:4A:B5:A0:D6:64:AC:38:FD:50:82:02:3C:45:5E:64:69:B1:F7:0E:43:04:14:1C:1A:3A');
return;
}
res.send('Unknown version');
});
app.listen(4000, () => {
console.log(`server running on port 4000`);
});
If the local hash does not match the server, that is a cheat report.
Check the signature
The signature shows the app was signed with your key.
Monitor
AndroidPackageFingerprintMonitor reads the signature. The detector compares it with the one in Project Settings.
Project settings
Open Android - App Fingerprint - Settings.

Fingerprint settings.
- Verify app fingerprint: Turn the check on.
- Used hash algorithm: SHA-256 is the recommended choice.
- Fingerprint: Your signature, as text.
The signature is the public part of the certificate. You apply it with a private key in a keystore. It stays the same as long as you keep that key.
The keystore is set under Edit > Project Settings > Player > Publishing Settings.

The Android publishing key.
Read the fingerprint from the keystore:
keytool -list -v -keystore "[Project]\user.keystore"

Copy the SHA-256 fingerprint into the AntiCheat settings.
Paste at least the SHA-256 line into Android - App Fingerprint - Settings. You only do this once, as long as you keep the same keystore.
If the app signature does not match, that is a cheat report.
Check the libraries
A common mod adds a new library instead of editing your code.
Monitor
AndroidPackageLibraryMonitor lists the libraries. The detector compares them with your allow list and block list.
Project settings
Open Android - App Library - Settings.

Library allow and block lists.
- White-/Blacklist libraries: Turn both lists on. Off means every library is allowed.
- Whitelisted libraries: Libraries that may be in the app. Anything else is reported.
- Blacklisted libraries: Libraries that must not be in the app.
Open the built app in a zip tool. Look in the library folder, for example lib\armeabi-v7a\. Enter each file name with its extension.

Libraries inside a built Android app.
A library that is missing from the allow list, or present on the block list, is a cheat report.
Read the detection in code
You can also listen yourself. Cast the status to AndroidCheatingDetectionStatus to read the Android fields.
using System;
using GUPS.AntiCheat;
using GUPS.AntiCheat.Core.Detector;
using GUPS.AntiCheat.Detector.Android;
using UnityEngine;
public class AndroidPackageDetectionLogger : MonoBehaviour, IObserver<IDetectorStatus>
{
private void Start()
{
var detector = AntiCheatMonitor.Instance.GetDetector<AndroidPackageTamperingDetector>();
detector.Subscribe(this);
}
public void OnNext(IDetectorStatus status)
{
if (status is AndroidCheatingDetectionStatus androidStatus)
{
Debug.LogWarning($"Android package tampering detected: {androidStatus.AndroidCheatingType} " +
$"(threat={androidStatus.ThreatRating}, failedToRetrieve={androidStatus.MonitorFailedToRetrieveData}).");
}
}
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<AndroidPackageTamperingDetector>();
detector.Subscribe(myObserver);
PossibleCheatingDetected stays true after the first find.