Can AI Deobfuscate Minecraft?
Tags: java, ai, chatgpt, minecraft
Published: 2023-01-05 19:05:19

OpenAI's ChatGPT, like many similar Large Language Models, has been used to perform many programming-related tasks. Writing obfuscation mappings seems to be a good fit for its abilities, as it involves reading and summarising text (code). I tested its capabilities.

## Background

If you already understand what obfuscation mappings are, feel free to skip this part.

_Minecraft: Java Edition_ is a game written in Java (_quelle surprise_). Java source code is compiled to JVM bytecode for distribution, a little like the relationship between C and machine code.

To modify the game, we want access to the Java source. However, there are two things in the way:

Firstly, we have to _decompile_ the JAR, turning the bytecode back into Java. This is a very difficult job: a lot of work has been put into the [tools](https://github.com/QuiltMC/quiltflower/) to make this happen.

However, even then, we do not have readable code: Mojang, the developers of Minecraft, put their code through [ProGuard](https://www.guardsquare.com/proguard), which shrinks the bytecode by doing things such as renaming all methods and classes to things like `a`, `b`, `c`, `az`, and so on. So we need a _mapping_ from these obfuscated names to readable names!

For a while, the main mappings available were made by the [MCP](http://www.modcoderpack.com/) project, and had a restrictive license. These were used first by MCP itself, and later [Minecraft Forge](https://minecraftforge.net/), a modloader and API.

When [Fabric](https://fabricmc.net/), another modloader/API project, came onto the scene in 2018, it had to make its own mappings, which it called Yarn. It made Yarn freely available (CC-0), and effected a 'cleanroom policy' - no MCP names were to be mentioned in any Fabric community spaces, in case a Yarn contributor was 'tainted' by the strictly-licensed MCP mappings.

And so it came to pass that the mappings were fractured; for there were two names for each class. "Lo," [said Mojang](https://www.minecraft.net/en-us/article/minecraft-snapshot-19w36a), "we shall bestow unto them an Official Mappings set, exported from Proguard itself".

![XKCD Standards Comic](https://imgs.xkcd.com/comics/standards.png)

Forge quickly switched to the Official Mappings. Fabric decided that a non-public domain mappings set wouldn't do; and besides, these names were all a bit strange and unlike what they were used to anyway, so they would prefer to maintain and use their own mappings, thank you very much.

These days, there are four main mappings sets:
 - [Official Mappings](https://piston-data.mojang.com/v1/objects/42366909cc612e76208d34bf1356f05a88e08a1d/client.txt) (aka. Mojmap) exactly like what Mojang sees but without parameter names.
 - [Parchment](https://parchmentmc.org/), a project to create mappings for parameter names, and also including Javadoc, to be layered on top of the Official Mappings.
 - [Yarn](https://github.com/fabricMC/yarn), still going with its cleanroom meant to protect against a mappings set that no longer exists.
 - [Quilt Mappings](https://github.com/QuiltMC/quilt-mappings/). Quilt is a fork of Fabric made by many former-Fabric developers. Quilt Mappings are like Yarn, but without a cleanroom, and so may take inspiration from the Official Mappings. Its main purpose is to be like Yarn, but Mojmappier, or to be like Mojmap, but Yarnier - depending on who you ask.

## First Tests

First, I set up a Quilt Mappings workspace. Why Quilt Mappings and not Yarn? Two reasons:
 - QM is missing mappings for more things, so it was easier to find obfuscated names for testing.
 - The QM workspace allows using the [QuiltFlower](https://github.com/QuiltMC/quiltflower/) decompiler, which can produce much nicer output than many other decompilers.

It took me a few tries to find a prompt that worked well. I settled on this:

> I have this Java method in the class `ClassName`, but I don't know what to call it:
> ```
> code snippet
> ```
> 
> Please suggest a name for this method and each of its parameters.

### NativeImage#copyRect

I first tried this method in `NativeImage`:
```java
    public void m_vmneozhc(NativeImage arg, int i, int j, int k, int l, int m, int n, boolean bl, boolean bl2) {
        for(int o = 0; o < n; ++o) {
            for(int p = 0; p < m; ++p) {
                int q = bl ? m - 1 - p : p;
                int r = bl2 ? n - 1 - o : o;
                int s = this.getPixelColor(i + p, j + o);
                arg.setPixelColor(k + q, l + r, s);
            }
        }
    }
```

![Will asks ChatGPT to name the method and each parameter. It replies with 'copyImage - sourceImage, sourceX, sourceY, targetX, targetY, width, height, flipHorizontally, flipVertically'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thhs3f8swyln21yt6ly3.png)

It suggests I call the method `copyImage(NativeImage sourceImage, int sourceX, int sourceY, int targetX, int targetY, int width, int height, boolean flipHorizontally, boolean flipVertically)`.

This is pretty good! Mojang calls the method `copyRect`, quite similar to ChatGPT's naming. It also names the parameters sensibly.

### LocalPlayer#sendIsSprintingIfNeeded

Next, I tried this method, in the class which QM calls `ClientPlayerEntity` (but Mojang calls `LocalPlayer`):
```java
    private void m_nfwipcth() {
        boolean bl = this.isSprinting();
        if (bl != this.lastSprinting) {
            ClientCommandC2SPacket.Mode lv = bl ? ClientCommandC2SPacket.Mode.START_SPRINTING : ClientCommandC2SPacket.Mode.STOP_SPRINTING;
            this.networkHandler.sendPacket(new ClientCommandC2SPacket(this, lv));
            this.lastSprinting = bl;
        }
    }
```

![Will asks ChatGPT to name the method. It replies with 'updateSprintingStatus'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1x2u5a8mxthadnfco9f6.png)

It suggests `updateSprintingStatus()`.

This is _not wrong_, but it's a bit generic as a name - it doesn't capture that it involves telling the server over the network about the player's sprinting status.

### MultiPlayerGameMode#performUseItemOn

Lets try a longer method. I'm not going to show you the whole method this time (Minecraft is closed-source, after all), but I'll show you a snippet of this method in the class QM calls `ClientPlayerInteractionManager`, and Mojang calls `MultiPlayerGameMode`:

```java
    private ActionResult m_wiqrsdhj(ClientPlayerEntity arg, Hand arg2, BlockHitResult arg3) {
        BlockPos lv = arg3.getBlockPos();
        ItemStack lv2 = arg.getStackInHand(arg2);
        if (this.gameMode == GameMode.SPECTATOR) {
            return ActionResult.SUCCESS;
        } else {
// ...
```


![Will asks ChatGPT to name the method and each parameter. It replies with 'attemptBlockInteraction - player, hand, blockHitResult'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ar76hmm1k4vfq7555u1.png)

This is alright. The method is run when right-clicking on a block - but only when holding an item. ChatGPT's name doesn't really capture this. Still, it's less ugly than Mojang's `performUseItemOn`.

## Can it do more?

So ChatGPT can name methods, not perfectly, but alright. But can it map whole classes?

I tried it out with this prompt:

> I have this Java class, but all the names are garbled:
> ```
> code snippet
> ```
> 
> For context, \<context of how the class is used>.
> 
> For each garbled name, suggest a proper name.

### BlockStatePredictionHandler

I found a class which wasn't mapped in QM - Mojang calls it `BlockStatePredictionHandler` - and stuck it in the prompt. I added some context, too:
> For context, the class `ClientWorld` has a private final field of this class. The methods of this class are called from the classes `ClientWorld` and `ClientPlayerInteractionManager`.

![Will asks ChatGPT to name the symbols in the class. It replies with the results shown below](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0p9cgtjy385w7y3q9nfz.png)

| Placeholder  | Mojmap                        | ChatGPT               |
|--------------|-------------------------------|-----------------------|
| `C_czisrdmd` | `BlockStatePredictionHandler` | `BlockStateTracker`   |
| `f_irkdwisi` | `serverVerifiedStates`        | `blockStateMap`       |
| `f_hcvpxtyt` | `currentSequenceNr`           | `tickCounter`         |
| `f_bmpbngyn` | `isPredicting`                | `isTracking`          |
| `m_plwiurco` | `retainKnownServerState`      | `addBlockState`       |
| `m_mmtdqsga` | `updateKnownServerState`      | `updateBlockState`    |
| `m_vfahppjl` | `endPredictionsUpTo`          | `rollbackBlockStates` |
| `m_rhbdpkkw` | `startPredicting`             | `startTracking`       |
| `close`      | N/A                           | `stopTracking`        |
| `m_gqtwgmpw` | `currentSequence`             | `getTickCounter`      |
| `m_nssimvch` | `isPredicting`                | `isTracking`          |
| `C_ivvmpfib` | `ServerVerifiedState`         | `TrackedBlockState`   |
| `f_bfinrexc` | `playerPos`                   | `playerPosition`      |
| `f_ovpelcnt` | `sequence`                    | `trackedTick`         |
| `state`      | `blockState`                  | `blockState`          |
| `m_twfpffxt` | `setSequence`                 | `updateTrackedTick`   |
| `m_fxchmlnj` | `setBlockState`               | `updateBlockState`    |

While ChatGPT's names are coherent, and certainly consistent (see the consistency between fields and their getters/setters), it has a few flaws. See, for example, how it tried to rename `close`, a non-obfuscated name inherited from Java's `AutoCloseable`.

I didn't know what this class was actually for, so I asked ChatGPT - perhaps this would also give us some insight into why it named the symbols in the way it did?

![Will asks ChatGPT what the class does. It replies: "The class is used to track changes to block states in a world. It stores the original block state and the tick it was changed, as well as the player's position when the change occurred. It has methods for adding new block states, updating existing ones, rolling back block states to a certain tick, and starting and stopping tracking. It is used to undo block changes made by a player in creative mode when the player leaves the game."](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ka514zij1emgcl66v4vv.png)

The last part of that, at least, is completely incorrect - block changes made by a player in creative mode are definitely _not_ rolled back when the player leaves the game. This cast doubt on the rest of the explanation, so I searched through a few Discord servers to see if anyone else had an explanation.

Here's a (trimmed down) explanation by [SizableShrimp](https://github.com/SizableShrimp).

> Basically, clients keep track of a sequence counter in their `BlockStatePredictionHandler`. When client wants to perform any block modifying actions from `MultiPlayerGameMode`, it will gen a new sequence number by increasing the counter.
> Now, while in prediction mode, any calls to `ClientLevel#setBlock` will not be actually set in-world! They will be stored in the prediction handler.
> Now, after client finishes its clientside block-modifying action, this sequence number is sent to the server alongside the action the client is requesting to do. Server will then do the block-modifying action that the client requested (if possible).
> Once the server is "done", it will send a packet back to the client that initiated the action to process all block sequences up to that sequence id.
> They use this predictive action system thingy so that there aren't visual glitches on the client when what the client thinks should happen is different from what the server actually performs.
> The client will now delay setting blocks during predictive actions, wait for a server response on what actually happened, and then update it on the client.

(Full explanation is available in [this message](https://discord.com/channels/313125603924639766/978656989506830406/982904891724927027) on the Forge Discord)

Interesting - so ChatGPT correctly told us that the class tracks block changes and can roll them back, but it misinterprets the sequence number as a tick counter, and doesn't understand that the class is used to hold off on committing to clientside predictions until verified by the server.

It doesn't understand _anything_, in fact. This is one of the flaws of asking ChatGPT to explain something to you - it has no consistent internal model of the world. ChatGPT, like other Large Language Models, is able only to read some text and write more text that sounds like it should come after - as a result, it is very prone to giving completely incorrect answers about things while sounding very confident and authoritative.

### MatrixUtil

Let's try one more class. This one has some methods for messing with matrices. It's unmapped in QM; Mojang calls it `MatrixUtil`. It's quite long (~200 lines) compared to our other tests.

I gave ChatGPT the class, with the context:

> For context, `m_xockxffa` is called from the class `ItemRenderer`, and `m_jeovkadj` and `m_ibrymiig` are called from the class `AffineTransformation`.

![Will asks ChatGPT to name the symbols in the class. It replies with the results shown below](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fnm0gqw3ocwq4xx3sso6.png)

| Placeholder  | Mojmap             | ChatGPT                            |
|--------------|--------------------|------------------------------------|
| `C_bmzqkdhp` | `MatrixUtil`       | `MatrixMathHelper`                 |
| `f_arxnkhow` | `G`                | `RADIUS_FACTOR`                    |
| `f_ddnzxrmh` | `CS`               | `COSINE_OF_45_DEGREES`             |
| `f_kqccjreh` | `SS`               | `SINE_OF_45_DEGREES`               |
| `m_xockxffa` | `mulComponentWise` | `scaleMatrix`                      |
| `m_kcqrzkbi` | `approxGivensQuat` | `computeUnitVectors`               |
| `m_kniviwwf` | `qrGivensQuat`     | `computeUnitVectorsFromYawPitch`   |
| `m_skxdryqk` | `stepJacobi`       | `computeQuaternionFromMatrix`      |
| `m_kkfnnzhk` | N/A                | `computeQuaternionFromYawPitch`    |
| `m_kjbxjxks` | N/A                | `computeUnitVectorsFromQuaternion` |
| `m_pjmgwfwq` | N/A                | `computeMatrixFromQuaternion`      |
| `m_geumxflx` | N/A                | `computeMatrixFromYawPitch`        |
| `m_ibrymiig` | `svdDecompose`     | N/A                                |
| `m_jeovkadj` | `toAffine`         | N/A                                |

_Whoever named the constants must've been a mathematician._

ChatGPT has a good start: `MatrixMathHelper` is a solid name; `SINE` and `COSINE` `_OF_45_DEGREES` is descriptive (and impressive, seeing as it's computed in radians). Unfortunately, it goes downhill from there.

The rest of the names _look_ descriptive - in many cases, they look at first glance to be _better_ names than Mojang's. But once again, it's a case of ChatGPT being very good at sounding confident whilst being totally incorrect.

The so-called `scaleMatrix` method, taking a 4x4 Matrix and a float and returning a matrix with each element multiplied by the float, is named in a way that confuses matrix-scalar multiplication with the linear transformation of scaling.

`computeQuaternionFromMatrix` is a technically-correct name - the method takes a 3x3 matrix and returns a quaternion. But it fails to convey what the method actually _does_. `stepJacobi` at least points you [in the right direction](https://en.wikipedia.org/wiki/Jacobi_method).

There's an even more damning problem, though: ChatGPT started hallucinating methods.

In the table, you'll see some methods for which there are no ChatGPT names (because it got distracted and didn't name them), and some without Mojang names (because they don't exist - ChatGPT made them up).

Oh dear.

## Conclusion

ChatGPT is an interesting tool. There is no doubt that it is very good at text prediction and generation. This sometimes generalises to being able to paraphrase, summarise, and appear to understand the meaning behind text. This is not true - it cannot understand meaning.

For deobfuscation name mapping, Large Language models such as ChatGPT may be useful as [assistive tools](https://medium.com/tenable-techblog/g-3po-a-protocol-droid-for-ghidra-4b46fa72f1ff). They cannot be relied on to automate the naming process.

In the specific case of Minecraft, with the existence of Mojmap, there is limited value in maintaining alternate mappings. There is more value in augmenting Mojmap, e.g. with Parchment. Therefore it would be useful to see how the process of mapping parameter names and writing Javadoc can be made more efficient.

There is less room for error in parameter naming than class/member naming. Large Language Models could be used to help automate this process - however, specialised non-ML tools may be as or more useful.

Stay tuned - I will test ChatGPT's ability to write Javadoc for decompiled methods in another post. I don't suspect it will do very well.

Thanks to:
 - Shedaniel's [Linkie](https://linkie.shedaniel.me/mappings), an enormously useful tool for translating between mappings.
 - The QM and Yarn [contributors](https://github.com/QuiltMC/quilt-mappings/graphs/contributors)
 - [OpenAI](https://openai.com/blog/chatgpt/).
 - The Quiltflower [contributors](https://github.com/QuiltMC/quiltflower/graphs/contributors).