Reverse Engineering Slay The Spire
Dave @ 2025-09-03
In this article, I describe the steps I took to reverse engineer the save game code of Slay the Spire to unlock an achievement more quickly.
What is reverse engineering?
Reverse engineering is the process of taking a product and observing its behaviour and deconstructing it to understand the how it works. This is often done with the aim of reproducing parts of it in another product or modifying the behaviour of the product in some way.
The game
Slay the Spire (2019) is a deck builder game developed and published by Mega Crit. In it, your goal is to climb the Spire by defeating a variety of enemies. To do so, you must assemble a deck of cards that are used in combat.
The goal
One of the most fun things to do in a deck builder game is to build decks that synergise in different ways to produce interesting effects. Unfortunately, the randomness involved in availability of cards can make this quite frustrating. If I could start the game with the deck the way we wanted it, I could get straight to the fun part without the frustration!
The problem
But how can I make that happen? An obvious answer is to create a saved game and then manipulate it to put the game into the state I want. If I start the game and save as soon as possible, I should have a fairly basic save file that will provide a good base to work from.
Starting a game as the Ironclad class and saving after the initial encounter provided me with a file called IRONCLAD.autosave
. Opening this, I saw what looked like a base64 string. Loading the file in CyberChef and running From Base64
gave an unreadable response, so I had to dig further.
Reverse engineering the game
In the game directory, there are several interesting files:
SlayTheSpire.exe
(364KB)desktop-1.0.jar
(356,531KB)mod-uploader.jar
(1,600KB)mts-launcher.jar
(1,250KB)
The presence of the jar (Java Archive) files suggested that this game was written in Java (or another JRE language). Given the small size of SlayTheSpire.exe
and comparatively large size of desktop-1.0.jar
, it's likely that the exe is a native wrapper for starting the jar, which would then contain the game logic.
I validated this theory by exploring the contents of the jar. jar files are zip archives structured in a particular way, so they can be extracted them with an archiving tool like 7-Zip.
After extracting the files and searching for filenames containing 'save', I found com/megacrit/cardcrawl/saveAndContinue/SaveFileObfuscator.class
, which sounded very promising. This is a Java class file which contains byte-code, which is run on a JVM (Java Virtual Machine). This can usually be decompiled into readable Java fairly easily.
In this particular case, I opened it in VSCode and used the Language Support for Java(TM) by Red Hat extension to decompile the class file and was shown the following:
// Source code is unavailable, and was generated by the Fernflower decompiler.
package com.megacrit.cardcrawl.saveAndContinue;
import org.apache.commons.codec.binary.Base64;
public class SaveFileObfuscator {
public static final String key = "key";
public static String encode(String s, String key) {
return base64Encode(xorWithKey(s.getBytes(), key.getBytes()));
}
public static String decode(String s, String key) {
return new String(xorWithKey(base64Decode(s), key.getBytes()));
}
private static byte[] xorWithKey(byte[] a, byte[] key) {
byte[] out = new byte[a.length];
for(int i = 0; i < a.length; ++i) {
out[i] = (byte)(a[i] ^ key[i % key.length]);
}
return out;
}
private static byte[] base64Decode(String s) {
return Base64.decodeBase64(s);
}
private static String base64Encode(byte[] bytes) {
return new String(Base64.encodeBase64(bytes));
}
public static boolean isObfuscated(String data) {
return !data.contains("{");
}
}
This shows a class with three public methods, encode
, decode
and isObfuscated
as well as a public field key
. The interesting one is decode
which does the following:
- Takes a string
s
and a stringkey
- Base64 decodes
s
into a byte array - Turns
key
into a byte array - Passes both byte arrays into
xorWithKey
, which xors them together for the length ofs
, repeatingkey
as necessary - Creates a string from the resulting byte array
Seeing this, I went back to CyberChef and added an xor step to the recipe, using "key" as the key. This output a JSON document containing the card list, as well as many other parts of the game state.
Putting it together
Using this recipe, I was able to decode the save file. After modifying it as needed, I could run the edited JSON through a recipe comprised of the steps in reverse order to produce a save file that the game would open1.
Summary
Deobfuscating save files required a small amount of effort, but provided a huge payoff in ability to control the game state. This approach can likely be applied to many other games.
-
I later discovered that the game will happily open an unobfuscated save file, so this step was unnecessary. ↩