diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
index d9e95e9a9..3937fa648 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
@@ -219,6 +219,12 @@ public class GeyserSession implements CommandSender {
      */
     private final Set<Vector3i> lecternCache;
 
+    /**
+     * A list of all players that have a player head on with a custom texture.
+     * Our workaround for these players is to give them a custom skin and geometry to emulate wearing a custom skull.
+     */
+    private final Set<UUID> playerWithCustomHeads = new ObjectOpenHashSet<>();
+
     @Setter
     private boolean droppingLecternBook;
 
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
index 3ff547c95..7d0c3c7bc 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
@@ -89,6 +89,10 @@ public class EntityCache {
     }
 
     public boolean removeEntity(Entity entity, boolean force) {
+        if (entity instanceof PlayerEntity player) {
+            session.getPlayerWithCustomHeads().remove(player.getUuid());
+        }
+
         if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) {
             long geyserId = entityIdTranslations.remove(entity.getEntityId());
             entities.remove(geyserId);
@@ -107,6 +111,7 @@ public class EntityCache {
             session.getEntityCache().removeEntity(entity, false);
         }
 
+        session.getPlayerWithCustomHeads().clear();
         // As a precaution
         cachedPlayerEntityLinks.clear();
     }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java
index f52a3053d..1de69c814 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java
@@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
 import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
 import com.github.steveice10.mc.protocol.data.game.window.WindowType;
 import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket;
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
 import com.nukkitx.protocol.bedrock.data.inventory.*;
 import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*;
 import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
@@ -44,6 +45,7 @@ import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot
 import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
 import org.geysermc.connector.network.translators.inventory.SlotType;
 import org.geysermc.connector.network.translators.item.ItemTranslator;
+import org.geysermc.connector.skin.FakeHeadProvider;
 import org.geysermc.connector.utils.InventoryUtils;
 import org.geysermc.connector.utils.LanguageUtils;
 
@@ -117,6 +119,20 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
 
     @Override
     public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
+        GeyserItemStack javaItem = inventory.getItem(slot);
+        ItemData bedrockItem = javaItem.getItemData(session);
+
+        if (slot == 5) {
+            // Check for custom skull
+            if (javaItem.getJavaId() == session.getItemMappings().getStoredItems().playerHead().getJavaId()
+                    && javaItem.getNbt() != null
+                    && javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) {
+                FakeHeadProvider.setHead(session, session.getPlayerEntity(), profile);
+            } else {
+                FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
+            }
+        }
+
         if (slot >= 1 && slot <= 44) {
             InventorySlotPacket slotPacket = new InventorySlotPacket();
             if (slot >= 9) {
@@ -133,12 +149,12 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
                 slotPacket.setContainerId(ContainerId.UI);
                 slotPacket.setSlot(slot + 27);
             }
-            slotPacket.setItem(inventory.getItem(slot).getItemData(session));
+            slotPacket.setItem(bedrockItem);
             session.sendUpstreamPacket(slotPacket);
         } else if (slot == 45) {
             InventoryContentPacket offhandPacket = new InventoryContentPacket();
             offhandPacket.setContainerId(ContainerId.OFFHAND);
-            offhandPacket.setContents(Collections.singletonList(inventory.getItem(slot).getItemData(session)));
+            offhandPacket.setContents(Collections.singletonList(bedrockItem));
             session.sendUpstreamPacket(offhandPacket);
         }
     }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java
