In this post we are going to look at the (simplified) data and protocol changes made from Minecraft 1.8 to Minecraft 1.9. Determine why previous Anti-Xray Code wont work anymore and build a new - event better Anti-XRay solution than before :)

Lets outline the changes:

Minecraft 1.8

  • Chunks are simple arrays you can easily parse.
  • Arrays contain the needed information directly
  • Chunks are being sent without any manipulation directly to the client.
  • Intercepting outgoing packages is a nice and easy way to manipulate data on the fly, as only minor changes are needed.

Minecraft 1.9 and forward

  • Chunks no longer are represented as simple arrays
  • A block-palette is used, the array only contains references to those palettes.
  • The system is pretty much exactly a dictionary-compression
  • Packet data is no longer easily accessible.
  • It would be possible for a chunk-section to contain no "stone" blocks. As we use stone to hide most ores we would need to add the stone-id to the palette and recode all data again on the fly. This would probably break our performance goals.
  • The array-size changes depending on the palette size. Adding/Changing blocks here is no longer trivial and can often result in in resizing/recreating the objects.

Here is an example of how block-changes are written to the new array types.

public void a(int i, int j) { // input is i= (x,y,z) and j = hashed block id for that chunk
        Validate.inclusiveBetween(0L, (long) (this.d - 1), (long) i);
        Validate.inclusiveBetween(0L, this.c, (long) j);
        int k = i * this.b; // 4 bits or more per entry
        int l = k / 64; 	// The long-position of the k entry
        int i1 = ((i + 1) * this.b - 1) / 64; // is (Z + ( 16 * Y ))
        int j1 = k % 64;

        // set the long with new data: convert the long data: first reset the bits by overwriting with zeros
        // and then add with blockHash 
        this.a[l] = this.a[l] & ~(this.c << j1) | ((long) j & this.c) << j1;

        if (l != i1) {
            int k1 = 64 - j1;
            int l1 = this.b - k1;
            this.a[i1] = this.a[i1] >>> l1 << l1 | ((long) j & this.c) >> k1;

As you can see these code parts are highly error prone and every time something here changes we have a lot of work to do. Even understanding these code parts takes up much time. I´d recommend not messing with the functionality of things like this unless you cannot find any other option (and keep in mind this will result in high workload on every update).

But we still want to mess with the code - we need Anti-XRay!
(If performance is not a problem use Orebfuscator)

Fortunately there is a pretty good way to implement Anti-XRay with this structure. And the best part: It is event more performant than all options previously used in 1.8!

In theory we can implement the code like this (strongly simplified):

  • Implement a derived classes of the ChunkSecion
  • Every time a Chunk is loaded not only create the normal chunk-data but create a obfuscated duplicate as well
  • Change the implementation of the Chunk-Packet. Send obfuscated data to users and normal data to admins.
  • Keep track of ore-locations for each ChunkSection and add them to the Service that gradually sends updates to the player depending on his position (e.g. ProximityHider but keep in mind this code has some flaws).
  • (Add some additional stuff like adjacency updating on block changes, neighbor updates on chunk loads etc.*)

*Sorry wont go into details on this one, this post is long enough already :)

Now let's highlight why this implementation is better than the one used in Minecraft 1.8:

  • We obfuscate the chunk on load. Generally every chunk that is being loaded will also be sent to at least one user. Normally a loaded chunk will be delivered to several players. The previous implementation would either re-encode the chunk-data every time it is sent to a player or at least re-check the cached data to make sure it is valid. The new implementation reduces the efforts of recalculations to a minium.
  • We generate the obfuscated data at the same time we load the data anyway. This means the memory-pages are already loaded in the L1,2,3 caches of the CPU and we do not load them multiple times like before.
  • Easy to upgrade. Our changes to the frequently changing minecraft-codes are very limited. Most of our work is simply deriving classes and adding new methods - not overwriting or re-implementing complex method flows.
  • Low memory usage.

At first the last point might seem wrong as we keep two versions of each chunk loaded. The memory usage should go up?
Yes and no.
If you only look on the actual occupied data at a given point: Yes we use more memory.

But memory-usage has more to it. Firstly the code-changes in Minecraft 1.9 reduce needed memory per chunk a bit. If you think further and account for unusable memory (waiting for GC) as well as the additional workload for the garbage collector with the old system:
No, we need less memory (related work).

To add some more information as to why this is true:
We can somewhat summarize the important parts of "memory" with:

  1. Actually occupied memory by needed data
  2. Occupied memory by no longer needed data (GC needed)
  3. Work to create new objects rather than re-using old ones
  4. Work to clean up no longer needed data (GC time & interruption frequency)

Memory is a pretty complex topic and reading guides online about "Optimizing the Java VM for Minecraft" often hand out plain wrong tips.
Specifically for Anti-XRay we want to re-use already existing objects in memory as they are pretty big and are needed often - but not short lived enough to die in the young generation.

Okay I think this is enough for this post. If you are interested in more information feel free to send me a message at kademlia[ä]

By the way for a survival server with a big playable area this is a good JVM startup at the current state of Minecraft (minimal loss of cpu-time from GC-Runs while not pausing for too long and never having to do a full GC). If you are running a minigame server don't bother changing the JVM/Java Garbace Collector at all.

java -XX:+UnlockExperimentalVMOptions -server -XX:+UseG1GC -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -Xmx8G -XX:G1HeapRegionSize=32m -XX:InitiatingHeapOccupancyPercent=45 -XX:G1HeapWastePercent=35 -XX:MaxGCPauseMillis=40 -XX:GCPauseIntervalMillis=100 -jar craftbukkit.jar