A license is permission to use something, under rules. While the game runs, it can ask whether the player is generally allowed to use it. Or whether they are allowed to use the specific content.
If you are not sure what a license is, read What is a license?.
Those permissions checks have to happen somewhere. If those only happen client side, they can be found. After that they can be skipped, or the stored value or method call can be changed.
In this article we are talking about exactly that, how the license check itself happens at runtime, and what you can do, to increase the protection against bypasses.
Where the license check usually lives
When the game starts, it asks the store, "did this player buy it?" Steam or Google Play answers yes or no. So far, so good. But the store only gives an answer. Your own code still has to take that answer and do something with it, like unlock the premium content or show a "buy now" screen.
That last step, your code reacting to the answer, is the part attackers care about. It is usually a small piece of C#: a method that returns true or false, the line that switches on the premium UI, a flag saved in PlayerPrefs, or a value in the save file.
The important thing is where that code runs. It ships inside the build, on the player's device, not on your server and not in the store dashboard. So whatever the game uses to decide "this player is licensed," the player can reach and change it.
How the check gets found
Attackers do not start with Steam or Google Play. They open the build you shipped and look for clues. A method named ValidateLicense or IsPremiumUnlocked is an obvious one. So are error messages, log lines, a URL like /validate-license, or a PlayerPrefs key called premium_unlocked.
It is slow, boring work, but it is not hard. They read through the assemblies and string tables, and on IL2CPP builds they can dig into the metadata as well. They are not after your whole project, just the spot where it decides yes or no.
Once they find that spot, the rest of your code barely matters to them.
How license checks are skipped
Once the check is found, a modified client can avoid it. The method is never called. The success path always runs. The code that should block access never runs. To the player it still looks like the game checked.
A single local yes or no is easy to get around for that reason. If your logic is "call this method, then unlock," skipping the method is enough. The premium scene and the DLC files are already in the build. The check was only blocking the path. If those features can run without a later server check, skip is a full bypass.
How license checks are patched
Skip means the check never really runs. Patch means the stored result is changed. A local bool named isPremium is flipped. A save or PlayerPrefs value is edited. An old "licensed" response is sent again. After that, the game may never ask again.
This can happen on disk or while the game is running. A save editor changes the stored flag. A memory editor changes the same flag in RAM. Your code still checks the license. It just reads a value the attacker already set to true.
License state here is the same kind of client value as a save or a health number. How save-game tampering works and How cheaters use memory editors cover those tools. Do not let a value the player can edit decide paid access forever.
What you can do against the bypasses
There are several ways to make it harder for attackers to bypass your license checks.
Obfuscation makes the first search harder
Readable names help your team. In a shipped build they also help the first search. Obfuscator can rename symbols and hide strings so IsPremiumUnlocked and "license failed" are not sitting in plain text. That does not make the check unbreakable. It makes the names harder to find. What exactly does a Unity Obfuscator do? explains the Unity side.
But obfuscation cannot hide everything. Even an encrypted public URL, a serialized field, or a save key can still say premium after decryption. If those are on the client, they can be found.
Let the server decide
For paid features, the backend should decide licensed or not. The client can collect evidence and show UI. It should not be the last word on ranked play or paid rewards.
If the client says "licensed" and the server believes it, skipping or patching the client is enough to fool the backend too.
Instead, the client sends store evidence to your server. The server checks the account and the purchase. Then it allows that feature for a limited time. If a modified client skips the local method, the server still refuses the reward.
Use the store as a second check
Stores already know who paid, so use their license APIs. On Android that can include Google Play Licensing, Play Integrity, package name checks, certificate fingerprints, and install source. Steam and Apple have their own receipts and license APIs. Do not trust only a bool inside your game. A store API called only on the device can still be skipped. Send the evidence to your server.
For the Android setup, read Protect your APK on Google Play. For purchases, receipt validation belongs on the server. A local purchase callback that says success is not enough.
Cached license flags should expire
A cached "licensed" value is useful when the player is offline. It is a problem when it never expires. Give it a time limit. Refresh it before a purchase or ranked match. Do not store the final unlock as plain PlayerPrefs.
If you need a local cache, decide what it is allowed to do. The player can keep playing a level they already started. They should not claim a weekly reward or enter ranked until the server answers again.
Hiding a button is not enough
If the premium scene or the reward endpoint still work without a server yes, a skipped check is enough. The client can show locked UI. Ranked play and paid rewards still need a server yes.
Offline play can stay usable. Limited mode is fine. Premium rewards should wait until the server can check.
A failed check is not always cheating or cracking
The store can be down, or the player might be on a plane. A new phone can fail a check once. Treat those as honest problems first. Show a clear message and let them retry. A short grace period is fine too. If you treat every failed check as a cheat, honest players get angry. You will also miss real bypasses.
If you later take action, base it on how sure you are. That is covered in Designing fair punishments for different cheat signals.
Do
- Let your backend decide licensed or not for paid features.
- Obfuscate license code and strings so they are harder to search for.
- Use store licensing and send that evidence to your server.
Don't
- Do not unlock paid content forever because one local flag is true.
- Do not store private license secrets in the client.
- Do not treat a failed store or network check as cheating.