index 6bbdb7421..fae36c2ea 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java
@@ -48,6 +48,7 @@ public class StoredItemMappings {
     private final ItemMapping lodestoneCompass;
     private final ItemMapping milkBucket;
     private final ItemMapping powderSnowBucket;
+    private final ItemMapping playerHead;
     private final ItemMapping egg;
     private final ItemMapping shield;
     private final ItemMapping wheat;
@@ -64,6 +65,7 @@ public class StoredItemMappings {
         this.lodestoneCompass = load(itemMappings, "lodestone_compass");
         this.milkBucket = load(itemMappings, "milk_bucket");
         this.powderSnowBucket = load(itemMappings, "powder_snow_bucket");
+        this.playerHead = load(itemMappings, "player_head");
         this.egg = load(itemMappings, "egg");
         this.shield = load(itemMappings, "shield");
         this.wheat = load(itemMappings, "wheat");
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java
index f297f1a38..affcdfe87 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java
@@ -26,14 +26,18 @@
 package org.geysermc.connector.network.translators.java.entity;
 
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.Equipment;
+import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
 import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityEquipmentPacket;
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
 import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
 import org.geysermc.connector.entity.Entity;
 import org.geysermc.connector.entity.LivingEntity;
+import org.geysermc.connector.entity.player.PlayerEntity;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.PacketTranslator;
 import org.geysermc.connector.network.translators.Translator;
 import org.geysermc.connector.network.translators.item.ItemTranslator;
+import org.geysermc.connector.skin.FakeHeadProvider;
 
 @Translator(packet = ServerEntityEquipmentPacket.class)
 public class JavaEntityEquipmentTranslator extends PacketTranslator<ServerEntityEquipmentPacket> {
@@ -63,6 +67,17 @@ public class JavaEntityEquipmentTranslator extends PacketTranslator<ServerEntity
             ItemData item = ItemTranslator.translateToBedrock(session, equipment.getItem());
             switch (equipment.getSlot()) {
                 case HELMET -> {
+                    ItemStack javaItem = equipment.getItem();
+                    if (livingEntity instanceof PlayerEntity
+                            && javaItem != null
+                            && javaItem.getId() == session.getItemMappings().getStoredItems().playerHead().getJavaId()
+                            && javaItem.getNbt() != null
+                            && javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) {
+                        FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile);
+                    } else {
+                        FakeHeadProvider.restoreOriginalSkin(session, livingEntity);
+                    }
+
                     livingEntity.setHelmet(item);
                     armorUpdated = true;
                 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
index 90458ca71..5bf75f059 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
@@ -48,7 +48,6 @@ import java.util.concurrent.TimeUnit;
 
 @BlockEntity(name = "Skull")
 public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
-    public static boolean ALLOW_CUSTOM_SKULLS;
 
     @Override
     public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
@@ -63,8 +62,8 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
     }
 
     public static CompletableFuture<GameProfile> getProfile(CompoundTag tag) {
-        if (tag.contains("SkullOwner")) {
-            CompoundTag owner = tag.get("SkullOwner");
+        CompoundTag owner = tag.get("SkullOwner");
+        if (owner != null) {
             CompoundTag properties = owner.get("Properties");
             if (properties == null) {
                 return SkinProvider.requestTexturesFromUsername(owner);
diff --git a/connector/src/main/java/org/geysermc/connector/skin/FakeHeadProvider.java b/connector/src/main/java/org/geysermc/connector/skin/FakeHeadProvider.java
new file mode 100644
index 000000000..b848c0c35
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/skin/FakeHeadProvider.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.skin;
+
+import com.github.steveice10.mc.auth.data.GameProfile;
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.nukkitx.protocol.bedrock.data.skin.ImageData;
+import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
+import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
+import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.entity.LivingEntity;
+import org.geysermc.connector.entity.player.PlayerEntity;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.utils.LanguageUtils;
+
+import javax.annotation.Nonnull;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Responsible for modifying a player's skin when wearing a player head
+ */
+public class FakeHeadProvider {
+    private static final LoadingCache<FakeHeadEntry, SkinProvider.SkinData> MERGED_SKINS_LOADING_CACHE = CacheBuilder.newBuilder()
+            .expireAfterAccess(1, TimeUnit.HOURS)
+            .maximumSize(10000)
+            .build(new CacheLoader<>() {
+                @Override
+                public SkinProvider.SkinData load(@Nonnull FakeHeadEntry fakeHeadEntry) throws Exception {
+                    SkinProvider.SkinData skinData = SkinProvider.getOrDefault(SkinProvider.requestSkinData(fakeHeadEntry.getEntity()), null, 5);
+
+                    if (skinData == null) {
+                        throw new Exception("Couldn't load player's original skin");
+                    }
+
+                    SkinProvider.Skin skin = skinData.skin();
+                    SkinProvider.Cape cape = skinData.cape();
+                    SkinProvider.SkinGeometry geometry = skinData.geometry().getGeometryName().equals("{\"geometry\" :{\"default\" :\"geometry.humanoid.customSlim\"}}")
+                            ? SkinProvider.WEARING_CUSTOM_SKULL_SLIM : SkinProvider.WEARING_CUSTOM_SKULL;
+
+                    SkinProvider.Skin headSkin = SkinProvider.getOrDefault(
+                            SkinProvider.requestSkin(fakeHeadEntry.getEntity().getUuid(), fakeHeadEntry.getFakeHeadSkinUrl(), false), SkinProvider.EMPTY_SKIN, 5);
+                    BufferedImage originalSkinImage = SkinProvider.imageDataToBufferedImage(skin.getSkinData(), 64, skin.getSkinData().length / 4 / 64);
+                    BufferedImage headSkinImage = SkinProvider.imageDataToBufferedImage(headSkin.getSkinData(), 64, headSkin.getSkinData().length / 4 / 64);
+
+                    Graphics2D graphics2D = originalSkinImage.createGraphics();
+                    graphics2D.setComposite(AlphaComposite.Clear);
+                    graphics2D.fillRect(0, 0, 64, 16);
+                    graphics2D.setComposite(AlphaComposite.SrcOver);
+                    graphics2D.drawImage(headSkinImage, 0, 0, 64, 16, 0, 0, 64, 16, null);
+                    graphics2D.dispose();
+
+                    // Make the skin key a combination of the current skin data and the new skin data
+                    // Don't tie it to a player - that player *can* change skins in-game
+                    String skinKey = "customPlayerHead_" + fakeHeadEntry.getFakeHeadSkinUrl() + "_" + skin.getTextureUrl();
+                    byte[] targetSkinData = SkinProvider.bufferedImageToImageData(originalSkinImage);
+                    SkinProvider.Skin mergedSkin = new SkinProvider.Skin(fakeHeadEntry.getEntity().getUuid(), skinKey, targetSkinData, System.currentTimeMillis(), false, false);
+
+                    // Avoiding memory leak
+                    fakeHeadEntry.setEntity(null);
+
+                    return new SkinProvider.SkinData(mergedSkin, cape, geometry);
+                }
+            });
+
+    public static void setHead(GeyserSession session, PlayerEntity entity, CompoundTag profileTag) {
+        SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.from(profileTag);
+        if (gameProfileData == null) {
+            return;
+        }
+        String fakeHeadSkinUrl = gameProfileData.skinUrl();
+
+        session.getPlayerWithCustomHeads().add(entity.getUuid());
+
+        GameProfile.Property texturesProperty = entity.getProfile().getProperty("textures");
+
+        SkinProvider.EXECUTOR_SERVICE.execute(() -> {
+            try {
+                SkinProvider.SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity));
+
+                if (session.getUpstream().isInitialized()) {
+                    sendSkinPacket(session, entity, mergedSkinData);
+                }
+            } catch (ExecutionException e) {
+                GeyserConnector.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e);
+            }
+        });
+    }
+
+    public static void restoreOriginalSkin(GeyserSession session, LivingEntity livingEntity) {
+        if (!(livingEntity instanceof PlayerEntity entity)) {
+            return;
+        }
+
+        if (!session.getPlayerWithCustomHeads().remove(entity.getUuid())) {
+            return;
+        }
+
+        SkinProvider.requestSkinData(entity).whenCompleteAsync((skinData, throwable) -> {
+            if (throwable != null) {
+                GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable);
+                return;
+            }
+
+            if (session.getUpstream().isInitialized()) {
+                sendSkinPacket(session, entity, skinData);
+            }
+        });
+    }
+
+    private static void sendSkinPacket(GeyserSession session, PlayerEntity entity, SkinProvider.SkinData skinData) {
+        SkinProvider.Skin skin = skinData.skin();
+        SkinProvider.Cape cape = skinData.cape();
+        SkinProvider.SkinGeometry geometry = skinData.geometry();
+
+        if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) {
+            PlayerListPacket.Entry updatedEntry = SkinManager.buildEntryManually(
+                    session,
+                    entity.getUuid(),
+                    entity.getUsername(),
+                    entity.getGeyserId(),
+                    skin.getTextureUrl(),
+                    skin.getSkinData(),
+                    cape.getCapeId(),
+                    cape.getCapeData(),
+                    geometry
+            );
+
+            PlayerListPacket playerAddPacket = new PlayerListPacket();
+            playerAddPacket.setAction(PlayerListPacket.Action.ADD);
+            playerAddPacket.getEntries().add(updatedEntry);
+            session.sendUpstreamPacket(playerAddPacket);
+        } else {
+            PlayerSkinPacket packet = new PlayerSkinPacket();
+            packet.setUuid(entity.getUuid());
+            packet.setOldSkinName("");
+            packet.setNewSkinName(skin.getTextureUrl());
+            packet.setSkin(getSkin(skin.getTextureUrl(), skin, cape, geometry));
+            packet.setTrustedSkin(true);
+            session.sendUpstreamPacket(packet);
+        }
+    }
+
+    private static SerializedSkin getSkin(String skinId, SkinProvider.Skin skin, SkinProvider.Cape cape, SkinProvider.SkinGeometry geometry) {
+        return SerializedSkin.of(skinId, "", geometry.getGeometryName(),
+                ImageData.of(skin.getSkinData()), Collections.emptyList(),
+                ImageData.of(cape.getCapeData()), geometry.getGeometryData(),
+                "", true, false, false, cape.getCapeId(), skinId);
+    }
+
+    @AllArgsConstructor
+    @Getter
+    @Setter
+    private static class FakeHeadEntry {
+        private final GameProfile.Property texturesProperty;
+        private final String fakeHeadSkinUrl;
+        private PlayerEntity entity;
+
+        @Override
+        public boolean equals(Object o) {
+            // We don't care about the equality of the entity as that is not used for caching purposes
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            FakeHeadEntry that = (FakeHeadEntry) o;
+            return equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl);
+        }
+
+        private boolean equals(GameProfile.Property a, GameProfile.Property b) {
+            //TODO actually fix this in MCAuthLib
+            if (a == b) {
+                return true;
+            }
+            if (a == null || b == null) {
+                return false;
+            }
+            return Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getValue(), b.getValue()) && Objects.equals(a.getSignature(), b.getSignature());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(texturesProperty, fakeHeadSkinUrl);
+        }
+    }
+
+}
diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java
index 70f9f8ff5..5aaabcffb 100644
--- a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java
+++ b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java
@@ -27,6 +27,9 @@ package org.geysermc.connector.skin;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.github.steveice10.mc.auth.data.GameProfile;
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import com.github.steveice10.opennbt.tag.builtin.ListTag;
+import com.github.steveice10.opennbt.tag.builtin.StringTag;
 import com.nukkitx.protocol.bedrock.data.skin.ImageData;
 import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
 import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
