diff --git a/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java b/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java
index 19957330a..9965acfad 100644
--- a/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java
+++ b/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java
@@ -1,6 +1,7 @@
 package org.geysermc.connector.world;
 
-import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -10,8 +11,8 @@ import java.util.concurrent.atomic.AtomicInteger;
  */
 public class GlobalBlockPalette {
 
-    private static final Int2IntArrayMap legacyToRuntimeId = new Int2IntArrayMap();
-    private static final Int2IntArrayMap runtimeIdToLegacy = new Int2IntArrayMap();
+    private static final Int2IntMap legacyToRuntimeId = new Int2IntOpenHashMap();
+    private static final Int2IntMap runtimeIdToLegacy = new Int2IntOpenHashMap();
     private static final AtomicInteger runtimeIdAllocator = new AtomicInteger(0);
 
     static {
@@ -39,4 +40,8 @@ public class GlobalBlockPalette {
         legacyToRuntimeId.put(legacyId, runtimeId);
         return runtimeId;
     }
+
+    public static int getLegacyId(int runtimeId) {
+        return runtimeIdToLegacy.get(runtimeId);
+    }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java
index f2c9a517b..c957d8409 100644
--- a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java
+++ b/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java
@@ -4,8 +4,8 @@ import com.nukkitx.network.VarInts;
 import gnu.trove.list.array.TIntArrayList;
 import io.netty.buffer.ByteBuf;
 import org.geysermc.connector.world.GlobalBlockPalette;
-import org.geysermc.connector.world.chunk.palette.Palette;
-import org.geysermc.connector.world.chunk.palette.PaletteVersion;
+import org.geysermc.connector.world.chunk.bitarray.BitArray;
+import org.geysermc.connector.world.chunk.bitarray.BitArrayVersion;
 
 /**
  * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
@@ -14,76 +14,76 @@ public class BlockStorage {
 
     private static final int SIZE = 4096;
 
-    private final TIntArrayList ids;
-    private Palette palette;
+    private final TIntArrayList palette;
+    private BitArray bitArray;
 
     public BlockStorage() {
-        this(PaletteVersion.V2);
+        this(BitArrayVersion.V2);
     }
 
-    public BlockStorage(PaletteVersion version) {
-        this.palette = version.createPalette(SIZE);
-        this.ids = new TIntArrayList(16, -1);
-        this.ids.add(0); // Air is at the start of every palette.
+    public BlockStorage(BitArrayVersion version) {
+        this.bitArray = version.createPalette(SIZE);
+        this.palette = new TIntArrayList(16, -1);
+        this.palette.add(0); // Air is at the start of every palette.
     }
 
-    private BlockStorage(Palette palette, TIntArrayList ids) {
-        this.ids = ids;
+    private BlockStorage(BitArray bitArray, TIntArrayList palette) {
         this.palette = palette;
+        this.bitArray = bitArray;
     }
 
-    public synchronized int getFullBlock(int xzy) {
-        return this.palette.get(xzy);
+    private static int getPaletteHeader(BitArrayVersion version, boolean runtime) {
+        return (version.getId() << 1) | (runtime ? 1 : 0);
+    }
+
+    private static BitArrayVersion getVersionFromHeader(byte header) {
+        return BitArrayVersion.get(header >> 1, true);
+    }
+
+    public synchronized int getFullBlock(int index) {
+        return this.legacyIdFor(this.bitArray.get(index));
     }
 
     public synchronized void setFullBlock(int index, int legacyId) {
-        this.palette.set(index, this.idFor(legacyId));
+        int idx = this.idFor(legacyId);
+        this.bitArray.set(index, idx);
     }
 
     public synchronized void writeToNetwork(ByteBuf buffer) {
-        buffer.writeByte(getPaletteHeader(palette.getVersion(), true));
+        buffer.writeByte(getPaletteHeader(bitArray.getVersion(), true));
 
-        for (int word : palette.getWords()) {
+        for (int word : bitArray.getWords()) {
             buffer.writeIntLE(word);
         }
 
-        VarInts.writeUnsignedInt(buffer, ids.size());
-        ids.forEach(id -> {
+        VarInts.writeUnsignedInt(buffer, palette.size());
+        palette.forEach(id -> {
             VarInts.writeUnsignedInt(buffer, id);
             return true;
         });
     }
 
-    public synchronized void writeToStorage(ByteBuf buffer) {
-        buffer.writeByte(getPaletteHeader(palette.getVersion(), false));
-        for (int word : palette.getWords()) {
-            buffer.writeIntLE(word);
-        }
-
-        //TODO: Write persistent NBT tags
-    }
-
-    private synchronized void onResize(PaletteVersion version) {
-        Palette oldPalette = this.palette;
-        this.palette = version.createPalette(SIZE);
+    private void onResize(BitArrayVersion version) {
+        BitArray newBitArray = version.createPalette(SIZE);
 
         for (int i = 0; i < SIZE; i++) {
-            this.palette.set(i, oldPalette.get(i));
+            newBitArray.set(i, this.bitArray.get(i));
         }
+        this.bitArray = newBitArray;
     }
 
     private int idFor(int legacyId) {
         int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(legacyId);
-        int index = this.ids.indexOf(runtimeId);
+        int index = this.palette.indexOf(runtimeId);
         if (index != -1) {
             return index;
         }
 
-        index = this.ids.size();
-        this.ids.add(runtimeId);
-        PaletteVersion version = this.palette.getVersion();
+        index = this.palette.size();
+        this.palette.add(runtimeId);
+        BitArrayVersion version = this.bitArray.getVersion();
         if (index > version.getMaxEntryValue()) {
-            PaletteVersion next = version.next();
+            BitArrayVersion next = version.next();
             if (next != null) {
                 this.onResize(next);
             }
@@ -91,13 +91,17 @@ public class BlockStorage {
         return index;
     }
 
-    private static int getPaletteHeader(PaletteVersion version, boolean runtime) {
-        return (version.getVersion() << 1) | (runtime ? 1 : 0);
+    private int legacyIdFor(int index) {
+        int runtimeId = this.palette.get(index);
+        return GlobalBlockPalette.getLegacyId(runtimeId);
     }
 
     public boolean isEmpty() {
-        for (int word : this.palette.getWords()) {
-            if (word != 0) {
+        if (this.palette.size() == 1) {
+            return true;
+        }
+        for (int word : this.bitArray.getWords()) {
+            if (Integer.toUnsignedLong(word) != 0L) {
                 return false;
             }
         }
@@ -105,6 +109,6 @@ public class BlockStorage {
     }
 
     public BlockStorage copy() {
-        return new BlockStorage(this.palette.copy(), new TIntArrayList(this.ids));
+        return new BlockStorage(this.bitArray.copy(), new TIntArrayList(this.palette));
     }
-}
+}
\ No newline at end of file
diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Palette.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java
similarity index 55%
rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/Palette.java
rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java
index 07640bf1d..2f08ae460 100644
--- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Palette.java
+++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java
@@ -1,9 +1,9 @@
-package org.geysermc.connector.world.chunk.palette;
+package org.geysermc.connector.world.chunk.bitarray;
 
 /**
  * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
  */
-public interface Palette {
+public interface BitArray {
 
     void set(int index, int value);
 
@@ -13,7 +13,7 @@ public interface Palette {
 
     int[] getWords();
 
-    PaletteVersion getVersion();
+    BitArrayVersion getVersion();
 
-    Palette copy();
+    BitArray copy();
 }
\ No newline at end of file
diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaletteVersion.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java
similarity index 58%
rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaletteVersion.java
rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java
index 61029a8fe..7bbf9fe81 100644
--- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaletteVersion.java
+++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java
@@ -1,12 +1,11 @@
-package org.geysermc.connector.world.chunk.palette;
+package org.geysermc.connector.world.chunk.bitarray;
 
 import org.geysermc.connector.utils.MathUtils;
 
 /**
  * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
  */
-public enum PaletteVersion {
-
+public enum BitArrayVersion {
     V16(16, 2, null),
     V8(8, 4, V16),
     V6(6, 5, V8), // 2 bit padding
@@ -19,20 +18,29 @@ public enum PaletteVersion {
     final byte bits;
     final byte entriesPerWord;
     final int maxEntryValue;
-    final PaletteVersion next;
+    final BitArrayVersion next;
 
-    PaletteVersion(int bits, int entriesPerWord, PaletteVersion next) {
+    BitArrayVersion(int bits, int entriesPerWord, BitArrayVersion next) {
         this.bits = (byte) bits;
         this.entriesPerWord = (byte) entriesPerWord;
         this.maxEntryValue = (1 << this.bits) - 1;
         this.next = next;
     }
 
-    public Palette createPalette(int size) {
+    public static BitArrayVersion get(int version, boolean read) {
+        for (BitArrayVersion ver : values()) {
+            if ((!read && ver.entriesPerWord <= version) || (read && ver.bits == version)) {
+                return ver;
+            }
+        }
+        throw new IllegalArgumentException("Invalid palette version: " + version);
+    }
+
+    public BitArray createPalette(int size) {
         return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]);
     }
 
-    public byte getVersion() {
+    public byte getId() {
         return bits;
     }
 
@@ -40,25 +48,20 @@ public enum PaletteVersion {
         return maxEntryValue;
     }
 
-    public PaletteVersion next() {
+    public int getWordsForSize(int size) {
+        return MathUtils.ceil((float) size / entriesPerWord);
+    }
+
+    public BitArrayVersion next() {
         return next;
     }
 
-    public Palette createPalette(int size, int[] words) {
+    public BitArray createPalette(int size, int[] words) {
         if (this == V3 || this == V5 || this == V6) {
             // Padded palettes aren't able to use bitwise operations due to their padding.
-            return new PaddedPalette(this, size, words);
+            return new PaddedBitArray(this, size, words);
         } else {
-            return new Pow2Palette(this, size, words);
+            return new Pow2BitArray(this, size, words);
         }
     }
-
-    private static PaletteVersion getVersion(int version, boolean read) {
-        for (PaletteVersion ver : values()) {
-            if ( ( !read && ver.entriesPerWord <= version ) || ( read && ver.bits == version ) ) {
-                return ver;
-            }
-        }
-        throw new IllegalArgumentException("Invalid palette version: " + version);
-    }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaddedPalette.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java
similarity index 83%
rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaddedPalette.java
rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java
index 9f88d0dd4..016fa840b 100644
--- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaddedPalette.java
+++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java
@@ -1,4 +1,4 @@
-package org.geysermc.connector.world.chunk.palette;
+package org.geysermc.connector.world.chunk.bitarray;
 
 import com.nukkitx.network.util.Preconditions;
 import org.geysermc.connector.utils.MathUtils;
@@ -8,7 +8,7 @@ import java.util.Arrays;
 /**
  * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
  */
-public class PaddedPalette implements Palette {
+public class PaddedBitArray implements BitArray {
 
     /**
      * Array used to store data
@@ -18,14 +18,14 @@ public class PaddedPalette implements Palette {
     /**
      * Palette version information
      */
-    private final PaletteVersion version;
+    private final BitArrayVersion version;
 
     /**
      * Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
      */
     private final int size;
 
-    PaddedPalette(PaletteVersion version, int size, int[] words) {
+    PaddedBitArray(BitArrayVersion version, int size, int[] words) {
         this.size = size;
         this.version = version;
         this.words = words;
@@ -66,12 +66,12 @@ public class PaddedPalette implements Palette {
     }
 
     @Override
-    public PaletteVersion getVersion() {
+    public BitArrayVersion getVersion() {
         return this.version;
     }
 
     @Override
-    public Palette copy() {
-        return new PaddedPalette(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
+    public BitArray copy() {
+        return new PaddedBitArray(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Pow2Palette.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java
similarity index 83%
rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/Pow2Palette.java
rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java
index 98ee87559..303d0709e 100644
--- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Pow2Palette.java
+++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java
@@ -1,4 +1,4 @@
-package org.geysermc.connector.world.chunk.palette;
+package org.geysermc.connector.world.chunk.bitarray;
 
 import com.nukkitx.network.util.Preconditions;
 import org.geysermc.connector.utils.MathUtils;
@@ -8,7 +8,7 @@ import java.util.Arrays;
 /**
  * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
  */
-public class Pow2Palette implements Palette {
+public class Pow2BitArray implements BitArray {
 
     /**
      * Array used to store data
@@ -18,14 +18,14 @@ public class Pow2Palette implements Palette {
     /**
      * Palette version information
      */
-    private final PaletteVersion version;
+    private final BitArrayVersion version;
 
     /**
      * Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
      */
     private final int size;
 
-    Pow2Palette(PaletteVersion version, int size, int[] words) {
+    Pow2BitArray(BitArrayVersion version, int size, int[] words) {
         this.size = size;
         this.version = version;
         this.words = words;
@@ -41,7 +41,7 @@ public class Pow2Palette implements Palette {
      */
     public void set(int index, int value) {
         Preconditions.checkElementIndex(index, this.size);
-        Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value");
+        Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value %s", value);
         int bitIndex = index * this.version.bits;
         int arrayIndex = bitIndex >> 5;
         int offset = bitIndex & 31;
@@ -75,12 +75,12 @@ public class Pow2Palette implements Palette {
         return this.words;
     }
 
-    public PaletteVersion getVersion() {
+    public BitArrayVersion getVersion() {
         return version;
     }
 
     @Override
-    public Palette copy() {
-        return new Pow2Palette(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
+    public BitArray copy() {
+        return new Pow2BitArray(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
     }
 }
\ No newline at end of file