How I turned a porn game into my bitch
Crush Crush is a 2016 indie idle game released by Sad Panda Studios. In the game, you date a couple of cool and unique girls who will each demand increasingly stupid and absurd things from you, all the while rewarding you with incredibly tame pictures of themselves and, if you have nudity enabled, some horny times.
Now, I don't really play idle games, in fact I think the term "playing" an idle game is quite generous when you consider the fact that the game pretty much just plays itself as long as you have an autoclicker on a couple of buttons.
This game in particular is Free-to-play meaning that it gets its money from microtransactions, most of which are for the game's premium currency, Diamonds. You earn diamonds by achieving little goals and can then spend them on money, speed increases, and more girls.
First break: Infinite diamonds
One day while I was bored out of my mind, I remembered playing around with Cheat Engine when I was younger, Cheat Engine is a little Windows program that allows you to scan and modify the memory of a running process. Looking around, I found a program for Linux called scanmem which does the same thing, but for Linux. It works as a command line program, but they also have an official GUI called GameConqueror which is what I used.
I believe you need to setuid the program for it to work, but I'm not sure as my polkit policies are very permissive.
To use GameConqueror or scanmem in general, you first need to identify the memory location you want to modify. CrushCrush is made in Unity, meaning that the memory allocation is likely going to be different each time you start the game. That's no big deal since you can just redo the process every time you need more diamonds.
The trick is that the game pretty frequently gives out diamonds in increments of 1 and you can always see your current diamond count. First, scan for your current number of diamonds, scan for =
a couple times at different intervals, unlock a new achievement, then scan for the new number of diamonds. After a while, you should only have one or two memory locations.
The =
instruction tells scanmem to only look for values that have not changed since the previous scan, this is really useful since often unrelated variables may be detected but since they are used for something other than diamonds, they will change before your diamond count does.
Now, you can just change the value of the memory location to whatever you want, just make sure it is a valid 32-bit integer since scanmem does not check if it's safe, I once accidentally set my diamond count too high, and it overflowed to a negative number, which is a pretty disappointing outcome of an attempt at getting infinite diamonds.
After a few seconds, the game will autosave and your diamond count will be overwritten with the value you set. Congratulations, you have just given yourself infinite free diamonds.
Since diamonds are the game's premium currency, they can be used to buy most other things and pretty much cheat your way to the endgame. However, a bunch of content is still locked away from you, namely, purchasable content and limited-time content.
Second break: Save data manipulation
There was no way that I could somehow emulate a purchase server and an event server to pretend that I'm able to access this content, so my next idea was gaslighting the game into thinking I did buy the content and that I did participate in all the limited time events.
The game's SaveData
folder didn't contain anything beyond the Steam cloud save metadata, so I turned to something else. I remember hearing somewhere about the pretty common rookie mistake of using Unity PlayerPrefs
to store game state. Anyway guess where I found the data.
Yuuuup, right there, .config/unity3d/Sad Panda Studios/CrushCrush/prefs
, the Unity PlayerPrefs
were right there.
Now, PlayerPrefs
is bad for storing gamestate for a variety of reasons, but one of them is the fact that it's actually just XML, so it's really wasteful for a lot of data and it's easy to manipulate. The devs were clever though, they obfuscated the keys and some values, meaning that I would have to somehow reverse engineer what each value was. With over 1000 keys, this was gonna take a long time!
Does something look strange in there? Look at that, strings ending in one or two =
signs! Yup, that's base64, meaning that this is the first time I've ever seen full reversible obfuscation in my life. I'm not gonna lie, this is fucking stupid.
This obfuscation could have worked as literally just a hash of the friendly key name, it would have been completely irreversible, but they did this! They were asking for it at this point.
Writing a python script to go through and decode what each key was supposed to be took a few minutes because python sucks but I ended up with this little gem:
#!/usr/bin/env python
import base64
import re
with open('prefs.new.xml', 'w') as n:
with open('prefs', 'r') as f:
encoded = f.read()
lines = encoded.split('\n')
for line in lines:
# Check if the line starts with ' <pref name="'
if not line.startswith(' <pref name="'):
n.write(line + '\n')
continue
starttrimmed = line[13:]
# Take the rest of the line until the next quote
found = re.findall(r'^.+?"', starttrimmed)
# Remove the last quote
found = found[0][:-1]
print(found)
# Decode the base64 string
try:
decoded = str(base64.b64decode(found))
except:
# If it fails, just write the line as it is
n.write(line + '\n')
continue
# Compensate for janky python stuff by removing the 'b' and the '
decoded = decoded[2:-1]
# Recompose the line
line = line.replace(found, decoded)
n.write(line + '\n')
This script takes the prefs
file and writes a new file called prefs.new.xml
which is the same as the original file, but with all the obfuscated keys and values decoded. Then I would simply manually edit the original file to change the values of the keys I wanted to change.
My excitement when this somehow worked was palpable
Exploring the file revealed a lot of what the game was tracking, I was able to find diamond, money, a bunch of stats for the various girls in the game, and a couple of weirder values.
You see, some keys could not actually be decoded as base 64, some keys had the letter h
appended at the end of another value's key, both would be numbers, and I wasn't able to correlate them with any values in the game at all. Intuition served me when I realized that integers here were all 32-bit, just like standard C# integers, but that some game values were doubles. These values with seemingly two keys actually had a key for the first 32 bits and a key for the second 32 bits, and the h
was just a suffix to differentiate them. Strange but I could work with it.
The first value I wanted to change was whatever keeps track of the events I've participated in. I found the key CompletedEvents
which stored a b64-encoded string, decoding it gave
echo "QUFBQUFBQUFBQUFBQUFBQUFBQkFDQUFBQUFBQUFBQkF3QU09" | base64 -d
AAAAAAAAAAAAAAAAAABACAAAAAAAAABAwAM=
Uh, is that another base64 string? Decoding it into a string gave back gibberish, but something told me this wasn't encoding a string. Think about it, all the game needs to track is each event and whether or not it was completed. This is a bitfield! I found a decoder online (on the cool website Cryptii) that could decode base64 directly into bytes and show be the bits, and as expected, I got this:
Now, I was aware that I completed the two latest events at the time of doing this, and look at that! Most bits aren't set but the last two are!
Reversing this process was simple, all I had to do was generate a new bitfield that was all ones, encode it to base64 and then encode that string to base64 again. The end result was this:
echo "//////////////////////////////////8=" | base64
Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLzg9
Plopping that right in there and starting the game assaulted me with pop-ups, I had just unlocked every limited-time event in the game! The only content that could hide from me now was the purchasable content.
I'd love to say that hacking the purchases was an exciting experience with lots of tinkering and reverse engineering, but it was actually completely identical to hacking the events. I found the keys PlayfabAwardedItems
and PlayfabFlingPurchases
. Setting those to the maximum 32-bit integer value and starting the game gave me all the purchasable content immediately, except for some reason the package of some mouse girl? I don't know, I didn't care. Exercise for the reader or whatever.
The lessons learned
This game really doesn't try that hard to protect itself from save data manipulation, but it still does try to some extent. That hints at the fact that the devs knew players could try to mess up their game, but either didn't have the skills or intention to implement proper protection. Additionally, the lack of any redundant or checks on the RAM means that the most basic of attack vectors could be used to manipulate the game state. If you really want to secure your game, you couldn't frankly do much worse, and I think that's great.
So many games nowadays have invasive anticheats, cloud requirements, and use too much DRM to the point where it's just not fun to play around with them outside of the very strict ways the devs intended for you. I think that sucks, games are like toys, I think you should be allowed to take them and play around with them however you want to. If games weren't like this years ago, we wouldn't have landmark titles like Half-Life, Counter-Strike, Team Fortress, and all games that took great inspiration from those titles. The game industry is built on top of the shoulders on these toys, and I think it's a shame that we're moving away from that. We are further and further away from an age where video games were, well, games, and not interactive movies. Where you could choose to play them truly however you wanted to, including fucking them up completely with cheats and mods.
Games need cheat options, they need debug menus, they need to be moddable, they need to be hackable. They need to be toys. They need to be fun.
This is why I love the indie scene, and why I think that they truly hold the future of gaming. Indie games are often a lot more open to community engagement with mods or other types of interactions, and they're usually pretty simple games where players can easily cheat their way to creating their own fun. Using the code and assets the game provides to create an experience truly like no other. I think that's what gaming is all about, and I think that's what we should be striving for.
Crush Crush could've been a game I just tried once and played for a few minutes, but Steam has over 33 hours recorded in it, and if we count all the time I spent on it by tinkering with its files, I'd say it's closer to 40 or 50 hours. That's so much enjoyment that I was able to squeeze out of the game because I could use it as a toy rather than a packaged experience. It's what the game represents to me, and to think that so many game companies are trying to move away from that is just sad.
I love you indie games, I love you moddable games, I love you games with cheat codes all around, I love you games with debug menus, I love you games that let me play around with them however I want to. I love you games that let me be a kid again, and I love you games that let me be a hacker again. I love you games that are still toys.
The end
I hope you enjoyed this little story of mine, do give Crush Crush a try, you might end up liking its minimal gameplay and what it offers, or you might end up bending it over the knee and turning it into your plaything. And sorry Sad Panda for hacking in all this content, I hope you don't mind too much.