From a8c9e3d7c284fd422334331361d20e2f44ae4b36 Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Sat, 13 Feb 2021 20:44:31 +0100 Subject: [PATCH] Add unsafe Entity serialization API (#139) --- ...-Add-unsafe-Entity-serialization-API.patch | 80 +++++++++++ ...-Add-unsafe-Entity-serialization-API.patch | 129 ++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 patches/api/0038-Add-unsafe-Entity-serialization-API.patch create mode 100644 patches/server/0174-Add-unsafe-Entity-serialization-API.patch diff --git a/patches/api/0038-Add-unsafe-Entity-serialization-API.patch b/patches/api/0038-Add-unsafe-Entity-serialization-API.patch new file mode 100644 index 000000000..4458d2b19 --- /dev/null +++ b/patches/api/0038-Add-unsafe-Entity-serialization-API.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 9 Jan 2021 21:21:27 +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 . + +diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java +index 97b9ade0e771eae663fb42f91e15545034d58fc9..0c9d3c8a28a791fe26bb1c014b568e955eca0e8f 100644 +--- a/src/main/java/org/bukkit/UnsafeValues.java ++++ b/src/main/java/org/bukkit/UnsafeValues.java +@@ -135,4 +135,28 @@ public interface UnsafeValues { + public int nextEntityId(); + + // Paper end ++ ++ // Purpur start ++ ++ /** ++ * Serialize entity to byte array ++ * ++ * @param entity entity to serialize ++ * @return serialized entity ++ */ ++ byte[] serializeEntity(org.bukkit.entity.Entity entity); ++ ++ /** ++ * Deserialize an entity from byte array ++ *

++ * The entity is not automatically spawned in the world. You will have to spawn ++ * the entity yourself with {@link org.bukkit.entity.Entity#spawnAt(Location)} or ++ * {@link org.bukkit.entity.Entity#spawnAt(Location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason)} ++ * ++ * @param data serialized entity ++ * @param world world entity belongs in ++ * @return deserialized entity ++ */ ++ org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world); ++ // Purpur end + } +diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java +index 08dbe8208fad174f03a0e08c26bb48a0729ec0ce..2b7e8c7f24b2d9dd49db901f6279b8b5930a3006 100644 +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -745,5 +745,24 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + * @return True if ridable in water + */ + boolean isRidableInWater(); ++ ++ /** ++ * Spawn this entity in the world at the given {@link Location} with the default spawn reason. ++ * ++ * @param location The location at which to spawn the entity. ++ * @return Whether the entity was successfully spawned. ++ */ ++ default boolean spawnAt(@NotNull Location location) { ++ return spawnAt(location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ /** ++ * Spawn this entity in the world at the given {@link Location} with the reason given. ++ * ++ * @param location The location at which to spawn the entity. ++ * @param spawnReason The reason for which the entity was spawned. ++ * @return Whether the entity was successfully spawned. ++ */ ++ boolean spawnAt(@NotNull Location location, @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason); + // Purpur end + } diff --git a/patches/server/0174-Add-unsafe-Entity-serialization-API.patch b/patches/server/0174-Add-unsafe-Entity-serialization-API.patch new file mode 100644 index 000000000..78886ad74 --- /dev/null +++ b/patches/server/0174-Add-unsafe-Entity-serialization-API.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +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 . + +diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java +index 919cf670327bed6faa50f29c9bf7a9b54174f7f2..631eb682e81e30d2a937fd1eafccd8a9ab82d21e 100644 +--- a/src/main/java/net/minecraft/server/EntityTypes.java ++++ b/src/main/java/net/minecraft/server/EntityTypes.java +@@ -380,6 +380,7 @@ public class EntityTypes { + return this.bf.create(this, world); + } + ++ public static Optional loadEntityFixedData(NBTTagCompound nbtTagCompound, World world) { return a(nbtTagCompound, world); } // Purpur - OBFHELPER + public static Optional a(NBTTagCompound nbttagcompound, World world) { + return SystemUtils.a(a(nbttagcompound).map((entitytypes) -> { + return entitytypes.a(world); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index eb0ce05d25ba33626d2dd3e3380d805c560bfe58..7c30064237bd58673de710915b1a9437335a3e8a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1185,5 +1185,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isRidableInWater() { + return getHandle().isRidableInWater(); + } ++ ++ @Override ++ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ entity.world = ((CraftWorld) location.getWorld()).getHandle(); ++ entity.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ return !entity.valid && entity.world.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 37c561fb775cf7dd955b185b4ea94fecc574be63..821b8665b4ed70c010a0824df99de2667fd4c8ba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -383,9 +383,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 NBTTagCompound()), true); ++ } ++ ++ public byte[] serializeNbtToBytes(NBTTagCompound compound, boolean addDataVersion) { ++ if (addDataVersion) compound.setInt("DataVersion", getDataVersion()); + java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); +- NBTTagCompound compound = (item instanceof CraftItemStack ? ((CraftItemStack) item).getHandle() : CraftItemStack.asNMSCopy(item)).save(new NBTTagCompound()); +- compound.setInt("DataVersion", getDataVersion()); ++ // Purpur end + try { + net.minecraft.server.NBTCompressedStreamTools.writeNBT( + compound, +@@ -398,26 +403,58 @@ public final class CraftMagicNumbers implements UnsafeValues { + return outputStream.toByteArray(); + } + ++ public static DynamicOpsNBT getDynamicOpsNbtInstance() { return DynamicOpsNBT.a; } // Purpur - OBFHELPER - keeping out of the class because it's FULL of decompile errors + @Override + public ItemStack deserializeItem(byte[] data) { + Preconditions.checkNotNull(data, "null cannot be deserialized"); + Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); + ++ // Purpur start - rework NBT <-> bytes ++ NBTTagCompound compound = deserializeNbtFromBytes(data, true); ++ Dynamic converted = DataConverterRegistry.getDataFixer().update(DataConverterTypes.ITEM_STACK, new Dynamic<>(getDynamicOpsNbtInstance(), compound), compound.getInt("DataVersion"), getDataVersion()); // TODO: obfhelper ++ return net.minecraft.server.ItemStack.fromCompound((NBTTagCompound) converted.getValue()).asBukkitMirror(); ++ } ++ ++ public NBTTagCompound deserializeNbtFromBytes(byte[] data, boolean verifyDataVersion) { ++ // Purpur end + try { + NBTTagCompound compound = net.minecraft.server.NBTCompressedStreamTools.readNBT( + 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 converted = DataConverterRegistry.getDataFixer().update(DataConverterTypes.ITEM_STACK, new Dynamic(DynamicOpsNBT.a, compound), dataVersion, getDataVersion()); +- return CraftItemStack.asCraftMirror(net.minecraft.server.ItemStack.fromCompound((NBTTagCompound) 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"); ++ NBTTagCompound compound = new NBTTagCompound(); ++ compound.setString("id", ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getMinecraftKeyString()); ++ return serializeNbtToBytes(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().save(compound), true); ++ } ++ ++ @Override ++ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world) { ++ NBTTagCompound compound = deserializeNbtFromBytes(data, true); ++ Dynamic converted = DataConverterRegistry.getDataFixer().update(DataConverterTypes.ENTITY, new Dynamic<>(getDynamicOpsNbtInstance(), compound), compound.getInt("DataVersion"), getDataVersion()); ++ compound = (NBTTagCompound) converted.getValue(); ++ compound.remove("UUID"); // Make the server make a new UUID for this entity; makes entities always spawnable. ++ return net.minecraft.server.EntityTypes.loadEntityFixedData(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle()) ++ .orElseThrow(() -> new IllegalArgumentException("unknown ID was found for the data; did you downgrade?")) ++ .getBukkitEntity(); ++ } ++ // Pupur end ++ + @Override + public String getTranslationKey(Material mat) { + return getItem(mat).getOrCreateDescriptionId();