Pack.png
A Search By The Minecraft Community
pack.png
This document documents the method used for reversing the seed of pack.png, the default icon of minecraft texture packs and servers.

Brief Summary of what was useful

(Full up-to-date methodology coming soon)
The pack.png seed-finding process was incredibly long and complicated, even discounting the many dead ends along the way. This section is meant to be a quick summary of how the seed was found; for the full methodology, see below (if you’re reading this right after the initial announcement, the method below is most likely being updated and may not reflect the actual method used).
To start, we determined the world orientation using the water texture, allowing us to determine the orientation of the player. The cloud texture was then used to find where the positive x axis was, and further confirmed a general approximate world location. After this, a recreation was made to nail down some of the block positions. Various regression algorithms worked alongside the recreation to get block positions accurate, while also refining the exact location and orientation of the player. This, combined with the clouds, eventually resulted in us determining the z-coordinate of the waterfall source block, specifically z=-31.
Once this was established, we used the dirt heights in conjunction with the pattern of dirt/sand on the beach to determine the most likely x coordinates of the waterfall. By far, the most common coordinate that resulted from this was x=116, and since this coordinate kept turning up we decided to go with it. We marked some clearly visible, unambiguous positions of dirt and sand along the beach, and if these were combined with the coordinates of the waterfall it was possible to filter the world seeds. That is, given a certain world seed, we could determine (without opening the game) whether it could potentially be pack.png.
Due to the very large number of potential seeds for randomly generated minecraft worlds (281,474,976,710,656), no one person could check every seed themselves. Instead, we used the compute-sharing platform BOINC to search through this entire seedspace to get a list of around 700,000 possible world seeds. While this may seem like a very large number, it made the rest of the search very easy to complete. Once the candidate seeds were determined, they were run through another program which checked if the height of the terrain matched the recreation of pack.png. This resulted in only one seed by the end: 3257840388504953787

Contents

Contents        2
Version        4
Reading the code        4
Modding        5
The world seed        5
World generation        6
Tree generation        7
Recreation        8
Orientation        8
Location        8
Y coordinate        8
Clouds        9
Perspective fitting        10
Upscaling attempts        14
Chunk borders        14
Biomes        16
Dirt anomaly        16
Seed-reversal-gpu        20
Seed-tester-native        21
Seed-reversal-merged        21
Terrain filter        23
Manual testing        23



Version

We have identified the version to be a development version of alpha 1.2.2a. It cannot be from alpha 1.2.2a or later, as alpha 1.2.2a was released with the image in the game files. It cannot be from alpha 1.1.2 or earlier, as that update did not contain biomes, and had much brighter leaf colours not seen in the pack.png image. Alpha 1.2.2a was released on November 9th 2010, and according to the file metadata of pack.png, the image was last modified on November 8th 2010.

Reading the code and modding

Reading the code

The recommended way to read the code is to set up the alpha example mod. To do this you will need:
  • A copy of JDK8 or newer.
  • A copy of IntelliJ IDEA. The Community edition is free for everyone and very good, but the Ultimate edition is better and free with a student license.
To set up the example mod, follow these steps:
  1. 1. Clone/download the alpha example mod repository.
  2. 2. Open the folder you cloned/downloaded and open the command prompt/terminal. In Windows run the command “gradlew genSources”, or in Linux/MacOS run “./gradlew genSources”. Wait for the command to finish.
  3. 3. Open IntelliJ IDEA, click “Import Project”, select the folder you cloned/downloaded, go to the next screen and select “gradle” from the project types. Leave all the rest of the settings on default and click finish. Wait for the project to finish importing.
  4. 4. The Minecraft alpha sources can be found in “External libraries” -> “net.minecraft:minecraft:blah blah” -> “minecraft blah.jar” -> “net.minecraft”.
  5. 5. Open any Minecraft class, and click “attach sources” on the blue banner at the top of the editor. It should automatically open to the location of the jar file; select the sources jar directly above it and press OK.
Useful shortcuts in IntelliJ IDEA:
  • Ctrl+N: jump to Java file by name (press ctrl+N again to toggle between only showing files from your mod and showing all files).
  • Ctrl+left click on a reference: jump to declaration of method/field/variable/class.
  • Ctrl+left click on a declaration: find references of method/field/variable/class.
Sections in this document may contain references to parts of the code, marked in footnotes.

Modding

