Fix entity trackers in 1.17

Addresses #1217
This commit is contained in:
Dan Mulloy 2021-06-15 18:39:34 -04:00
parent fa317c1167
commit 42bec5a858
No known key found for this signature in database
GPG Key ID: BFACD592A5F0DFD6
3 changed files with 47 additions and 21 deletions

View File

@ -17,6 +17,7 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector;
import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -157,9 +158,21 @@ class EntityUtilities {
Object playerChunkMap = chunkMapField.get(chunkProvider); Object playerChunkMap = chunkMapField.get(chunkProvider);
if (trackedEntitiesField == null) { if (trackedEntitiesField == null) {
trackedEntitiesField = Accessors.getFieldAccessor( if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), true).getField(
FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requirePublic()
.typeExact(org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectMap.class)
.build()
)
);
} else {
trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField( FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build())); FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
}
} }
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) trackedEntitiesField.get(playerChunkMap); Map<Integer, Object> trackedEntities = (Map<Integer, Object>) trackedEntitiesField.get(playerChunkMap);
@ -229,14 +242,17 @@ class EntityUtilities {
Object tracker = null; Object tracker = null;
if (trackerEntry != null) { if (trackerEntry != null) {
// plugins like citizens will use their own tracker // plugins like citizens will use their own tracker class, so cache the result
FieldAccessor trackerField = trackerFields.computeIfAbsent(trackerEntry.getClass(), x -> { FieldAccessor trackerField = trackerFields.computeIfAbsent(trackerEntry.getClass(), x -> {
// get the first entity field
try { try {
return Accessors.getFieldAccessor(trackerEntry.getClass(), "tracker", true); return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntry.getClass(), true)
} catch (Exception e) { .getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build()));
// Assume it's the first entity field then } catch (Exception ex) {
return Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry, true) // try with the default class
.getFieldByType("tracker", MinecraftReflection.getEntityClass())); Class<?> trackerEntryClass = MinecraftReflection.getEntityTrackerClass();
return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntryClass, true)
.getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build()));
} }
}); });

View File

@ -7,6 +7,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import net.minecraft.server.level.ChunkProviderServer; import net.minecraft.server.level.ChunkProviderServer;
import net.minecraft.server.level.EntityTrackerEntry; import net.minecraft.server.level.EntityTrackerEntry;
@ -24,6 +25,8 @@ import org.junit.Test;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import static com.comphenix.protocol.utility.TestUtils.setFinalField;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -35,7 +38,7 @@ public class EntityUtilitiesTest {
BukkitInitialization.initializeItemMeta(); BukkitInitialization.initializeItemMeta();
} }
// @Test @Test
public void testReflection() throws ReflectiveOperationException { public void testReflection() throws ReflectiveOperationException {
CraftWorld bukkit = mock(CraftWorld.class); CraftWorld bukkit = mock(CraftWorld.class);
WorldServer world = mock(WorldServer.class); WorldServer world = mock(WorldServer.class);
@ -44,29 +47,25 @@ public class EntityUtilitiesTest {
ChunkProviderServer provider = mock(ChunkProviderServer.class); ChunkProviderServer provider = mock(ChunkProviderServer.class);
when(world.getChunkProvider()).thenReturn(provider); when(world.getChunkProvider()).thenReturn(provider);
// TODO unsetting final doesn't work anymore
PlayerChunkMap chunkMap = mock(PlayerChunkMap.class); PlayerChunkMap chunkMap = mock(PlayerChunkMap.class);
Field chunkMapField = FuzzyReflection.fromClass(ChunkProviderServer.class, true) Field chunkMapField = FuzzyReflection.fromClass(ChunkProviderServer.class, true)
.getField(FuzzyFieldContract.newBuilder().typeExact(PlayerChunkMap.class).build()); .getField(FuzzyFieldContract.newBuilder().typeExact(PlayerChunkMap.class).build());
chunkMapField.setAccessible(true); setFinalField(provider, chunkMapField, chunkMap);
chunkMapField.set(provider, chunkMap);
CraftEntity bukkitEntity = mock(CraftEntity.class); CraftEntity bukkitEntity = mock(CraftEntity.class);
Entity fakeEntity = mock(Entity.class); Entity fakeEntity = mock(Entity.class);
when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity); when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity);
PlayerChunkMap.EntityTracker tracker = mock(PlayerChunkMap.EntityTracker.class); EntityTracker tracker = mock(EntityTracker.class);
FuzzyReflection.fromClass(EntityTracker.class, true) Field trackerField = FuzzyReflection.fromClass(EntityTracker.class, true)
.getField(FuzzyFieldContract.newBuilder().typeExact(EntityTrackerEntry.class).build()) .getField(FuzzyFieldContract.newBuilder().typeExact(Entity.class).build());
.set(tracker, fakeEntity); setFinalField(tracker, trackerField, fakeEntity);
Int2ObjectMap<PlayerChunkMap.EntityTracker> trackerMap = new Int2ObjectOpenHashMap<>(); Int2ObjectMap<EntityTracker> trackerMap = new Int2ObjectOpenHashMap<>();
trackerMap.put(1, tracker); trackerMap.put(1, tracker);
Field trackedEntitiesField = FuzzyReflection.fromClass(PlayerChunkMap.class, true)
new StructureModifier<>(PlayerChunkMap.class, true) .getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build());
.withTarget(chunkMap) setFinalField(chunkMap, trackedEntitiesField, trackerMap);
.withParamType(Int2ObjectMap.class, null, EntityTracker.class)
.write(0, trackerMap);
assertEquals(bukkitEntity, EntityUtilities.getInstance().getEntityFromID(bukkit, 1)); assertEquals(bukkitEntity, EntityUtilities.getInstance().getEntityFromID(bukkit, 1));
} }

View File

@ -1,7 +1,9 @@
package com.comphenix.protocol.utility; package com.comphenix.protocol.utility;
import java.lang.reflect.Field;
import java.util.List; import java.util.List;
import sun.misc.Unsafe;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -42,4 +44,13 @@ public class TestUtils {
return first.getType().equals(second.getType()); return first.getType().equals(second.getType());
} }
} }
public static void setFinalField(Object obj, Field field, Object newValue) throws ReflectiveOperationException {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
long offset = unsafe.objectFieldOffset(field);
unsafe.putObject(obj, offset, newValue);
}
} }