diff --git a/src/main/java/us/myles/ViaVersion/chunks/Chunk.java b/src/main/java/us/myles/ViaVersion/chunks/Chunk.java
index 8168cd1b1..e57029742 100644
--- a/src/main/java/us/myles/ViaVersion/chunks/Chunk.java
+++ b/src/main/java/us/myles/ViaVersion/chunks/Chunk.java
@@ -1,10 +1,9 @@
 package us.myles.ViaVersion.chunks;
 
-import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
-@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+@RequiredArgsConstructor
 @Getter
 public class Chunk {
     private final int x;
@@ -21,7 +20,7 @@ public class Chunk {
      * @param x coord
      * @param z coord
      */
-    protected Chunk(int x, int z) {
+    public Chunk(int x, int z) {
         this(x, z, true, 0, new ChunkSection[16], null);
         this.unloadPacket = true;
     }
diff --git a/src/main/java/us/myles/ViaVersion/packets/PacketType.java b/src/main/java/us/myles/ViaVersion/packets/PacketType.java
index 340db1679..3bf0bc66a 100644
--- a/src/main/java/us/myles/ViaVersion/packets/PacketType.java
+++ b/src/main/java/us/myles/ViaVersion/packets/PacketType.java
@@ -92,7 +92,7 @@ public enum PacketType {
     PLAY_UNLOAD_CHUNK(State.PLAY, Direction.OUTGOING, -1, 0x1D),
     PLAY_CHANGE_GAME_STATE(State.PLAY, Direction.OUTGOING, 0x2B, 0x1E),
     PLAY_KEEP_ALIVE(State.PLAY, Direction.OUTGOING, 0x00, 0x1F), // Mapped
-    PLAY_CHUNK_DATA(State.PLAY, Direction.OUTGOING, 0x21, 0x20), // TODO
+    PLAY_CHUNK_DATA(State.PLAY, Direction.OUTGOING, 0x21, 0x20), // Mapped
     PLAY_EFFECT(State.PLAY, Direction.OUTGOING, 0x28, 0x21), // Mapped
     PLAY_PARTICLE(State.PLAY, Direction.OUTGOING, 0x2A, 0x22), // Mapped
     PLAY_JOIN_GAME(State.PLAY, Direction.OUTGOING, 0x01, 0x23), // Mapped
diff --git a/src/main/java/us/myles/ViaVersion2/api/PacketWrapper.java b/src/main/java/us/myles/ViaVersion2/api/PacketWrapper.java
index cd8178e8e..09b883a55 100644
--- a/src/main/java/us/myles/ViaVersion2/api/PacketWrapper.java
+++ b/src/main/java/us/myles/ViaVersion2/api/PacketWrapper.java
@@ -45,7 +45,7 @@ public class PacketWrapper {
         throw new ArrayIndexOutOfBoundsException("Could not find type " + type.getTypeName() + " at " + index);
     }
 
-    public <T> T read(Type<T> type) {
+    public <T> T read(Type<T> type) throws Exception {
         System.out.println("Reading: " + type.getTypeName());
         // We could in the future log input read values, but honestly for things like bulk maps, mem waste D:
         return type.read(inputBuffer);
@@ -57,13 +57,13 @@ public class PacketWrapper {
         packetValues.add(new Pair<Type, Object>(type, value));
     }
 
-    public <T> T passthrough(Type<T> type) {
+    public <T> T passthrough(Type<T> type) throws Exception {
         T value = read(type);
         write(type, value);
         return value;
     }
 
-    public void writeToBuffer(ByteBuf buffer) {
+    public void writeToBuffer(ByteBuf buffer) throws Exception {
         for (Pair<Type, Object> packetValue : packetValues) {
             packetValue.getKey().write(buffer, packetValue.getValue());
         }
diff --git a/src/main/java/us/myles/ViaVersion2/api/item/Item.java b/src/main/java/us/myles/ViaVersion2/api/item/Item.java
index c850a713f..71c972a92 100644
--- a/src/main/java/us/myles/ViaVersion2/api/item/Item.java
+++ b/src/main/java/us/myles/ViaVersion2/api/item/Item.java
@@ -1,4 +1,14 @@
 package us.myles.ViaVersion2.api.item;
 
+import lombok.Getter;
+import lombok.Setter;
+import org.spacehq.opennbt.tag.builtin.CompoundTag;
+
+@Getter
+@Setter
 public class Item {
+    private short id;
+    private byte amount;
+    private short data;
+    private CompoundTag tag;
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol/Protocol.java b/src/main/java/us/myles/ViaVersion2/api/protocol/Protocol.java
index 48a7fa40f..0bce245c8 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol/Protocol.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol/Protocol.java
@@ -47,7 +47,7 @@ public abstract class Protocol {
         outgoing.put(new Pair<>(state, oldPacketID), protocolPacket);
     }
 
-    public void transform(Direction direction, State state, int packetID, PacketWrapper packetWrapper, ByteBuf output) {
+    public void transform(Direction direction, State state, int packetID, PacketWrapper packetWrapper, ByteBuf output) throws Exception {
         Pair<State, Integer> statePacket = new Pair<>(state, packetID);
         Map<Pair<State, Integer>, ProtocolPacket> packetMap = (direction == Direction.OUTGOING ? outgoing : incoming);
         ProtocolPacket protocolPacket;
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/Protocol1_9TO1_8.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/Protocol1_9TO1_8.java
index f09c592c7..e69db5733 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/Protocol1_9TO1_8.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/Protocol1_9TO1_8.java
@@ -7,6 +7,7 @@ import us.myles.ViaVersion2.api.data.UserConnection;
 import us.myles.ViaVersion2.api.metadata.Metadata;
 import us.myles.ViaVersion2.api.protocol.Protocol;
 import us.myles.ViaVersion2.api.protocol1_9to1_8.packets.*;
+import us.myles.ViaVersion2.api.protocol1_9to1_8.storage.ClientChunks;
 import us.myles.ViaVersion2.api.protocol1_9to1_8.storage.EntityTracker;
 import us.myles.ViaVersion2.api.protocol1_9to1_8.types.MetadataListType;
 import us.myles.ViaVersion2.api.protocol1_9to1_8.types.MetadataType;
@@ -57,5 +58,7 @@ public class Protocol1_9TO1_8 extends Protocol {
     public void init(UserConnection userConnection) {
         // Entity tracker
         userConnection.put(new EntityTracker());
+        // Chunk tracker
+        userConnection.put(new ClientChunks());
     }
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/EntityPackets.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/EntityPackets.java
index d4ba00067..774ac0eb3 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/EntityPackets.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/EntityPackets.java
@@ -174,7 +174,7 @@ public class EntityPackets {
                 map(Type.VAR_INT); // 1 - Action Type
                 handler(new PacketHandler() {
                     @Override
-                    public void handle(PacketWrapper wrapper) {
+                    public void handle(PacketWrapper wrapper) throws Exception {
                         int type = wrapper.get(Type.VAR_INT, 1);
                         if (type == 2) {
                             wrapper.passthrough(Type.FLOAT); // 2 - X
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/PlayerPackets.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/PlayerPackets.java
index ee98d18dc..544526ea4 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/PlayerPackets.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/PlayerPackets.java
@@ -49,7 +49,7 @@ public class PlayerPackets {
                 // We only handle if the title or subtitle is set then just write through.
                 handler(new PacketHandler() {
                     @Override
-                    public void handle(PacketWrapper wrapper) {
+                    public void handle(PacketWrapper wrapper) throws Exception {
                         int action = wrapper.get(Type.VAR_INT, 0);
                         if (action == 0 || action == 1) {
                             Protocol1_9TO1_8.FIX_JSON.write(wrapper, wrapper.read(Type.STRING));
@@ -90,7 +90,7 @@ public class PlayerPackets {
                 map(Type.BYTE);
                 handler(new PacketHandler() {
                     @Override
-                    public void handle(PacketWrapper wrapper) {
+                    public void handle(PacketWrapper wrapper) throws Exception {
                         byte mode = wrapper.get(Type.BYTE, 1);
                         if (mode == 0 || mode == 2) {
                             wrapper.passthrough(Type.STRING);
@@ -152,7 +152,7 @@ public class PlayerPackets {
                 handler(new PacketHandler() {
 
                     @Override
-                    public void handle(PacketWrapper wrapper) {
+                    public void handle(PacketWrapper wrapper) throws Exception {
                         int action = wrapper.get(Type.VAR_INT, 0);
                         int count = wrapper.get(Type.VAR_INT, 1);
 
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/SpawnPackets.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/SpawnPackets.java
index 56e68e51b..ba747cfd0 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/SpawnPackets.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/SpawnPackets.java
@@ -61,7 +61,7 @@ public class SpawnPackets {
                 // Create last 3 shorts
                 create(new ValueCreator() {
                     @Override
-                    public void write(PacketWrapper wrapper) {
+                    public void write(PacketWrapper wrapper) throws Exception {
                         int data = wrapper.get(Type.INT, 3); // Data (4th Integer)
 
                         short vX = 0, vY = 0, vZ = 0;
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/WorldPackets.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/WorldPackets.java
index 5633e1b69..625da36fa 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/WorldPackets.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/packets/WorldPackets.java
@@ -1,8 +1,12 @@
 package us.myles.ViaVersion2.api.protocol1_9to1_8.packets;
 
 import us.myles.ViaVersion.packets.State;
+import us.myles.ViaVersion2.api.PacketWrapper;
 import us.myles.ViaVersion2.api.protocol.Protocol;
 import us.myles.ViaVersion2.api.protocol1_9to1_8.Protocol1_9TO1_8;
+import us.myles.ViaVersion2.api.protocol1_9to1_8.storage.ClientChunks;
+import us.myles.ViaVersion2.api.protocol1_9to1_8.types.ChunkType;
+import us.myles.ViaVersion2.api.remapper.PacketHandler;
 import us.myles.ViaVersion2.api.remapper.PacketRemapper;
 import us.myles.ViaVersion2.api.type.Type;
 
@@ -43,6 +47,20 @@ public class WorldPackets {
             }
         });
 
+        // Chunk Packet
+        protocol.registerOutgoing(State.PLAY, 0x21, 0x20, new PacketRemapper() {
+            @Override
+            public void registerMap() {
+                handler(new PacketHandler() {
+                    @Override
+                    public void handle(PacketWrapper wrapper) throws Exception{
+                        ClientChunks clientChunks = wrapper.user().get(ClientChunks.class);
+                        wrapper.passthrough(new ChunkType(clientChunks));
+                    }
+                });
+            }
+        });
+
         /* Packets which do not have any field remapping or handlers */
 
         protocol.registerOutgoing(State.PLAY, 0x25, 0x08); // Block Break Animation Packet
@@ -58,8 +76,6 @@ public class WorldPackets {
         protocol.registerOutgoing(State.PLAY, 0x03, 0x44); // Update Time Packet
         protocol.registerOutgoing(State.PLAY, 0x44, 0x35); // World Border Packet
 
-        // TODO: Chunk Data, Bulk Chunk :)
-
         /* Incoming Packets */
 
         // Sign Update Request Packet
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/storage/ClientChunks.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/storage/ClientChunks.java
new file mode 100644
index 000000000..dfe96fc7f
--- /dev/null
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/storage/ClientChunks.java
@@ -0,0 +1,13 @@
+package us.myles.ViaVersion2.api.protocol1_9to1_8.storage;
+
+import com.google.common.collect.Sets;
+import lombok.Getter;
+import us.myles.ViaVersion2.api.data.StoredObject;
+
+import java.util.Set;
+
+@Getter
+public class ClientChunks extends StoredObject {
+    private final Set<Long> loadedChunks = Sets.newConcurrentHashSet();
+    private final Set<Long> bulkChunks = Sets.newConcurrentHashSet();
+}
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/ChunkType.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/ChunkType.java
new file mode 100644
index 000000000..29a609405
--- /dev/null
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/ChunkType.java
@@ -0,0 +1,162 @@
+package us.myles.ViaVersion2.api.protocol1_9to1_8.types;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.bukkit.Bukkit;
+import us.myles.ViaVersion.chunks.Chunk;
+import us.myles.ViaVersion.chunks.ChunkSection;
+import us.myles.ViaVersion.util.PacketUtil;
+import us.myles.ViaVersion2.api.protocol1_9to1_8.storage.ClientChunks;
+import us.myles.ViaVersion2.api.type.PartialType;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+import java.util.BitSet;
+import java.util.logging.Level;
+
+public class ChunkType extends PartialType<Chunk, ClientChunks> {
+    /**
+     * Amount of sections in a chunk.
+     */
+    private static final int SECTION_COUNT = 16;
+    /**
+     * size of each chunk section (16x16x16).
+     */
+    private static final int SECTION_SIZE = 16;
+    /**
+     * Length of biome data.
+     */
+    private static final int BIOME_DATA_LENGTH = 256;
+
+    public ChunkType(ClientChunks chunks) {
+        super(chunks, Chunk.class);
+    }
+
+    @Override
+    public Chunk read(ByteBuf input, ClientChunks param) {
+        int chunkX = input.readInt();
+        int chunkZ = input.readInt();
+        long chunkHash = toLong(chunkX, chunkZ);
+        boolean groundUp = input.readByte() != 0;
+        int bitmask = input.readUnsignedShort();
+        int dataLength = PacketUtil.readVarInt(input);
+
+        // Data to be read
+        BitSet usedSections = new BitSet(16);
+        ChunkSection[] sections = new ChunkSection[16];
+        byte[] biomeData = null;
+
+        // Calculate section count from bitmask
+        for (int i = 0; i < 16; i++) {
+            if ((bitmask & (1 << i)) != 0) {
+                usedSections.set(i);
+            }
+        }
+        int sectionCount = usedSections.cardinality(); // the amount of sections set
+
+        // If the chunk is from a chunk bulk, it is never an unload packet
+        // Other wise, if it has no data, it is :)
+        boolean isBulkPacket = param.getBulkChunks().remove(chunkHash);
+        if (sectionCount == 0 && groundUp && !isBulkPacket && param.getLoadedChunks().contains(chunkHash)) {
+            // This is a chunk unload packet
+            param.getLoadedChunks().remove(chunkHash);
+            return new Chunk(chunkX, chunkZ);
+        }
+
+        int startIndex = input.readerIndex();
+        param.getLoadedChunks().add(chunkHash); // mark chunk as loaded
+
+        // Read blocks
+        for (int i = 0; i < SECTION_COUNT; i++) {
+            if (!usedSections.get(i)) continue; // Section not set
+            ChunkSection section = new ChunkSection();
+            sections[i] = section;
+
+            // Read block data and convert to short buffer
+            byte[] blockData = new byte[ChunkSection.SIZE * 2];
+            input.readBytes(blockData);
+            ShortBuffer blockBuf = ByteBuffer.wrap(blockData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
+
+            for (int j = 0; j < ChunkSection.SIZE; j++) {
+                int mask = blockBuf.get();
+                int type = mask >> 4;
+                int data = mask & 0xF;
+                section.setBlock(j, type, data);
+            }
+        }
+
+        // Read block light
+        for (int i = 0; i < SECTION_COUNT; i++) {
+            if (!usedSections.get(i)) continue; // Section not set, has no light
+            byte[] blockLightArray = new byte[ChunkSection.LIGHT_LENGTH];
+            input.readBytes(blockLightArray);
+            sections[i].setBlockLight(blockLightArray);
+        }
+
+        // Read sky light
+        int bytesLeft = dataLength - (input.readerIndex() - startIndex);
+        if (bytesLeft >= ChunkSection.LIGHT_LENGTH) {
+            for (int i = 0; i < SECTION_COUNT; i++) {
+                if (!usedSections.get(i)) continue; // Section not set, has no light
+                byte[] skyLightArray = new byte[ChunkSection.LIGHT_LENGTH];
+                input.readBytes(skyLightArray);
+                sections[i].setSkyLight(skyLightArray);
+                bytesLeft -= ChunkSection.LIGHT_LENGTH;
+            }
+        }
+
+        // Read biome data
+        if (bytesLeft >= BIOME_DATA_LENGTH) {
+            biomeData = new byte[BIOME_DATA_LENGTH];
+            input.readBytes(biomeData);
+            bytesLeft -= BIOME_DATA_LENGTH;
+        }
+
+        // Check remaining bytes
+        if (bytesLeft > 0) {
+            Bukkit.getLogger().log(Level.WARNING, bytesLeft + " Bytes left after reading chunk! (" + groundUp + ")");
+        }
+
+        // Return chunk
+        return new Chunk(chunkX, chunkZ, groundUp, bitmask, sections, biomeData);
+    }
+
+    @Override
+    public void write(ByteBuf output, ClientChunks param, Chunk chunk) {
+        if(chunk.isUnloadPacket()) {
+            output.clear();
+            PacketUtil.writeVarInt(0x1D, output); // Unload packet ID
+        }
+
+        // Write primary info
+        output.writeInt(chunk.getX());
+        output.writeInt(chunk.getZ());
+        if(chunk.isUnloadPacket()) return;
+        output.writeByte(chunk.isGroundUp() ? 0x01 : 0x00);
+        PacketUtil.writeVarInt(chunk.getPrimaryBitmask(), output);
+
+        ByteBuf buf = Unpooled.buffer();
+        for(int i = 0; i < SECTION_COUNT; i++) {
+            ChunkSection section = chunk.getSections()[i];
+            if(section == null) continue; // Section not set
+            section.writeBlocks(buf);
+            section.writeBlockLight(buf);
+            if(!section.hasSkyLight()) continue; // No sky light, we're done here.
+            section.writeSkyLight(buf);
+        }
+        buf.readerIndex(0);
+        PacketUtil.writeVarInt(buf.readableBytes() + (chunk.hasBiomeData() ? 256 : 0), output);
+        output.writeBytes(buf);
+        buf.release(); // release buffer
+
+        // Write biome data
+        if(chunk.hasBiomeData()) {
+            output.writeBytes(chunk.getBiomeData());
+        }
+    }
+
+    private static long toLong(int msw, int lsw) {
+        return ((long) msw << 32) + lsw - -2147483648L;
+    }
+}
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataListType.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataListType.java
index 7be6e913d..b7d3af678 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataListType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataListType.java
@@ -14,7 +14,7 @@ public class MetadataListType extends Type<List<Metadata>> {
     }
 
     @Override
-    public List<Metadata> read(ByteBuf buffer) {
+    public List<Metadata> read(ByteBuf buffer) throws Exception {
         List<Metadata> list = new ArrayList<>();
         Metadata m;
         do {
@@ -28,8 +28,8 @@ public class MetadataListType extends Type<List<Metadata>> {
     }
 
     @Override
-    public void write(ByteBuf buffer, List<Metadata> object) {
-        for(Metadata m:object){
+    public void write(ByteBuf buffer, List<Metadata> object) throws Exception {
+        for (Metadata m : object) {
             Protocol1_9TO1_8.METADATA.write(buffer, m);
         }
         // Write end of list
diff --git a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataType.java b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataType.java
index b17625b2f..f60e51884 100644
--- a/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/protocol1_9to1_8/types/MetadataType.java
@@ -13,7 +13,7 @@ public class MetadataType extends Type<Metadata> {
     }
 
     @Override
-    public Metadata read(ByteBuf buffer) {
+    public Metadata read(ByteBuf buffer) throws Exception {
         byte item = buffer.readByte();
         if (item == 127) return null; // end of metadata
         MetadataTypes type = MetadataTypes.byId((item & 0xE0) >> 5);
@@ -22,7 +22,7 @@ public class MetadataType extends Type<Metadata> {
     }
 
     @Override
-    public void write(ByteBuf buffer, Metadata object) {
+    public void write(ByteBuf buffer, Metadata object) throws Exception {
         if (object == null) {
             buffer.writeByte(127);
         } else {
diff --git a/src/main/java/us/myles/ViaVersion2/api/remapper/PacketHandler.java b/src/main/java/us/myles/ViaVersion2/api/remapper/PacketHandler.java
index 1e486e7b5..55668cc17 100644
--- a/src/main/java/us/myles/ViaVersion2/api/remapper/PacketHandler.java
+++ b/src/main/java/us/myles/ViaVersion2/api/remapper/PacketHandler.java
@@ -3,10 +3,10 @@ package us.myles.ViaVersion2.api.remapper;
 import us.myles.ViaVersion2.api.PacketWrapper;
 
 public abstract class PacketHandler implements ValueWriter {
-    public abstract void handle(PacketWrapper wrapper);
+    public abstract void handle(PacketWrapper wrapper) throws Exception;
 
     @Override
-    public void write(PacketWrapper writer, Object inputValue) {
+    public void write(PacketWrapper writer, Object inputValue) throws Exception {
         handle(writer);
     }
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/remapper/PacketRemapper.java b/src/main/java/us/myles/ViaVersion2/api/remapper/PacketRemapper.java
index 5005e40a1..6d68aa497 100644
--- a/src/main/java/us/myles/ViaVersion2/api/remapper/PacketRemapper.java
+++ b/src/main/java/us/myles/ViaVersion2/api/remapper/PacketRemapper.java
@@ -41,7 +41,7 @@ public abstract class PacketRemapper {
 
     public abstract void registerMap();
 
-    public void remap(PacketWrapper packetWrapper) {
+    public void remap(PacketWrapper packetWrapper) throws Exception{
         // Read all the current values
         for (Pair<ValueReader, ValueWriter> valueRemapper : valueRemappers) {
             Object object = valueRemapper.getKey().read(packetWrapper);
diff --git a/src/main/java/us/myles/ViaVersion2/api/remapper/TypeRemapper.java b/src/main/java/us/myles/ViaVersion2/api/remapper/TypeRemapper.java
index 148c3ea8e..8b23117a7 100644
--- a/src/main/java/us/myles/ViaVersion2/api/remapper/TypeRemapper.java
+++ b/src/main/java/us/myles/ViaVersion2/api/remapper/TypeRemapper.java
@@ -11,7 +11,7 @@ public class TypeRemapper<T> implements ValueReader<T>, ValueWriter<T> {
     }
 
     @Override
-    public T read(PacketWrapper wrapper) {
+    public T read(PacketWrapper wrapper) throws Exception {
         return wrapper.read(type);
     }
 
diff --git a/src/main/java/us/myles/ViaVersion2/api/remapper/ValueCreator.java b/src/main/java/us/myles/ViaVersion2/api/remapper/ValueCreator.java
index 30d475527..00952ed1a 100644
--- a/src/main/java/us/myles/ViaVersion2/api/remapper/ValueCreator.java
+++ b/src/main/java/us/myles/ViaVersion2/api/remapper/ValueCreator.java
@@ -3,10 +3,10 @@ package us.myles.ViaVersion2.api.remapper;
 import us.myles.ViaVersion2.api.PacketWrapper;
 
 public abstract class ValueCreator implements ValueWriter {
-    public abstract void write(PacketWrapper wrapper);
+    public abstract void write(PacketWrapper wrapper) throws Exception;
 
     @Override
-    public void write(PacketWrapper writer, Object inputValue) {
+    public void write(PacketWrapper writer, Object inputValue) throws Exception {
         write(writer);
     }
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/remapper/ValueReader.java b/src/main/java/us/myles/ViaVersion2/api/remapper/ValueReader.java
index 81326ea72..042eb4845 100644
--- a/src/main/java/us/myles/ViaVersion2/api/remapper/ValueReader.java
+++ b/src/main/java/us/myles/ViaVersion2/api/remapper/ValueReader.java
@@ -4,5 +4,5 @@ import io.netty.buffer.ByteBuf;
 import us.myles.ViaVersion2.api.PacketWrapper;
 
 public interface ValueReader<T> {
-    public T read(PacketWrapper wrapper);
+    public T read(PacketWrapper wrapper) throws Exception;
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/remapper/ValueWriter.java b/src/main/java/us/myles/ViaVersion2/api/remapper/ValueWriter.java
index ad2f528c1..ad7dff859 100644
--- a/src/main/java/us/myles/ViaVersion2/api/remapper/ValueWriter.java
+++ b/src/main/java/us/myles/ViaVersion2/api/remapper/ValueWriter.java
@@ -3,5 +3,5 @@ package us.myles.ViaVersion2.api.remapper;
 import us.myles.ViaVersion2.api.PacketWrapper;
 
 public interface ValueWriter<T> {
-    public void write(PacketWrapper writer, T inputValue);
+    public void write(PacketWrapper writer, T inputValue)  throws Exception;
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/ByteBufReader.java b/src/main/java/us/myles/ViaVersion2/api/type/ByteBufReader.java
index 88359c6a9..26befd28a 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/ByteBufReader.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/ByteBufReader.java
@@ -3,5 +3,5 @@ package us.myles.ViaVersion2.api.type;
 import io.netty.buffer.ByteBuf;
 
 public interface ByteBufReader<T> {
-    public T read(ByteBuf buffer);
+    public T read(ByteBuf buffer) throws Exception;
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/ByteBufWriter.java b/src/main/java/us/myles/ViaVersion2/api/type/ByteBufWriter.java
index 570fabb5c..fd376e7cc 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/ByteBufWriter.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/ByteBufWriter.java
@@ -3,5 +3,5 @@ package us.myles.ViaVersion2.api.type;
 import io.netty.buffer.ByteBuf;
 
 public interface ByteBufWriter<T> {
-    public void write(ByteBuf buffer, T object);
+    public void write(ByteBuf buffer, T object) throws Exception;
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/PartialType.java b/src/main/java/us/myles/ViaVersion2/api/type/PartialType.java
new file mode 100644
index 000000000..63e6ae540
--- /dev/null
+++ b/src/main/java/us/myles/ViaVersion2/api/type/PartialType.java
@@ -0,0 +1,26 @@
+package us.myles.ViaVersion2.api.type;
+
+import io.netty.buffer.ByteBuf;
+
+public abstract class PartialType<T, X> extends Type<T> {
+    private final X param;
+
+    public PartialType(X param, Class<T> type) {
+        super(type);
+        this.param = param;
+    }
+
+    public abstract T read(ByteBuf buffer, X param);
+
+    public abstract void write(ByteBuf buffer, X param, T object);
+
+    @Override
+    public T read(ByteBuf buffer) {
+        return read(buffer, this.param);
+    }
+
+    @Override
+    public void write(ByteBuf buffer, T object) {
+        write(buffer, this.param, object);
+    }
+}
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/Type.java b/src/main/java/us/myles/ViaVersion2/api/type/Type.java
index 52eb641fd..3eaff4e45 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/Type.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/Type.java
@@ -4,6 +4,7 @@ package us.myles.ViaVersion2.api.type;
 import lombok.Getter;
 import org.bukkit.util.EulerAngle;
 import org.bukkit.util.Vector;
+import org.spacehq.opennbt.tag.builtin.CompoundTag;
 import us.myles.ViaVersion2.api.item.Item;
 import us.myles.ViaVersion2.api.type.types.*;
 import us.myles.ViaVersion2.api.type.types.minecraft.*;
@@ -57,7 +58,9 @@ public abstract class Type<T> implements ByteBufReader<T>, ByteBufWriter<T> {
     public static final Type<Position> POSITION = new PositionType();
     public static final Type<EulerAngle> ROTATION = new EulerAngleType();
     public static final Type<Vector> VECTOR = new VectorType();
-    public static final Type<Item> ITEM = new ItemType(); // TODO
+    public static final Type<CompoundTag> NBT = new NBTType();
+
+    public static final Type<Item> ITEM = new ItemType();
     public static final Type<Item[]> ITEM_ARRAY = new ItemArrayType();
     /* Actual Class */
 
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/types/ArrayType.java b/src/main/java/us/myles/ViaVersion2/api/type/types/ArrayType.java
index 169dba013..e27eb92a8 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/types/ArrayType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/types/ArrayType.java
@@ -12,7 +12,7 @@ public class ArrayType<T> extends Type<T[]> {
     }
 
     @Override
-    public T[] read(ByteBuf buffer) {
+    public T[] read(ByteBuf buffer) throws Exception{
         int amount = Type.VAR_INT.read(buffer);
         Object[] array = new Object[amount];
         for (int i = 0; i < amount; i++) {
@@ -22,7 +22,7 @@ public class ArrayType<T> extends Type<T[]> {
     }
 
     @Override
-    public void write(ByteBuf buffer, T[] object) {
+    public void write(ByteBuf buffer, T[] object) throws Exception{
         Type.VAR_INT.write(buffer, object.length);
         for (T o : object) {
             elementType.write(buffer, o);
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/types/StringType.java b/src/main/java/us/myles/ViaVersion2/api/type/types/StringType.java
index 7476feb43..a4eede755 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/types/StringType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/types/StringType.java
@@ -11,7 +11,7 @@ public class StringType extends Type<String> {
     }
 
     @Override
-    public String read(ByteBuf buffer) {
+    public String read(ByteBuf buffer) throws Exception {
         int len = Type.VAR_INT.read(buffer);
         Preconditions.checkArgument(len <= Short.MAX_VALUE, "Cannot receive string longer than Short.MAX_VALUE (got %s characters)", len);
 
@@ -22,7 +22,7 @@ public class StringType extends Type<String> {
     }
 
     @Override
-    public void write(ByteBuf buffer, String object) {
+    public void write(ByteBuf buffer, String object) throws Exception {
         Preconditions.checkArgument(object.length() <= Short.MAX_VALUE, "Cannot send string longer than Short.MAX_VALUE (got %s characters)", object.length());
 
         byte[] b = object.getBytes(Charsets.UTF_8);
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/EulerAngleType.java b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/EulerAngleType.java
index f0e44dfc4..566931fe6 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/EulerAngleType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/EulerAngleType.java
@@ -11,7 +11,7 @@ public class EulerAngleType extends Type<EulerAngle> {
     }
 
     @Override
-    public EulerAngle read(ByteBuf buffer) {
+    public EulerAngle read(ByteBuf buffer) throws Exception {
         float x = Type.FLOAT.read(buffer);
         float y = Type.FLOAT.read(buffer);
         float z = Type.FLOAT.read(buffer);
@@ -20,7 +20,7 @@ public class EulerAngleType extends Type<EulerAngle> {
     }
 
     @Override
-    public void write(ByteBuf buffer, EulerAngle object) {
+    public void write(ByteBuf buffer, EulerAngle object) throws Exception {
         Type.FLOAT.write(buffer, (float) object.getX());
         Type.FLOAT.write(buffer, (float) object.getY());
         Type.FLOAT.write(buffer, (float) object.getZ());
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemArrayType.java b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemArrayType.java
index 86d552df5..0ef8f1e27 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemArrayType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemArrayType.java
@@ -11,7 +11,7 @@ public class ItemArrayType extends Type<Item[]> {
     }
 
     @Override
-    public Item[] read(ByteBuf buffer) {
+    public Item[] read(ByteBuf buffer) throws Exception {
         int amount = Type.SHORT.read(buffer);
         Item[] array = new Item[amount];
         for (int i = 0; i < amount; i++) {
@@ -21,7 +21,7 @@ public class ItemArrayType extends Type<Item[]> {
     }
 
     @Override
-    public void write(ByteBuf buffer, Item[] object) {
+    public void write(ByteBuf buffer, Item[] object) throws Exception {
         Type.VAR_INT.write(buffer, object.length);
         for (Item o : object) {
             Type.ITEM.write(buffer, o);
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemType.java b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemType.java
index 0c33ecf2e..402766e7a 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/ItemType.java
@@ -4,19 +4,35 @@ import io.netty.buffer.ByteBuf;
 import us.myles.ViaVersion2.api.item.Item;
 import us.myles.ViaVersion2.api.type.Type;
 
-// TODO: Implement this class
 public class ItemType extends Type<Item> {
     public ItemType() {
         super(Item.class);
     }
 
     @Override
-    public Item read(ByteBuf buffer) {
-        return null;
+    public Item read(ByteBuf buffer) throws Exception {
+        short id = buffer.readShort();
+        if (id < 0) {
+            return null;
+        } else {
+            Item item = new Item();
+            item.setId(id);
+            item.setAmount(buffer.readByte());
+            item.setData(buffer.readShort());
+            item.setTag(Type.NBT.read(buffer));
+            return item;
+        }
     }
 
     @Override
-    public void write(ByteBuf buffer, Item object) {
-
+    public void write(ByteBuf buffer, Item object) throws Exception {
+        if (object == null) {
+            buffer.writeShort(-1);
+        } else {
+            buffer.writeShort(object.getId());
+            buffer.writeByte(object.getAmount());
+            buffer.writeShort(object.getData());
+            Type.NBT.write(buffer, object.getTag());
+        }
     }
 }
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/NBTType.java b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/NBTType.java
new file mode 100644
index 000000000..c7067aa58
--- /dev/null
+++ b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/NBTType.java
@@ -0,0 +1,52 @@
+package us.myles.ViaVersion2.api.type.types.minecraft;
+
+import com.google.common.base.Preconditions;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.buffer.ByteBufOutputStream;
+import org.spacehq.opennbt.NBTIO;
+import org.spacehq.opennbt.tag.builtin.CompoundTag;
+import us.myles.ViaVersion2.api.type.Type;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class NBTType extends Type<CompoundTag> {
+    public NBTType() {
+        super(CompoundTag.class);
+    }
+
+    @Override
+    public CompoundTag read(ByteBuf buffer) throws Exception {
+        Preconditions.checkArgument(buffer.readableBytes() <= 2097152, "Cannot read NBT (got %s bytes)", buffer.readableBytes());
+
+        int readerIndex = buffer.readerIndex();
+        byte b = buffer.readByte();
+        if (b == 0) {
+            return null;
+        } else {
+            buffer.readerIndex(readerIndex);
+            ByteBufInputStream bytebufStream = new ByteBufInputStream(buffer);
+            DataInputStream dataInputStream = new DataInputStream(bytebufStream);
+            try {
+                return (CompoundTag) NBTIO.readTag(dataInputStream);
+            } finally {
+                dataInputStream.close();
+            }
+        }
+    }
+
+    @Override
+    public void write(ByteBuf buffer, CompoundTag object) throws Exception {
+        if (object == null) {
+            buffer.writeByte(0);
+        } else {
+            ByteBufOutputStream bytebufStream = new ByteBufOutputStream(buffer);
+            DataOutputStream dataOutputStream = new DataOutputStream(bytebufStream);
+
+            NBTIO.writeTag(dataOutputStream, object);
+
+            dataOutputStream.close();
+        }
+    }
+}
diff --git a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/VectorType.java b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/VectorType.java
index fde8a2dbc..bdb5716e7 100644
--- a/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/VectorType.java
+++ b/src/main/java/us/myles/ViaVersion2/api/type/types/minecraft/VectorType.java
@@ -10,7 +10,7 @@ public class VectorType extends Type<Vector> {
     }
 
     @Override
-    public Vector read(ByteBuf buffer) {
+    public Vector read(ByteBuf buffer) throws Exception {
         int x = Type.INT.read(buffer);
         int y = Type.INT.read(buffer);
         int z = Type.INT.read(buffer);
@@ -19,7 +19,7 @@ public class VectorType extends Type<Vector> {
     }
 
     @Override
-    public void write(ByteBuf buffer, Vector object) {
+    public void write(ByteBuf buffer, Vector object) throws Exception {
         Type.INT.write(buffer, object.getBlockX());
         Type.INT.write(buffer, object.getBlockY());
         Type.INT.write(buffer, object.getBlockZ());