Archiviert
13
0

Fix entity tracking in 1.14

Fixes #600
Dieser Commit ist enthalten in:
Dan Mulloy 2019-05-04 22:41:32 -04:00
Ursprung e41bb8753b
Commit b14b4cc345
5 geänderte Dateien mit 184 neuen und 149 gelöschten Zeilen

Datei anzeigen

@ -5,28 +5,15 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.*;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket; import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
@ -37,6 +24,13 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
/** /**
* A protocol manager that delays all packet listener registrations and unregistrations until * A protocol manager that delays all packet listener registrations and unregistrations until
* an underlying protocol manager can be constructed. * an underlying protocol manager can be constructed.
@ -440,7 +434,7 @@ public class DelayedPacketManager implements InternalManager {
if (delegate != null) if (delegate != null)
delegate.updateEntity(entity, observers); delegate.updateEntity(entity, observers);
else else
EntityUtilities.updateEntity(entity, observers); EntityUtilities.getInstance().updateEntity(entity, observers);
} }
@Override @Override
@ -448,7 +442,7 @@ public class DelayedPacketManager implements InternalManager {
if (delegate != null) if (delegate != null)
return delegate.getEntityFromID(container, id); return delegate.getEntityFromID(container, id);
else else
return EntityUtilities.getEntityFromID(container, id); return EntityUtilities.getInstance().getEntityFromID(container, id);
} }
@Override @Override
@ -456,7 +450,7 @@ public class DelayedPacketManager implements InternalManager {
if (delegate != null) if (delegate != null)
return delegate.getEntityTrackers(entity); return delegate.getEntityTrackers(entity);
else else
return EntityUtilities.getEntityTrackers(entity); return EntityUtilities.getInstance().getEntityTrackers(entity);
} }
@Override @Override

Datei anzeigen

@ -17,25 +17,27 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector;
import java.lang.reflect.Field; import com.comphenix.protocol.reflect.FieldAccessException;
import java.lang.reflect.InvocationTargetException; import com.comphenix.protocol.reflect.FuzzyReflection;
import java.lang.reflect.Method; import com.comphenix.protocol.reflect.accessors.Accessors;
import java.util.ArrayList; import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import java.util.Collection; import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import java.util.List; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import java.util.Map; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
import com.google.common.collect.Lists;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.reflect.FieldAccessException; import java.util.ArrayList;
import com.comphenix.protocol.reflect.FieldUtils; import java.util.Collection;
import com.comphenix.protocol.reflect.FuzzyReflection; import java.util.List;
import com.comphenix.protocol.utility.MinecraftReflection; import java.util.Map;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
import com.google.common.collect.Lists;
/** /**
* Used to perform certain operations on entities. * Used to perform certain operations on entities.
@ -43,64 +45,41 @@ import com.google.common.collect.Lists;
* @author Kristian * @author Kristian
*/ */
class EntityUtilities { class EntityUtilities {
private static final boolean NEW_TRACKER = MinecraftVersion.atOrAbove(MinecraftVersion.VILLAGE_UPDATE);
private static final EntityUtilities INSTANCE = new EntityUtilities();
private static Field entityTrackerField; public static EntityUtilities getInstance() {
private static Field trackedEntitiesField; return INSTANCE;
private static Field trackedPlayersField;
private static Field trackerField;
private static Method scanPlayersMethod;
/*
public static void updateEntity2(Entity entity, List<Player> observers) {
EntityTrackerEntry entry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
entry.trackedPlayers.removeAll(nmsPlayers);
entry.scanPlayers(nmsPlayers);
} }
*/
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException { private EntityUtilities() { }
private FieldAccessor entityTrackerField;
private FieldAccessor trackedEntitiesField;
private FieldAccessor trackedPlayersField;
private FieldAccessor trackerField;
private MethodAccessor scanPlayersMethod;
public void updateEntity(Entity entity, List<Player> observers) {
if (entity == null || !entity.isValid()) { if (entity == null || !entity.isValid()) {
return; return;
} }
try { Collection<?> trackedPlayers = getTrackedPlayers(entity);
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId()); List<Object> nmsPlayers = unwrapBukkit(observers);
if (trackerEntry == null) {
throw new IllegalArgumentException("Cannot find entity trackers for " + entity + ".");
}
if (trackedPlayersField == null) { trackedPlayers.removeAll(nmsPlayers);
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there. // TODO Find equivalent method for newer versions
Collection<?> trackedPlayers = getTrackedPlayers(trackedPlayersField, trackerEntry);
List<Object> nmsPlayers = unwrapBukkit(observers);
trackedPlayers.removeAll(nmsPlayers); if (scanPlayersMethod == null) {
Class<?> trackerEntry = MinecraftReflection.getMinecraftClass("EntityTrackerEntry");
// We have to rely on a NAME once again. Damn it. scanPlayersMethod = Accessors.getMethodAccessor(trackerEntry, "scanPlayers");
// TODO: Make sure this stays up to date with version changes - 1.8 - 1.10
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) {
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Cannot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
} }
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} }
/** /**
@ -109,45 +88,37 @@ class EntityUtilities {
* @return Every client/player that is tracking the given entity. * @return Every client/player that is tracking the given entity.
* @throws FieldAccessException If reflection failed. * @throws FieldAccessException If reflection failed.
*/ */
public static List<Player> getEntityTrackers(Entity entity) { public List<Player> getEntityTrackers(Entity entity) {
if (entity == null || !entity.isValid()) { if (entity == null || !entity.isValid()) {
return new ArrayList<>(); return new ArrayList<>();
} }
try { List<Player> result = new ArrayList<>();
List<Player> result = new ArrayList<Player>(); Collection<?> trackedPlayers = getTrackedPlayers(entity);
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId()); // Wrap every player - we also ensure that the underlying tracker list is immutable
if (trackerEntry == null) { for (Object tracker : trackedPlayers) {
throw new IllegalArgumentException("Cannot find entity trackers for " + entity + "."); if (MinecraftReflection.isMinecraftPlayer(tracker)) {
result.add((Player) MinecraftReflection.getBukkitEntity(tracker));
} }
if (trackedPlayersField == null) {
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
Collection<?> trackedPlayers = getTrackedPlayers(trackedPlayersField, trackerEntry);
// Wrap every player - we also ensure that the underlying tracker list is immutable
for (Object tracker : trackedPlayers) {
if (MinecraftReflection.isMinecraftPlayer(tracker)) {
result.add((Player) MinecraftReflection.getBukkitEntity(tracker));
}
}
return result;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevented access to the list of tracked players.", e);
} }
return result;
} }
// Damn you, Paper private Collection<?> getTrackedPlayers(Entity entity) {
private static Collection<?> getTrackedPlayers(Field field, Object entry) throws IllegalAccessException { Validate.notNull(entity, "entity cannot be null");
Validate.notNull(field, "Cannot find 'trackedPlayers' field.");
Validate.notNull(entry, "entry cannot be null!");
Object value = FieldUtils.readField(field, entry, false); Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
Validate.notNull(trackerEntry, "Could not find entity trackers for " + entity);
if (trackedPlayersField == null) {
trackedPlayersField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*"));
}
Validate.notNull(trackedPlayersField, "Could not find trackedPlayers field");
Object value = trackedPlayersField.get(trackerEntry);
if (value instanceof Collection) { if (value instanceof Collection) {
return (Collection<?>) value; return (Collection<?>) value;
} else if (value instanceof Map) { } else if (value instanceof Map) {
@ -158,46 +129,62 @@ class EntityUtilities {
} }
} }
/* private MethodAccessor getChunkProvider;
private static EntityTrackerEntry getEntityTrackerEntry2(World world, int entityID) { private FieldAccessor chunkMapField;
WorldServer worldServer = ((CraftWorld) world).getHandle();
EntityTracker tracker = worldServer.tracker;
return tracker.trackedEntities.get(entityID);
}
*/
private static Object getEntityTrackerEntry(World world, int entityID) throws FieldAccessException, IllegalArgumentException { @SuppressWarnings("unchecked")
private Object getNewEntityTracker(Object worldServer, int entityId) {
if (getChunkProvider == null) {
Class<?> chunkProviderClass = MinecraftReflection.getMinecraftClass("ChunkProviderServer");
getChunkProvider = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(worldServer.getClass(), false).getMethod(
FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
}
Object chunkProvider = getChunkProvider.invoke(worldServer);
if (chunkMapField == null) {
Class<?> chunkMapClass = MinecraftReflection.getMinecraftClass("PlayerChunkMap");
chunkMapField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(chunkProvider.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
}
Object playerChunkMap = chunkMapField.get(chunkProvider);
if (trackedEntitiesField == null) {
trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
}
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) trackedEntitiesField.get(playerChunkMap);
return trackedEntities.get(entityId);
}
private Object getEntityTrackerEntry(World world, int entityID) {
BukkitUnwrapper unwrapper = new BukkitUnwrapper(); BukkitUnwrapper unwrapper = new BukkitUnwrapper();
Object worldServer = unwrapper.unwrapItem(world); Object worldServer = unwrapper.unwrapItem(world);
if (NEW_TRACKER) {
return getNewEntityTracker(worldServer, entityID);
}
if (entityTrackerField == null) if (entityTrackerField == null)
entityTrackerField = FuzzyReflection.fromObject(worldServer). entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()); getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()));
// Get the tracker // Get the tracker
Object tracker = null; Object tracker = entityTrackerField.get(worldServer);
try {
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
}
// Looking for an IntHashMap in the tracker entry // Looking for an IntHashMap in the tracker entry
if (trackedEntitiesField == null) { if (trackedEntitiesField == null) {
trackedEntitiesField = FuzzyReflection.fromObject(tracker, false) trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false)
.getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass()); .getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass()));
} }
// Read the map // Read the map
Object trackedEntities = null; Object trackedEntities = trackedEntitiesField.get(tracker);
try {
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
}
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID); return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
} }
@ -206,7 +193,10 @@ class EntityUtilities {
* @return The associated entity. * @return The associated entity.
* @throws FieldAccessException Reflection error. * @throws FieldAccessException Reflection error.
*/ */
public static Entity getEntityFromID(World world, int entityID) throws FieldAccessException { public Entity getEntityFromID(World world, int entityID) {
Validate.notNull(world, "world cannot be null");
Validate.isTrue(entityID >= 0, "entityID cannot be negative");
try { try {
Object trackerEntry = getEntityTrackerEntry(world, entityID); Object trackerEntry = getEntityTrackerEntry(world, entityID);
Object tracker = null; Object tracker = null;
@ -215,29 +205,25 @@ class EntityUtilities {
if (trackerEntry != null) { if (trackerEntry != null) {
if (trackerField == null) { if (trackerField == null) {
try { try {
Class<?> entryClass = MinecraftReflection.getMinecraftClass("EntityTrackerEntry"); trackerField = Accessors.getFieldAccessor(trackerEntry.getClass(), "tracker", true);
trackerField = entryClass.getDeclaredField("tracker"); } catch (Exception e) {
} catch (NoSuchFieldException e) {
// Assume it's the first entity field then // Assume it's the first entity field then
trackerField = FuzzyReflection.fromObject(trackerEntry, true) trackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry, true)
.getFieldByType("tracker", MinecraftReflection.getEntityClass()); .getFieldByType("tracker", MinecraftReflection.getEntityClass()));
} }
} }
tracker = FieldUtils.readField(trackerField, trackerEntry, true); tracker = trackerField.get(trackerEntry);
} }
// If the tracker is NULL, we'll just assume this entity doesn't exist // If the tracker is NULL, we'll just assume this entity doesn't exist
if (tracker != null) return tracker != null ? (Entity) MinecraftReflection.getBukkitEntity(tracker) : null;
return (Entity) MinecraftReflection.getBukkitEntity(tracker);
else
return null;
} catch (Exception e) { } catch (Exception e) {
throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e); throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e);
} }
} }
private static List<Object> unwrapBukkit(List<Player> players) { private List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList(); List<Object> output = Lists.newArrayList();
BukkitUnwrapper unwrapper = new BukkitUnwrapper(); BukkitUnwrapper unwrapper = new BukkitUnwrapper();

Datei anzeigen

@ -56,8 +56,6 @@ import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import io.netty.channel.Channel;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Server; import org.bukkit.Server;
@ -74,6 +72,8 @@ import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import io.netty.channel.Channel;
public final class PacketFilterManager implements ListenerInvoker, InternalManager { public final class PacketFilterManager implements ListenerInvoker, InternalManager {
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list."); public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
@ -924,17 +924,17 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
@Override @Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException { public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
EntityUtilities.updateEntity(entity, observers); EntityUtilities.getInstance().updateEntity(entity, observers);
} }
@Override @Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException { public Entity getEntityFromID(World container, int id) throws FieldAccessException {
return EntityUtilities.getEntityFromID(container, id); return EntityUtilities.getInstance().getEntityFromID(container, id);
} }
@Override @Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException { public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
return EntityUtilities.getEntityTrackers(entity); return EntityUtilities.getInstance().getEntityTrackers(entity);
} }
/** /**

Datei anzeigen

@ -1527,7 +1527,7 @@ public class MinecraftReflection {
*/ */
public static Class<?> getEntityTrackerClass() { public static Class<?> getEntityTrackerClass() {
try { try {
return getMinecraftClass("EntityTracker"); return getMinecraftClass("EntityTracker", "PlayerChunkMap$EntityTracker");
} catch (RuntimeException e) { } catch (RuntimeException e) {
FuzzyClassContract entityTrackerContract = FuzzyClassContract.newBuilder(). FuzzyClassContract entityTrackerContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder(). field(FuzzyFieldContract.newBuilder().

Datei anzeigen

@ -0,0 +1,55 @@
package com.comphenix.protocol.injector;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.reflect.accessors.Accessors;
import net.minecraft.server.v1_14_R1.ChunkProviderServer;
import net.minecraft.server.v1_14_R1.Entity;
import net.minecraft.server.v1_14_R1.PlayerChunkMap;
import net.minecraft.server.v1_14_R1.PlayerChunkMap.EntityTracker;
import net.minecraft.server.v1_14_R1.WorldServer;
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.bukkit.craftbukkit.v1_14_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftEntity;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class EntityUtilitiesTest {
@BeforeClass
public static void beforeClass() {
BukkitInitialization.initializeItemMeta();
}
@Test
public void testReflection() {
CraftWorld bukkit = mock(CraftWorld.class);
WorldServer world = mock(WorldServer.class);
when(bukkit.getHandle()).thenReturn(world);
ChunkProviderServer provider = mock(ChunkProviderServer.class);
when(world.getChunkProvider()).thenReturn(provider);
PlayerChunkMap chunkMap = mock(PlayerChunkMap.class);
Accessors.getFieldAccessor(ChunkProviderServer.class, "playerChunkMap", true).set(provider, chunkMap);
CraftEntity bukkitEntity = mock(CraftEntity.class);
Entity fakeEntity = mock(Entity.class);
when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity);
EntityTracker tracker = mock(EntityTracker.class);
Accessors.getFieldAccessor(EntityTracker.class, "tracker", true).set(tracker, fakeEntity);
Int2ObjectMap<EntityTracker> trackerMap = new Int2ObjectOpenHashMap<>();
trackerMap.put(1, tracker);
Accessors.getFieldAccessor(PlayerChunkMap.class, "trackedEntities", true).set(chunkMap, trackerMap);
assertEquals(bukkitEntity, EntityUtilities.getInstance().getEntityFromID(bukkit, 1));
}
}