Basically the first step would be to read the bytes of the blueprint file and decompress it (the game uses GZIP for compression). Once you have the decompressed byte buffer/array, you can start reading the single bytes. This is the structure of every blueprint (I hope I didn't miss anything - if you have any questions, don't hesitate to ask):
| Bytes | Info |
| 1 | blueprint version |
| 2 | blueprint size x |
| 2 | blueprint size y |
| 2 | blueprint size z |
| 8 | creation date (timestamp, amount of milliseconds passed since 1st of January 1970) |
| 4 | name length (String bytes) |
| xx | blueprint name (byte length see previous 4 bytes) |
| 4 | author name length (String bytes) |
| xx | author name (byte length see previous 4 bytes) |
| 4 | world name length (String bytes) |
| xx | world name (byte length see previous 4 bytes) |
| 4 | terrain data length |
| xx | terrain data (see below) |
| 4 | block data length |
| xx | block data (see below) |
| 4 | object data length |
| xx | object data (see below) |
| 4 | construction data length |
| xx | construction data (see below) |
| 4 | vegetation data length |
| xx | vegetation data (N/A) |
| 4 | preview image data length |
| xx | preview image data |
Compression information
The blueprint data is compressed using GZIP. Java provides a class "GZIPInputStream" for decompression (and GZIPOutputStream for compression). Java example for decompression:
public static byte[] decompress(byte[] input, int length) throws IOException{
try(ByteArrayInputStream in = new ByteArrayInputStream(input); ByteArrayOutputStream out = new ByteArrayOutputStream(); GZIPInputStream gzip = new GZIPInputStream(in)){
//create a temp buffer/array to read the data from the stream.
//length describes the length of the temp array (e.g. 2048)
byte[] tmpBuffer = new byte[length];
//once the end of stream has been reached, the read() method returns -1,
//so while the number of read bytes is >= 0, we continue to read to our
while((n = gzip.read(tmpBuffer)) >= 0){
//write the content of the temp buffer/array to the output stream
out.write(tmpBuffer, 0, n);
//store the content of the output stream in a new byte array
return out.toByteArray();
Display More
Block data information
Block arrays are short arrays (i.e. every index consists of 2 bytes). Block arrays are 3-dimensional arrays (blueprint size x * size y * size z) which are flattened (i.e. it is 1-dimensional). To access a particular index, you have to calculate it (x + y*sizex + z*sizex*sizez), e.g. instead of blocks[x][y][z] (which would be the regular way of accessing a 3d array) you have to access it like blocks[x + y * sizey + z * sizex * sizez)].
Java way to read the blocks:
//assumed the blueprint bytes are stored in a
//ByteBuffer called "buffer"
//get the block data length (4 bytes == int)
int blen = buffer.getInt();
byte[] blockdata = new byte[blen];
//fill the blockdata array with buffer data
//create a new short array (to hold the actual block data)
short[] blocks = new short[blockdata.length / 2];
//easiest way to fill the short array is to use a ByteBuffer
//it would be faster if you read the single bytes and convert
//them to shorts (big-endian), but this way is by far more convenient
ByteBuffer.wrap(blockdata).asShortBuffer().get(blocks);
Display More
Terrain data information
Terrain data is stored as byte array, i.e. a particular byte represents the actual terrain id. Similar to block arrays, the terrain array is flattened, so you have to access it in the same way (see above).
Object data information
Objects (furniture, doors etc) are represented by a special object which holds information about the typeid (short), the variation (byte), position x y and z (float) and rotation x y z (short). The first 4 bytes in the encoded blueprint bytes hold information about the actual amount of objects in this blueprint. Java way to read the objects:
//assumed the blueprint bytes are stored in a
//ByteBuffer called "buffer"
//get the total amount of bytes (4 bytes == int)
int olen = buffer.getInt();
byte[] objectdata = new byte[olen];
//fill the object byte array
//easiest way is to create a new ByteBuffer, although
//it would be faster to work with the blueprint buffer
//directly. However, we will take the easiest way here:
ByteBuffer objects = ByteBuffer.wrap(objectdata);
//get the amount of objects (4 bytes == int)
int amount = objects.getInt();
//create all objects. It's recommendable to create a
//separate class to hold the particular object information.
//Maybe create a new ArrayList or something like that to
ArrayList<BlueprintObject> list = new ArrayList<>(amount);
for(int i = 0; i < amount; i++){
BlueprintObject obj = new BlueprintObject();
obj.typeid = objects.getShort(); //short (2 bytes)
obj.variation = objects.get(); //1 byte
obj.posx = objects.getFloat(); //float (4 bytes)
obj.posy = objects.getFloat();
obj.posz = objects.getFloat();
obj.rotx = objects.getShort(); //short (2 bytes)
obj.roty = objects.getShort();
obj.rotz = objects.getShort();
//////////////////////////////////////////
//our individual object class to hold blueprint objects:
public class BlueprintObject{
Display More
Construction data information
Construction data is very much like the object data, only the information per construction element is different. Every construction element holds information about the typeid (short), the texture (int), position x y z (float), rotation x y z (short), size x y z (short), horizontal repetitions (byte), vertical repetitions (byte), gap (byte). Java way to get the construction elements (only rough information, since it's mostly the same as for object elements [see above]):
//assumed the blueprint bytes are stored in a
//ByteBuffer called "buffer"
//get total amount of bytes
int clen = buffer.getInt();
byte[] constructiondata = new byte[clen];
buffer.get(constructiondata);
//easiest way is to create a new ByteBuffer
ByteBuffer constructions = ByteBuffer.wrap(constructiondata);
//get the amount of construction elements (4 bytes == int)
int amount = constructions.getInt();
//create all constructions. It's recommendable to create a
//separate class to hold the construction information.
ArrayList<BlueprintConstruction> list = new ArrayList<>(amount);
for(int i = 0; i < amount; i++){
BlueprintConstruction con = new BlueprintConstruction();
con.typeid = constructions.getShort(); //short (2 bytes)
con.texture = constructions.getInt(); //int (4 bytes)
con.posx = constructions.getFloat(); //float (4 bytes)
con.posy = constructions.getFloat();
con.posz = constructions.getFloat();
con.rotx = constructions.getShort(); //short (2 bytes)
con.roty = constructions.getShort();
con.rotz = constructions.getShort();
con.sizex = constructions.getShort(); //short (2 bytes)
con.sizey = constructions.getShort();
con.sizez = constructions.getShort();
con.repetitionsh = constructions.get(); //1 byte
con.repetitionsv = constructions.get(); //1 byte
con.gap = constructions.get(); //1 byte
//////////////////////////////////////////
//our individual construction class to hold blueprint
public class BlueprintConstruction{
public byte repetitionsh;
public byte repetitionsv;
Display More
Additional information about object elements and construction elements
To convert the rotation to a world rotation or the size (of construction elements) to the world size, you have to multiply the short values with a conversion factor. For rotations, it is currently 0.025, for the size, it is 0.01. Example:
//convert to world rotation (in degree)
float worldrotation = rotationx * 0.025f;
//convert to world size (world units)
float worldsize = sizex * 0.01f;