--- a/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -15,7 +15,24 @@
 import net.minecraft.world.level.block.state.BlockState;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.v1_20_R2.persistence.CraftPersistentDataContainer;
+import org.bukkit.craftbukkit.v1_20_R2.persistence.CraftPersistentDataTypeRegistry;
+import org.bukkit.inventory.InventoryHolder;
+// CraftBukkit end
+
+import org.spigotmc.CustomTimingsHandler; // Spigot
+import co.aikar.timings.MinecraftTimings; // Paper
+import co.aikar.timings.Timing; // Paper
+
 public abstract class BlockEntity {
+    static boolean ignoreTileUpdates; // Paper
+
+    public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper
+    // CraftBukkit start - data containers
+    private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
+    public CraftPersistentDataContainer persistentDataContainer;
+    // CraftBukkit end
     private static final Logger LOGGER = LogUtils.getLogger();
     private final BlockEntityType<?> type;
     @Nullable
@@ -28,6 +45,7 @@
         this.type = type;
         this.worldPosition = pos.immutable();
         this.blockState = state;
+        this.persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init
     }
 
     public static BlockPos getPosFromTag(CompoundTag nbt) {
@@ -47,36 +65,52 @@
         return this.level != null;
     }
 
+    // CraftBukkit start - read container
     public void load(CompoundTag nbt) {
-    }
+        this.persistentDataContainer.clear(); // Paper - clear instead of init
 
-    protected void saveAdditional(CompoundTag nbt) {
+        net.minecraft.nbt.Tag persistentDataTag = nbt.get("PublicBukkitValues");
+        if (persistentDataTag instanceof CompoundTag) {
+            this.persistentDataContainer.putAll((CompoundTag) persistentDataTag);
+        }
     }
+    // CraftBukkit end
+
+    protected void saveAdditional(CompoundTag nbt) {}
 
     public final CompoundTag saveWithFullMetadata() {
-        CompoundTag compoundTag = this.saveWithoutMetadata();
-        this.saveMetadata(compoundTag);
-        return compoundTag;
+        CompoundTag nbttagcompound = this.saveWithoutMetadata();
+
+        this.saveMetadata(nbttagcompound);
+        return nbttagcompound;
     }
 
     public final CompoundTag saveWithId() {
-        CompoundTag compoundTag = this.saveWithoutMetadata();
-        this.saveId(compoundTag);
-        return compoundTag;
+        CompoundTag nbttagcompound = this.saveWithoutMetadata();
+
+        this.saveId(nbttagcompound);
+        return nbttagcompound;
     }
 
     public final CompoundTag saveWithoutMetadata() {
-        CompoundTag compoundTag = new CompoundTag();
-        this.saveAdditional(compoundTag);
-        return compoundTag;
+        CompoundTag nbttagcompound = new CompoundTag();
+
+        this.saveAdditional(nbttagcompound);
+        // CraftBukkit start - store container
+        if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
+            nbttagcompound.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
+        }
+        // CraftBukkit end
+        return nbttagcompound;
     }
 
     private void saveId(CompoundTag nbt) {
-        ResourceLocation resourceLocation = BlockEntityType.getKey(this.getType());
-        if (resourceLocation == null) {
+        ResourceLocation minecraftkey = BlockEntityType.getKey(this.getType());
+
+        if (minecraftkey == null) {
             throw new RuntimeException(this.getClass() + " is missing a mapping! This is a bug!");
         } else {
-            nbt.putString("id", resourceLocation.toString());
+            nbt.putString("id", minecraftkey.toString());
         }
     }
 
@@ -97,29 +131,30 @@
 
     @Nullable
     public static BlockEntity loadStatic(BlockPos pos, BlockState state, CompoundTag nbt) {
-        String string = nbt.getString("id");
-        ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
-        if (resourceLocation == null) {
-            LOGGER.error("Block entity has invalid type: {}", (Object)string);
+        String s = nbt.getString("id");
+        ResourceLocation minecraftkey = ResourceLocation.tryParse(s);
+
+        if (minecraftkey == null) {
+            BlockEntity.LOGGER.error("Block entity has invalid type: {}", s);
             return null;
         } else {
-            return BuiltInRegistries.BLOCK_ENTITY_TYPE.getOptional(resourceLocation).map((type) -> {
+            return (BlockEntity) BuiltInRegistries.BLOCK_ENTITY_TYPE.getOptional(minecraftkey).map((tileentitytypes) -> {
                 try {
-                    return type.create(pos, state);
-                } catch (Throwable var5) {
-                    LOGGER.error("Failed to create block entity {}", string, var5);
+                    return tileentitytypes.create(pos, state);
+                } catch (Throwable throwable) {
+                    BlockEntity.LOGGER.error("Failed to create block entity {}", s, throwable);
                     return null;
                 }
-            }).map((blockEntity) -> {
+            }).map((tileentity) -> {
                 try {
-                    blockEntity.load(nbt);
-                    return blockEntity;
-                } catch (Throwable var4) {
-                    LOGGER.error("Failed to load data for block entity {}", string, var4);
+                    tileentity.load(nbt);
+                    return tileentity;
+                } catch (Throwable throwable) {
+                    BlockEntity.LOGGER.error("Failed to load data for block entity {}", s, throwable);
                     return null;
                 }
             }).orElseGet(() -> {
-                LOGGER.warn("Skipping BlockEntity with id {}", (Object)string);
+                BlockEntity.LOGGER.warn("Skipping BlockEntity with id {}", s);
                 return null;
             });
         }
@@ -127,7 +162,8 @@
 
     public void setChanged() {
         if (this.level != null) {
-            setChanged(this.level, this.worldPosition, this.blockState);
+            if (ignoreTileUpdates) return; // Paper
+            BlockEntity.setChanged(this.level, this.worldPosition, this.blockState);
         }
 
     }
@@ -175,10 +211,17 @@
 
     public void fillCrashReportCategory(CrashReportCategory crashReportSection) {
         crashReportSection.setDetail("Name", () -> {
-            return BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(this.getType()) + " // " + this.getClass().getCanonicalName();
+            ResourceLocation minecraftkey = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(this.getType());
+
+            return minecraftkey + " // " + this.getClass().getCanonicalName();
         });
         if (this.level != null) {
-            CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, this.getBlockState());
+            // Paper start - Prevent TileEntity and Entity crashes
+            BlockState block = this.getBlockState();
+            if (block != null) {
+                CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, block);
+            }
+            // Paper end
             CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, this.level.getBlockState(this.worldPosition));
         }
     }
@@ -196,4 +239,28 @@
     public void setBlockState(BlockState state) {
         this.blockState = state;
     }
+
+    // CraftBukkit start - add method
+    public InventoryHolder getOwner() {
+        // Paper start
+        return getOwner(true);
+    }
+    public InventoryHolder getOwner(boolean useSnapshot) {
+        // Paper end
+        if (this.level == null) return null;
+        org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ());
+        // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists
+        org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper
+        if (state instanceof InventoryHolder) return (InventoryHolder) state;
+        return null;
+    }
+    // CraftBukkit end
+    // Paper start
+    public CompoundTag sanitizeSentNbt(CompoundTag tag) {
+        tag.remove("PublicBukkitValues");
+
+        return tag;
+    }
+    // Paper end
+
 }
