Skip to content

Commit 42bec5a

Browse files
committed
Fix entity trackers in 1.17
Addresses #1217
1 parent fa317c1 commit 42bec5a

File tree

3 files changed

+47
-21
lines changed

3 files changed

+47
-21
lines changed

src/main/java/com/comphenix/protocol/injector/EntityUtilities.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.comphenix.protocol.injector;
1919

20+
import java.lang.reflect.Modifier;
2021
import java.util.*;
2122
import java.util.concurrent.ConcurrentHashMap;
2223

@@ -157,9 +158,21 @@ private Object getNewEntityTracker(Object worldServer, int entityId) {
157158
Object playerChunkMap = chunkMapField.get(chunkProvider);
158159

159160
if (trackedEntitiesField == null) {
160-
trackedEntitiesField = Accessors.getFieldAccessor(
161+
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
162+
trackedEntitiesField = Accessors.getFieldAccessor(
163+
FuzzyReflection.fromClass(playerChunkMap.getClass(), true).getField(
164+
FuzzyFieldContract.newBuilder()
165+
.banModifier(Modifier.STATIC)
166+
.requirePublic()
167+
.typeExact(org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectMap.class)
168+
.build()
169+
)
170+
);
171+
} else {
172+
trackedEntitiesField = Accessors.getFieldAccessor(
161173
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
162174
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
175+
}
163176
}
164177

165178
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) trackedEntitiesField.get(playerChunkMap);
@@ -229,14 +242,17 @@ public Entity getEntityFromID(World world, int entityID) {
229242
Object tracker = null;
230243

231244
if (trackerEntry != null) {
232-
// plugins like citizens will use their own tracker
245+
// plugins like citizens will use their own tracker class, so cache the result
233246
FieldAccessor trackerField = trackerFields.computeIfAbsent(trackerEntry.getClass(), x -> {
247+
// get the first entity field
234248
try {
235-
return Accessors.getFieldAccessor(trackerEntry.getClass(), "tracker", true);
236-
} catch (Exception e) {
237-
// Assume it's the first entity field then
238-
return Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry, true)
239-
.getFieldByType("tracker", MinecraftReflection.getEntityClass()));
249+
return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntry.getClass(), true)
250+
.getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build()));
251+
} catch (Exception ex) {
252+
// try with the default class
253+
Class<?> trackerEntryClass = MinecraftReflection.getEntityTrackerClass();
254+
return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntryClass, true)
255+
.getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build()));
240256
}
241257
});
242258

src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.comphenix.protocol.reflect.StructureModifier;
88
import com.comphenix.protocol.reflect.accessors.Accessors;
99

10+
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
1011
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
1112
import net.minecraft.server.level.ChunkProviderServer;
1213
import net.minecraft.server.level.EntityTrackerEntry;
@@ -24,6 +25,8 @@
2425

2526
import java.lang.reflect.Field;
2627

28+
import static com.comphenix.protocol.utility.TestUtils.setFinalField;
29+
2730
import static org.junit.Assert.assertEquals;
2831
import static org.mockito.Mockito.mock;
2932
import static org.mockito.Mockito.when;
@@ -35,7 +38,7 @@ public static void beforeClass() {
3538
BukkitInitialization.initializeItemMeta();
3639
}
3740

38-
// @Test
41+
@Test
3942
public void testReflection() throws ReflectiveOperationException {
4043
CraftWorld bukkit = mock(CraftWorld.class);
4144
WorldServer world = mock(WorldServer.class);
@@ -44,29 +47,25 @@ public void testReflection() throws ReflectiveOperationException {
4447
ChunkProviderServer provider = mock(ChunkProviderServer.class);
4548
when(world.getChunkProvider()).thenReturn(provider);
4649

47-
// TODO unsetting final doesn't work anymore
4850
PlayerChunkMap chunkMap = mock(PlayerChunkMap.class);
4951
Field chunkMapField = FuzzyReflection.fromClass(ChunkProviderServer.class, true)
5052
.getField(FuzzyFieldContract.newBuilder().typeExact(PlayerChunkMap.class).build());
51-
chunkMapField.setAccessible(true);
52-
chunkMapField.set(provider, chunkMap);
53+
setFinalField(provider, chunkMapField, chunkMap);
5354

5455
CraftEntity bukkitEntity = mock(CraftEntity.class);
5556
Entity fakeEntity = mock(Entity.class);
5657
when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity);
5758

58-
PlayerChunkMap.EntityTracker tracker = mock(PlayerChunkMap.EntityTracker.class);
59-
FuzzyReflection.fromClass(EntityTracker.class, true)
60-
.getField(FuzzyFieldContract.newBuilder().typeExact(EntityTrackerEntry.class).build())
61-
.set(tracker, fakeEntity);
59+
EntityTracker tracker = mock(EntityTracker.class);
60+
Field trackerField = FuzzyReflection.fromClass(EntityTracker.class, true)
61+
.getField(FuzzyFieldContract.newBuilder().typeExact(Entity.class).build());
62+
setFinalField(tracker, trackerField, fakeEntity);
6263

63-
Int2ObjectMap<PlayerChunkMap.EntityTracker> trackerMap = new Int2ObjectOpenHashMap<>();
64+
Int2ObjectMap<EntityTracker> trackerMap = new Int2ObjectOpenHashMap<>();
6465
trackerMap.put(1, tracker);
65-
66-
new StructureModifier<>(PlayerChunkMap.class, true)
67-
.withTarget(chunkMap)
68-
.withParamType(Int2ObjectMap.class, null, EntityTracker.class)
69-
.write(0, trackerMap);
66+
Field trackedEntitiesField = FuzzyReflection.fromClass(PlayerChunkMap.class, true)
67+
.getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build());
68+
setFinalField(chunkMap, trackedEntitiesField, trackerMap);
7069

7170
assertEquals(bukkitEntity, EntityUtilities.getInstance().getEntityFromID(bukkit, 1));
7271
}

src/test/java/com/comphenix/protocol/utility/TestUtils.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.comphenix.protocol.utility;
22

3+
import java.lang.reflect.Field;
34
import java.util.List;
45

6+
import sun.misc.Unsafe;
57
import org.bukkit.Bukkit;
68
import org.bukkit.inventory.ItemStack;
79

@@ -42,4 +44,13 @@ public static boolean equivalentItem(ItemStack first, ItemStack second) {
4244
return first.getType().equals(second.getType());
4345
}
4446
}
47+
48+
public static void setFinalField(Object obj, Field field, Object newValue) throws ReflectiveOperationException {
49+
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
50+
unsafeField.setAccessible(true);
51+
Unsafe unsafe = (Unsafe) unsafeField.get(null);
52+
53+
long offset = unsafe.objectFieldOffset(field);
54+
unsafe.putObject(obj, offset, newValue);
55+
}
4556
}

0 commit comments

Comments
 (0)