packetevents icon indicating copy to clipboard operation
packetevents copied to clipboard

Better way of dealing with EntityData (Meta)

Open SkyLicks opened this issue 2 years ago • 5 comments

Is your feature request related to a problem? Please describe.

  1. There is currently no way in PacketEvents to accurately create List<EntityData> for a given player.
  2. I did attempt to use PlayerDataProvider to create a List<EntityData> but since PlayerDataProvider does not respect client's version, sending packets that had List<EntityData> generated from PlayerDataProvider would result in the client being kicked (unless the client was on the latest version of MC supported by PLib)

Describe the solution you'd like Well, ProtocolLib has a method called WrappedDataWatcher.getEntityWatcher(player) that creates the PacketEvents equivalent of List<EntityData> for the selected player. I suppose we could add a method somewhere in PlayerManager that allows us to access the player's entity data at least on spigot. PlayerDataProvider should also have an option to specify what version of Minecraft to generate the entity data for.

Describe alternatives you've considered to solve your solution without us adding this as a feature?

  • One thing I tried is already described above.
  • Another thing I attempted to do was to try and capture "natural" spawn player and player metadata packets to store the player's current metadata but that not fool proof either.
  • The method I already had working in a previous version of packet events was: Create packet in protocol lib -> translate the packet to nms -> send the nms packet in sendPacketSilently. But As I discussed with you before, this no longer works.

Additional context Add any other context or screenshots about the feature request here.

SkyLicks avatar Dec 21 '22 01:12 SkyLicks

Hello,

This should work:

import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.util.reflection.Reflection;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import io.github.retrooper.packetevents.util.SpigotReflectionUtil;
import lombok.experimental.UtilityClass;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

@UtilityClass
public class BukkitDataWatcherConverter {

    private Field DATA_WATCHER_FIELD;
    private Method DATA_WATCHER_WRITE_METHOD;
    private Constructor<?> PACKET_DATA_SERIALIZER_CONSTRUCTOR;

    public List<EntityData> convert(@NotNull Entity entity) {
        if (DATA_WATCHER_FIELD == null) {
            Class<?> entityClass = SpigotReflectionUtil.NMS_ENTITY_CLASS;

            Class<?> dataWatcherClass = SpigotReflectionUtil.getServerClass("network.syncher.DataWatcher", "DataWatcher");
            if (dataWatcherClass == null) {
                dataWatcherClass = SpigotReflectionUtil.getServerClass("network.syncher.SynchedEntityData", "DataWatcher");
            }

            Class<?> packetDataSerializerClass = SpigotReflectionUtil.NMS_PACKET_DATA_SERIALIZER_CLASS;
            try {
                PACKET_DATA_SERIALIZER_CONSTRUCTOR = packetDataSerializerClass.getConstructor(SpigotReflectionUtil.BYTE_BUF_CLASS);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

            DATA_WATCHER_FIELD = Reflection.getField(entityClass, dataWatcherClass, 0, true);
            DATA_WATCHER_WRITE_METHOD = Reflection.getMethod(dataWatcherClass, void.class, 0, packetDataSerializerClass);
        }

        try {
            Object handle = SpigotReflectionUtil.getNMSEntity(entity);
            Object dataWatcher = DATA_WATCHER_FIELD.get(handle);

            Object byteBuf = PacketEvents.getAPI().getNettyManager().getByteBufAllocationOperator().buffer();
            Object packetDataSerializer = PACKET_DATA_SERIALIZER_CONSTRUCTOR.newInstance(byteBuf);

            DATA_WATCHER_WRITE_METHOD.invoke(dataWatcher, packetDataSerializer);

            PacketWrapper<?> packetWrapper = PacketWrapper.createUniversalPacketWrapper(byteBuf);
            return packetWrapper.readEntityMetadata();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
}

Edit: fixed and tested (1.8.8 server)

Valyrox avatar Jan 29 '23 12:01 Valyrox

Does it

retrooper avatar Feb 15 '23 22:02 retrooper

Does it

ghost avatar Oct 08 '23 03:10 ghost

I'll add a converter in the 2.1.0 release and see if it works

retrooper avatar Oct 08 '23 08:10 retrooper

Yes it works, I have been using it since the beginning of the year on 1.8.8 servers. There was just an issue with the reflection of a method on the code snippet I had given.

I just edited the original post. 😉

Valyrox avatar Oct 10 '23 16:10 Valyrox