[Android Software/Hacking General [Developers Only]] mahler47:
You're playing your favorite Android game and you know that you've definitely met the requirements for one of the game's achievements, but for some reason the game doesn't register you as having obtained that achievement. If you're like me, this is extremely frustrating--enough to make you want to figure out how to hack the Google Play achievement system. So, here is a rough outline of a guide to help you do the same thing.

[IMG="example of achievement bliss"]http://i.imgur.com/nAtBRpS.png[/IMG]

Note: I acknowledge how useless and pointless these game achievements are. There is no real value to be gained by doing what I'm about to describe. There is only the personal satisfaction that comes from having figured out something for yourself.

Tools needed:

access to shell with root via adb on your computer AND/OR

SQLite Editor (https://play.google.com/store/apps/details?id=com.speedsoftware.sqleditor) (requires root)

It turns out that all of the Google Play achievement data resides in a sqlite database stored in /data/data/com.google.android.gms/databases/games_1234abcd.db. (The name of your db file will be different--you may have multiple versions of this file for each Google account that lives on your device.)

There are several important tables in this SQLite db file:

achievement_definitions --> defines all of the achievements that your device knows about

achievement_instances --> a record of which achievements have been obtained and when, progress toward incremental achievements

achievement_pending_ops --> table of pending updates to achievements that haven't been synced with Google's servers

games --> lists external_game_ids for all games that have been encountered

game_instances --> lists package names so that we can identify what games correspond to what context_ids

client_contexts --> contains information about apps that have recently communicated with Google's servers (get the context_client_id from this table)

To trick Google Play to add achievements, we add entries to the achievement_pending_ops table. Then opening Play Games will cause a sync and register those achievements as having been obtained. My advice is that you first get one of these pending_ops so that you can see its structure so as to create your own spoof entries. To do this, play any game in which you can get an easy achievement. Start the game on your device with internet connectivity and make sure you are signed in with your Google play account. Check achievements list. Turn off wifi. Continue to play game and get an achievement. Confirm that the achievement is listed in the achievements list. Still with no internet connectivity, use SQLite Editor (or use sqlite3 directly--see Step 1 below) to look at the contents of the achievement_pending_ops table. Make a note of your external_player_id---this number is the same for all games, so you just need to determine it once.

The tricky part of spoofing achievement_pending_ops entries is that you need a valid context_client_id from the table client_contexts for each pending_op row entry. This context_client_id seems to be created when an app syncs with Play services to obtain the list of achievements that the user has obtained. Once that transaction has occurred, then the table client_contexts will have a row that will show the appropriate context_client_id. But, you have to match up the game with the appropriate entry in the list of games in "games" and "game_instances" tables. Note that the name of the game is sometimes different between different tables, and you may need to fix that.

Once you (1) have identified your db file and (2) have recently played the game that you want to "hack", you're ready to follow these general steps:

Step 1: Pull the tables from your phone to your computer for analysis.

Connect to your device via adb from your computer (via USB or over the air). Issue these commands from your computer's shell/command prompt.

adb shell
su
sqlite3 -header -csv /data/data/com.google.android.gms/databases/games_1234abcd.db "select * from achievement_definitions" > /sdcard/achievement_definitions.csv
sqlite3 -header -csv /data/data/com.google.android.gms/databases/games_1234abcd.db "select * from achievement_instances" > /sdcard/achievement_instances.csv
sqlite3 -header -csv /data/data/com.google.android.gms/databases/games_1234abcd.db "select * from achievement_pending_ops" > /sdcard/achievement_pending_ops.csv
sqlite3 -header -csv /data/data/com.google.android.gms/databases/games_1234abcd.db "select * from client_contexts" > /sdcard/client_contexts.csv
sqlite3 -header -csv /data/data/com.google.android.gms/databases/games_1234abcd.db "select _id, external_game_id, display_name from games" > /sdcard/games.csv
sqlite3 -header -csv /data/data/com.google.android.gms/databases/games_1234abcd.db "select * from game_instances" > /sdcard/game_instances.csv
exit
exit
adb pull /sdcard/achievement_definitions.csv
adb pull /sdcard/achievement_instances.csv
adb pull /sdcard/achievement_pending_ops.csv
adb pull /sdcard/client_contexts.csv
adb pull /sdcard/games.csv
adb pull /sdcard/game_instances.csv
adb shell rm -f /sdcard/achievement_definitions.csv
adb shell rm -f /sdcard/achievement_instances.csv
adb shell rm -f /sdcard/achievement_pending_ops.csv
adb shell rm -f /sdcard/client_contexts.csv
adb shell rm -f /sdcard/games.csv
adb shell rm -f /sdcard/game_instances.csv

Replace games_1234abcd.db with your own db filename above.

If you don't know your external_player_id yet, you will also want to dump the players table. Look for your profile name in that table and make note of the corresponding external_player_id. You just need to figure this out once.

Note that if you don't have a computer handy, you could probably also just use SQLite Editor app to browse through the tables in Step 2. Look for the databases associated with Google Play Services (com.google.android.gms). The tables can be huge though, so that's why I'm recommending dumping the tables to your computer so that you can look at them more easily.

Step 2: Sift through the tables you've pulled to gather information for your new entry in the achievement_pending_ops table.

These are the necessary joins between the tables:

_id in achievement_instances corresponds to _id in achievement_definitions

_id in games corresponds to game_id in achievement_definitions

display_name in games somewhat corresponds to instance_display_name in game_instances

package_name in game_instances corresponds to package_name in client_contexts

Achievements that haven't been obtained yet will have a state of 1 or 2 in the achievement_instances table. State of 2 corresponds to a "secret" achievement.

Achievements that are one-off (nonincremental) will have a type of 0 in achievement_definitions. Achievements that are incremental (e.g. "kill 100 enemies" or "play 20 times") will have a type of 1.

You want to look for achievements that you haven't yet obtained, for which you can find a matching _id in client_contexts. For each achievement that you want to spoof, you will create a new row in the achievement_pending_ops table.

These are the fields of the achievement_pending_ops table and how you determine the right information to put in each field for a new row.

_id : doesn't really matter what you set this to as long as all of the numbers are unique

client_context_id : grab the _id in the client_contexts table corresponding to the game that you're seeking to hack

external_achievement_id : matches with external_achievement_id from the achievement_definitions table corresponding to the achievement you want to add

achievement_type : matches with type from the achievement_definitions table

new_state : put a 0 here

steps_to_increment: if the achievement is an incremental one, you'll fill in the number of increments to add to current value on the achievement. otherwise, leave blank

min_steps_to_set : i left this as blank

external_game_id : matches with external_game_id from the games table

external_player_id : fill in your external_player_id here

I created a Microsoft Access table that automates this process. If you are only doing this for a few achievements, it's probably not worth the effort to do something similar.

Step 3. Turn off wifi. Create new row(s) in your achievement_pending_ops table.

Turn off internet connectivity so that Google Play services won't start syncing until you're ready for it to do so.

Example: (Replace games_1234abcd.db with your own db filename below.)

adb shell
su
sqlite3 /data/data/com.google.android.gms/databases/games_1234abcd.db
INSERT INTO achievement_pending_ops values('1','100','CgkIqLeu8pkYEAIQFA','0','0','','','831584443304','100000000000000000000');
INSERT INTO achievement_pending_ops values('2','99','CgkI2MbIkbgNEAIQBg','1','0','62','','461745824600','100000000000000000000');
.exit

These two INSERT lines add two new rows to the achievement_pending_ops table with the fields listed in order. The first example above is for a non-incremental type achievement, and the second is for an incremental achievement.

If you mess up and need to erase the rows in the achievement_pending_ops table, issue this sqlite3 command:

DELETE FROM achievement_pending_ops;

Step 4: Initiate sync. Verify that it worked. Do a dance.

Restore internet connectivity on your device. To force a sync, open the Google Play Games app on your device, then touch your profile, then wait for the sync to occur. Sometimes a reboot is needed.

Sync is complete when you can see all of the new achievements showing up under each game in the Google Play Games app. It will take a while for the sync to occur. In my experience, you'll first see the XP points added to your profile. Then you'll see the achievements marked as obtained when you browse to the appropriate game in the Google Play Games app. Then finally, you'll see the achievements in the long list under your user profile. Once the achievements are synced, they should then show up in the achievement_instances table as having state = 0 and they'll be marked as achieved in your game.

At this point, you can launch some other games so as to trigger rows in the client_contexts table. Then repeat steps 1-4.

Note #1: Some games have their own achievement systems apart from the centralized Google Play services system. What I've described here has nothing to do with the former, and only has to do with the latter.

Note #2: Some games don't have a button to show Google Play achievements so it can be tricky to get these games to show up in the client_contexts table. In some cases, you can force an entry in the client_contexts table by playing the game to get at least one achievement. Once the banner notification for the achievement shows up, tap it and tap on "view all achievements"--this should be enough to get a row in the client_contexts table to appear.