The Deobfuscation: Analyzing Minecraft c0.0.13a

The version where Notch gave the names back, and we learned something about build pipelines.

On May 30, 2009 — nine days after c0.0.13a_03 introduced the obfuscation wall — Notch released c0.0.13a. Not c0.0.13b. Not c0.0.14a. The same version number, minus the _03 suffix. The same game, with one dramatic difference.

The class names are back.

Where c0.0.13a_03 gave us a.class, b.class, c.class through the alphabet, c0.0.13a gives us CalmLiquidTile.class, LiquidTile.class, ImprovedNoise.class, LevelLoaderListener.class. The single-letter identifiers that forced us to reverse-engineer every class by its structure are gone. In their place: readable, descriptive names that tell you exactly what each class does before you open it.

This is the same game. The same water physics, the same lava, the same bedrock, the same terrain generation, the same GUI system. The block IDs are the same. The rendering is the same. The behavior is the same. If you ran both versions side by side, you could not tell them apart. The only difference is that the source code — the .class files inside the JAR — now has human-readable identifiers instead of ProGuard-mangled single characters.

Which raises an interesting question: why?

The Build Pipeline Theory

Software does not obfuscate itself. ProGuard is a build tool. It sits between the Java compiler and the JAR packager, rewriting bytecode to strip debug information and rename identifiers. To produce an obfuscated build, you configure your build pipeline to include the ProGuard step. To produce an unobfuscated build, you skip it.

c0.0.13a_03 was obfuscated. c0.0.13a is not. The simplest explanation is a build configuration change: Notch toggled the ProGuard step off, either intentionally or by accident.

The intentional theory: Notch may have decided that obfuscation was not worth the effort for a game distributed as an in-browser applet in 2009. The JAR size reduction from ProGuard was real but modest — a few kilobytes on a JAR that was already small. The reverse-engineering protection was minimal for a game whose community was already poking at the internals. Perhaps he tried obfuscation, decided the cost-benefit was wrong, and turned it off.

The accidental theory: build configurations are fragile. A developer working rapidly — and Notch was shipping a new version nearly every day in this period — might switch between build profiles, test with a debug build, and accidentally publish the debug build as the release. The _03 suffix on c0.0.13a_03 suggests it was the third iteration of version 0.0.13a. The unsuffixed c0.0.13a may have been built from the same source but with a different build profile selected.

We cannot know which theory is correct. Notch’s build infrastructure from 2009 is lost to time. But the result is clear: the community received a readable version of the same game that had been opaque nine days earlier. For anyone trying to understand how the game worked — modders, curious programmers, or, seventeen years later, a Rust porting project — this was a gift.

What Changed: The File Count

c0.0.13a_03 had 44 files and approximately 4,699 lines of code. c0.0.13a has 52 files and approximately 4,927 lines. That is 8 new files and 228 new lines. Where did they come from?

The answer is mostly structural: deobfuscation reveals files that were previously merged or inlined by ProGuard, and it introduces a handful of new data classes that ProGuard had folded into their consumers.

The new and renamed classes fall into several categories:

Liquid tile specialization. Where c0.0.13a_03 had a single obfuscated liquid class that handled both water and lava through parameterization, c0.0.13a splits this into a hierarchy: LiquidTile as the base class and CalmLiquidTile as the settled variant. The names tell the story that the obfuscated version hid: there are two behavioral modes for liquids, and they are modeled as a class hierarchy. The “calm” variant is what we called “still” during deobfuscation — liquid that has stopped flowing and no longer needs tick updates.

Noise synthesis classes. Five new classes appear: Distort, Emboss, Rotate, Scale, and Synth. These are noise transformation primitives — operations that take a noise function and produce a modified noise function. Synth is the base class or interface. Scale multiplies the input coordinates by a scaling factor before sampling. Rotate applies a rotation matrix to the input coordinates. Distort uses one noise function to perturb the sampling coordinates of another (this is the operation we identified as CombinedNoise during deobfuscation). Emboss applies a bump-mapping-like transformation that emphasizes edges in the noise output.

These classes are a noise synthesis framework. They compose: you can scale a noise function, then distort it with another noise function, then emboss the result. The framework enables building complex terrain shapes from simple noise primitives by chaining transformations. In c0.0.13a_03, the same operations existed but were implemented as anonymous inner classes or inlined code that ProGuard had mangled beyond recognition. The deobfuscated version extracts them into named, standalone classes with clear purposes.

Whether these classes were always separate files that ProGuard merged, or whether Notch refactored them into standalone classes between the two releases, is uncertain. The behavior is identical either way. The terrain generator produces the same landscapes. The noise functions compute the same values. The class structure is different, but the output is the same.

Data classes. Three small classes appear that did not have clear counterparts in the obfuscated version:

  • Coord.java — a simple coordinate holder, likely x, y, z integer fields. This may have been inlined by ProGuard or may be new. It is a data class with no behavior beyond storage.
  • User.java — a user identity class, probably holding a username string for the multiplayer/server-save features. In the obfuscated version, this was likely a field on the main game class or an anonymous inner class.
  • LevelLoaderListener.java — a callback interface for the level loading system. This is an event listener pattern: when the level finishes loading (from the server or from disk), the listener is notified. In the obfuscated version, this was probably an anonymous implementation that ProGuard inlined into its call site.

These are not new features. They are new files that contain code that previously existed in other forms. The functionality is the same. The organization is different.

The Noise Synthesis Framework in Detail

The noise classes deserve closer examination because they represent a design pattern that will recur throughout Minecraft’s history: composable terrain generation primitives.

The Synth class is the root of the hierarchy. It defines the interface for a noise function: given coordinates, produce a value. The specific implementation might be Perlin noise, simplex noise, or any mathematical function that maps coordinates to scalar values.