First, follow the steps for reading the code. Then, it’s the same as writing a normal Fabric mod, except remember there is no Fabric API, only the Fabric loader. For help with Fabric modding in general, ask in the Fabric discord (https://discord.gg/v6v4pMv), but remember they have a rule against mentioning MCP names there, so don’t share any alpha class/field/method names there. For mixin help, you can ask in the #mixin channel in the Sponge discord (https://discord.gg/sponge).

Basics of world generation in alpha

The world seed

There was no way to choose a world seed in alpha 1.2.2a, all world seeds were random.
World seeds have a data type of “long”, which means they can be in the range -2^63 to 2^63-1 inclusive, so at first glance it looks like there should be 2^64 possible seeds. However, random seeds are generated using the following code:
worldSeed = new Random().nextLong();[1]
Java’s Random object uses an RNG with only a 48-bit seed internally, meaning that there are only 2^48 possible outputs of nextLong. Moreover, some of these outputs are repeated, meaning that in actual fact there are only 2^48 * 0.82 possible seeds, or about 231 trillion.

System time (spoiler: this got us nowhere)

How “new Random()” initializes its seed is platform and Java version dependent. Notch confirmed that they were using Java 6 and Windows 7. In Java 6, the seed was initialized as follows:
setSeed(++seedUniquifier + System.nanoTime());[2]
Where “seedUniquifier” is a global variable that starts at 0 on program startup and increments each time a Random object is created.
System.nanoTime() returns a nanosecond-precision counter relative to an arbitrary point in time. On Windows 7, this returned the number of nanoseconds since system startup, except in 2010 most system clocks weren’t as advanced as today, and only had microsecond accuracy, which meant that until it overflowed (after a long time), System.nanoTime() would always be a multiple of 1000, and once it did overflow, it would still be a multiple of 8 (the greatest common divisor between 1000 and 2^64). However, since Random seeds are only 48-bit, the lowest 48 bits of System.nanoTime() would overflow sooner, after 2^48 nanoseconds or approximately 3 days. According to a Tumblr post by Notch from around the time, he tended to leave his computer on so it may well have overflowed. If the pack.png world is the first world to be created after game startup, then the Random which generated the world seed is the 17th Random to have been created, which would make the lowest 3 bits of the seed 001. However, this is a risky assumption to make, as multiple worlds may have been created. If it wasn’t the first world, then we can make no accurate guess on any bits of the seed using the system time.

World generation

Chunks are generated in 2 stages: “generation” and then “population”.
Generation[3] handles the basic terrain: its height, shape, the top decoration and caves. The shape of the terrain[4]  is decided by perlin noise generators seeded based on the world seed. Decoration[5] refers to the top blocks of the terrain, usually grass and dirt and water below sea level, but sometimes sand and gravel and other blocks too, and the bedrock underground. The RNG used for this step is independent from the world seed and only dependent on location, which is why bedrock is the same in every world; however, some things in this step also use perlin noise generators which are dependent on the world seed, so we can’t tell location from the sand on the beaches, for example. This independent from world seed fact will be used later on. Caves are seeded in the same way as population[6], but cannot really be used as it is hard to tell from just the pack.png image where any caves start generating.
Population[7] handles the more varied aspects of world generation, such as trees, flowers, dungeons and ores (and in later versions, structures). Population occurs the first time a 2x2 area of chunks is loaded, in the 16x16 block region in the middle of them (therefore offset by 8 blocks). This is done so that trees and other features don’t get cut off on chunk borders. This 16x16 region offset by 8 blocks is referred to as the “population region”.
Almost the entirety of population is done using a single Random object seeded from the world seed and location of the region as follows:
rand.setSeed(worldSeed);
long a = rand.nextLong() / 2 * 2 + 1;
long b = rand.nextLong() / 2 * 2 + 1;
rand.setSeed((chunkX * a + chunkZ * b) ^ worldSeed);
Where ^ is the bitwise xor operator, and / is the integer division operator, which always removes the decimal part (i.e. rounding towards zero). The seed of the Random after this point is referred to as the “population seed” or “chunk seed”.
Chunk seeds are important because there are only 2^48 of them, they are a way to gain information about the seed without explicitly knowing the location. Instead of cracking the world seed directly, we instead crack the seed at this point, and work backwards from there. A methodology for converting a chunk seed to a world seed given a guess of location will be shown later in this document.

Tree generation

There are a couple of annoying tree-related things in population which use simplex/perlin noise instead. One is biomes, which affects the number and types of trees which generate, and the other is what we call “tree density noise” or just “tree noise”[8], which is a perlin noise generator that also affects the number of trees which generate in a population region.
In rainforests, large trees can randomly generate where they otherwise couldn’t, which is both an extra random call (throwing off seed-reversal-gpu and seed-tester-native), and breaks one of our assumptions for the Z coordinate too (although we would still be very confident on that value). By observation of the image and foliage colour, we have concluded that the mountain is not in a rainforest biome.
Other than that, the number of tree attempts in a chunk is based on the tree density noise, with a biome-dependent constant added to it. The theoretical maximum number of tree attempts is actually very high, and if we went all the way to this maximum our search would be too slow. Therefore, we picked a lower maximum number of tree attempts of 12 which it is almost certainly below. To decide on this number, we simulated a large number of chunks for each biome:
Biome
Number of chunks tested
Total tree attempts in sample
Min. tree attempts in sample
Mean tree attempts in sample
Max. tree attempts in sample
Standard deviation
Rainforest
611
4356
2
7.129296
13
1.712436
Swampland
905
96
0
0.106077
1
0.308107
Seasonal Forest
2856
11191
-1
3.918417
9
1.575646
Forest
10130
68557
2
6.767720
13
1.540529
Savanna
2756
276
0
0.100145
1
0.300248
Shrubland
3037
299
0
0.098452
1
0.297974
Taiga
2019
13621
3
6.746409
0%
10%
20%
30%
40%
50%
60%
70%
80%
90%
100%