The Obfuscation Wall: Analyzing Minecraft c0.0.13a_03
The version where the source code stopped being readable, and the world gained water.
On May 21, 2009 — five days after Minecraft got its name — Notch released c0.0.13a_03. It is the seventh distinct version in a week. The cadence is staggering: a new build roughly every day, each one adding features, fixing bugs, pushing the game further. But this version is different from every version before it in a way that has nothing to do with gameplay.
The class names are gone.
Every previous version shipped with readable, descriptive class names. Level.java. Player.java. Chunk.java. Tesselator.java. You could open the JAR, decompile the classes, and read the source like a textbook. The intent of each class was evident from its name. The architecture was transparent.
c0.0.13a_03 is the first obfuscated version. The classes are named a.class, b.class, c.class through single-letter identifiers. Method names are mangled. Field names are reduced to single characters. The decompiler produces output that compiles but reads like a cipher: a.a(b.c, d.e()) where previously there was level.setTile(hit.x, block.getId()).
This is ProGuard, the Java obfuscation tool that Notch adopted somewhere between c0.0.11a and c0.0.13a_03. ProGuard strips debug information, renames identifiers to minimal strings, and can inline and optimize bytecode. Its primary purpose is to reduce JAR file size and mildly discourage reverse engineering. For a game distributed as an applet over 2009 internet connections, smaller JARs meant faster load times. For us, trying to analyze and port the game seventeen years later, it means every class must be identified by its structure rather than its name.
Deobfuscation by Structure
The deobfuscation process is detective work. Without names, you identify classes by what they do.
A class with width, height, depth fields and a method that writes a byte array through GZIPOutputStream is the Level class. A class with x, y, z, xd, yd, zd fields and a method that calls moveRelative and applies gravity is the Player or Entity class. A class that allocates a FloatBuffer, calls glVertex3f, and counts vertices is the Tesselator. A class with a run() method that generates Perlin noise and fills a block array is LevelGen.
The process is systematic:
- Start with the entry point. The
main()method is in the class that extendsAppletorCanvas. Trace its constructor to find the game loop, the level, the renderer. - Follow the dependency graph. The entry point creates a level. The level has dimensions and a block array. The renderer references the level and calls the tesselator.
- Match signatures to known classes. A class with the exact same method count, field types, and algorithmic structure as
Level.javafrom c0.0.11a is probably the Level class, even if it is now calledf.class. - Verify by behavior. Rename the class in the decompiled source, recompile, and run. If the game behaves the same, the identification is correct.
This process took longer than the analysis of any previous version. Not because the code is more complex — it is not dramatically more complex than c0.0.11a — but because every line requires translation. Reading a.a(b, c, d) and understanding that it means level.setTile(x, y, z, blockId) requires tracing a to identify it as the Level class, finding that its method a takes three integers and a byte, and confirming that the byte array it writes to is the block storage array.
The obfuscation adds approximately zero technical difficulty to the port. Once the classes are identified, the code is the same code. But it adds substantial analytical difficulty. Every method call must be traced to its definition. Every field access must be mapped to a semantic name. The deobfuscation is a prerequisite to analysis, and the analysis is a prerequisite to porting.
What Changed: Liquids
The headline feature of c0.0.13a_03 is water and lava.
Previous versions had water and lava in the terrain generator — pools were carved into the landscape and filled with a water or lava block ID. But those blocks were simple: they occupied a cell in the block array, they had a texture, and the terrain generator placed them. They were static, decorative, and functionally identical to any other block except that the player could walk through them.
c0.0.13a_03 introduces a proper liquid system with four new block types:
- Flowing water (ID 8): water that has a direction and will spread.
- Still water (ID 9): water that has settled into an equilibrium.
- Flowing lava (ID 10): lava that has a direction and will spread.
- Still lava (ID 11): lava that has settled.
The flowing/still distinction is fundamental to how Minecraft’s liquid system works. When water is placed or a source block is created, it is “flowing” — it will attempt to spread to adjacent blocks on subsequent ticks. When it has spread as far as it can, it becomes “still.” The still/flowing distinction is a state flag that determines whether the liquid needs to be updated on future ticks.
Water uses texture index 14 on the terrain atlas. Lava uses texture index 30. Both are rendered differently from solid blocks:
- Non-solid: Liquid blocks do not block movement. Entities can walk through them (with modified physics — water slows movement, lava damages).
- Lowered top face: The top surface of a liquid block is rendered slightly below the full block height. This creates the visual effect of water filling a basin rather than forming a perfect cube. The top face sits at approximately y + 0.9 rather than y + 1.0.
- Face culling against same liquid: Adjacent liquid blocks of the same type do not render shared faces. Two water blocks side by side do not render the face between them, creating a continuous body of water rather than a grid of visible cubes.
- Darkened rendering: Liquid blocks are rendered with reduced brightness, giving them a denser, more translucent appearance.
The liquid rendering is the most visually impactful change in this version. A lake of water in rd-161348 was a flat expanse of blue-textured cubes. A lake of water in c0.0.13a_03 is a continuous body with a slightly recessed surface, no internal faces, and a darkened tone that suggests depth. The visual improvement is dramatic.
Bedrock
Block ID 7, texture index 17. Bedrock is the indestructible floor.
In previous versions, the bottom of the world was simply the lowest layer of generated terrain — usually stone or dirt. A player could dig through it and fall into the void. There was no bottom.
Bedrock changes this. The terrain generator now places bedrock at y = 0, forming an unbreakable floor beneath all other terrain. The block is rendered with a distinct cracked-stone texture and cannot be destroyed by player interaction. The isDestructible() check (or its obfuscated equivalent) returns false for block ID 7.
This is a small addition with large gameplay implications. The world now has a defined bottom. The player can dig all the way down and hit something immovable. The void below the world is no longer accessible through normal gameplay. Bedrock is Minecraft’s first truly indestructible element — the first thing in the game that the player cannot change.
Terrain Generation: LevelGen
The terrain generator is now a substantial piece of code: 338 lines in the deobfuscated source, extracted into its own class. The algorithm has evolved significantly from the simple Perlin noise heightmap of earlier versions.
The noise system now uses three distinct noise implementations:
- ImprovedNoise: Ken Perlin’s “improved” noise algorithm, as published in his 2002 paper. This produces smoother gradients than the simple Perlin noise used in earlier versions. The implementation includes Perlin’s permutation table and gradient computation.
- OctaveNoise: A wrapper that sums multiple octaves of ImprovedNoise at different frequencies and amplitudes. This produces fractal noise with both large-scale terrain features (mountains, valleys) and small-scale surface detail (bumps, roughness). The number of octaves controls the level of detail.
- CombinedNoise: Composes two OctaveNoise instances by using the output of one as an offset to the input of another. This produces terrain features that are more varied and natural-looking than simple octave noise — the output of one noise function warps the sampling coordinates of another, creating non-uniform terrain patterns that resist the repetitive look of standard fractal noise.
The terrain generation algorithm now includes:
- Height map generation using CombinedNoise for base terrain shape.
- Cave carving: random worms that bore through solid terrain, creating underground tunnel systems. Caves are carved by tracing paths through the block array and removing blocks along the path.
- Water pools: water is placed at a fixed level (typically half the world height or slightly below average terrain height), filling any air block below the water level that is adjacent to other water or at the surface. Water pools form in valleys and caves.
- Lava pools: lava is placed at the lowest levels of the world, filling cavities near the bedrock layer.
- Surface decoration: grass is placed on the topmost dirt block at each column.
The terrain generator has evolved from “noise-based heightmap with simple material assignment” to a multi-pass system that creates varied underground and surface features. The worlds generated by c0.0.13a_03 are recognizably Minecraft-like in a way that earlier versions were not: they have caves to explore, water to swim in, lava to avoid, and bedrock to stand on.
GUI System
Seven new files relate to a graphical user interface system: pause menu, save/load screens, interactive buttons. The GUI system renders 2D overlays on top of the game view, handles mouse click events on button widgets, and manages screen transitions (pause to save, save to load, etc.).
The save/load system communicates with minecraft.net via HTTP to store and retrieve levels on Notch’s server. This is Minecraft’s first cloud save system — players could save their worlds to the internet and load them from any computer running the applet.
Both the GUI system and the server-based save/load are deferred in the Rust port. The GUI adds seven files of screen management and widget rendering infrastructure that builds on the font system but introduces its own event handling, layout logic, and state machine. The server save/load requires HTTP communication with a service that no longer exists. Neither feature affects core gameplay — the game runs, renders, and plays identically with or without the pause menu. These features will be ported when they become prerequisites for gameplay changes in a future version.
New Assets
Four individual texture files appear alongside the existing terrain.png atlas:
dirt.png— standalone dirt block texturegrass.png— standalone grass block texturerock.png— standalone stone/rock block texturewater.png— standalone water texture
These are individual textures that duplicate what already exists in the terrain atlas. Their purpose is unclear — they may have been used for GUI previews, for the block selection HUD, or they may simply be source files that were accidentally included in the JAR. The terrain atlas remains the authoritative source for block rendering. The individual textures are extracted to assets/c0.0.13a_03/ for completeness but are not used in the Rust port’s rendering pipeline.
Fog Effects
Underwater and in-lava fog effects are added. When the camera is submerged in water, the fog distance is dramatically reduced and the fog color shifts to a blue-green tint, simulating reduced visibility underwater. When submerged in lava, the fog distance is even shorter and the color shifts to deep orange-red.
The fog effect is implemented by checking which block the camera position occupies. If the camera is inside a water block, fog parameters are modified. If inside a lava block, different fog parameters are applied. In the OpenGL source, this is a glFog call with modified color and distance values. In our wgpu port, fog is a uniform value passed to the fragment shader, and the underwater/in-lava state modifies that uniform.
By the Numbers
| Metric | c0.0.11a | c0.0.13a_03 | Delta |
|---|---|---|---|
| Files | 38 | 44 | +6 |
| Lines of code | ~3421 | ~4699 | +1278 |
| Block types | 9 (0-8) | 12 (0-11) | +3 new (bedrock, flowing/still water, flowing/still lava) |
| Terrain gen lines | ~200 | ~338 | +138 |
| GUI files | 0 | 7 | +7 (deferred) |
| Noise implementations | 1 (Perlin) | 3 (Improved, Octave, Combined) | +2 |
| New textures | 0 | 4 individual files | +4 |
| Fog states | 1 (normal) | 3 (normal, underwater, in-lava) | +2 |
| Obfuscated | No | Yes | First obfuscated |
The Obfuscation Inflection
c0.0.13a_03 is an inflection point for this project, and not because of water or lava or bedrock. It is an inflection point because of the obfuscation.
Every version from rd-132211 through c0.0.11a was a gift. The source code was readable. Classes had names that described their purpose. Methods had names that described their behavior. You could decompile a JAR and read the code like documentation. The architecture was transparent because the naming was transparent.
From c0.0.13a_03 forward, that gift is gone. Every version requires deobfuscation before analysis. Every class must be identified by its structure. Every method must be traced through its call graph. The analysis overhead doubles or triples, not because the code is more complex, but because the code is deliberately opaque.
This is the reality of reverse engineering a commercial product. The early builds were experiments, shared openly, with no thought given to protecting the source. As the project became a product — as “Minecraft” replaced “RubyDung” — the source became something worth protecting. Obfuscation is the natural consequence of a project transitioning from open experiment to commercial software.
For this project, it means every future analysis post will include a deobfuscation section. It means the time per version increases. It means the detective work of “what does class q do?” becomes a permanent part of the workflow.
But it does not change what we are doing. The code is the same code whether the class is called Level or f. The blocks are the same blocks. The rendering is the same rendering. The obfuscation is a barrier to reading, not a barrier to understanding. Once you have read past the single-letter names and mapped them to their purposes, the code is as clear as it ever was.
The game is growing. The source is hiding. The port continues.