ProtocolLib

3M Downloads

Cannot get Bukkit entity from EntityEgg exception in WrappedDataWatcher.deepClone()

bo3tools opened this issue ยท 2 comments

commented

Describe the bug
An exception is thrown when a deepClone method is called on a WrappedDataWatcher.

[ERROR]: [ProtocolPlugin] Unhandled exception occured in onPacketSending(PacketEvent) for ProtocolPlugin
java.lang.IllegalArgumentException: Cannot get Bukkit entity from EntityEgg['Thrown Egg'/126, uuid='b5a2a865-e43c-4ed1-848e-56b115e8b7ae', l='~NULL~', x=0.00, y=0.00, z=0.00, cx=0, cz=0, tl=0, v=false, d=false]
        at com.comphenix.protocol.utility.MinecraftReflection.getBukkitEntity(MinecraftReflection.java:370) ~[?:?]
        at com.comphenix.protocol.wrappers.WrappedDataWatcher.getEntity(WrappedDataWatcher.java:572) ~[?:?]
        at com.comphenix.protocol.wrappers.WrappedDataWatcher.deepClone(WrappedDataWatcher.java:532) ~[?:?]
        ...
Caused by: java.lang.RuntimeException: An internal error occured.
        at com.comphenix.protocol.reflect.accessors.DefaultMethodAccessor.invoke(DefaultMethodAccessor.java:20) ~[?:?]
        at com.comphenix.protocol.utility.MinecraftReflection.getBukkitEntity(MinecraftReflection.java:368) ~[?:?]
        ...
Caused by: java.lang.NullPointerException
        at net.minecraft.server.v1_16_R1.Entity.getBukkitEntity(Entity.java:88) ~[patched_1.16.1.jar:git-Paper-68]
        at jdk.internal.reflect.GeneratedMethodAccessor5.invoke(Unknown Source) ~[?:?]
        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
        at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
        at com.comphenix.protocol.reflect.accessors.DefaultMethodAccessor.invoke(DefaultMethodAccessor.java:16) ~[?:?]
        at com.comphenix.protocol.utility.MinecraftReflection.getBukkitEntity(MinecraftReflection.java:368) ~[?:?]
        ...
[ERROR]: Parameters:
  net.minecraft.server.v1_16_R1.PacketPlayOutEntityMetadata@68208ba2[
    a=74
    b=[net.minecraft.server.v1_16_R1.DataWatcher$Item@69f29af5, net.minecraft.server.v1_16_R1.DataWatcher$Item@5159daca, net.minecraft.server.v1_16_R1.DataWatcher$Item@55a83fd6, net.minecraft.server.v1_16_R1.DataWatcher$Item@7366068a, net.minecraft.server.v1_16_R1.DataWatcher$Item@e3e0f21, net.minecraft.server.v1_16_R1.DataWatcher$Item@112e1ba6, net.minecraft.server.v1_16_R1.DataWatcher$Item@56cc0da, net.minecraft.server.v1_16_R1.DataWatcher$Item@46fdc4ab]
  ]

To Reproduce
Use the following example, then observe a dropped item.

protocolManager.addPacketListener(new PacketAdapter(params) {
    @Override
    public void onPacketSending(PacketEvent event) {
        PacketContainer packet = event.getPacket();
        if (event.getPacketType() == PacketType.Play.Server.ENTITY_METADATA) {
            Entity entity = packet.getEntityModifier(event).read(0);

            if (entity.getType() == EntityType.DROPPED_ITEM) {
                WrappedDataWatcher watcher = new WrappedDataWatcher(packet.getWatchableCollectionModifier().read(0));
                ItemStack itemStack = watcher.getItemStack(7);

                if (itemStack != null) {
                    watcher.deepClone();
                    // ...
                }
            }
        }
    }
}

Expected behavior
The exception to not be thrown.

Version Info
https://pastebin.com/82UCAPNV

commented

Does this happen with entities other than eggs? or other dropped items? What about in 1.15?

commented

Does this happen with entities other than eggs? or other dropped items? What about in 1.15?

This happens regardless of the item or entity whenever a WrappedDataWatcher is deepCloned. I believe the egg comes from this implementation:

private static Object fakeEntity() {
if (fakeEntity != null) {
return fakeEntity;
}
// We can create a fake egg without it affecting anything
// Mojang added difficulty to lightning strikes, so this'll have to do
if (eggConstructor == null) {
eggConstructor = Accessors.getConstructorAccessor(MinecraftReflection.getMinecraftClass("EntityEgg"),
MinecraftReflection.getNmsWorldClass(), double.class, double.class, double.class);
}
return fakeEntity = eggConstructor.invoke(null, 0, 0, 0);
}

Which is then used here:

/**
* Constructs a new DataWatcher using a fake egg entity. The
* resulting DataWatcher will not have any keys or values and new ones will
* have to be added using watcher objects.
*/
public WrappedDataWatcher() {
this(newHandle(fakeEntity()));
}

For context, I wanted to make dropped items appear differently on the client side but I've encountered an issue where different clients see how items appear for other clients -- let's say I'm "disguising" a specific item type, stick, as an apple for client A, as a melon for client B; and other clients are unaffected.

I've noticed that sometimes client B sees an apple instead of a melon even though the information deciding what item type you're getting is calculated separately. My guess is that behind the scenes the server only generates one packet per update and it passes it for every client:

clients = [a, b, c, d]

on update:
    packet = new_metadata_packet(...)
    for (client in clients):
        client.send_packet(packet)

instead of

on update:
   for (client in clients):
       packet = new_metadata_packet(...)
       client.send_packet(packet)

I think this should be fixed by deepCloning the PacketContainer but this doesn't help. Instead I found this example by comphenix where he solves exactly my problem.

Here he clones the DataWatcher instead:

// Note: See https://gist.github.com/aadnk/7420881

// See if we are modifying an item stack
if (packet.getEntityModifier(event).read(0) instanceof Item) {
    WrappedDataWatcher watcher = new WrappedDataWatcher(packet.getWatchableCollectionModifier().read(0));
    ItemStack stack = watcher.getItemStack(10);
    
    if (stack != null) {
        // You have to clone the watcher, unfortunately
        watcher = watcher.deepClone();
        watcher.removeObject(10); // Not needed after build #148
        watcher.setObject(10, processItemStack(stack));
        
        // Save the modification
        packet.getWatchableCollectionModifier().write(0, watcher.getWatchableObjects());
    }
}

I tried to use it but it generates an exception in the current version.