Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

Pl3xMap File Protocol

BillyGalbreath edited this page Sep 20, 2022 · 8 revisions

Pl3xMap files are designed to hold relevant information about a Minecraft region in order to display data on the map which cannot be obtained from a PNG/WebP image.

Header

Signature

The 8 bytes at the start of this file will always contain the Pl3xMap signature.

The first 7 bytes, 70 6C 33 78 6D 61 70, can be decoded from HEX -> DEC -> ASCII to read as pl3xmap.

HEX:    70  6C  33  78  6D  61  70
DEC:    112 108 51  120 109 97  112
ASCII:  p   l   3   x   m   a   p

The eighth byte is the version of this file (currently 01).

Region Info

The next 12 bytes are reserved for 3 integers; The x and z coordinates of a region followed by the region's minimum build height.

int header  = 8;                         // the number of bytes to skip for the header
int regionX = getInt(bytes, header);     // get integer starting at the end of header
int regionZ = getInt(bytes, header + 4); // get next integer (integers are 32 bits, so 4 bytes)
int minY    = getInt(bytes, header + 8); // get next integer

// convenience method to get an integer from a byte array at a specific position
public int getInt(byte[] bytes, int position) {
    int val = 0;
    for (int i = 0; i < 4; i++) {
        val |= (bytes[position + i] & 0xFF) << (i * 8);
    }
    return val;
}

Body

Block Info

All remaining bytes represent blocks in the region. Each block is a 32 bit packed integer.

This is ordered from left-to-right then top-to-bottom, starting from the northwest corner of a region (described as z * 512 + x).

int count = 0;
for (int z = 0; z < 512; z++) {
    for (int x = 0; x < 512; x++) {
        int index = z * 512 + x;
        System.out.println(index == count++); // will always output true
    }
}

You can get a packed integer for a specific block from the file with this formula.

int header = 20;                           // the number of bytes to skip for the header and region data
int index = ((z & 511) * 512 + (x & 511)); // get the index from world coordinates
int position = header + index * 4;         // get the start position of a packed integer
                                           // (4 because 32 bit integer is 4 bytes)
int packed = getInt(bytes, position);      // getInt convenience method from above
System.out.println(packed);                // outputs the packed integer

Packed Integer

The packed integer contains the block, biome, and y coordinate. These are all integers after being unpacked.

The first 10 bits are the block.
The next 10 bits are the biome.
The remaining 12 bits are the y coordinate.

11111111111111111111111111111111 - 32 bits
1111111111                       - 10 bits (block)
          1111111111             - 10 bits (biome)
                    111111111111 - 12 bits (yPos)

To pack

int packed = ((block & 1023) << 22) | ((biome & 1023) << 12) | (yPos & 4095)

To unpack

int block = packed >> 22;
int biome = (packed << 10) >> 22;
int yPos = (packed << 20) >> 20;

Palette Indexes

Both blocks and biomes are stored as their palette index. Indexes are built when the BlockInfo plugin loads and stores the data in separate files.

Block indexes are stored at web/tiles/blocks.gz globally.
Biome indexes are stored at web/tiles/world/biomes.gz per world.

The palettes are simply JSON files with key->value pairs of a unique index and name.

Y Coordinate

The y coordinate is offset with the region's minimum build height. You can obtain the minimum build height from the region section of the file (explained above). Once you have both values you can simply add them together to get the actual y coordinate.

int y = yPos + minY;