How to Save your Game’s Data: Part 2/2
In the second part of this tutorial, you will be adding your own anti-cheat system as well as saving your game’s data into iCloud. By Marin Todorov.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Save your Game’s Data: Part 2/2
25 mins
Welcome to part two of this tutorial series that’s all about learning how to save data in a game!
In the first part of this series, you learned how to store your game’s data in a separate class, and save it to disk using NSCoding
.
In this second and final part of the series, you’ll:
- Take on cheaters and deal with them once and forever
- Add iCloud capabilities to your game.
Feel free to continue coding from where you left off, or you can download the project, completed to this point.
Now enable your hacking scanners and set your ship’s auto-pilot to iCloud City!
Getting Started
So far everything is perfect in your game-data-land; you track the player achievements, store them to the disc, and you even have a pilot photo showing somewhere there!
Now, imagine this scenario coming to life before your very eyes: Peter is a top-notch Space Shooter, and he achieves a high score of 10,000 points. He then loads his game on Stan’s phone, which overwrites Stan’s game data. Since the file has no protection so now Stan’s high score is also at 10,000 points! Peter is upset because now Stan is doing a victory dance and tweeting out that he’s the king of Space Shooter.
With a simple game like Space Shooter, this problem isn’t a big deal. Well, except for Peter, but he’ll muddle through somehow. However, you can probably imagine a case where this would be a big problem, for example, if you were to send the high score to a leaderboard server.
The first thing that comes to mind when you need to add a smidgen of security to an app is to store the data in the user Keychain. However, it’s designed to store short pieces of text, like passwords, and you need to store more complicated data like photos. Luckily, there is a workaround for this. By the time you’re at the end of this tutorial, you’ll be ready to start working with it in your projects.
When you use Keychain to save the game data to the disc, you’ll also store a hash of the file contents in the Keychain. What does that mean? A malicious user could possibly read the data, and for example see the high score, but they won’t be able to tinker with anything without invalidating the hash.
It won’t take too long to implement the necessary modifications. First, download the KeychainWrapper class, which will make it easier for you to work with the device Keychain storage:
Note: The KeychainWrapper
comes to you from another useful tutorial on RayWenderlich.com; check it out to learn more about the Keychain: Basic Security in iOS5, by Chris Lowe. Please note there are a couple of very small modifications to the source so that it’s a better fit for this tutorial.
Note: The KeychainWrapper
comes to you from another useful tutorial on RayWenderlich.com; check it out to learn more about the Keychain: Basic Security in iOS5, by Chris Lowe. Please note there are a couple of very small modifications to the source so that it’s a better fit for this tutorial.
Extract the contents of the zip file and copy the two resulting files to your project. You should see KeychainWrapper
‘s files visible, like so:
Open RWGameData.m. At the top file, import the Keychain helper class header file.
#import "KeychainWrapper.h"
Underneath the @implementation
direction, add the following:
static NSString* const SSGameDataChecksumKey = @"SSGameDataChecksumKey";
Since you’re tracking the hash, you’ll need to update it when you save the game data. Can you guess where it will to go? Scroll down to the save
method.
KeychainWrapper
sports a rather handy method that will help you achieve your goal. Add this line to get the SHA256 hash of encodedData
to show up at the end of save
:
NSString* checksum = [KeychainWrapper computeSHA256DigestForData: encodedData];
computeSHA256DigestForData:
gets in an NSData
instance and returns the SHA256 as a string, which is pretty handy to store in the Keychain. Add this code:
if ([KeychainWrapper keychainStringFromMatchingIdentifier: SSGameDataChecksumKey]) {
[KeychainWrapper updateKeychainValue:checksum forIdentifier:SSGameDataChecksumKey];
} else {
[KeychainWrapper createKeychainValue:checksum forIdentifier:SSGameDataChecksumKey];
}
You use keychainStringFromMatchingIdentifier:
to check whether a value for the given key exists, and if so, you update it with updateKeychainValue:forIdentifier:
. If not, you create new value storage for the checksum key createKeychainValue:forIdentifier:
.
One part of the operation is now complete. Every time you save the game data file, you also store its checksum securely in the Keychain. Next, you’ll change the code so that it loads a stored file only when the file checksum is identical to most recently saved game. Locate this piece of code in loadInstance
:
if (decodedData) {
RWGameData* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
Currently the class unarchives provided data into an instance and returns it straight away. Replace the whole chunk of code above with this new logic:
if (decodedData) {
//1
NSString* checksumOfSavedFile = [KeychainWrapper computeSHA256DigestForData: decodedData];
//2
NSString* checksumInKeychain = [KeychainWrapper keychainStringFromMatchingIdentifier: SSGameDataChecksumKey];
//3
if ([checksumOfSavedFile isEqualToString: checksumInKeychain]) {
RWGameData* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
//4
}
That’s more like it! Here’s what you did when you added this new code:
- You generate the SHA256 hash of the decoded data and store it in
checksumOfSavedFile
- Then you get the most recent SHA256 hash from the Keychain and store it in
checksumInKeychain
. If you get decoded data, you must’ve stored its hash in the Keychain before. - Finally, you compare both checksum strings. If they are equal, you unarchive the data and return the resulting
RWGameData
as the result. - If the checksums don’t match – you don’t do anything. The code execution continues, and the method returns a new blank
RWGameData
on the next line.
Your basic security is in place! Good job!
Note: How you punish the user for tinkering with their game data file is entirely in your hands. In this tutorial, you’ll learn how to take a subtle approach by quietly deleting their high score and distance flown, but hey … you can be mischievous too and do things like show a sassy meme or alert the cheat police — any punishment you feel fits the crime.
The all time winner goes to the makers of Serious Sam 3. When the game detected that it was pirated, it spawned – midgame, no less – a player hunting indestructible pink scorpion that pretty much ended the game.
Note: How you punish the user for tinkering with their game data file is entirely in your hands. In this tutorial, you’ll learn how to take a subtle approach by quietly deleting their high score and distance flown, but hey … you can be mischievous too and do things like show a sassy meme or alert the cheat police — any punishment you feel fits the crime.
The all time winner goes to the makers of Serious Sam 3. When the game detected that it was pirated, it spawned – midgame, no less – a player hunting indestructible pink scorpion that pretty much ended the game.
Build and run the project again. Look at that! Your high score is back to zero, but you didn’t modify your game file. Can you guess what happened? Try to figure it out. If you give up, click below for the solution.
[spoiler title=”Solution”]Your game data file was just fine. The issue is that you don’t have a saved checksum in your Keychain, so RWGameData
ignored the file contents and zeroed out your high score. From now on the game will behave as expected, so no worries.[/spoiler]
Now, play a round or two to set a new high score, and check whether everything is working. What’s the highest score you can achieve?