When integrating with MiniTon it becomes extremely important to take precautions against potential cheating in a game. To give themselves an unfair advantage, players can use 3rd party tools to modify scores or important gameplay values like health and ammo. To maintain a fair and competitive gameplay experience, we recommend that developers take anti-cheating measures before going live with MiniTon.
Cheating Through Memory Modification
One technique for cheating involves modifying values in run time memory. Using this technique, it would be possible for a user to modify their score before submitting to MiniTon or to rig gameplay variables, like health or time, to give themselves an unfair advantage.
This technique works by searching memory for known values. For instance, say the player knew the score was 19, he would search the memory space of the application for a value of 19 and this would most likely give him a lot of memory addresses, one of which is the score value. At this point there are probably too many possibilities to accurately modify the score but if he then does something in game to increase your score, say to 20, he can now search for 20 in the memory space and reduce the number of memory addresses that are possible.This process is repeated until the probable addresses for scores are reduced to one. The cheater can now modify that memory address directly and change the score.
We recommend that game developers employ the techniques listed in the following pages. Keep in mind protecting against cheating is never going to be absolute, like most security measures it is about making things more difficult for cheaters. They are not a comprehensive overview of all forms of anti-cheat protection, and you may decide to use other anti-cheat techniques in your app as appropriate. If you suspect players are cheating in your game, please reach out directly to MiniTon and we will work with you to get cheaters out of your game.
Duplicate and Verify Data
One technique to protect memory from being modified is to duplicate and verify data. The idea is to copy key data into separate variables and then compare the copy to the original, if the two don't match at any point in your game, it's likely that someone is cheating.
This ensures there are at least two places in memory that both have to be modified to cheat.
Example Code
privateint score =0;privateint scoreCheck =0;// Make sure score and scoreCheck start at the same numbervoidAwake(){ scoreCheck = score;}// Any code that alters the score should also alter the scoreCheckprivatevoidAddScore(int increase){ score += increase; scoreCheck += increase;}// Accessors just need to return the real scoreprivateintGetScore (){return score;}// Assume this is your in-game function triggered upon// finishing the game (eg: time runs out, no more moves)privatevoidGameOver(){if (ConfirmScoreValidity()) { // The score matches the copy, so report itMiniTonCrossPlatform.ReportFinalScore(score); }else { // Abort the match because suspicious behavior was detectedMiniTonCrossPlatform.AbortMatch(); }}// Confirms that the score is valid.privateboolConfirmScoreValidity(){return score == scoreCheck;}
constMiniTonCrossPlatform= {ReportFinalScore:function (finalScore) {console.log("Reporting final score: "+ finalScore);// Implement your logic to report the final score to MiniTon },AbortMatch:function () {console.log("Aborting the match due to suspicious behavior");// Implement your logic to abort the match },};classScoreManager {constructor() {this.score =0;this.scoreCheck =0;// Make sure score and scoreCheck start at the same numberthis.Awake(); }// Make sure score and scoreCheck start at the same numberAwake() {this.scoreCheck =this.score; }// Any code that alters the score should also alter the scoreCheckAddScore(increase) {this.score += increase;this.scoreCheck += increase; }// Accessors just need to return the real scoreGetScore() {returnthis.score; }// Assume this is your in-game function triggered upon// finishing the game (e.g., time runs out, no more moves)GameOver() {if (this.ConfirmScoreValidity()) {// The score matches the copy, so report itMiniTonCrossPlatform.ReportFinalScore(this.score); } else {// Abort the match because suspicious behavior was detectedMiniTonCrossPlatform.AbortMatch(); } }// Confirms that the score is validConfirmScoreValidity() {returnthis.score ===this.scoreCheck; }}// Usage exampleconstscoreManager=newScoreManager();scoreManager.AddScore(10); // Increase the score (simulated)scoreManager.GameOver(); // Check and report the final score
Obfuscate Data
Another technique to protect memory from being modified is to obfuscate data. Obfuscating or encrypting key data is possible through various means. Before writing to memory, either obfuscate a variable or encrypt it. There are various techniques for achieving this, ranging from a simple XOR to more complex encryption methods. If you are combining this with a duplication technique you could even hash the duplicate and incorporate the hash check into the verify logic.
Obfuscating your data will ensure that it is harder for a cheater to find key variables in memory.
Example Code
privateint xorCode =12345; // USE A DIFFERENT XOR VALUE THAN THIS!privateint score =0;// Make sure to xor the initial scorevoidAwake() { score = score ^ xorCode;}// Functions and mutators should xor the value before using it, and xor// the value again before the function exitsprivatevoidAddScore(int increase) { score ^= xorCode; score += increase; score ^= xorCode;}// Accessors just need to return the xor'd valueprivateintGetScore () {return score ^ xorCode;}
classScoreManager {constructor(xorCode) {this.xorCode = xorCode; // Use a different XOR value than thisthis.score =0;// Make sure to XOR the initial scorethis.Awake(); }// Make sure to XOR the initial scoreAwake() {this.score =this.score ^this.xorCode; }// Functions and mutators should XOR the value before using it,// and XOR the value again before the function exitsAddScore(increase) {this.score ^=this.xorCode;this.score += increase;this.score ^=this.xorCode; }// Accessors just need to return the XOR'd valueGetScore() {returnthis.score ^this.xorCode; }}// Usage example with a different XOR codeconstxorCode=54321; // Use a different XOR value than thisconstscoreManager=newScoreManager(xorCode);scoreManager.AddScore(10); // Increase the score (XOR-encoded)constcurrentScore=scoreManager.GetScore(); // Retrieve the decoded scoreconsole.log("Current Score:", currentScore);
Advanced Data Protection
You can combine the data-duplication and obfuscation techniques for added security. Ideally you should use a different XOR hash for the score and scoreCheck values. That will make it harder for a cheater to notice, otherwise the memory addresses will contain matching values and make it easier to find your checksum.
Example Code
usingSystem;publicclassScoreManager{privateint xorCode =12345; // USE A DIFFERENT XOR VALUE THAN THIS!privateint scoreCheckXorCode =54321; // USE A DIFFERENT XOR VALUE THAN THIS!privateint score =0;privateint scoreCheck =0; // Make sure to xor the initial valuespublicScoreManager() { score = score ^ xorCode; scoreCheck = scoreCheck ^ scoreCheckXorCode; } // Functions and mutators should xor the value before using it, and xor the value again before the function exitspublicvoidAddScore(int increase) { score ^= xorCode; score += increase; score ^= xorCode; scoreCheck ^= scoreCheckXorCode; scoreCheck += increase; scoreCheck ^= scoreCheckXorCode; } // Confirms that the score is valid.publicboolConfirmScoreValidity() {return (score ^ xorCode) == (scoreCheck ^ scoreCheckXorCode); }publicstaticvoidMain() { // Example usageScoreManager scoreManager =newScoreManager();scoreManager.AddScore(10);Console.WriteLine("Score is valid: "+scoreManager.ConfirmScoreValidity()); }}
classScoreManager {constructor(xorCode, scoreCheckXorCode) {this.xorCode = xorCode; // Use a different XOR value than thisthis.scoreCheckXorCode = scoreCheckXorCode; // Use a different XOR value than thisthis.score =0;this.scoreCheck =0;// Make sure to XOR the initial valuesthis.Awake(); }// Make sure to XOR the initial valuesAwake() {this.score =this.score ^this.xorCode;this.scoreCheck =this.scoreCheck ^this.scoreCheckXorCode; }// Functions and mutators should XOR the value before using it,// and XOR the value again before the function exitsAddScore(increase) {this.score ^=this.xorCode;this.score += increase;this.score ^=this.xorCode;this.scoreCheck ^=this.scoreCheckXorCode;this.scoreCheck += increase;this.scoreCheck ^=this.scoreCheckXorCode; }// Confirms that the score is valid.ConfirmScoreValidity() {return (this.score ^this.xorCode) === (this.scoreCheck ^this.scoreCheckXorCode); }}// Usage example with different XOR codesconstxorCode=12345; // Use a different XOR value than thisconstscoreCheckXorCode=54321; // Use a different XOR value than thisconstscoreManager=newScoreManager(xorCode, scoreCheckXorCode);scoreManager.AddScore(10); // Increase the score (XOR-encoded)constisScoreValid=scoreManager.ConfirmScoreValidity();console.log("Score is valid:", isScoreValid);