@@ -37,6 +40,8 @@ import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.session.auth.BedrockClientData;
 import org.geysermc.connector.utils.LanguageUtils;
 
+import javax.annotation.Nullable;
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 import java.util.Collections;
@@ -75,9 +80,9 @@ public class SkinManager {
      * With all the information needed, build a Bedrock player entry with translated skin information.
      */
     public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId,
-                                                                 String skinId, byte[] skinData,
-                                                                 String capeId, byte[] capeData,
-                                                                 SkinProvider.SkinGeometry geometry) {
+                                                            String skinId, byte[] skinData,
+                                                            String capeId, byte[] capeData,
+                                                            SkinProvider.SkinGeometry geometry) {
         SerializedSkin serializedSkin = SerializedSkin.of(
                 skinId, "", geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
                 ImageData.of(capeData), geometry.getGeometryData(), "", true, false,
@@ -114,93 +119,52 @@ public class SkinManager {
 
     public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session,
                                                    Consumer<SkinProvider.SkinAndCape> skinAndCapeConsumer) {
-        GameProfileData data = GameProfileData.from(entity.getProfile());
+        SkinProvider.requestSkinData(entity).whenCompleteAsync((skinData, throwable) -> {
+            if (skinData == null) {
+                if (skinAndCapeConsumer != null) {
+                    skinAndCapeConsumer.accept(null);
+                }
 
-        SkinProvider.requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl())
-                .whenCompleteAsync((skinAndCape, throwable) -> {
-                    try {
-                        SkinProvider.Skin skin = skinAndCape.getSkin();
-                        SkinProvider.Cape cape = skinAndCape.getCape();
-                        SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex());
+                return;
+            }
 
-                        if (cape.isFailed()) {
-                            cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape(entity.getUuid()),
-                                    SkinProvider.EMPTY_CAPE, 3);
-                        }
+            if (skinData.geometry() != null) {
+                SkinProvider.Skin skin = skinData.skin();
+                SkinProvider.Cape cape = skinData.cape();
+                SkinProvider.SkinGeometry geometry = skinData.geometry();
 
-                        if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) {
-                            cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape(
-                                    cape, entity.getUuid(),
-                                    entity.getUsername(), false
-                            ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3);
-                        }
-
-                        geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry(
-                                geometry, entity.getUuid()
-                        ), geometry, 3);
-
-                        boolean isDeadmau5 = "deadmau5".equals(entity.getUsername());
-                        // Not a bedrock player check for ears
-                        if (geometry.isFailed() && (SkinProvider.ALLOW_THIRD_PARTY_EARS || isDeadmau5)) {
-                            boolean isEars;
-
-                            // Its deadmau5, gotta support his skin :)
-                            if (isDeadmau5) {
-                                isEars = true;
-                            } else {
-                                // Get the ears texture for the player
-                                skin = SkinProvider.getOrDefault(SkinProvider.requestUnofficialEars(
-                                        skin, entity.getUuid(), entity.getUsername(), false
-                                ), skin, 3);
-
-                                isEars = skin.isEars();
-                            }
-
-                            // Does the skin have an ears texture
-                            if (isEars) {
-                                // Get the new geometry
-                                geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());
-
-                                // Store the skin and geometry for the ears
-                                SkinProvider.storeEarSkin(skin);
-                                SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
-                            }
-                        }
-
-                        if (session.getUpstream().isInitialized()) {
-                            PlayerListPacket.Entry updatedEntry = buildEntryManually(
-                                    session,
-                                    entity.getUuid(),
-                                    entity.getUsername(),
-                                    entity.getGeyserId(),
-                                    skin.getTextureUrl(),
-                                    skin.getSkinData(),
-                                    cape.getCapeId(),
-                                    cape.getCapeData(),
-                                    geometry
-                            );
+                if (session.getUpstream().isInitialized()) {
+                    PlayerListPacket.Entry updatedEntry = buildEntryManually(
+                            session,
+                            entity.getUuid(),
+                            entity.getUsername(),
+                            entity.getGeyserId(),
+                            skin.getTextureUrl(),
+                            skin.getSkinData(),
+                            cape.getCapeId(),
+                            cape.getCapeData(),
+                            geometry
+                    );
 
 
-                            PlayerListPacket playerAddPacket = new PlayerListPacket();
-                            playerAddPacket.setAction(PlayerListPacket.Action.ADD);
-                            playerAddPacket.getEntries().add(updatedEntry);
-                            session.sendUpstreamPacket(playerAddPacket);
+                    PlayerListPacket playerAddPacket = new PlayerListPacket();
+                    playerAddPacket.setAction(PlayerListPacket.Action.ADD);
+                    playerAddPacket.getEntries().add(updatedEntry);
+                    session.sendUpstreamPacket(playerAddPacket);
 
-                            if (!entity.isPlayerList()) {
-                                PlayerListPacket playerRemovePacket = new PlayerListPacket();
-                                playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
-                                playerRemovePacket.getEntries().add(updatedEntry);
-                                session.sendUpstreamPacket(playerRemovePacket);
-                            }
-                        }
-                    } catch (Exception e) {
-                        GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
+                    if (!entity.isPlayerList()) {
+                        PlayerListPacket playerRemovePacket = new PlayerListPacket();
+                        playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
+                        playerRemovePacket.getEntries().add(updatedEntry);
+                        session.sendUpstreamPacket(playerRemovePacket);
                     }
+                }
+            }
 
-                    if (skinAndCapeConsumer != null) {
-                        skinAndCapeConsumer.accept(skinAndCape);
-                    }
-                });
+            if (skinAndCapeConsumer != null) {
+                skinAndCapeConsumer.accept(new SkinProvider.SkinAndCape(skinData.skin(), skinData.cape()));
+            }
+        });
     }
 
     public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) {
@@ -233,6 +197,37 @@ public class SkinManager {
     }
 
     public record GameProfileData(String skinUrl, String capeUrl, boolean isAlex) {
+        /**
+         * Generate the GameProfileData from the given CompoundTag representing a GameProfile
+         *
+         * @param tag tag to build the GameProfileData from
+         * @return The built GameProfileData, or null if this wasn't a valid tag
+         */
+        public static @Nullable GameProfileData from(CompoundTag tag) {
+            if (!(tag.get("Properties") instanceof CompoundTag propertiesTag)) {
+                return null;
+            }
+            if (!(propertiesTag.get("textures") instanceof ListTag texturesTag) || texturesTag.size() == 0) {
+                return null;
+            }
+            if (!(texturesTag.get(0) instanceof CompoundTag texturesData)) {
+                return null;
+            }
+            if (!(texturesData.get("Value") instanceof StringTag skinDataValue)) {
+                return null;
+            }
+
+            try {
+                return loadFromJson(skinDataValue.getValue());
+            } catch (IOException e) {
+                GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for tag " + tag);
+                if (GeyserConnector.getInstance().getConfig().isDebugMode()) {
+                    e.printStackTrace();
+                }
+                return null;
+            }
+        }
+
         /**
          * Generate the GameProfileData from the given GameProfile
          *
@@ -247,22 +242,8 @@ public class SkinManager {
                     // Likely offline mode
                     return loadBedrockOrOfflineSkin(profile);
                 }
-                JsonNode skinObject = GeyserConnector.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8));
-                JsonNode textures = skinObject.get("textures");
-
-                JsonNode skinTexture = textures.get("SKIN");
-                String skinUrl = skinTexture.get("url").asText().replace("http://", "https://");
-
-                boolean isAlex = skinTexture.has("metadata");
-
-                String capeUrl = null;
-                if (textures.has("CAPE")) {
-                    JsonNode capeTexture = textures.get("CAPE");
-                    capeUrl = capeTexture.get("url").asText().replace("http://", "https://");
-                }
-
-                return new GameProfileData(skinUrl, capeUrl, isAlex);
-            } catch (Exception exception) {
+                return loadFromJson(skinProperty.getValue());
+            } catch (IOException exception) {
                 GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName());
                 if (GeyserConnector.getInstance().getConfig().isDebugMode()) {
                     exception.printStackTrace();
@@ -271,6 +252,24 @@ public class SkinManager {
             }
         }
 
+        private static GameProfileData loadFromJson(String encodedJson) throws IOException {
+            JsonNode skinObject = GeyserConnector.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8));
+            JsonNode textures = skinObject.get("textures");
+
+            JsonNode skinTexture = textures.get("SKIN");
+            String skinUrl = skinTexture.get("url").asText().replace("http://", "https://");
+
+            boolean isAlex = skinTexture.has("metadata");
+
+            String capeUrl = null;
+            JsonNode capeTexture = textures.get("CAPE");
+            if (capeTexture != null) {
+                capeUrl = capeTexture.get("url").asText().replace("http://", "https://");
+            }
+
+            return new GameProfileData(skinUrl, capeUrl, isAlex);
+        }
+
         /**
          * @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this
          * is a Bedrock player.
diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java
index 567e52ace..7b1ea9550 100644
--- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java
+++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java
@@ -37,8 +37,10 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.entity.player.PlayerEntity;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.utils.FileUtils;
+import org.geysermc.connector.utils.LanguageUtils;
 import org.geysermc.connector.utils.WebUtils;
 
 import javax.imageio.ImageIO;
@@ -57,7 +59,7 @@ import java.util.concurrent.*;
 
 public class SkinProvider {
     public static final boolean ALLOW_THIRD_PARTY_CAPES = GeyserConnector.getInstance().getConfig().isAllowThirdPartyCapes();
-    private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14);
+    static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14);
 
     public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin();
     public static final Skin EMPTY_SKIN = new Skin(-1, "steve", STEVE_SKIN);
@@ -85,6 +87,8 @@ public class SkinProvider {
     public static final String EARS_GEOMETRY;
     public static final String EARS_GEOMETRY_SLIM;
     public static final SkinGeometry SKULL_GEOMETRY;
+    public static final SkinGeometry WEARING_CUSTOM_SKULL;
+    public static final SkinGeometry WEARING_CUSTOM_SKULL_SLIM;
 
     public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
@@ -99,6 +103,12 @@ public class SkinProvider {
         String skullData = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.customskull.json")), StandardCharsets.UTF_8);
         SKULL_GEOMETRY = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.customskull\"}}", skullData, false);
 
+        /* Load in the player head skull geometry */
+        String wearingCustomSkull = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.wearingCustomSkull.json")), StandardCharsets.UTF_8);
+        WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false);
+        String wearingCustomSkullSlim = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json")), StandardCharsets.UTF_8);
+        WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false);
+
         // Schedule Daily Image Expiry if we are caching them
         if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) {
             GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> {
@@ -108,7 +118,7 @@ public class SkinProvider {
                 }
 
                 int count = 0;
-                final long expireTime = ((long)GeyserConnector.getInstance().getConfig().getCacheImages()) * ((long)1000 * 60 * 60 * 24);
+                final long expireTime = ((long) GeyserConnector.getInstance().getConfig().getCacheImages()) * ((long) 1000 * 60 * 60 * 24);
                 for (File imageFile : Objects.requireNonNull(cacheFolder.listFiles())) {
                     if (imageFile.lastModified() < System.currentTimeMillis() - expireTime) {
                         //noinspection ResultOfMethodCallIgnored
@@ -137,6 +147,69 @@ public class SkinProvider {
         return cape != null ? cape : EMPTY_CAPE;
     }
 
+    public static CompletableFuture<SkinProvider.SkinData> requestSkinData(PlayerEntity entity) {
+        SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity.getProfile());
+
+        return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl())
+                .thenApplyAsync(skinAndCape -> {
+                    try {
+                        Skin skin = skinAndCape.getSkin();
+                        Cape cape = skinAndCape.getCape();
+                        SkinGeometry geometry = SkinGeometry.getLegacy(data.isAlex());
+
+                        if (cape.isFailed()) {
+                            cape = getOrDefault(requestBedrockCape(entity.getUuid()),
+                                    EMPTY_CAPE, 3);
+                        }
+
+                        if (cape.isFailed() && ALLOW_THIRD_PARTY_CAPES) {
+                            cape = getOrDefault(requestUnofficialCape(
+                                    cape, entity.getUuid(),
+                                    entity.getUsername(), false
+                            ), EMPTY_CAPE, CapeProvider.VALUES.length * 3);
+                        }
+
+                        geometry = getOrDefault(requestBedrockGeometry(
+                                geometry, entity.getUuid()
+                        ), geometry, 3);
+
+                        boolean isDeadmau5 = "deadmau5".equals(entity.getUsername());
+                        // Not a bedrock player check for ears
+                        if (geometry.isFailed() && (ALLOW_THIRD_PARTY_EARS || isDeadmau5)) {
+                            boolean isEars;
+
+                            // Its deadmau5, gotta support his skin :)
+                            if (isDeadmau5) {
+                                isEars = true;
+                            } else {
+                                // Get the ears texture for the player
+                                skin = getOrDefault(requestUnofficialEars(
+                                        skin, entity.getUuid(), entity.getUsername(), false
+                                ), skin, 3);
+
+                                isEars = skin.isEars();
+                            }
+
+                            // Does the skin have an ears texture
+                            if (isEars) {
+                                // Get the new geometry
+                                geometry = SkinGeometry.getEars(data.isAlex());
+
+                                // Store the skin and geometry for the ears
+                                storeEarSkin(skin);
+                                storeEarGeometry(entity.getUuid(), data.isAlex());
+                            }
+                        }
+
+                        return new SkinData(skin, cape, geometry);
+                    } catch (Exception e) {
+                        GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
+                    }
+
+                    return new SkinData(skinAndCape.getSkin(), skinAndCape.getCape(), null);
+                });
+    }
+
     public static CompletableFuture<SkinAndCape> requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) {
         return CompletableFuture.supplyAsync(() -> {
             long time = System.currentTimeMillis();
@@ -329,7 +402,8 @@ public class SkinProvider {
         byte[] cape = EMPTY_CAPE.getCapeData();
         try {
             cape = requestImage(capeUrl, provider);
-        } catch (Exception ignored) {} // just ignore I guess
+        } catch (Exception ignored) {
+        } // just ignore I guess
 
         String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage
 
@@ -451,6 +525,7 @@ public class SkinProvider {
 
     /**
      * If a skull has a username but no textures, request them.
+     *
      * @param skullOwner the CompoundTag of the skull with no textures
      * @return a completable GameProfile with textures included
      */
@@ -602,6 +677,9 @@ public class SkinProvider {
         private final Cape cape;
     }
 
+    public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) {
+    }
+
     @AllArgsConstructor
     @Getter
     public static class Skin {
diff --git a/connector/src/main/resources/bedrock/skin/geometry.humanoid.wearingCustomSkull.json b/connector/src/main/resources/bedrock/skin/geometry.humanoid.wearingCustomSkull.json
new file mode 100644
index 000000000..b18d1205b
--- /dev/null
+++ b/connector/src/main/resources/bedrock/skin/geometry.humanoid.wearingCustomSkull.json
@@ -0,0 +1,222 @@
+{
+  "format_version": "1.14.0",
+  "minecraft:geometry": [
+    {
+      "bones": [
+        {
+          "name" : "root",
+          "pivot" : [ 0.0, 0.0, 0.0 ]
+        },
+
+        {
+          "name" : "waist",
+          "parent" : "root",
+          "pivot" : [ 0.0, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes" : []
+        },
+
+
+        {
+          "name": "body",
+          "parent" : "waist",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 12.0, -2.0 ],
+              "size": [ 8, 12, 4 ],
+              "uv": [ 16, 16 ]
+            }
+          ]
+        },
+
+        {
+          "name": "jacket",
+          "parent" : "body",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 12.0, -2.0 ],
+              "size": [ 8, 12, 4 ],
+              "uv": [ 16, 32 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+
+        {
+          "name": "head",
+          "parent" : "body",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 24.75, -4.0 ],
+              "size": [ 8, 8, 8 ],
+              "uv": [ 0, 0 ],
+              "inflate": 0.8
+            }
+          ]
+        },
+
+        {
+          "name": "hat",
+          "parent" : "head",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 24.75, -4.0 ],
+              "size": [ 8, 8, 8 ],
+              "uv": [ 32, 0 ],
+              "inflate": 1.125
+            }
+          ]
+        },
+
+
+        {
+          "name": "leftArm",
+          "parent" : "body",
+          "pivot": [ 5.0, 22.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ 4.0, 12.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 32, 48 ]
+            }
+          ]
+        },
+        {
+          "name": "rightArm",
+          "parent" : "body",
+          "pivot": [ -5.0, 22.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -8.0, 12.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 40, 16 ]
+            }
+          ]
+        },
+
+        {
+          "name": "leftSleeve",
+          "parent" : "leftArm",
+          "pivot": [ 5.0, 22.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ 4.0, 12.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 48, 48 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+        {
+          "name": "rightSleeve",
+          "parent" : "rightArm",
+          "pivot": [ -5.0, 22.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -8.0, 12.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 40, 32 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+
+        {
+          "name": "leftLeg",
+          "parent" : "root",
+          "pivot": [ 1.9, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -0.1, 0.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 16 ]
+            }
+          ],
+          "mirror": true
+        },
+
+        {
+          "name": "rightLeg",
+          "parent" : "root",
+          "pivot": [ -1.9, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -3.9, 0.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 16 ]
+            }
+          ]
+        },
+
+        {
+          "name": "leftPants",
+          "parent" : "leftLeg",
+          "pivot": [1.9, 12.0, 0.0],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -0.1, 0.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 48 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+        {
+          "name": "rightPants",
+          "parent" : "rightLeg",
+          "pivot": [ -1.9, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -3.9, 0.0, -2.0] ,
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 32],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+
+        {
+          "name" : "rightItem",
+          "parent" : "rightArm",
+          "pivot" : [ -6.0, 15.0, 1.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes" : []
+        },
+
+        {
+          "name" : "leftItem",
+          "parent" : "leftArm",
+          "pivot" : [ 6.0, 15.0, 1.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes" : []
+        }
+      ],
+      "description": {
+        "identifier": "geometry.humanoid.wearingCustomSkull",
+        "texture_height": 64,
+        "texture_width": 64
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/connector/src/main/resources/bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json b/connector/src/main/resources/bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json
new file mode 100644
index 000000000..3855c92ec
--- /dev/null
+++ b/connector/src/main/resources/bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json
@@ -0,0 +1,222 @@
+{
+  "format_version": "1.14.0",
+  "minecraft:geometry": [
+    {
+      "bones": [
+        {
+          "name" : "root",
+          "pivot" : [ 0.0, 0.0, 0.0 ]
+        },
+
+        {
+          "name" : "waist",
+          "parent" : "root",
+          "pivot" : [ 0.0, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes" : []
+        },
+
+
+        {
+          "name": "body",
+          "parent" : "waist",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 12.0, -2.0 ],
+              "size": [ 8, 12, 4 ],
+              "uv": [ 16, 16 ]
+            }
+          ]
+        },
+
+        {
+          "name": "jacket",
+          "parent" : "body",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 12.0, -2.0 ],
+              "size": [ 8, 12, 4 ],
+              "uv": [ 16, 32 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+
+        {
+          "name": "head",
+          "parent" : "body",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 24.75, -4.0 ],
+              "size": [ 8, 8, 8 ],
+              "uv": [ 0, 0 ],
+              "inflate": 0.8
+            }
+          ]
+        },
+
+        {
+          "name": "hat",
+          "parent" : "head",
+          "pivot": [ 0.0, 24.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -4.0, 24.75, -4.0 ],
+              "size": [ 8, 8, 8 ],
+              "uv": [ 32, 0 ],
+              "inflate": 1.125
+            }
+          ]
+        },
+
+
+        {
+          "name": "leftArm",
+          "parent" : "body",
+          "pivot": [ 5.0, 21.5, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [4.0, 11.5, -2.0],
+              "size": [ 3, 12, 4 ],
+              "uv": [ 32, 48 ]
+            }
+          ]
+        },
+        {
+          "name": "rightArm",
+          "parent" : "body",
+          "pivot": [ -5.0, 21.5, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [-7.0, 11.5, -2.0],
+              "size": [ 3, 12, 4 ],
+              "uv": [ 40, 16 ]
+            }
+          ]
+        },
+
+        {
+          "name": "leftSleeve",
+          "parent" : "leftArm",
+          "pivot": [ 5.0, 21.5, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ 4.0, 11.5, -2.0 ],
+              "size": [ 3, 12, 4 ],
+              "uv": [ 48, 48 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+        {
+          "name": "rightSleeve",
+          "parent" : "rightArm",
+          "pivot": [ -5.0, 21.5, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -7.0, 11.5, -2.0 ],
+              "size": [ 3, 12, 4 ],
+              "uv": [ 40, 32 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+
+        {
+          "name": "leftLeg",
+          "parent" : "root",
+          "pivot": [ 1.9, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -0.1, 0.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 16 ]
+            }
+          ],
+          "mirror": true
+        },
+
+        {
+          "name": "rightLeg",
+          "parent" : "root",
+          "pivot": [ -1.9, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -3.9, 0.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 16 ]
+            }
+          ]
+        },
+
+        {
+          "name": "leftPants",
+          "parent" : "leftLeg",
+          "pivot": [1.9, 12.0, 0.0],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -0.1, 0.0, -2.0 ],
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 48 ],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+        {
+          "name": "rightPants",
+          "parent" : "rightLeg",
+          "pivot": [ -1.9, 12.0, 0.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes": [
+            {
+              "origin": [ -3.9, 0.0, -2.0] ,
+              "size": [ 4, 12, 4 ],
+              "uv": [ 0, 32],
+              "inflate": 0.25
+            }
+          ]
+        },
+
+
+        {
+          "name" : "rightItem",
+          "parent" : "rightArm",
+          "pivot" : [ -6.0, 14.5, 1.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes" : []
+        },
+
+        {
+          "name" : "leftItem",
+          "parent" : "leftArm",
+          "pivot" : [ 6.0, 14.5, 1.0 ],
+          "rotation" : [ 0.0, 0.0, 0.0 ],
+          "cubes" : []
+        }
+      ],
+      "description": {
+        "identifier": "geometry.humanoid.wearingCustomSkullSlim",
+        "texture_height": 64,
+        "texture_width": 64
+      }
+    }
+  ]
+}
\ No newline at end of file