Scale wraps a Synth and multiplies the input coordinates by a scaling factor before delegating to the wrapped function. Conceptually: scale(noise, 2.0) produces a noise function that varies twice as quickly. This is how you control the “zoom level” of noise — large scale factors produce fine detail, small scale factors produce broad features.

Rotate wraps a Synth and applies a rotation transformation to the input coordinates. This breaks the axis-alignment of the noise, preventing terrain features from running strictly north-south or east-west. A rotated noise function produces ridges and valleys at arbitrary angles.

Distort is the most interesting. It takes two Synth instances and uses the output of the first as an offset to the input coordinates of the second. This is domain warping — the sampling coordinates of one noise function are perturbed by another noise function. The result is terrain that avoids the uniform, predictable quality of standard fractal noise. Mountains bend. Valleys curve. The landscape has an organic irregularity that straight Perlin noise cannot achieve.

Emboss applies a bump-mapping operation. It samples the noise at two nearby points, computes the difference, and uses that difference to emphasize edges. The effect is similar to embossing in image processing: flat areas remain flat, but transitions between high and low areas are sharpened. In terrain terms, this makes ridges sharper and valleys more defined.

These five classes compose into a directed acyclic graph of noise transformations. The terrain generator builds this graph, feeds coordinates in at the root, and reads terrain height from the output. The same graph, with different random seeds and parameters, produces different but structurally similar terrain each time.

In c0.0.13a_03, this graph existed in the bytecode but was invisible behind obfuscation. In c0.0.13a, the graph is explicit in the class hierarchy. The architecture was always there. Now we can see it.

What Did Not Change

Everything else.

The block type registry is identical: air (0), stone (1), grass (2), dirt (3), cobblestone (4), wood planks (5), bush (6), bedrock (7), flowing water (8), still water (9), flowing lava (10), still lava (11). Twelve block types, the same twelve block types, with the same IDs, the same textures, and the same properties.

The rendering is identical. Solid blocks render as cubes with face culling against opaque neighbors. Bushes render as X-crossed quads. Liquids render with lowered top faces, same-type face culling, and darkened brightness. The chunk mesher dispatches on the same tile types to the same geometry generation paths.

The terrain generator produces the same terrain. Bedrock at y = 0. Water table at two-thirds depth. Caves carved by worm algorithms. Grass on exposed dirt. The noise functions compute the same values through the same algorithm, whether the class is called CombinedNoise or Distort.

The player physics are the same. The entity system is the same. The timer ticks at the same rate. The fog effects apply the same parameters when the camera is submerged. The particle system emits the same particles. The font renderer draws the same characters.

The GUI system — pause menu, save/load screens, button widgets — is present in the same form. It was deferred in the c0.0.13a_03 port and remains deferred here.

The game is identical. The source is readable. That is the entire difference.

By the Numbers

Metric c0.0.13a_03 c0.0.13a Delta
Files 44 52 +8
Lines of code ~4699 ~4927 +228
Block types 12 (0-11) 12 (0-11) 0
Obfuscated Yes No Reverted
Noise classes 3 (mangled) 5 (named) + Synth base +3 visible files
Data classes Inlined/merged 3 standalone +3
GUI files 7 (obfuscated) 7 (readable) 0 functional change
Terrain output Identical Identical No change
Rendering Identical Identical No change

The +8 files and +228 lines are entirely organizational. Zero new features. Zero behavior changes. The delta is naming and file structure, not functionality.

The Value of Names

c0.0.13a is a lesson in what names are worth.

We spent substantial effort deobfuscating c0.0.13a_03. We traced method calls through layers of indirection, matched class structures to known patterns, and reconstructed semantic names from behavioral analysis. The work produced correct results — we correctly identified every class and method that mattered for the port — but it took longer than any previous analysis. Not because the code was complex, but because the code was opaque.

c0.0.13a hands us the answer key. Every name we reconstructed through detective work is confirmed (or corrected) by the deobfuscated source. CalmLiquidTile confirms our “still liquid” identification. LiquidTile confirms our “flowing liquid” identification. ImprovedNoise confirms our noise class mapping. The deobfuscated source validates the deobfuscation work we did on the previous version.

But more than validation, the names reveal intent. We identified a class as “the noise class that composes two other noise functions.” The name Distort tells us that Notch thought of this operation as distortion — warping one function’s output with another. We identified a class as “the noise class that emphasizes edges.” The name Emboss tells us that Notch thought of this as embossing — a visual metaphor from image processing. The names carry information about the developer’s mental model that no amount of structural analysis can reconstruct.

Names are documentation. They are the cheapest, most pervasive form of documentation a codebase has. When they are stripped away, the code still works — the compiler does not need names to generate correct bytecode — but the code stops communicating. c0.0.13a_03 works. c0.0.13a communicates.

For the Port

The Rust port requires no code changes. The game is functionally identical to c0.0.13a_03, which is already ported. The window title updates to reflect the new version. The assets are extracted from the new JAR to assets/c0.0.13a/. The 97 existing tests all pass without modification, because the tests verify behavior, and the behavior has not changed.

This is the first version where the port is trivial — not because the version is simple, but because the previous version already did the work. The obfuscated c0.0.13a_03 and the deobfuscated c0.0.13a are the same game. The port of one is the port of both.

The interesting question is what happens next. Notch turned obfuscation on for c0.0.13a_03. He turned it off (or left it off) for c0.0.13a. Will the next version be obfuscated or readable? The answer will tell us whether c0.0.13a was a deliberate choice or an accident. Either way, the detective work from c0.0.13a_03 taught us the workflow. If obfuscation returns, we are ready.

The game has not changed. The source has become legible. We move on.

By Clara

Leave a Reply

Your email address will not be published. Required fields are marked *