Game uses in app purchases to purchase a set amount of gems, say 1000 for a dollar.
You can also earn gems in game.
My issue is how to prevent people hacking or modifying the playerprefs file so they can get an easy fix - i understand it to be easy on jailbroken phones. Since the game is free, it is likely to just be fiddled around with in the source files than have a dedicated hacker waste his time on it.
Well, you could also save a hash of the current amount of gems.
Let’s say, you have 500 gems and a secret keyword, i.e. 0c8a51e5b234fec38b6a1bd681. When you save the amount, you will save the gems in “gemAmount” PlayerPrefs variable and an md5(“500:0c8a51e5b234fec38b6a1bd681”) in “gemAmountHash”. When you read it, you check against it.
edit: You can also add a user specific string to the hash, i.e. uuid, the unique user id (at least on Android, no idea about iOS, but they should have something similar too), so the hash would be device specific and harder to make a general tool or instruction to cheat /edit
This will protect it against simple and “blind” editing of the PlayerPrefs. However, someone who really wants to hack it, will just download the APK (in case of Android, dunno about iOS), decompile/unpack it (it’s a simple signed zip file) and decompile your Unity-xxx.dll in ILSpy or some similar tool and look for the method used to create the hash (and most probably will also see your secret key used for it).
Of course the most secure way is to store it on the server, but that would make your game only work while you’re connected to the internet. There are some ways to hide the keys too. Usually you’d obfuscate the dll but this doesn’t seems to be supported by Unity3D which really sucks
edit:
And just as a side note, I wouldn’t bother trying too hard to prevent cheaters and pirates, because people who’d like to cheat and get gems without paying aren’t going to purchase anyways, even if you prevent them from cheating. More likely they are going to uninstall the game. If they can cheat, they still can play the game and i.e. generate revenue by in-game ads or by recommending your game to others. This is unlikely if they uninstall it because they can’t get the gems and are unwilling to pay anyway
The safest way I can think of the top of my head right now is to store everything on the server as well. If they get to see your source code, then I think any defensive mechanism relying on the client side is pretty much useless.
Anyway, this is my code used for storing integer
using UnityEngine;
using System.Collections;
public class HiddenInt
{
private int _value;
private int r;
public HiddenInt(int startValue = 0)
{
Value = startValue;
}
public int Value
{
get
{
return _value - r;
}
set
{
r = Random.Range(-1000000,1000000);
_value = r + value;
}
}
}
This is to prevent memory hacking. You’ll never store the actual amount of coin in the variable. The actual amount of coin must come from a calculation involving _value and r. R is also random so it should take them a while before they know what’s going on.
Another way is to keep track of the amount of coin player has gain since last sync with server.
and if
current local amount of coin == coin stored on server + coin gained since last sync
then, that means the data is correct.
Depending on the game play, you can also record the amount of time player spend on playing, and the average amount of coin a player can get. If the time he spend on playing is not realistic, comparing to the amount of coin he has right now, then something’s fishy is going on. This would be harder and take more effort to cheat, considering how they have to come up with a realistic number that only you know, unless they play games many time and predict the same number that you have.
Maybe I used a wrong term. By memory hacking, I mean they can’t look at their current number of coins, and then find the same number in the memory, because the actual number of coins never stored anywhere.
Or if this still doesn’t help, could you please elaborate?
Heh, you’re right. That random number technique is what I got from a Flash game developer regarding submitting an online highscore or flash game cheating in general. But yeah, memory manipulation is probably not an issue in a mobile phone game.
What about storing a secondary variable that is a hash of the variables you want to protect.
Then when you first load the game and access your playerprefs, you decompose the hash and compare it to each of the individual keys making up the hash(or their sum). If the sum of the hash values isn’t equal to the sum of the individual keys, someone changed the playerprefs and you can set them all to 0.
Or when you save to playerPrefs you have a secretVar which adds up your individual keys and performs some type of operation to it (factor of, multiply by, etc) then do the same thing to the individual key values when the game first loads and compare it to the same equation used with the secretVar, if it’s the same then nothing was changed between last time application was exited and the next time it loads.
I made 3 classes for protection against runtime value manipulations. Code is below. You can use them as a secure value type. Just add the AntiCheatManager script to the Camera or GameManager object, and you can start using them.
Use the AntiCheatExtensions.Synchronize() to assign the value.
As for the SaveGame Files, the most effective way to secure yourself from cheating is to:
Encrypt the save file with AES.
Encrypt the AES key with RSA using application’s public key.
Hash the result with SHA2.
For loading:
Check the hash.(throw “!=” as cheating)
Decrypt the AES key with application’s private key.
Decrypt the save file with AES using decrypted key.
public class AntiCheat
{
public static int lastID;
public int ID;
public Single Value;
public Single HackCheck;
public Action OnHack = null;
public AntiCheat ()
{
Value = 0;
HackCheck = 11;
lastID++;
ID = lastID;
AntiCheatManager.MonitoredValues.RemoveAll(item => item == null);
AntiCheatManager.MonitoredValues.Add(this);
}
public AntiCheat (Single value)
{
Value = (float)(value);
HackCheck = 11;
lastID++;
ID = lastID;
AntiCheatManager.MonitoredValues.RemoveAll(item => item == null);
AntiCheatManager.MonitoredValues.Add(this);
}
public AntiCheat (Single value, Action onHack)
{
Value = (float)(value);
HackCheck = 11;
OnHack = onHack;
lastID++;
ID = lastID;
AntiCheatManager.MonitoredValues.RemoveAll(item => item == null);
AntiCheatManager.MonitoredValues.Add(this);
}
public static bool operator < (AntiCheat a, AntiCheat b)
{
if(a.Value < b.Value)
return true;
else
return false;
}
public static bool operator < (Single a, AntiCheat b)
{
if(a < b.Value)
return true;
else
return false;
}
public static bool operator < (AntiCheat a, Single b)
{
if(a.Value < b)
return true;
else
return false;
}
public static bool operator > (AntiCheat a, AntiCheat b)
{
if(a.Value > b.Value)
return true;
else
return false;
}
public static bool operator > (Single a, AntiCheat b)
{
if(a > b.Value)
return true;
else
return false;
}
public static bool operator > (AntiCheat a, Single b)
{
if(a.Value > b)
return true;
else
return false;
}
public static bool operator <= (AntiCheat a, AntiCheat b)
{
if(a.Value <= b.Value)
return true;
else
return false;
}
public static bool operator <= (Single a, AntiCheat b)
{
if(a <= b.Value)
return true;
else
return false;
}
public static bool operator <= (AntiCheat a, Single b)
{
if(a.Value <= b)
return true;
else
return false;
}
public static bool operator >= (AntiCheat a, AntiCheat b)
{
if(a.Value >= b.Value)
return true;
else
return false;
}
public static bool operator >= (Single a, AntiCheat b)
{
if(a >= b.Value)
return true;
else
return false;
}
public static bool operator >= (AntiCheat a, Single b)
{
if(a.Value >= b)
return true;
else
return false;
}
public static bool operator == (AntiCheat a, AntiCheat b)
{
if(Equals(a,b))
return true;
else
return false;
}
public static bool operator == (Single a, AntiCheat b)
{
if(a == b.Value)
return true;
else
return false;
}
public static bool operator == (AntiCheat a, Single b)
{
if(a.Value == b)
return true;
else
return false;
}
public static bool operator != (AntiCheat a, AntiCheat b)
{
if(!Equals(a,b))
return true;
else
return false;
}
public static bool operator != (Single a, AntiCheat b)
{
if(a != b.Value)
return true;
else
return false;
}
public static bool operator != (AntiCheat a, Single b)
{
if(a.Value != b)
return true;
else
return false;
}
}
public class AntiCheatManager : MonoBehaviour
{
public static List<AntiCheat> MonitoredValues = new List<AntiCheat>();
void Update()
{
foreach(var ob in AntiCheatManager.MonitoredValues)
{
if(ob.Value != 11 - ob.HackCheck)
{
ob.Value = 11 - ob.HackCheck;
}
}
}
}
public static class AntiCheatExtensions
{
public static void Synchronize(this AntiCheat ac, float SecureValue)
{
ac.Value = SecureValue;
ac.HackCheck = 11-SecureValue;
}
}