From 4683ed8bed8970cdaa975c2285de4a19121c8200 Mon Sep 17 00:00:00 2001 From: ds58 <30220598+ds58@users.noreply.github.com> Date: Tue, 12 Dec 2023 19:32:29 -0600 Subject: [PATCH] Support for 1.20.3/1.20.4 --- bukkit/build.gradle | 9 +- .../panilla/bukkit/PanillaPlugin.java | 12 + .../v1_20_R2/io/PacketInspector.java | 4 +- craftbukkit-v1_20_R3/build.gradle | 4 + .../v1_20_R3/InventoryCleaner.java | 54 +++++ .../v1_20_R3/io/PacketInspector.java | 226 ++++++++++++++++++ .../v1_20_R3/io/PlayerInjector.java | 48 ++++ .../v1_20_R3/io/dplx/PacketSerializer.java | 35 +++ .../v1_20_R3/nbt/NbtTagCompound.java | 87 +++++++ .../craftbukkit/v1_20_R3/nbt/NbtTagList.java | 36 +++ settings.gradle | 2 + 11 files changed, 511 insertions(+), 6 deletions(-) create mode 100644 craftbukkit-v1_20_R3/build.gradle create mode 100644 craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/InventoryCleaner.java create mode 100644 craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PacketInspector.java create mode 100644 craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PlayerInjector.java create mode 100644 craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/dplx/PacketSerializer.java create mode 100644 craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagCompound.java create mode 100644 craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagList.java diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 3cc0e23..15247d5 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -11,11 +11,12 @@ dependencies { implementation project(':panilla-craftbukkit-v1_17_R1') implementation project(':panilla-craftbukkit-v1_18_R1') implementation project(':panilla-craftbukkit-v1_18_R2') - implementation project(':panilla-craftbukkit-v1_19_R1') - implementation project(':panilla-craftbukkit-v1_19_R2') - implementation project(':panilla-craftbukkit-v1_19_R3') - implementation project(':panilla-craftbukkit-v1_20_R1') + implementation project(':panilla-craftbukkit-v1_19_R1') + implementation project(':panilla-craftbukkit-v1_19_R2') + implementation project(':panilla-craftbukkit-v1_19_R3') + implementation project(':panilla-craftbukkit-v1_20_R1') implementation project(':panilla-craftbukkit-v1_20_R2') + implementation project(':panilla-craftbukkit-v1_20_R3') compileOnly 'org.bukkit:bukkit:1.13.2-R0.1-SNAPSHOT' // use 1.13 Bukkit API } diff --git a/bukkit/src/main/java/com/ruinscraft/panilla/bukkit/PanillaPlugin.java b/bukkit/src/main/java/com/ruinscraft/panilla/bukkit/PanillaPlugin.java index 85c8ec1..1886d36 100644 --- a/bukkit/src/main/java/com/ruinscraft/panilla/bukkit/PanillaPlugin.java +++ b/bukkit/src/main/java/com/ruinscraft/panilla/bukkit/PanillaPlugin.java @@ -312,6 +312,18 @@ public int maxBookPages() { packetInspector = new com.ruinscraft.panilla.craftbukkit.v1_20_R2.io.PacketInspector(this); containerCleaner = new com.ruinscraft.panilla.craftbukkit.v1_20_R2.InventoryCleaner(this); break imp; + case "v1_20_R3": + packetSerializerClass = com.ruinscraft.panilla.craftbukkit.v1_20_R3.io.dplx.PacketSerializer.class; + protocolConstants = new IProtocolConstants() { + @Override + public int maxBookPages() { + return 100; + } + }; + playerInjector = new com.ruinscraft.panilla.craftbukkit.v1_20_R3.io.PlayerInjector(); + packetInspector = new com.ruinscraft.panilla.craftbukkit.v1_20_R3.io.PacketInspector(this); + containerCleaner = new com.ruinscraft.panilla.craftbukkit.v1_20_R3.InventoryCleaner(this); + break imp; } default: getLogger().warning("Unknown server implementation. " + Bukkit.getVersion() + " may not be supported by Panilla."); diff --git a/craftbukkit-v1_20_R2/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R2/io/PacketInspector.java b/craftbukkit-v1_20_R2/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R2/io/PacketInspector.java index 18f3107..ff771b5 100644 --- a/craftbukkit-v1_20_R2/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R2/io/PacketInspector.java +++ b/craftbukkit-v1_20_R2/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R2/io/PacketInspector.java @@ -157,7 +157,7 @@ public void checkPacketPlayOutSpawnEntity(Object _packet) throws EntityNbtNotPer if (entity instanceof EntityItem) { EntityItem item = (EntityItem) entity; - if (item.v() == null) { + if (item.q() == null) { return; } @@ -207,7 +207,7 @@ public void stripNbtFromItemEntity(UUID entityId) { if (entity instanceof EntityItem) { EntityItem item = (EntityItem) entity; - if (item.v() == null) return; + if (item.q() == null) return; if (!item.q().u()) return; item.q().c((NBTTagCompound) null); } diff --git a/craftbukkit-v1_20_R3/build.gradle b/craftbukkit-v1_20_R3/build.gradle new file mode 100644 index 0000000..2cb0d73 --- /dev/null +++ b/craftbukkit-v1_20_R3/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compileOnly project(':panilla-api') + compileOnly 'org.spigotmc:spigot:1.20.4-R0.1-SNAPSHOT' +} diff --git a/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/InventoryCleaner.java b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/InventoryCleaner.java new file mode 100644 index 0000000..9ada18c --- /dev/null +++ b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/InventoryCleaner.java @@ -0,0 +1,54 @@ +package com.ruinscraft.panilla.craftbukkit.v1_20_R3; + +import com.ruinscraft.panilla.api.IInventoryCleaner; +import com.ruinscraft.panilla.api.IPanilla; +import com.ruinscraft.panilla.api.IPanillaPlayer; +import com.ruinscraft.panilla.api.exception.FailedNbt; +import com.ruinscraft.panilla.api.nbt.INbtTagCompound; +import com.ruinscraft.panilla.api.nbt.checks.NbtChecks; +import com.ruinscraft.panilla.craftbukkit.v1_20_R3.nbt.NbtTagCompound; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.inventory.Container; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; + +public class InventoryCleaner implements IInventoryCleaner { + + private final IPanilla panilla; + + public InventoryCleaner(IPanilla panilla) { + this.panilla = panilla; + } + + @Override + public void clean(IPanillaPlayer player) { + CraftPlayer craftPlayer = (CraftPlayer) player.getHandle(); + Container container = craftPlayer.getHandle().bR; + + for (int slot = 0; slot < container.i.size(); slot++) { + ItemStack itemStack = container.b(slot).g(); + + if (itemStack == null || !itemStack.u()) { + continue; + } + + NBTTagCompound nmsTag = itemStack.w(); + INbtTagCompound tag = new NbtTagCompound(nmsTag); + String itemName = itemStack.d().a(); + + if (nmsTag == null || itemName == null) { + continue; + } + + FailedNbt failedNbt = NbtChecks.checkAll(tag, itemName, panilla); + + if (FailedNbt.failsThreshold(failedNbt)) { + container.b(slot).g().c((NBTTagCompound) null); + } else if (FailedNbt.fails(failedNbt)) { + nmsTag.r(failedNbt.key); + container.b(slot).g().c(nmsTag); + } + } + } + +} diff --git a/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PacketInspector.java b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PacketInspector.java new file mode 100644 index 0000000..b5394d9 --- /dev/null +++ b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PacketInspector.java @@ -0,0 +1,226 @@ +package com.ruinscraft.panilla.craftbukkit.v1_20_R3.io; + +import com.ruinscraft.panilla.api.IPanilla; +import com.ruinscraft.panilla.api.IPanillaPlayer; +import com.ruinscraft.panilla.api.exception.EntityNbtNotPermittedException; +import com.ruinscraft.panilla.api.exception.FailedNbt; +import com.ruinscraft.panilla.api.exception.NbtNotPermittedException; +import com.ruinscraft.panilla.api.io.IPacketInspector; +import com.ruinscraft.panilla.api.nbt.INbtTagCompound; +import com.ruinscraft.panilla.api.nbt.checks.NbtChecks; +import com.ruinscraft.panilla.craftbukkit.v1_20_R3.nbt.NbtTagCompound; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity; +import net.minecraft.network.protocol.game.PacketPlayOutWindowItems; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.EntityPlayer; +import net.minecraft.server.level.WorldServer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.EntityItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.World; +import net.minecraft.world.level.block.Blocks; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.util.List; +import java.util.UUID; + +public class PacketInspector implements IPacketInspector { + + private static boolean paperChunkSystem = false; + private static MethodHandle getEntityLookupMethodHandle = null; + private static MethodHandle getEntityMethodHandle = null; + + static { + try { + Class entityLookupClass = Class.forName("io.papermc.paper.chunk.system.entity.EntityLookup"); + getEntityLookupMethodHandle = MethodHandles.lookup().findVirtual(WorldServer.class, "getEntityLookup", MethodType.methodType(entityLookupClass)); + getEntityMethodHandle = MethodHandles.lookup().findVirtual(entityLookupClass, "get", MethodType.methodType(Entity.class, UUID.class)); + paperChunkSystem = true; + } catch (ClassNotFoundException ignored) { + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + private Entity getChunkSystemEntity(WorldServer worldServer, UUID entityId) { + try { + Object entityLookup = getEntityLookupMethodHandle.invoke(worldServer); + return (Entity) getEntityMethodHandle.invoke(entityLookup, entityId); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + private final IPanilla panilla; + + public PacketInspector(IPanilla panilla) { + this.panilla = panilla; + } + + @Override + public void checkPacketPlayInSetCreativeSlot(Object _packet) throws NbtNotPermittedException { + if (_packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packet = (PacketPlayInSetCreativeSlot) _packet; + + int slot = packet.a(); + ItemStack itemStack = packet.d(); + + if (itemStack == null || !itemStack.u()) return; + + NbtTagCompound tag = new NbtTagCompound(itemStack.w()); + String itemClass = itemStack.q(); + String packetClass = "PacketPlayInSetCreativeSlot"; + + NbtChecks.checkPacketPlayIn(slot, tag, itemClass, packetClass, panilla); + } + } + + @Override + public void checkPacketPlayOutSetSlot(Object _packet) throws NbtNotPermittedException { + if (_packet instanceof PacketPlayOutSetSlot) { + PacketPlayOutSetSlot packet = (PacketPlayOutSetSlot) _packet; + + int windowId = packet.a(); + + // check if window is not player inventory and we are ignoring non-player inventories + if (windowId != 0 && panilla.getPConfig().ignoreNonPlayerInventories) { + return; + } + + int slot = packet.a(); + + ItemStack itemStack = packet.e(); + + if (itemStack == null || !itemStack.u()) { + return; + } + + NbtTagCompound tag = new NbtTagCompound(itemStack.w()); + String itemClass = itemStack.getClass().getSimpleName(); + String packetClass = packet.getClass().getSimpleName(); + + NbtChecks.checkPacketPlayOut(slot, tag, itemClass, packetClass, panilla); + } + } + + @Override + public void checkPacketPlayOutWindowItems(Object _packet) throws NbtNotPermittedException { + if (_packet instanceof PacketPlayOutWindowItems) { + PacketPlayOutWindowItems packet = (PacketPlayOutWindowItems) _packet; + + int windowId = packet.a(); + + // check if window is not player inventory + if (windowId != 0) { + return; + } + + List itemStacks = packet.d(); + + for (ItemStack itemStack : itemStacks) { + if (itemStack == null || !itemStack.u()) { + continue; + } + + NbtTagCompound tag = new NbtTagCompound(itemStack.w()); + String itemClass = itemStack.getClass().getSimpleName(); + String packetClass = packet.getClass().getSimpleName(); + + NbtChecks.checkPacketPlayOut(0, tag, itemClass, packetClass, panilla); // TODO: set slot? + } + } + } + + @Override + public void checkPacketPlayOutSpawnEntity(Object _packet) throws EntityNbtNotPermittedException { + if (_packet instanceof PacketPlayOutSpawnEntity) { + PacketPlayOutSpawnEntity packet = (PacketPlayOutSpawnEntity) _packet; + + UUID entityId = packet.d(); + Entity entity = null; + + for (WorldServer worldServer : MinecraftServer.getServer().H()) { + entity = paperChunkSystem ? getChunkSystemEntity(worldServer, entityId) : worldServer.M.d().a(entityId); + if (entity != null) break; + } + + if (entity != null) { + if (entity instanceof EntityItem) { + EntityItem item = (EntityItem) entity; + + if (item.q() == null) { + return; + } + + if (!item.q().u()) { + return; + } + + INbtTagCompound tag = new NbtTagCompound(item.q().w()); + String itemName = item.q().d().a(); + FailedNbt failedNbt = NbtChecks.checkAll(tag, itemName, panilla); + + if (FailedNbt.fails(failedNbt)) { + String worldName = ""; + + try { + Field worldField = Entity.class.getDeclaredField("t"); + worldField.setAccessible(true); + World world = (World) worldField.get(entity); + worldName = world.getWorld().getName(); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + + throw new EntityNbtNotPermittedException(packet.getClass().getSimpleName(), false, failedNbt, entityId, worldName); + } + } + } + } + } + + @Override + public void sendPacketPlayOutSetSlotAir(IPanillaPlayer player, int slot) { + CraftPlayer craftPlayer = (CraftPlayer) player.getHandle(); + EntityPlayer entityPlayer = craftPlayer.getHandle(); + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(entityPlayer.bR.j, entityPlayer.bR.k(), slot, new ItemStack(Blocks.a)); + entityPlayer.c.b(packet); + } + + @Override + public void stripNbtFromItemEntity(UUID entityId) { + Entity entity = null; + + for (WorldServer worldServer : MinecraftServer.getServer().H()) { + entity = paperChunkSystem ? getChunkSystemEntity(worldServer, entityId) : worldServer.M.d().a(entityId); + if (entity != null) break; + } + + if (entity instanceof EntityItem) { + EntityItem item = (EntityItem) entity; + if (item.q() == null) return; + if (!item.q().u()) return; + item.q().c((NBTTagCompound) null); + } + } + + @Override + public void stripNbtFromItemEntityLegacy(int entityId) { + throw new RuntimeException("cannot use #stripNbtFromItemEntityLegacy on 1.20.2"); + } + + @Override + public void validateBaseComponentParse(String string) throws Exception { + IChatBaseComponent.ChatSerializer.a(string); + } + +} diff --git a/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PlayerInjector.java b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PlayerInjector.java new file mode 100644 index 0000000..8931546 --- /dev/null +++ b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/PlayerInjector.java @@ -0,0 +1,48 @@ +package com.ruinscraft.panilla.craftbukkit.v1_20_R3.io; + +import com.ruinscraft.panilla.api.IPanillaPlayer; +import com.ruinscraft.panilla.api.io.IPlayerInjector; +import io.netty.channel.Channel; +import io.netty.handler.codec.ByteToMessageDecoder; +import net.minecraft.network.NetworkManager; +import net.minecraft.server.level.EntityPlayer; +import net.minecraft.server.network.PlayerConnection; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; + +import java.lang.reflect.Field; + +public class PlayerInjector implements IPlayerInjector { + + @Override + public Channel getPlayerChannel(IPanillaPlayer player) throws IllegalArgumentException { + CraftPlayer craftPlayer = (CraftPlayer) player.getHandle(); + EntityPlayer entityPlayer = craftPlayer.getHandle(); + PlayerConnection playerConnection = entityPlayer.c; + + try { + Field networkManagerField = PlayerConnection.class.getSuperclass().getDeclaredField("c"); + networkManagerField.setAccessible(true); + NetworkManager networkManager = (NetworkManager) networkManagerField.get(playerConnection); + return networkManager.n; + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public int getCompressionLevel() { + return 256; + } + + @Override + public ByteToMessageDecoder getDecompressor() { + return null; + } + + @Override + public ByteToMessageDecoder getDecoder() { + throw new RuntimeException("Not implemented"); + } + +} diff --git a/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/dplx/PacketSerializer.java b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/dplx/PacketSerializer.java new file mode 100644 index 0000000..66f7f68 --- /dev/null +++ b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/io/dplx/PacketSerializer.java @@ -0,0 +1,35 @@ +package com.ruinscraft.panilla.craftbukkit.v1_20_R3.io.dplx; + +import com.ruinscraft.panilla.api.io.IPacketSerializer; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.PacketDataSerializer; + +public class PacketSerializer implements IPacketSerializer { + + private final PacketDataSerializer handle; + + public PacketSerializer(ByteBuf byteBuf) { + this.handle = new PacketDataSerializer(byteBuf); + } + + @Override + public int readableBytes() { + return handle.readableBytes(); + } + + @Override + public int readVarInt() { + return handle.n(); + } + + @Override + public ByteBuf readBytes(int i) { + return handle.readBytes(i); + } + + @Override + public ByteBuf readBytes(byte[] buffer) { + return handle.readBytes(buffer); + } + +} diff --git a/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagCompound.java b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagCompound.java new file mode 100644 index 0000000..a177368 --- /dev/null +++ b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagCompound.java @@ -0,0 +1,87 @@ +package com.ruinscraft.panilla.craftbukkit.v1_20_R3.nbt; + +import com.ruinscraft.panilla.api.nbt.INbtTagCompound; +import com.ruinscraft.panilla.api.nbt.INbtTagList; +import com.ruinscraft.panilla.api.nbt.NbtDataType; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import java.util.Set; + +public class NbtTagCompound implements INbtTagCompound { + + private final NBTTagCompound handle; + + public NbtTagCompound(NBTTagCompound handle) { + this.handle = handle; + } + + @Override + public Object getHandle() { + return handle; + } + + @Override + public boolean hasKey(String key) { + return handle.e(key); + } + + @Override + public boolean hasKeyOfType(String key, NbtDataType nbtDataType) { + return handle.b(key, nbtDataType.id); + } + + @Override + public Set getKeys() { + return handle.e(); + } + + @Override + public int getInt(String key) { + return handle.h(key); + } + + @Override + public double getDouble(String key) { + return handle.k(key); + } + + @Override + public short getShort(String key) { + return handle.g(key); + } + + @Override + public String getString(String key) { + return handle.l(key); + } + + @Override + public int[] getIntArray(String key) { + return handle.n(key); + } + + @Override + public INbtTagList getList(String key, NbtDataType nbtDataType) { + return new NbtTagList(handle.c(key, nbtDataType.id)); + } + + @Override + public INbtTagList getList(String key) { + NBTBase base = handle.c(key); + + if (base instanceof NBTTagList) { + NBTTagList list = (NBTTagList) base; + return new NbtTagList(list); + } + + return null; + } + + @Override + public INbtTagCompound getCompound(String key) { + return new NbtTagCompound(handle.p(key)); + } + +} diff --git a/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagList.java b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagList.java new file mode 100644 index 0000000..608a898 --- /dev/null +++ b/craftbukkit-v1_20_R3/src/main/java/com/ruinscraft/panilla/craftbukkit/v1_20_R3/nbt/NbtTagList.java @@ -0,0 +1,36 @@ +package com.ruinscraft.panilla.craftbukkit.v1_20_R3.nbt; + +import com.ruinscraft.panilla.api.nbt.INbtTagCompound; +import com.ruinscraft.panilla.api.nbt.INbtTagList; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +public class NbtTagList implements INbtTagList { + + private final NBTTagList handle; + + public NbtTagList(NBTTagList handle) { + this.handle = handle; + } + + @Override + public INbtTagCompound getCompound(int index) { + return new NbtTagCompound(handle.a(index)); + } + + @Override + public String getString(int index) { + return handle.j(index); + } + + @Override + public boolean isCompound(int index) { + return handle.get(index) instanceof NBTTagCompound; + } + + @Override + public int size() { + return handle.size(); + } + +} diff --git a/settings.gradle b/settings.gradle index 9b7de99..0e68b48 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,7 @@ include(':panilla-craftbukkit-v1_19_R2') include(':panilla-craftbukkit-v1_19_R3') include(':panilla-craftbukkit-v1_20_R1') include(':panilla-craftbukkit-v1_20_R2') +include(':panilla-craftbukkit-v1_20_R3') include(':panilla-bukkit') // api @@ -42,4 +43,5 @@ project(':panilla-craftbukkit-v1_19_R2').projectDir = file('craftbukkit-v1_19_R2 project(':panilla-craftbukkit-v1_19_R3').projectDir = file('craftbukkit-v1_19_R3') project(':panilla-craftbukkit-v1_20_R1').projectDir = file('craftbukkit-v1_20_R1') project(':panilla-craftbukkit-v1_20_R2').projectDir = file('craftbukkit-v1_20_R2') +project(':panilla-craftbukkit-v1_20_R3').projectDir = file('craftbukkit-v1_20_R3') project(':panilla-bukkit').projectDir = file('bukkit')