Skip to content

Commit

Permalink
documentation for new classes
Browse files Browse the repository at this point in the history
  • Loading branch information
mircokroon committed May 19, 2019
1 parent 3bae685 commit 2f25bdb
Show file tree
Hide file tree
Showing 17 changed files with 121 additions and 22 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A Minecraft world downloader that works by intercepting & decrypting network tra

### Requirements
- Java 8 or higher
- Minecraft version 1.12.2 // 1.13.2 // 1.14.1
- Minecraft version 1.12.2+ // 1.13.2+ // 1.14.1+

### Basic usage
[Download](https://github.com/mircokroon/minecraft-world-downloader/releases) the latest release and execute the jar file using the commandline by running:
Expand All @@ -26,7 +26,7 @@ Then connect to ```localhost``` in Minecraft to start downloading the world. The
| --port | 25565 | Server port |
| --local-port | 25565 | Local server port |
| --output | world | Output directory (world root) |
| --mask-bedrock | false | If true, replace bedrock with stone |
| --mask-bedrock | false | If true, replace bedrock with stone. Does not currently work on 1.13+. |
| --center-x | 0 | Offset the world so that the given coordinate is at 0 |
| --center-y | 0 | Offset the world so that the given coordinate is at 0 |
| --gui | true | If false, hides the saved chunks GUI |
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private static Namespace getArguments(String[] args) {
parser.addArgument("-b", "--mask-bedrock").dest("mask-bedrock")
.setDefault(false)
.type(boolean.class)
.help("Convert all bedrock to stone to make world locations harder to find.");
.help("Convert all bedrock to stone to make world locations harder to find. Currently only for 1.12.2.");
parser.addArgument("-x", "--center-x")
.setDefault(0)
.type(Integer.class)
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/game/VersionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@
import java.util.List;
import java.util.stream.Collectors;

/**
* Class to handle versions from version.json file.
*/
public class VersionHandler {
private static final String VERSION_PATH = "version.json";
HashMap<Integer, Protocol> protocols;
private HashMap<Integer, Protocol> protocols;

// instantiated by Gson
private VersionHandler() {}

/**
* Create a new version handler by reading from version.json file. This file contains the packet IDs for each
* of the supported protocol versions.
*/
public static VersionHandler createVersionHandler() {
GsonBuilder g = new GsonBuilder();
g.registerTypeAdapter(Integer.class, new TypeAdapter() {
Expand All @@ -28,6 +36,7 @@ public void write(JsonWriter jsonWriter, Object o) { }

@Override
public Object read(JsonReader jsonReader) throws IOException {
// use decode instead of parse so that we can use hex values to display the packet ID
return Integer.decode(jsonReader.nextString());
}
});
Expand All @@ -37,6 +46,13 @@ public Object read(JsonReader jsonReader) throws IOException {
return g.create().fromJson(file, VersionHandler.class);
}

/**
* Get a protocol object, which contains the current game and protocol versions, as well as the packet IDs
* for the specific version. If the protocol version does not exist, it will pick the closest available one
* that is lower than the given version. If the given version is lower than all the existing ones, pick the lowest
* one.
* @param protocolVersion the protocol version (not game version)
*/
public Protocol getProtocol(int protocolVersion) {
List<Integer> versions = protocols.keySet().stream().sorted().collect(Collectors.toList());
int chosenVersion = versions.get(0);
Expand Down
22 changes: 18 additions & 4 deletions src/main/java/game/data/chunk/Chunk.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
import java.util.Objects;
import java.util.stream.Collectors;

/**
* Basic chunk class. May be extended by version-specific ones as they can have implementation differences.
*/
public abstract class Chunk {
protected static final int LIGHT_SIZE = 2048;
protected static final int CHUNK_HEIGHT = 256;
public static final int SECTION_HEIGHT = 16;
public static final int SECTION_WIDTH = 16;

private static final int DataVersion = 1631;

public final int x;
public final int z;
private Map<Coordinate3D, SpecificTag> tileEntities;
Expand Down Expand Up @@ -77,6 +78,10 @@ public void addTileEntity(Coordinate3D location, SpecificTag tag) {
tileEntities.put(location, tag);
}

/**
* Add a tile entity, if the position is not known yet. This method will also offset the position.
* @param nbtTag the NBT data of the tile entity, should include X, Y, Z of the entity
*/
private void addTileEntity(SpecificTag nbtTag) {
CompoundTag entity = (CompoundTag) nbtTag;
Coordinate3D position = new Coordinate3D(entity.get("x").intValue(), entity.get("y").intValue(), entity.get("z").intValue());
Expand Down Expand Up @@ -138,8 +143,6 @@ protected void parseLights(ChunkSection section, DataTypeProvider dataProvider)
}
}



private void setSection(int sectionY, ChunkSection section) {
chunkSections[sectionY] = section;
}
Expand Down Expand Up @@ -179,6 +182,10 @@ private CompoundTag createNbtLevel() {
return levelTag;
}

/**
* Add NBT tags to the level tag. May be overriden by versioned chunks to add extra tags. Those should probably
* call this (super) method.
*/
protected void addLevelNbtTags(CompoundTag map) {
map.add("xPos", new IntTag(x));
map.add("zPos", new IntTag(z));
Expand All @@ -204,9 +211,15 @@ private List<SpecificTag> getSectionList() {
.collect(Collectors.toList());
}

/**
* Parse the chunk data.
* @param dataProvider network input
* @param full indicates if its the full chunk or a part of it
*/
public void parse(DataTypeProvider dataProvider, boolean full) {
int mask = dataProvider.readVarInt();

// for 1.14+
parseHeightMaps(dataProvider);

int size = dataProvider.readVarInt();
Expand All @@ -218,6 +231,7 @@ public void parse(DataTypeProvider dataProvider, boolean full) {
addTileEntity(dataProvider.readNbtTag());
}

// ensure the chunk is (re)saved
this.saved = false;
}
}
13 changes: 12 additions & 1 deletion src/main/java/game/data/chunk/ChunkFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

import java.util.concurrent.ConcurrentLinkedQueue;

/**
* Class responsible for creating chunks.
*/
public class ChunkFactory extends Thread {
private static ChunkFactory factory;

Expand All @@ -29,13 +32,21 @@ private ChunkFactory() {
this.unparsedChunks = new ConcurrentLinkedQueue<>();
}

/**
* Update a tile entity that was given individually.
* @param position the uncorrected position of the tile entity
* @param entityData the NBT data of the entity
*/
public void updateTileEntity(Coordinate3D position, SpecificTag entityData) {
position.offset();

Chunk chunk = WorldManager.getChunk(position.chunkPos());

// if the chunk doesn't exist yet, ignore it
if (chunk != null) {
if (chunk == null) {
// TODO: support tile entities coming in before chunk packets have been parsed
System.out.println("Chunk does not exist yet! Skipping this tile entity.");
} else {
chunk.addTileEntity(position, entityData);
chunk.setSaved(false);
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/game/data/chunk/ChunkSection.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public void setBlockLight(byte[] blockLight) {
public void setBlocks(long[] blocks) {
this.blocks = blocks;
}

/**
* Convert this section to NBT.
*/
Expand Down
30 changes: 20 additions & 10 deletions src/main/java/game/data/chunk/Palette.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ public class Palette {
private Palette(int bitsPerBlock, int[] palette) {
this.bitsPerBlock = bitsPerBlock;
this.palette = palette;

if (bitsPerBlock > 8) {
System.out.println("WARNING: palette type not supported");
}
}

public static void setMaskBedrock(boolean maskBedrock) {
Palette.maskBedrock = maskBedrock;
}

/**
* Read the palette from the network stream.
* @param bitsPerBlock the number of bits per block that is used, indicates the palette type
* @param dataTypeProvider network stream reader
*/
public static Palette readPalette(int bitsPerBlock, DataTypeProvider dataTypeProvider) {
int size = dataTypeProvider.readVarInt();

Expand All @@ -45,23 +46,32 @@ public static Palette readPalette(int bitsPerBlock, DataTypeProvider dataTypePro
return new Palette(bitsPerBlock, palette);
}

public int stateFromId(int data) {
/**
* Get the block state from the palette index.
*/
public int stateFromId(int index) {
if (bitsPerBlock > 8) {
return data;
}
if (palette.length == data) {
System.out.println("Index oob! : " + data + " // " + bitsPerBlock);
return index;
}
return palette[data];

return palette[index];
}

public boolean isEmpty() {
return palette.length == 0 || (palette.length == 1 && palette[0] == 0);
}

/**
* Create an NBT version of this palette using the global palette.
*/
public List<SpecificTag> toNbt() {
List<SpecificTag> tags = new ArrayList<>();
GlobalPalette globalPalette = WorldManager.getGlobalPalette();

if (globalPalette == null) {
throw new UnsupportedOperationException("Cannot create palette NBT without a global palette.");
}

for (int i : palette) {
tags.add(globalPalette.getState(i).toNbt());
}
Expand Down
11 changes: 9 additions & 2 deletions src/main/java/game/data/chunk/palette/BlockState.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@

import java.util.Map;

/**
* A block state in the global palette (1.13+).
*/
public class BlockState {
String name;
Map<String, String> properties;
private String name;
private Map<String, String> properties;

public BlockState(String name, Map<String, String> properties) {
this.name = name;
this.properties = properties;
}

/**
* Convert this palette state to NBT format.
*/
public CompoundTag toNbt() {
CompoundTag rootTag = new CompoundTag();
rootTag.add("Name", new StringTag(name));

// only add the properties tag if there are any
if (properties != null && !properties.isEmpty()) {
CompoundTag propertyTag = new CompoundTag();
this.properties.forEach((propertyName, propertyValue) -> {
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/game/data/chunk/palette/GlobalPalette.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@
import java.io.InputStreamReader;
import java.util.HashMap;

/**
* Holds a global palette as introduced in 1.13. These are read from a simple JSON file that is generated by the
* Minecraft server.jar. More details are in the readme file in the resource folder.
*/
public class GlobalPalette {
HashMap<Integer, BlockState> states;
private HashMap<Integer, BlockState> states;

/**
* Instantiate a global palette using the given Minecraft version.
* @param version the Minecraft version, NOT protocol version
*/
public GlobalPalette(String version) {
this.states = new HashMap<>();

Expand All @@ -24,9 +32,13 @@ public GlobalPalette(String version) {
}));
}

/**
* Get a block state from a given index. Used to convert packet palettes to the global palette.
*/
public BlockState getState(int key) {
return states.getOrDefault(key, null);
}
}

// we need a class to represent this type because of type erasure nonsense, otherwise Gson will get angry over casting.
class JsonResult extends HashMap<String, JsonBlockType> { }
3 changes: 3 additions & 0 deletions src/main/java/game/data/chunk/palette/JsonBlockState.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.HashMap;

/**
* Simple class for JSON deserialization.
*/
public class JsonBlockState {
int id;
HashMap<String, String> properties;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/game/data/chunk/palette/JsonBlockType.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.ArrayList;

/**
* Simple class for JSON deserialization.
*/
public class JsonBlockType {
ArrayList<JsonBlockState> states;
}
4 changes: 4 additions & 0 deletions src/main/java/game/data/chunk/version/ChunkSection_1_12.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import se.llbit.nbt.ByteArrayTag;
import se.llbit.nbt.CompoundTag;

/**
* Chunk sections in 1.12 require parsing of the full block data as the level format does not include a palette
* as the new versions do. As such this class is more lengthy than the new versions.
*/
public class ChunkSection_1_12 extends ChunkSection {
protected int[][][] blockStates;
private int bitsPerBlock;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/game/data/chunk/version/ChunkSection_1_13.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import se.llbit.nbt.LongArrayTag;
import se.llbit.nbt.Tag;

/**
* Starting with 1.13, the chunk format requires a palette and the palette indices. This is actually
* much easier for us as the packet also comes in palette indices, so we can just copy those over and
* convert the palette from the packet to an NBT palette.
*/
public class ChunkSection_1_13 extends ChunkSection {

public ChunkSection_1_13(byte y, Palette palette) {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/game/data/chunk/version/ChunkSection_1_14.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import game.data.chunk.Palette;

/**
* No changes here over 1.13.
*/
public class ChunkSection_1_14 extends ChunkSection_1_13 {
public ChunkSection_1_14(byte y, Palette palette) {
super(y, palette);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/game/data/chunk/version/Chunk_1_12.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import se.llbit.nbt.ByteArrayTag;
import se.llbit.nbt.SpecificTag;

/**
* Chunks in the 1.12(.2) format. Biomes were a byte array in this version.
*/
public class Chunk_1_12 extends Chunk {

private byte[] biomes;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/game/data/chunk/version/Chunk_1_13.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import se.llbit.nbt.SpecificTag;
import se.llbit.nbt.StringTag;

/**
* Chunk format for 1.13+. Now includes a status tag and the biomes are integers.
*/
public class Chunk_1_13 extends Chunk {

private int[] biomes;
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/game/data/chunk/version/Chunk_1_14.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import se.llbit.nbt.CompoundTag;
import se.llbit.nbt.SpecificTag;

/**
* In 1.14 the chunks are now given a heightmap in the packet. They also no longer contain light information, as
* this was moved to a different packet. Also, a block count?
*/
public class Chunk_1_14 extends Chunk_1_13 {

SpecificTag heightMap;
Expand Down

0 comments on commit 2f25bdb

Please sign in to comment.