How to Protect Your Android Game from Cheat Apps
I make CheatDroid, so I spend a lot of time finding out where games keep their coins. For the tutorials on this site I install popular games on a rooted test phone and try to change them. Most keep the balance in a plain file that one edit changes. A few don’t, and what stopped me there was rarely expensive.
What root gives a cheat app
Android walls every app’s files and memory off from every other app. Root takes the wall down. With it, CheatDroid can:
- open any app’s shared preferences, Unity PlayerPrefs, DataStore files, SQLite databases and plain files, and save them back (PlayerPrefs and SharedPreferences, save files and databases);
- search a running game’s memory for a number, narrow it down, then overwrite or freeze it (values in memory);
- route one app’s traffic through itself, read its HTTPS and send any request again with changes (purchases and network traffic);
- start any of your activities directly, send your app intents, read your BuildConfig and search the APK for keys (activities, intents and the APK).
What you can find out about the phone itself, and why a root check is the weakest of these, is in root detection and Play Integrity.
CheatDroid doesn’t inject code, hook functions or rebuild APKs. Tools that do exist, and nothing that runs on the player’s phone holds out against them for long. Everything here is aimed at the apps people actually use to cheat in single-player games, mine and GameGuardian among them. It’s cheap enough that there’s little reason to skip it.
What the games did
Each one was the current Google Play version on the day I tried it, in October 2026.
| Game | Where the coins were | Did it hold? |
|---|---|---|
| Perfect Slices 1.4.55 | coinsAmount, a plain int in PlayerPrefs | No |
| Aquapark.io 6.51.1 | Currency_Balance_Coins in PlayerPrefs | No |
| Count Masters 1.102.3 | SoftCurrency in PlayerPrefs | No |
| Hill Climb Racing 1.72.2 | Its own preferences file, plain text | No |
| Kick the Buddy 2.20.2 | A JSON save with a checksum in front | The file did, memory didn’t |
| AdVenture Capitalist 9.28.0 | Zipped, base64’d JSON in PlayerPrefs, each block signed with a plain SHA-256 | No: the hash has no key |
| Traffic Racer 4.0 | A scrambled save | The file did, memory didn’t |
| Pizza Ready! 60.4.0 | Money in a file that is base64 of UTF-16 JSON, under a base64 name; other values in Anti-Cheat Toolkit’s ObscuredPrefs with the default key | No: base64 hides nothing, and the default key is the same in every game |
| Knife Hit 1.8.34 | Cocos2d-x preferences, names and values scrambled | The file did |
| Rider 3.06 | One encrypted PlayerPrefs entry | Yes |
| Kick the Buddy: Second Kick 1.14 | JSON save behind a salted 32-bit hash | The file did, memory didn’t |
| Temple Run 2 | JSON save with a salted MD5 | The file did |
| Merge Master 3.50.4 | An Easy Save 3 file, plain JSON | No |
| Extreme Car Driving Simulator 7.19.1 | On the server, behind a guest login | Yes |
| Stack 4.1 | I never got that far: it only starts when Google Play vouches for the install | Yes |
Of the games whose save held, I tried memory on three: Kick the Buddy, its sequel and Traffic Racer. All three gave up their coins there in two scans.
Where I’d start
- If the game talks to a server anyway, keep the balance there. The phone then only shows a number, and everything below is a speed bump for the rest.
- Sign what you save. An HMAC over each value, or over the whole save, is an afternoon’s work and stops every file editor, mine included.
- Keep the currency XORed in memory with a key that changes on every write, and keep the key away from the value. Another afternoon, and a memory scanner finds nothing, a pair search included.
- Grant purchases from your server after Google confirms them. A local “bought” flag is one line in an XML file.
- Play Integrity, checked on your server, for whatever is competitive: leaderboards, PvP, events with real prizes.
Time away
An idle game that pays for time away reads the phone’s clock, and root moves that clock in a tap; CheatDroid’s time jump does nothing else. What keeps it cheap:
- Cap what time away pays. With a cap of a few hours, a month’s jump is worth a few hours.
- While the game runs, count time with
SystemClock.elapsedRealtime(), which doesn’t move when the clock does. It starts over at every reboot, so it can’t cover time the phone was off. - If the game is online anyway, take the time from your server’s response, the
Dateheader will do, and pay from that. - A clock that is earlier than the last save means it was moved, and back. Paying nothing then costs an honest player little.
Root detection on its own I’d leave out. Magisk hides itself from any app the user picks, so a root check mostly blocks the honest people who root their phones for other reasons.
The quickest test of any of this is the tool itself. Put your release build on a rooted phone, open it in CheatDroid, change the number and start the game again. If the old number comes back, it held.