From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mariell Hoversholm <proximyst@proximyst.com>
Date: Sat, 9 Jan 2021 21:22:58 +0100
Subject: [PATCH] Add unsafe Entity serialization API

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 4175462de0d6306e4cd0871853ef4f0a06895359..2fb5c5ea01d5f1071c8e8a39e7863ed0640d3fc9 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1260,5 +1260,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
     public boolean isRidableInWater() {
         return getHandle().rideableUnderWater();
     }
+
+    @Override
+    public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
+        entity.level = ((CraftWorld) location.getWorld()).getHandle();
+        entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
+        return !entity.valid && entity.level.addEntity(entity, spawnReason);
+    }
     // Purpur end
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 72cfffd80ad76abe7cb16bc9133730338c07b6f6..09b99e3465ae142081f01c483116c1d1adb34842 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -407,9 +407,14 @@ public final class CraftMagicNumbers implements UnsafeValues {
         Preconditions.checkNotNull(item, "null cannot be serialized");
         Preconditions.checkArgument(item.getType() != Material.AIR, "air cannot be serialized");
 
+        // Purpur start - rework NBT <-> bytes
+        return serializeNbtToBytes(CraftItemStack.asNMSCopy(item).save(new CompoundTag()), true);
+    }
+
+    public byte[] serializeNbtToBytes(CompoundTag compound, boolean addDataVersion) {
+        if (addDataVersion) compound.putInt("DataVersion", getDataVersion());
         java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();
-        CompoundTag compound = (item instanceof CraftItemStack ? ((CraftItemStack) item).handle : CraftItemStack.asNMSCopy(item)).save(new CompoundTag());
-        compound.putInt("DataVersion", getDataVersion());
+        // Purpur end
         try {
             net.minecraft.nbt.NbtIo.writeCompressed(
                 compound,
@@ -427,21 +432,52 @@ public final class CraftMagicNumbers implements UnsafeValues {
         Preconditions.checkNotNull(data, "null cannot be deserialized");
         Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing");
 
+        // Purpur start - rework NBT <-> bytes
+        CompoundTag compound = deserializeNbtFromBytes(data, true);
+        Dynamic<Tag> converted = DataFixers.getDataFixer().update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), compound.getInt("DataVersion"), getDataVersion());
+        return net.minecraft.world.item.ItemStack.of((CompoundTag) converted.getValue()).asBukkitMirror();
+    }
+
+    public CompoundTag deserializeNbtFromBytes(byte[] data, boolean verifyDataVersion) {
+        // Purpur end
         try {
             CompoundTag compound = net.minecraft.nbt.NbtIo.readCompressed(
                 new java.io.ByteArrayInputStream(data)
             );
+            if (verifyDataVersion) { // Purpur
             int dataVersion = compound.getInt("DataVersion");
 
             Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!");
-            Dynamic<Tag> converted = DataFixers.getDataFixer().update(References.ITEM_STACK, new Dynamic<Tag>(NbtOps.INSTANCE, compound), dataVersion, getDataVersion());
-            return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of((CompoundTag) converted.getValue()));
+            } // Purpur
+            return compound; // Purpur
         } catch (IOException ex) {
             com.destroystokyo.paper.util.SneakyThrow.sneaky(ex);
             throw new RuntimeException();
         }
     }
 
+    // Purpur start
+    @Override
+    public byte[] serializeEntity(org.bukkit.entity.Entity entity) {
+        Preconditions.checkNotNull(entity, "null cannot be serialized");
+        Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "non-CraftEntity cannot be serialized");
+        CompoundTag compound = new CompoundTag();
+        compound.putString("id", ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getMinecraftKeyString());
+        return serializeNbtToBytes(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().saveWithoutId(compound), true);
+    }
+
+    @Override
+    public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world) {
+        CompoundTag compound = deserializeNbtFromBytes(data, true);
+        Dynamic<Tag> converted = DataFixers.getDataFixer().update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), compound.getInt("DataVersion"), getDataVersion());
+        compound = (CompoundTag) converted.getValue();
+        compound.remove("UUID"); // Make the server make a new UUID for this entity; makes entities always spawnable.
+        return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle())
+                .orElseThrow(() -> new IllegalArgumentException("unknown ID was found for the data; did you downgrade?"))
+                .getBukkitEntity();
+    }
+    // Purpur end
+
     @Override
     public String getTranslationKey(Material mat) {
         if (mat.isBlock()) {
