diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java
index c81514eae..bc567ab91 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java
@@ -37,7 +37,7 @@ import java.util.Map;
 public final class GeyserDirtyMetadata {
     private final Map<EntityDataType<?>, Object> metadata = new Object2ObjectLinkedOpenHashMap<>();
 
-    public void put(EntityDataType<?> entityData, Object value) {
+    public <T> void put(EntityDataType<T> entityData, T value) {
         metadata.put(entityData, value);
     }
 
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
index f9e6879da..f9da5c69f 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
@@ -73,7 +73,7 @@ public class BoatEntity extends Entity {
         super(session, entityId, geyserId, uuid, definition, position.add(0d, definition.offset(), 0d), motion, yaw + 90, 0, yaw + 90);
 
         // Required to be able to move on land 1.16.200+ or apply gravity not in the water 1.16.100+
-        dirtyMetadata.put(EntityDataTypes.IS_BUOYANT, (byte) 1);
+        dirtyMetadata.put(EntityDataTypes.IS_BUOYANT, true);
         dirtyMetadata.put(EntityDataTypes.BUOYANCY_DATA, BUOYANCY_DATA);
     }
 
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java
index 6b0630546..6e40d288a 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java
@@ -50,7 +50,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
         dirtyMetadata.put(EntityDataTypes.CONTAINER_TYPE, (byte) 16);
         dirtyMetadata.put(EntityDataTypes.CONTAINER_SIZE, 1);
         // Required, or else the client does not bother to send a packet back with the new information
-        dirtyMetadata.put(EntityDataTypes.COMMAND_BLOCK_ENABLED, (byte) 1);
+        dirtyMetadata.put(EntityDataTypes.COMMAND_BLOCK_ENABLED, true);
     }
 
     /**
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java
index 243cfd23d..75bd34d68 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java
@@ -62,7 +62,7 @@ public class ThrownPotionEntity extends ThrowableItemEntity {
                 if (potionTag instanceof StringTag) {
                     Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue());
                     if (potion != null) {
-                        dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, potion.getBedrockId());
+                        dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, (int) potion.getBedrockId());
                         setFlag(EntityFlag.ENCHANTED, !NON_ENCHANTED_POTIONS.contains(potion));
                     } else {
                         dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, 0);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java
index 27188c79e..856c4cc66 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java
@@ -47,13 +47,13 @@ public class TippedArrowEntity extends AbstractArrowEntity {
         int potionColor = entityMetadata.getPrimitiveValue();
         // -1 means no color
         if (potionColor == -1) {
-            dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, 0);
+            dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0);
         } else {
             TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor);
             if (potion != null && potion.getJavaColor() != -1) {
                 dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) potion.getBedrockId());
             } else {
-                dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, 0);
+                dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0);
             }
         }
     }
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java
index 5ad7d8740..ee622dc42 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java
@@ -47,7 +47,7 @@ public class ShulkerEntity extends GolemEntity {
 
     public void setAttachedFace(EntityMetadata<Direction, ?> entityMetadata) {
         Direction direction = entityMetadata.getValue();
-        dirtyMetadata.put(EntityDataTypes.SHULKER_ATTACH_FACE, (byte) direction.ordinal());
+        dirtyMetadata.put(EntityDataTypes.SHULKER_ATTACH_FACE, direction.ordinal());
     }
 
     public void setShulkerHeight(ByteEntityMetadata entityMetadata) {
@@ -56,7 +56,7 @@ public class ShulkerEntity extends GolemEntity {
     }
 
     public void setShulkerColor(ByteEntityMetadata entityMetadata) {
-        byte color = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue();
+        byte color = entityMetadata.getPrimitiveValue();
         if (color == 16) {
             // 16 is default on both editions
             dirtyMetadata.put(EntityDataTypes.VARIANT, 16);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
index ddd9a41e0..fe188ddb2 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
@@ -288,7 +288,7 @@ public class PlayerEntity extends LivingEntity {
             ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(),
                     null, EntityDefinitions.PARROT, position, motion, getYaw(), getPitch(), getHeadYaw());
             parrot.spawnEntity();
-            parrot.getDirtyMetadata().put(EntityDataTypes.VARIANT, tag.get("Variant").getValue());
+            parrot.getDirtyMetadata().put(EntityDataTypes.VARIANT, (Integer) tag.get("Variant").getValue());
             // Different position whether the parrot is left or right
             float offset = isLeft ? 0.4f : -0.4f;
             parrot.getDirtyMetadata().put(EntityDataTypes.SEAT_OFFSET, Vector3f.from(offset, -0.22, -0.1));
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
index 7ca006231..35cad7d6e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
@@ -244,9 +244,9 @@ public class SessionPlayerEntity extends PlayerEntity {
         if (pos != null) {
             dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition());
             dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_DIMENSION, DimensionUtils.javaToBedrock(pos.getDimension()));
-            dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, (byte) 1);
+            dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, true);
         } else {
-            dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, (byte) 0);
+            dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false);
         }
     }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java
index 73528a6a5..732769287 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java
@@ -92,9 +92,10 @@ public class GeyserItemStack {
     }
 
     public ItemData getItemData(GeyserSession session) {
-        ItemData itemData = ItemTranslator.translateToBedrock(session, getItemStack());
-        itemData.setNetId(getNetId());
-        return itemData;
+        ItemData.Builder itemData = ItemTranslator.translateToBedrock(session, javaId, amount, nbt);
+        itemData.netId(getNetId());
+        itemData.usingNetId(true);
+        return itemData.build();
     }
 
     public ItemMapping getMapping(GeyserSession session) {
diff --git a/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java b/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java
index dd29527af..9bcf30789 100644
--- a/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java
+++ b/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java
@@ -28,8 +28,6 @@ package org.geysermc.geyser.network;
 import io.netty.channel.DefaultEventLoopGroup;
 import io.netty.util.concurrent.DefaultThreadFactory;
 import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
-import org.cloudburstmc.protocol.bedrock.codec.v554.Bedrock_v554;
-import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
 import org.cloudburstmc.protocol.bedrock.netty.initializer.BedrockServerInitializer;
 import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.session.GeyserSession;
@@ -72,14 +70,9 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
 
     @Override
     public void initSession(@Nonnull BedrockServerSession bedrockServerSession) {
-        System.out.println("init session");
         try {
-            bedrockServerSession.setCodec(Bedrock_v554.CODEC); // Has the RequestNetworkSettingsPacket
             bedrockServerSession.setLogging(true);
             bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(this.geyser, new GeyserSession(this.geyser, bedrockServerSession, this.eventLoopGroup.next())));
-            bedrockServerSession.setCompression(PacketCompressionAlgorithm.ZLIB);
-            bedrockServerSession.setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
-            // Set the packet codec to default just in case we need to send disconnect packets.
         } catch (Throwable e) {
             // Error must be caught or it will be swallowed
             this.geyser.getLogger().error("Error occurred while initializing player!", e);
diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java
index d5bb17aa8..0a81f430d 100644
--- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java
+++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java
@@ -73,7 +73,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
     }
 
     private PacketSignal translateAndDefault(BedrockPacket packet) {
-        return Registries.BEDROCK_PACKET_TRANSLATORS.translate(packet.getClass(), packet, session) ? PacketSignal.HANDLED : PacketSignal.UNHANDLED;
+        Registries.BEDROCK_PACKET_TRANSLATORS.translate(packet.getClass(), packet, session);
+        return PacketSignal.HANDLED; // PacketSignal.UNHANDLED will log a WARN publicly
     }
 
     @Override
@@ -109,6 +110,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
         } else if (BedrockDisconnectReasons.TIMEOUT.equals(reason)) {
             this.session.getUpstream().getSession().setDisconnectReason(GeyserLocale.getLocaleStringLog("geyser.network.disconnect.timed_out"));
         }
+        this.session.disconnect(this.session.getUpstream().getSession().getDisconnectReason());
     }
 
     @Override
@@ -128,6 +130,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
         session.sendUpstreamPacketImmediately(responsePacket);
 
         session.getUpstream().getSession().setCompression(algorithm);
+        session.getUpstream().getSession().setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
         return PacketSignal.HANDLED;
     }
 
@@ -139,6 +142,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
             return PacketSignal.HANDLED;
         }
 
+//        session.getUpstream().getSession().getCodec() == null
+
         if (!newProtocol) {
             if (!setCorrectCodec(loginPacket.getProtocolVersion())) { // REMOVE WHEN ONLY 1.19.30 IS SUPPORTED OR 1.20
                 return PacketSignal.HANDLED;
diff --git a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java
index f3a49400d..0acfeee93 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java
@@ -27,9 +27,7 @@ package org.geysermc.geyser.registry;
 
 import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundTabListPacket;
 import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket;
-import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
 import io.netty.channel.EventLoop;
-import org.geysermc.common.PlatformType;
 import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.registry.loader.RegistryLoaders;
 import org.geysermc.geyser.session.GeyserSession;
@@ -68,9 +66,10 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
             }
             return true;
         } else {
-            if ((GeyserImpl.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) {
-                // Other debug logs already take care of Bedrock packets for us if on standalone
-                GeyserImpl.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
+            if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
+                if (!IGNORED_PACKETS.contains(clazz)) {
+                    GeyserImpl.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
+                }
             }
 
             return false;
diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
index dce59352a..aec39cdd3 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
@@ -28,22 +28,13 @@ package org.geysermc.geyser.registry.populator;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.google.common.collect.ImmutableMap;
-import org.cloudburstmc.nbt.NBTInputStream;
-import org.cloudburstmc.nbt.NbtList;
-import org.cloudburstmc.nbt.NbtMap;
-import org.cloudburstmc.nbt.NbtMapBuilder;
-import org.cloudburstmc.nbt.NbtType;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 import it.unimi.dsi.fastutil.ints.IntSet;
-import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
-import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.objects.ObjectIntPair;
-import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import it.unimi.dsi.fastutil.objects.*;
+import org.cloudburstmc.nbt.*;
 import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
 import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
 import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
-import org.cloudburstmc.protocol.bedrock.data.defintions.SimpleBlockDefinition;
 import org.cloudburstmc.protocol.common.SimpleDefinitionRegistry;
 import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.level.block.BlockStateValues;
@@ -51,6 +42,7 @@ import org.geysermc.geyser.level.physics.PistonBehavior;
 import org.geysermc.geyser.registry.BlockRegistries;
 import org.geysermc.geyser.registry.type.BlockMapping;
 import org.geysermc.geyser.registry.type.BlockMappings;
+import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
 import org.geysermc.geyser.util.BlockUtils;
 
 import java.io.DataInputStream;
@@ -93,7 +85,7 @@ public final class BlockRegistryPopulator {
             }
             // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette,
             // as we no longer send a block palette
-            Object2ObjectMap<NbtMap, BlockDefinition> blockStateOrderedMap = new Object2ObjectOpenHashMap<>(blocksTag.size());
+            Object2ObjectMap<NbtMap, GeyserBedrockBlock> blockStateOrderedMap = new Object2ObjectOpenHashMap<>(blocksTag.size());
 
             int stateVersion = -1;
             for (int i = 0; i < blocksTag.size(); i++) {
@@ -103,14 +95,15 @@ public final class BlockRegistryPopulator {
                 if (blockStateOrderedMap.containsKey(tag)) {
                     throw new AssertionError("Duplicate block states in Bedrock palette: " + tag);
                 }
-                blockStateOrderedMap.put(tag, new SimpleBlockDefinition(tag.getString("name"), i, tag));
+                GeyserBedrockBlock block = new GeyserBedrockBlock(i, tag);
+                blockStateOrderedMap.put(tag, block);
                 if (stateVersion == -1) {
                     stateVersion = tag.getInt("version");
                 }
             }
             int javaRuntimeId = -1;
 
-            BlockDefinition airDefinition = null;
+            GeyserBedrockBlock airDefinition = null;
             BlockDefinition commandBlockDefinition = null;
             BlockDefinition waterDefinition = null;
             BlockDefinition movingBlockDefinition = null;
@@ -118,7 +111,7 @@ public final class BlockRegistryPopulator {
 
             BiFunction<String, NbtMapBuilder, String> stateMapper = blockMappers.getOrDefault(palette.getKey(), emptyMapper);
 
-            BlockDefinition[] javaToBedrockBlocks = new BlockDefinition[BLOCKS_JSON.size()];
+            GeyserBedrockBlock[] javaToBedrockBlocks = new GeyserBedrockBlock[BLOCKS_JSON.size()];
             SimpleDefinitionRegistry.Builder<BlockDefinition> registry = SimpleDefinitionRegistry.builder();
 
             Map<String, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>();
@@ -132,7 +125,7 @@ public final class BlockRegistryPopulator {
                 Map.Entry<String, JsonNode> entry = blocksIterator.next();
                 String javaId = entry.getKey();
 
-                BlockDefinition bedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateVersion, stateMapper));
+                GeyserBedrockBlock bedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateVersion, stateMapper));
                 if (bedrockDefinition == null) {
                     throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition! Built NBT tag: \n" +
                             buildBedrockState(entry.getValue(), stateVersion, stateMapper));
diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java
index f40e66c47..57ea15030 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java
@@ -34,7 +34,6 @@ import org.geysermc.geyser.GeyserBootstrap;
 import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.registry.BlockRegistries;
 import org.geysermc.geyser.registry.type.BlockMappings;
-import org.geysermc.geyser.registry.type.ItemMappings;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -80,7 +79,7 @@ public class CreativeItemRegistryPopulator {
     private static ItemData.Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
         int count = 1;
         int damage = 0;
-        int blockRuntimeId = 0;
+        int bedrockBlockRuntimeId = 0;
         NbtMap tag = null;
         JsonNode damageNode = itemNode.get("damage");
         if (damageNode != null) {
@@ -94,7 +93,7 @@ public class CreativeItemRegistryPopulator {
 
         JsonNode blockRuntimeIdNode = itemNode.get("blockRuntimeId");
         if (blockRuntimeIdNode != null) {
-            blockRuntimeId = blockRuntimeIdNode.asInt();
+            bedrockBlockRuntimeId = blockRuntimeIdNode.asInt();
         }
 
         JsonNode nbtNode = itemNode.get("nbt_b64");
@@ -126,6 +125,6 @@ public class CreativeItemRegistryPopulator {
                 .damage(damage)
                 .count(count)
                 .tag(tag)
-                .blockDefinition(blockMappings.getBedrockBlock(blockRuntimeId));
+                .blockDefinition(blockMappings.getDefinitionRegistry().getDefinition(bedrockBlockRuntimeId));
     }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
index 33e6eea38..8fe8723f3 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.MultimapBuilder;
 import it.unimi.dsi.fastutil.Pair;
+import it.unimi.dsi.fastutil.objects.*;
 import org.cloudburstmc.nbt.NbtMap;
 import org.cloudburstmc.nbt.NbtMapBuilder;
 import org.cloudburstmc.nbt.NbtType;
@@ -37,15 +38,10 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 import it.unimi.dsi.fastutil.ints.IntSet;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
 import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
 import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
 import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
+import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
 import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
 import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
 import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
@@ -141,7 +137,7 @@ public class ItemRegistryPopulator {
                 registry.add(definition);
             }
 
-            Object2IntMap<String> bedrockBlockIdOverrides = new Object2IntOpenHashMap<>();
+            Object2ObjectMap<String, BlockDefinition> bedrockBlockIdOverrides = new Object2ObjectOpenHashMap<>();
             Object2IntMap<String> blacklistedIdentifiers = new Object2IntOpenHashMap<>();
 
             List<ItemDefinition> boats = new ObjectArrayList<>();
@@ -157,7 +153,7 @@ public class ItemRegistryPopulator {
 
             AtomicInteger creativeNetId = new AtomicInteger();
             CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
-                ItemData item = itemBuilder.netId(creativeNetId.getAndIncrement()).build();
+                ItemData item = itemBuilder.netId(creativeNetId.incrementAndGet()).build();
                 creativeItems.add(item);
 
                 if (item.getBlockDefinition() != null) {
@@ -166,12 +162,12 @@ public class ItemRegistryPopulator {
                     // Add override for item mapping, unless it already exists... then we know multiple states can exist
                     if (!blacklistedIdentifiers.containsKey(identifier)) {
                         if (bedrockBlockIdOverrides.containsKey(identifier)) {
-                            bedrockBlockIdOverrides.removeInt(identifier);
+                            bedrockBlockIdOverrides.remove(identifier);
                             // Save this as a blacklist, but also as knowledge of what the block state name should be
                             blacklistedIdentifiers.put(identifier, item.getBlockDefinition().getRuntimeId());
                         } else {
                             // Unless there's multiple possibilities for this one state, let this be
-                            bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition().getRuntimeId());
+                            bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition());
                         }
                     }
                 }
@@ -221,19 +217,19 @@ public class ItemRegistryPopulator {
 
                 int stackSize = mappingItem.getStackSize();
 
-                int bedrockBlockId = -1;
+                BlockDefinition bedrockBlock = null;
                 Integer firstBlockRuntimeId = entry.getValue().getFirstBlockRuntimeId();
                 if (firstBlockRuntimeId != null) {
-                    int blockIdOverride = bedrockBlockIdOverrides.getOrDefault(bedrockIdentifier, -1);
-                    if (blockIdOverride != -1) {
+                    BlockDefinition blockOverride = bedrockBlockIdOverrides.get(bedrockIdentifier);
+                    if (blockOverride != null) {
                         // Straight from BDS is our best chance of getting an item that doesn't run into issues
-                        bedrockBlockId = blockIdOverride;
+                        bedrockBlock = blockOverride;
                     } else {
                         // Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining
                         int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, -1);
                         if (aValidBedrockBlockId == -1) {
                             // Fallback
-                            bedrockBlockId = blockMappings.getBedrockBlockId(firstBlockRuntimeId);
+                            bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId);
                         } else {
                             // As of 1.16.220, every item requires a block runtime ID attached to it.
                             // This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls.
@@ -247,8 +243,8 @@ public class ItemRegistryPopulator {
                             // and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents
                             Integer lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId();
                             for (int i = firstBlockRuntimeId; i <= lastBlockRuntimeId; i++) {
-                                int bedrockBlockRuntimeId = blockMappings.getBedrockBlockId(i);
-                                NbtMap blockTag = blockMappings.getBedrockBlockPalette().get(bedrockBlockRuntimeId);
+                                GeyserBedrockBlock bedrockBlockRuntimeId = blockMappings.getBedrockBlock(i);
+                                NbtMap blockTag = bedrockBlockRuntimeId.getState();
                                 String bedrockName = blockTag.getString("name");
                                 if (!bedrockName.equals(correctBedrockIdentifier)) {
                                     continue;
@@ -259,7 +255,7 @@ public class ItemRegistryPopulator {
                                     firstPass = false;
                                     if (states.size() == 0) {
                                         // No need to iterate and find all block states - this is the one, as there can't be any others
-                                        bedrockBlockId = bedrockBlockRuntimeId;
+                                        bedrockBlock = bedrockBlockRuntimeId;
                                         break;
                                     }
                                     requiredBlockStatesBuilder.putAll(states);
@@ -282,7 +278,7 @@ public class ItemRegistryPopulator {
                             }
 
                             NbtMap requiredBlockStates = requiredBlockStatesBuilder.build();
-                            if (bedrockBlockId == -1) {
+                            if (bedrockBlock == null) {
                                 int i = -1;
                                 // We need to loop around again (we can't cache the block tags above) because Bedrock can include states that we don't have a pairing for
                                 // in it's "preferred" block state - I.E. the first matching block state in the list
@@ -299,12 +295,12 @@ public class ItemRegistryPopulator {
                                             }
                                         }
                                         if (valid) {
-                                            bedrockBlockId = i;
+                                            bedrockBlock = blockMappings.getDefinitionRegistry().getDefinition(i);
                                             break;
                                         }
                                     }
                                 }
-                                if (bedrockBlockId == -1) {
+                                if (bedrockBlock == null) {
                                     throw new RuntimeException("Could not find a block match for " + entry.getKey());
                                 }
                             }
@@ -328,7 +324,7 @@ public class ItemRegistryPopulator {
                                         }
                                     }
                                     if (valid) {
-                                        creativeItems.set(j, itemData.toBuilder().blockDefinition(blockMappings.getBedrockBlock(bedrockBlockId)).build());
+                                        creativeItems.set(j, itemData.toBuilder().blockDefinition(bedrockBlock).build());
                                         break;
                                     }
                                 }
@@ -343,7 +339,7 @@ public class ItemRegistryPopulator {
                         .bedrockIdentifier(bedrockIdentifier.intern())
                         .bedrockDefinition(definition)
                         .bedrockData(mappingItem.getBedrockData())
-                        .bedrockBlockDefinition(blockMappings.getBedrockBlock(bedrockBlockId))
+                        .bedrockBlockDefinition(bedrockBlock)
                         .stackSize(stackSize)
                         .maxDamage(mappingItem.getMaxDamage())
                         .hasSuspiciousStewEffect(mappingItem.isHasSuspiciousStewEffect());
@@ -364,7 +360,7 @@ public class ItemRegistryPopulator {
 
                 if (javaOnlyItems.contains(javaIdentifier)) {
                     // These items don't exist on Bedrock, so set up a variable that indicates they should have custom names
-                    mappingBuilder = mappingBuilder.translationString((bedrockBlockId != -1 ? "block." : "item.") + entry.getKey().replace(":", "."));
+                    mappingBuilder = mappingBuilder.translationString((bedrockBlock != null ? "block." : "item.") + entry.getKey().replace(":", "."));
                     GeyserImpl.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated.");
                 }
 
diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java
index ed9463a4e..a565e7309 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java
@@ -30,7 +30,6 @@ import lombok.Value;
 import org.cloudburstmc.nbt.NbtList;
 import org.cloudburstmc.nbt.NbtMap;
 import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
-import org.cloudburstmc.protocol.bedrock.data.defintions.SimpleBlockDefinition;
 import org.cloudburstmc.protocol.common.DefinitionRegistry;
 
 import java.util.Map;
@@ -39,13 +38,13 @@ import java.util.Set;
 @Builder
 @Value
 public class BlockMappings {
-    BlockDefinition bedrockAir;
+    GeyserBedrockBlock bedrockAir;
     BlockDefinition bedrockWater;
     BlockDefinition bedrockMovingBlock;
 
     int blockStateVersion;
 
-    BlockDefinition[] javaToBedrockBlocks;
+    GeyserBedrockBlock[] javaToBedrockBlocks;
     DefinitionRegistry<BlockDefinition> definitionRegistry;
 
     NbtList<NbtMap> bedrockBlockPalette;
@@ -57,18 +56,15 @@ public class BlockMappings {
 
     Set<BlockDefinition> jigsawStates;
 
-    public int getBedrockBlockId(int state) {
-        if (state < 0 || state >= this.javaToBedrockBlocks.length) {
-            return bedrockAir.getRuntimeId();
-        }
-        return this.javaToBedrockBlocks[state].getRuntimeId();
+    public int getBedrockBlockId(int javaState) {
+        return getBedrockBlock(javaState).getRuntimeId();
     }
 
-    public BlockDefinition getBedrockBlock(int state) {
-        if (state < 0 || state >= this.javaToBedrockBlocks.length) {
+    public GeyserBedrockBlock getBedrockBlock(int javaState) {
+        if (javaState < 0 || javaState >= this.javaToBedrockBlocks.length) {
             return bedrockAir;
         }
-        return this.javaToBedrockBlocks[state];
+        return this.javaToBedrockBlocks[javaState];
     }
 
     public BlockDefinition getItemFrame(NbtMap tag) {
@@ -76,7 +72,7 @@ public class BlockMappings {
     }
 
     public boolean isItemFrame(BlockDefinition definition) {
-        if (definition instanceof SimpleBlockDefinition def) {
+        if (definition instanceof GeyserBedrockBlock def) {
             return this.itemFrames.containsKey(def.getState());
         }
 
diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserBedrockBlock.java b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserBedrockBlock.java
new file mode 100644
index 000000000..2fefc539b
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserBedrockBlock.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.registry.type;
+
+import org.cloudburstmc.nbt.NbtMap;
+import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
+
+public class GeyserBedrockBlock implements BlockDefinition {
+    private final int runtimeId;
+    private final NbtMap state;
+
+    public GeyserBedrockBlock(int runtimeId, NbtMap state) {
+        this.runtimeId = runtimeId;
+        this.state = state;
+    }
+
+    @Override
+    public int getRuntimeId() {
+        return runtimeId;
+    }
+
+    public NbtMap getState() {
+        return state;
+    }
+
+    @Override
+    public String toString() {
+        return "GeyserBedrockBlock{" + state.getString("name") + "}";
+    }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
index 41068b76d..229649ded 100644
--- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
+++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
@@ -89,19 +89,11 @@ import org.cloudburstmc.math.vector.Vector3f;
 import org.cloudburstmc.math.vector.Vector3i;
 import org.cloudburstmc.nbt.NbtMap;
 import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
-import org.cloudburstmc.protocol.bedrock.data.Ability;
-import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
-import org.cloudburstmc.protocol.bedrock.data.AttributeData;
-import org.cloudburstmc.protocol.bedrock.data.AuthoritativeMovementMode;
-import org.cloudburstmc.protocol.bedrock.data.ChatRestrictionLevel;
-import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting;
-import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
-import org.cloudburstmc.protocol.bedrock.data.GameType;
-import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
-import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
+import org.cloudburstmc.protocol.bedrock.data.*;
 import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
 import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
 import org.cloudburstmc.protocol.bedrock.packet.*;
+import org.cloudburstmc.protocol.common.util.OptionalBoolean;
 import org.geysermc.api.util.BedrockPlatform;
 import org.geysermc.api.util.InputMode;
 import org.geysermc.api.util.UiProfile;
@@ -879,14 +871,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
             downstream = new LocalSession(this.remoteServer.address(), this.remoteServer.port(),
                     geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(),
                     this.protocol, this.protocol.createHelper());
+            this.downstream = new DownstreamSession(downstream);
         } else {
             downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), this.protocol);
+            this.downstream = new DownstreamSession(downstream);
             disableSrvResolving();
         }
 
-        // Wrap in DownstreamSession
-        this.downstream = new DownstreamSession(downstream);
-
         if (geyser.getConfig().getRemote().isUseProxyProtocol()) {
             downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
             downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
@@ -1473,6 +1464,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
         startGamePacket.setUsingMsaGamertagsOnly(false);
         startGamePacket.setFromWorldTemplate(false);
         startGamePacket.setWorldTemplateOptionLocked(false);
+        startGamePacket.setSpawnBiomeType(SpawnBiomeType.DEFAULT);
+        startGamePacket.setCustomBiomeName("");
+        startGamePacket.setEducationProductionId("");
+        startGamePacket.setForceExperimentalGameplay(OptionalBoolean.empty());
 
         String serverName = geyser.getConfig().getBedrock().serverName();
         startGamePacket.setLevelId(serverName);
diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java
index 0b079fffd..b69e00ad7 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java
@@ -147,6 +147,16 @@ public abstract class ItemTranslator {
         return itemStack;
     }
 
+    @Nonnull
+    public static ItemData.Builder translateToBedrock(GeyserSession session, int javaId, int count, CompoundTag tag) {
+        ItemMapping bedrockItem = session.getItemMappings().getMapping(javaId);
+        if (bedrockItem == ItemMapping.AIR) {
+            session.getGeyser().getLogger().debug("ItemMapping returned air: " + javaId);
+            return ItemData.builder();
+        }
+        return translateToBedrock(session, bedrockItem, count, tag);
+    }
+
     @Nonnull
     public static ItemData translateToBedrock(GeyserSession session, ItemStack stack) {
         if (stack == null) {
@@ -154,38 +164,43 @@ public abstract class ItemTranslator {
         }
 
         ItemMapping bedrockItem = session.getItemMappings().getMapping(stack);
-        if (bedrockItem == null) {
-            session.getGeyser().getLogger().debug("No matching ItemMapping for " + stack);
+        if (bedrockItem == ItemMapping.AIR) {
+            session.getGeyser().getLogger().debug("ItemMapping returned air: " + stack);
             return ItemData.AIR;
         }
+        return translateToBedrock(session, bedrockItem, stack.getAmount(), stack.getNbt())
+                .build();
+    }
 
-        CompoundTag nbt = stack.getNbt() != null ? stack.getNbt().clone() : null;
+    @Nonnull
+    private static ItemData.Builder translateToBedrock(GeyserSession session, ItemMapping mapping, int count, CompoundTag tag) {
+        CompoundTag nbt = tag != null ? tag.clone() : null;
 
         // This is a fallback for maps with no nbt
-        if (nbt == null && bedrockItem.getJavaIdentifier().equals("minecraft:filled_map")) {
+        if (nbt == null && mapping.getJavaIdentifier().equals("minecraft:filled_map")) {
             nbt = new CompoundTag("");
             nbt.put(new IntTag("map", 0));
         }
 
         if (nbt != null) {
             for (NbtItemStackTranslator translator : NBT_TRANSLATORS) {
-                if (translator.acceptItem(bedrockItem)) {
-                    translator.translateToBedrock(session, nbt, bedrockItem);
+                if (translator.acceptItem(mapping)) {
+                    translator.translateToBedrock(session, nbt, mapping);
                 }
             }
         }
 
-        nbt = translateDisplayProperties(session, nbt, bedrockItem);
+        nbt = translateDisplayProperties(session, nbt, mapping);
         if (session.isAdvancedTooltips()) {
-            nbt = addAdvancedTooltips(nbt, bedrockItem, session.locale());
+            nbt = addAdvancedTooltips(nbt, mapping, session.locale());
         }
 
-        ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt);
+        ItemStack itemStack = new ItemStack(mapping.getJavaId(), count, nbt);
 
-        ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.getOrDefault(bedrockItem.getJavaId(), DEFAULT_TRANSLATOR);
-        ItemData.Builder builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
-        if (bedrockItem.isBlock()) {
-            builder.blockDefinition(bedrockItem.getBedrockBlockDefinition());
+        ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.getOrDefault(mapping.getJavaId(), DEFAULT_TRANSLATOR);
+        ItemData.Builder builder = itemStackTranslator.translateToBedrock(itemStack, mapping, session.getItemMappings());
+        if (mapping.isBlock()) {
+            builder.blockDefinition(mapping.getBedrockBlockDefinition());
         }
 
         if (nbt != null) {
@@ -202,7 +217,7 @@ public abstract class ItemTranslator {
             }
         }
 
-        return builder.build();
+        return builder;
     }
 
     private static CompoundTag addAdvancedTooltips(CompoundTag nbt, ItemMapping mapping, String language) {
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java
index 08f40429b..e284bbbea 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java
@@ -109,7 +109,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
     @Override
     public void translate(GeyserSession session, ClientboundCommandsPacket packet) {
         // Don't send command suggestions if they are disabled
-        if (!session.getGeyser().getConfig().isCommandSuggestions()) {
+        if (!session.getGeyser().getConfig().isCommandSuggestions() || true) {
             session.getGeyser().getLogger().debug("Not sending translated command suggestions as they are disabled.");
 
             // Send an empty packet so Bedrock doesn't override /help with its own, built-in help command.
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java
index 7fd303604..712e7fa89 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java
@@ -301,6 +301,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
 
             // Allocate output buffer
             byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
+            byteBuf.retain();
             for (int i = 0; i < sectionCount; i++) {
                 GeyserChunkSection section = sections[i];
                 if (section != null) {
@@ -349,7 +350,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
         levelChunkPacket.setCachingEnabled(false);
         levelChunkPacket.setChunkX(packet.getX());
         levelChunkPacket.setChunkZ(packet.getZ());
-        levelChunkPacket.setData(byteBuf);
+        levelChunkPacket.setData(byteBuf.retain());
         session.sendUpstreamPacket(levelChunkPacket);
 
         for (Map.Entry<Vector3i, ItemFrameEntity> entry : session.getItemFrameCache().entrySet()) {
diff --git a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java
index 8141675ff..894a19539 100644
--- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java
+++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java
@@ -25,17 +25,17 @@
 
 package org.geysermc.geyser.util;
 
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.Unpooled;
+import it.unimi.dsi.fastutil.ints.IntLists;
+import lombok.experimental.UtilityClass;
 import org.cloudburstmc.math.vector.Vector2i;
 import org.cloudburstmc.math.vector.Vector3i;
 import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
 import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket;
 import org.cloudburstmc.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
 import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufAllocator;
-import io.netty.buffer.Unpooled;
-import it.unimi.dsi.fastutil.ints.IntLists;
-import lombok.experimental.UtilityClass;
 import org.geysermc.geyser.entity.type.ItemFrameEntity;
 import org.geysermc.geyser.level.BedrockDimension;
 import org.geysermc.geyser.level.JavaDimension;
@@ -181,18 +181,18 @@ public class ChunkUtils {
             }
 
             byteBuf.writeByte(0); // Border blocks - Edu edition only
+
+            LevelChunkPacket data = new LevelChunkPacket();
+            data.setChunkX(chunkX);
+            data.setChunkZ(chunkZ);
+            data.setSubChunksLength(0);
+            data.setData(byteBuf.retain());
+            data.setCachingEnabled(false);
+            session.sendUpstreamPacket(data);
         } finally {
             byteBuf.release();
         }
 
-        LevelChunkPacket data = new LevelChunkPacket();
-        data.setChunkX(chunkX);
-        data.setChunkZ(chunkZ);
-        data.setSubChunksLength(0);
-        data.setData(byteBuf);
-        data.setCachingEnabled(false);
-        session.sendUpstreamPacket(data);
-
         if (forceUpdate) {
             Vector3i pos = Vector3i.from(chunkX << 4, 80, chunkZ << 4);
             UpdateBlockPacket blockPacket = new UpdateBlockPacket();
diff --git a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
index ec37ca6bd..0504b6321 100644
--- a/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
+++ b/core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
@@ -54,7 +54,6 @@ import org.geysermc.geyser.text.ChatColor;
 import org.geysermc.geyser.text.GeyserLocale;
 
 import javax.crypto.SecretKey;
-import java.io.IOException;
 import java.net.URI;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -192,12 +191,13 @@ public class LoginEncryptionUtils {
         KeyPair serverKeyPair = generator.generateKeyPair();
 
         byte[] token = EncryptionUtils.generateRandomToken();
-        SecretKey encryptionKey = EncryptionUtils.getSecretKey(serverKeyPair.getPrivate(), key, token);
-        session.getUpstream().getSession().enableEncryption(encryptionKey);
 
         ServerToClientHandshakePacket packet = new ServerToClientHandshakePacket();
         packet.setJwt(EncryptionUtils.createHandshakeJwt(serverKeyPair, token).serialize());
         session.sendUpstreamPacketImmediately(packet);
+
+        SecretKey encryptionKey = EncryptionUtils.getSecretKey(serverKeyPair.getPrivate(), key, token);
+        session.getUpstream().getSession().enableEncryption(encryptionKey);
     }
 
     private static void sendEncryptionFailedMessage(GeyserImpl geyser) {